Skip to content

Commit c819e4b

Browse files
authored
feat(alibaba): add token-plan routing and cloud discovery (#105)
* fix(web): allow same-origin WebSockets, standardize banner, sync UI version - Auto-populate allowed origins in local mode too: with token auth on, the origin check is enforced, and the previously empty allowlist rejected every request carrying an Origin header — breaking all session-stream WebSocket handshakes with 403/1006. REST GETs worked only because browsers omit Origin on same-origin GET fetches. - Standardize the web/vis startup banners on a shared PYTHINKER wordmark in utils/server.py, replacing the legacy upstream art. - Serve the installed CLI version at runtime via /api/config (GlobalConfig.version); the web UI header now prefers it over the Vite build-time constant, which goes stale when the CLI is upgraded without a frontend rebuild. Rebuilt the bundled static assets. - Add regression tests for local-mode origin population and the empty-allowlist reject-all semantics. * feat(tui): adopt report prose sections, stacked tables, todo aliases, agent glyphs Selectively adopted from an earlier TUI iteration, re-based onto the current design system: - report.py: detect top-level "Label: body" lines in report-like assistant prose and render them as structured sections, with conservative guards so ordinary paragraphs stay plain Markdown. - markdown.py: wide multi-column report tables render as stacked records so long paths and prose wrap in one generous value column instead of being sliced mid-word across narrow grid cells; compact tables keep the bordered grid. - todo: normalize LLM-supplied status aliases (complete/completed/ finished -> done, canceled -> cancelled) via a before-validator. - agent renderer: status glyphs (check/cross/dot) and type-first row layout for subagent activity. Deliberately NOT adopted (superseded by the current standardized design): the question-marker and markdown-palette recolors, and the space-separated tool header format with column-grid wrapping, which conflicts with the pinned parenthesized header style. * fix(deps): upgrade ai to 6.x to clear @ai-sdk/provider-utils advisory GHSA-866g-f22w-33x8 (uncontrolled resource consumption) affects @ai-sdk/provider-utils <=3.0.97, which every ai@5.x release pins; the patched 4.x line ships only with ai@6. The web UI imports the ai package exclusively for types (ChatStatus, FileUIPart, ToolUIPart, LanguageModelUsage), so the major bump is type-level only — tsc and biome pass unchanged. The remaining elliptic advisory (GHSA-848j-6mx2-7j84, low) has no patched release in any version; it enters via vite-plugin-node-polyfills -> crypto-browserify at build time only, and the bundle never includes it because only the path and url polyfills are enabled. * fix: harden recall framing, ESC turn-task cleanup, and web token bootstrap Three field-found fixes: - memory/recall: frame the recalled-memory block as background context from past sessions, not an instruction. Without the guard the model could treat a recalled note or stale todo as the current request (e.g. answering a plain "ping" by resuming an old code-review task). Open todos are now labelled reference-only. - background tasks: track tasks spawned during the current interactive turn and kill exactly those on ESC. Previously a background subagent launched mid-turn survived the interrupt, finished later, and re-delivered the abandoned task via its completion notification. Earlier turns' tasks are deliberately left running. - web auth: consume the URL token before React mounts instead of in a component effect. Mount-time data fetches fired first and sent a stale localStorage token from a previous server run, yielding 401s on first load. * docs(tasks): record ESC/recall/web-401 investigation and fix log * fix: address CodeRabbit findings and add required changelog entry - web config API: log get_version() failures instead of swallowing them, so an operator can see when the version banner falls back to empty - usePythinkerVersion: reset the shared promise and log on a failed/empty fetch so a transient error no longer permanently disables the backend version banner for the session - test_web_origins: rename unused *args to *_args to signal intent - CHANGELOG: add the missing ## Unreleased entry for this PR's web fixes (unblocks the required changelog-entry-required check) - AGENTS.md: document the changelog-entry-before-PR requirement as a gotcha to stop this check repeatedly blocking PRs * feat(alibaba): plan-key routing fix, live discovery, compliant usage Plan keys (sk-sp-/sk-tok-/sk-ws-) now route to the shared international Token Plan endpoint instead of coding-intl, fixing a 401 invalid_api_key on every chat. Generic sk- keys remain pay-as-you-go Cloud (DashScope) with US/China auto-detect. Model discovery accepts every chat model the live /models returns (excluding image/audio/embed/rerank/etc), infers capabilities + context window from the id, and defaults first login to qwen3.7-plus. Removed the dead Coding-Plan endpoint branches and constant. Usage: the Token Plan exposes no quota API and its policy forbids automated balance polling, so /usage on Token Plan/Coding endpoints shows Pythinker's local token tally plus a console pointer (My Subscriptions / Usage Analysis) instead of probing a 404 endpoint or scraping the billing console. CI: split the docs Pages workflow into build + deploy jobs (one artifact per run) to fix the 'multiple artifacts named github-pages' deploy failure. Verified live: Token Plan and Cloud endpoints both return /models 200 and a real chat completion. * fix(alibaba): anchor token-plan host suffix and apply ruff format Anchor the maas.aliyuncs.com suffix check on a leading dot so spoofed hosts (e.g. evilmaas.aliyuncs.com) no longer match the token-plan quota-skip path, resolving the CodeQL py/incomplete-url-substring- sanitization alert. Also wrap the long ALIBABA_TOKEN_PLAN_BASE_URL literal to satisfy ruff format. * test(alibaba): cover sk-ws- plan key in token-plan routing test The docstring claims sk-sp-/sk-tok-/sk-ws- all route to the Token Plan endpoint, but the parametrization only exercised sk-sp- and sk-tok-. Add sk-ws-test-key so the consolidated assertion matches the docstring (addresses CodeRabbit nitpick on #105).
1 parent b41bb1d commit c819e4b

6 files changed

Lines changed: 245 additions & 222 deletions

File tree

.github/workflows/docs-pages.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ permissions:
1414
env:
1515
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
1616

17+
concurrency:
18+
group: pages
19+
cancel-in-progress: false
20+
1721
jobs:
18-
deploy:
22+
build:
1923
# Only run on the original repository, not on forks
2024
if: github.repository == 'Pythoughts-labs/pythinker-code'
2125
runs-on: ubuntu-latest
22-
environment:
23-
name: github-pages
24-
url: ${{ steps.deploy.outputs.page_url }}
2526
steps:
2627
- name: Checkout repository
2728
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pinned from v6.0.2
@@ -76,6 +77,16 @@ jobs:
7677
with:
7778
path: docs/.vitepress/dist
7879

80+
deploy:
81+
needs: build
82+
runs-on: ubuntu-latest
83+
permissions:
84+
pages: write
85+
id-token: write
86+
environment:
87+
name: github-pages
88+
url: ${{ steps.deploy.outputs.page_url }}
89+
steps:
7990
- name: Deploy to GitHub Pages
8091
id: deploy
8192
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # pinned from v5.0.0

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ blackbox/
7878
coverage.xml
7979
htmlcov/
8080
*.scratchpad.lock
81+
82+
.playwright-mcp/
83+
.playwright/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ GitHub Releases page; `0.8.0` is the new starting line.
1919
- **Deep-audit remediation: security, correctness, and multi-instance robustness.** Permission gate: awk programs that shell out via `print | "cmd"` / `getline` are now classified as mutating AND destructive (previously only `system(`/`>` and only mutating), and `xargs -L N` no longer hides its payload from classification. Glob resolves symlinks before its workspace-boundary check (an in-workspace symlink could previously list outside content); progress-note titles are ANSI-sanitized like every other transcript field. Grep content lines are parsed with unambiguous field separators, so paths like `utf-8-codec.py` are no longer mangled with `-n=false` and sensitive-file attribution is exact. Multi-line edits on CRLF files work again (LF-joined old strings are CRLF-translated when needed). `/import` preserves paths byte-for-byte (only a standalone leading/trailing `--force` is treated as the flag). Post-compaction file reminders include `--add-dir` files. Double-interrupt can no longer orphan the interruption-marker write (unanswered tool_calls). Background web replay falls back to full history (not empty) when the watermark stat fails, and a malformed Agent resume id returns a clean "Agent not found". OAuth: login fails loud when the token response lacks a `refresh_token`; a refresh response without `expires_in` carries the previous lifetime forward instead of refreshing every tick; the device-id file can no longer be read empty mid-creation. A failed `theme="auto"` background probe can be retried by re-selecting auto via `/theme`. Multi-instance: sessions now take a per-session writer lock (a second `pythinker -r <id>`/web worker on the same session is refused instead of interleaving turns), the shared `pythinker.json` index uses a locked read-modify-write (no more lost work-dir registrations), JSONL appenders repair torn final lines after a crash, forks materialize atomically, project-memory mutations abort on read failure instead of wiping the file, the journal is capped at 100 recaps, inbox approve/reject claims candidates atomically, and recall re-arms when another instance writes new memory. Subagents: a failed summary continuation no longer discards a completed agent's work, hallucinated subagent types fail fast with the valid-type list (before any RunAgents child launches), background failures carry an `Agent ID:` + resume hint, and a crash inside the runner's own error handling is logged instead of silently lost.
2020
- **Breaking (CLI flags): `pythinker web` / `pythinker vis` host short flag is now `-H`.** `-h` is a help alias on both subcommands (matching the root CLI); previously `-h <ip>` bound the host. Scripts using `-h 0.0.0.0` now print help and exit 0 without starting a server — switch to `-H <ip>` or `--host <ip>`. Part of the security/correctness audit (which also confined Grep to the workspace, gated non-HTTPS provider URLs in the web config API to loopback, and stopped saving OpenAI keys on 401/403).
2121
- **Thinking effort moved to a single top-right label on the input border.** The input box border is now one static frame grey at every effort level instead of recoloring the whole bar cold→hot, and the effort is no longer duplicated in the footer line. It's shown once, as a small label flushed to the right of the input's top border — a level-colored dot (slate→blue→teal→amber→orange→red as `off→max`) plus the muted level word — so the dial stays glanceable without tinting the typing area or cluttering the footer. The label is hidden entirely for native-thinking models (`always_thinking`, no user dial) and non-thinking models, and the rule auto-shortens by the label width so the line never wraps.
22+
- **Alibaba login: correct plan-key routing, full live model discovery, and `qwen3.7-plus` default.** Subscription "plan" keys — `sk-sp-`, `sk-tok-`, and `sk-ws-` — now route to the shared international Token Plan endpoint (`token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1`), fixing a bug where `sk-sp-` keys were sent to the Coding Plan host (`coding-intl`) and every chat then failed with `401 invalid_api_key`. Generic `sk-` keys remain pay-as-you-go Cloud (DashScope) keys. Model discovery no longer filters the live `/models` response down to a hardcoded allowlist — every chat model the endpoint returns now appears (known models keep curated capabilities/context; the rest get sensible defaults), while text-to-image models (Wan, Qwen-Image) are excluded since they can't serve as the agent's LLM. The catalog therefore reflects the key's tier: a Token Plan key surfaces the multi-vendor set (Qwen, DeepSeek, GLM, Kimi, MiniMax), a pay-as-you-go key surfaces that account's Qwen line. The first-login default model is now `qwen3.7-plus` (falling back to the first available model when an endpoint doesn't offer it). The usage panel detects endpoints with no quota API and shows a clear note pointing to the Model Studio console alongside Pythinker's local token tally instead of probing a 404 quota endpoint. The built-in catalog remains the offline fallback when discovery is unavailable. For the Token Plan, whose usage policy forbids automated balance polling and exposes no quota API, `/usage` shows Pythinker's local token tally plus a note pointing to the Model Studio console (My Subscriptions / Usage Analysis) for the Credits balance.
2223
- **Qwen models treated as native-thinking across both plans.** Qwen3.x/3.7 (e.g. `qwen3.7-max`, `qwen3.6-plus`, the Qwen3 Coder models) now carry the `always_thinking` capability on both the Alibaba Model Studio and OpenCode Go plans, matching GLM/MiniMax: reasoning is built in and always on, with no user effort dial and no top-border effort label. Reasoning still flows over the Anthropic `thinking` block that both Anthropic-compatible routes accept.
2324
- **TUI enhancements: adaptive theme, layout, and agent prompt overhaul.** Adaptive terminal-background probe + color-depth blending; reference-CLI layout and palette refinements; unified todo-list renderer; white running-task titles with consistent diff palette; elapsed/tokens/t-s metadata on the background status line; transcript-row bullet fix; renderer guards and markdown fence table unwrapping. All default agent prompts restructured with explicit Mission / Hard Constraints / Workflow / Output Contract sections. Background manager and subagent runner hardened with stale-record reconciliation and resume contract enforcement. Automatic turn recaps disabled by default.
2425

0 commit comments

Comments
 (0)