Skip to content

fix: same-origin WS, ESC turn cleanup, recall framing, web token bootstrap - #104

Merged
elkaix merged 6 commits into
mainfrom
fix/web-origins-banner-version
Jun 10, 2026
Merged

fix: same-origin WS, ESC turn cleanup, recall framing, web token bootstrap#104
elkaix merged 6 commits into
mainfrom
fix/web-origins-banner-version

Conversation

@elkaix

@elkaix elkaix commented Jun 10, 2026

Copy link
Copy Markdown
Member

Summary

Bundle of field-found fixes around the web server, interrupt handling, and memory recall, plus TUI report polish and a dependency advisory fix.

Web origins, banner & version sync (0d0d549)

  • Allow same-origin WebSockets so the local web UI streams work without extra origin config.
  • Standardize the startup banner and sync the UI version with the package version.

TUI report rendering (94327b3)

  • Report prose sections, stacked tables, todo aliases, and agent glyphs, re-based onto the current design system.

Interrupt & recall hardening (8cb8efa)

Root-caused from a real session where a fresh-session "ping" was answered by resuming an old code-review task, and ESC failed to stop a background subagent:

  • memory/recall: the recalled-memory injection is now explicitly framed as background context from past sessions, not an instruction; stale open todos are labelled reference-only ("do not resume unprompted"). Previously a weak model treated recalled todos/scratch imperatives as the current request.
  • background tasks: new begin_turn() / kill_turn_tasks() registry. ESC now kills exactly the background tasks spawned by the interrupted turn; earlier turns' tasks deliberately survive. Previously a mid-turn background subagent survived the interrupt and re-delivered the abandoned task via its completion notification.
  • web auth: the ?token= URL parameter is consumed before React mounts. Previously a component effect ran after mount-time data fetches, which went out with a stale localStorage token from a prior server run and 401'd on first load (GET /api/sessions/?archived=true).

Dependency advisory (44f46f7)

  • Bump ai 5.x → 6.x to clear the @ai-sdk/provider-utils advisory.

Testing

  • 162 pytest green across tests/background, recall (tests/core/test_recall_*), and shell suites, including 5 new regression tests (tests/ui_and_conv/test_shell_interrupt_cleanup.py, turn-kill tests in tests/background/test_manager.py, recall framing pin in tests/core/test_recall_provider.py).
  • ruff + pyright clean on touched Python files; tsc -b + biome clean on the web frontend; npm run build succeeds.

Summary by CodeRabbit

  • New Features

    • Background tasks tied to a user turn are tracked and stopped on interrupt
    • Markdown now renders very wide tables as stacked, readable records
    • UI shows visual status glyphs for agent runs and surfaces backend version info
  • Bug Fixes

    • Fixes to token bootstrap timing to avoid initial auth failures
    • Recall framing clarified to avoid treating past notes as instructions
    • Todo status aliases normalized (e.g., "completed" → "done")
  • Tests

    • Added coverage for interrupt cleanup, recall framing, todo status, and web origin handling
  • Documentation

    • Updated changelog guidance and unreleased entry requirements

elkaix added 5 commits June 10, 2026 11:05
…ersion

- 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.
… 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.
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.
…strap

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.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 788bd263-c203-477c-a395-60b4cbc16cc5

📥 Commits

Reviewing files that changed from the base of the PR and between f531418 and e917178.

📒 Files selected for processing (5)
  • AGENTS.md
  • CHANGELOG.md
  • src/pythinker_code/web/api/config.py
  • tests/web/test_web_origins.py
  • web/src/hooks/usePythinkerVersion.ts
 _____________________________________________________
< Making Elsa proud by letting it go...to production. >
 -----------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

This PR hardens interrupt handling for background tasks, strengthens recall context framing to prevent hallucination, normalizes todo status fields, reorders web auth token consumption, improves UI rendering for tables and reports, exposes version information to the frontend, consolidates banner assets, and enforces web origin deduplication.

Changes

Interrupt handling, recall hardening, and UI improvements

Layer / File(s) Summary
Background task turn registry and interrupt cleanup
src/pythinker_code/background/manager.py, src/pythinker_code/ui/shell/__init__.py, tests/background/test_manager.py, tests/ui_and_conv/test_shell_interrupt_cleanup.py
BackgroundTaskManager tracks task IDs per interactive turn via _current_turn_task_ids set. Shell calls begin_turn() when prompt view is ready and before each queued message drain. On user ESC interruption, shell calls kill_turn_tasks() to stop turn-spawned background tasks and reports count. Earlier turns' tasks survive; only current turn's work is killed.
Recall block hardening for context-only framing
src/pythinker_code/memory/recall.py, tests/core/test_recall_provider.py
"Relevant project memory" preface expanded with explicit reference-only guidance preventing instruction misinterpretation. Past-session todos heading updated to state "reference only — do not resume unprompted" instead of generic wording. Tests verify framing blocks hallucination of todo-resumption directives.
Todo status alias normalization
src/pythinker_code/tools/todo/__init__.py, tests/tools/test_todo.py
TodoStatus literal type introduced. Field validator normalizes incoming status strings (trim, lowercase, space/hyphen → underscore) and maps common aliases (complete, completed, finisheddone; canceledcancelled). Unknown normalized values pass through unchanged. Regression tests cover alias handling in both Params coercion and output persistence.
Markdown table stacking and report prose rendering
src/pythinker_code/ui/shell/components/markdown.py, src/pythinker_code/ui/shell/components/report.py, src/pythinker_code/ui/shell/tool_renderers/agent.py, tests/ui/test_shell_markdown.py, tests/ui_and_conv/test_report.py
ReportTableElement detects wide/multi-column tables and renders as stacked records (bullet-prefixed titles + label/value grids with em-dash placeholders). Report prose parser detects top-level Label: body sections and renders structured layout only when no report-fence JSON found. Agent rows display status glyphs (✘/✓/●/○ mapped to error/success/active/idle). Finding and panel title styling updated (border and warning tokens). Tests updated for stacked output and new prose rendering.
Web auth token consumption at bootstrap
web/src/App.tsx, web/src/bootstrap.tsx, web/package.json
Auth token handling moved from App.tsx mount effect to bootstrap.tsx top-level before React mounts, preventing stale tokens in initial data fetches. ai package upgraded from ^5.0.99 to ^6.0.199.
Backend version exposure to frontend
src/pythinker_code/web/api/config.py, web/src/lib/api/models/GlobalConfig.ts, web/src/hooks/usePythinkerVersion.ts, web/src/components/pythinker-code-brand.tsx
GlobalConfig.version field added to config API snapshot. usePythinkerVersion hook fetches backend version on mount with module-level cache and build-time fallback. PythinkerCodeBrand wired to hook instead of static import for dynamic version display.
Shared banner art constant
src/pythinker_code/utils/server.py, src/pythinker_code/vis/app.py, src/pythinker_code/web/app.py
PYTHINKER_BANNER_ART extracted as module constant eliminating hardcoded duplication across vis/app and web/app startup banners.
Web origins auto-population and enforcement
src/pythinker_code/web/app.py, tests/web/test_web_origins.py
Web server auto-populates allowed origins whenever normalized list is empty (removed public_mode gate). Generated origins de-duplicated before assignment. Tests verify localhost and 127.0.0.1 both included without duplication and confirm empty allowlist rejects all origins.
Task documentation
tasks/todo.md
New dated section (2026-06-10) documents root causes, completed fixes across recall/background/shell/web, test coverage, and scope boundaries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

bug, dependencies

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.17% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (fix scope) and accurately summarizes the main changes: web origins/WS, interrupt cleanup, recall framing, and token bootstrap.
Description check ✅ Passed Description is comprehensive with clear sections covering web origins, TUI rendering, interrupt/recall hardening, and dependency advisory; includes testing details. All template sections are present or reasonably addressed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/web-origins-banner-version

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pythinker_code/web/api/config.py`:
- Around line 121-124: The try/except around get_version() silently swallows
errors; update the except to capture the exception (e.g., except Exception as e)
and log it before falling back to cli_version = ""—use the module logger (or
logging.getLogger(__name__)) and logger.exception or logger.error(...,
exc_info=True) so the failure of get_version() is observable while preserving
the existing fallback behavior for cli_version.

In `@tests/web/test_web_origins.py`:
- Around line 47-73: The test's fake_uvicorn_run callback declares an unused
positional parameter named args which triggers linters; rename that parameter to
_args or _ (in the fake_uvicorn_run definition inside
test_local_mode_populates_allowed_origins) to indicate it is intentionally
unused, keeping the kwargs handling and captured_port logic and leaving
monkeypatch.setattr("uvicorn.run", fake_uvicorn_run) and calls to run_web_server
unchanged.

In `@web/src/hooks/usePythinkerVersion.ts`:
- Around line 7-17: The current fetch logic uses the module-level
serverVersionPromise and never resets it, so a failed fetch permanently blocks
retries; update the fetch flow so that when fetchServerVersion (or the code that
assigns serverVersionPromise) catches an error or returns null it resets
serverVersionPromise = null and logs the error (use processLogger or console) to
allow a subsequent mount to retry; specifically modify fetchServerVersion's
catch block (or the promise assignment site that uses
apiClient.config.getGlobalConfigApiConfigGet) to clear serverVersionPromise on
failure and emit a brief error log.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a96cf52-99d0-48e1-87ac-4855904c71d3

📥 Commits

Reviewing files that changed from the base of the PR and between 9996984 and f531418.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • src/pythinker_code/background/manager.py
  • src/pythinker_code/memory/recall.py
  • src/pythinker_code/tools/todo/__init__.py
  • src/pythinker_code/ui/shell/__init__.py
  • src/pythinker_code/ui/shell/components/markdown.py
  • src/pythinker_code/ui/shell/components/report.py
  • src/pythinker_code/ui/shell/tool_renderers/agent.py
  • src/pythinker_code/utils/server.py
  • src/pythinker_code/vis/app.py
  • src/pythinker_code/web/api/config.py
  • src/pythinker_code/web/app.py
  • tasks/todo.md
  • tests/background/test_manager.py
  • tests/core/test_recall_provider.py
  • tests/tools/test_todo.py
  • tests/ui/test_shell_markdown.py
  • tests/ui_and_conv/test_report.py
  • tests/ui_and_conv/test_shell_interrupt_cleanup.py
  • tests/web/test_web_origins.py
  • web/package.json
  • web/src/App.tsx
  • web/src/bootstrap.tsx
  • web/src/components/pythinker-code-brand.tsx
  • web/src/hooks/usePythinkerVersion.ts
  • web/src/lib/api/models/GlobalConfig.ts
💤 Files with no reviewable changes (1)
  • web/src/App.tsx

Comment thread src/pythinker_code/web/api/config.py
Comment thread tests/web/test_web_origins.py
Comment thread web/src/hooks/usePythinkerVersion.ts
- 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
@elkaix
elkaix merged commit 312dc8c into main Jun 10, 2026
35 checks passed
@elkaix
elkaix deleted the fix/web-origins-banner-version branch July 17, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant