Skip to content

chore: upstream reconcile through reference 99eaa993b - #323

Merged
elkaix merged 132 commits into
mainfrom
sync/upstream-reconcile-2026-09-19
Sep 19, 2026
Merged

elkaix merged 132 commits into
mainfrom
sync/upstream-reconcile-2026-09-19

Conversation

@elkaix

@elkaix elkaix commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Lands the local upstream-reconcile wave onto PyModel/pythinker-code main.

  • 57 commits on local main ahead of origin/main (268d6914c → d91c801a4)
  • Reference head-seen through 99eaa993b
  • Pythinker identity preserved (English-only, managed-service stripped, brand cleaned)
  • Local-only waves were verified with focused tsc / leak-check before each land

Highlights (recent tips)

  • Graduate session titles, compaction limit, fold prompt queue, deferred MCP tools
  • reasoning_content / OpenRouter multi-key, usage quota model, pi-tui rebaseline
  • Session interaction events, drop flat entity protocol, oauth goods_version parse
  • Turn ids + cold transcript fold; cwd/project-root prompt fix; no duplicate turn.steer; fs-watch default off
  • Engine-wave / remote-control / brand-strip batches earlier in the stack

Test plan

  • CI green on this PR
  • Spot-check agent-core-v2 + agent-gateway typecheck
  • Confirm no kimi/moonshot/managed-service leaks in tracked paths

elkaix added 30 commits August 18, 2026 21:21
Close the session, wait for listeners to finish journal and live-state
cleanup, then remove it and broadcast a workspace-scoped deletion event
so clients can drop the session without races.
Arm terminal-notification suppression at the start of agent teardown so
late background-task settlements cannot wake the model during quiescence
or after the agent is gone.
Save MCP media that the model cannot take into session attachment
storage, surface recoverable references in the tool result, and let
Read open those daemon attachment URLs.
Mark sessions dirty on mirror writes and reconcile only those sessions
on restart, replacing full mtime rescans with a schema-versioned
checkpoint and session count.
Expose suggestFiles on the SDK and prefer the engine fs suggest path for
extension @ mentions, with the existing directory browse and local search
kept as fallbacks.
Seed config synchronously so experimental tower assembles at startup, and
return typed enter failures so clients can name the real blocker.
Replace the agent telemetry context service with parent-linked ambient
context so session, agent, and turn fields reach every event, and bind
scopes from session and agent lifecycle.
Keep the free-row sweep acceptance ledger untracked.
Import a rebranded full-tree snapshot for the deferred engine wave so
local main can merge it through the recorded sync path.
Land the rebranded engine-wave freeze on a sync branch so deferred
loop-machine and related rows can clear through the recorded merge path.
Keep the English-only product surface after the vendor merge.
Keep a single implementation after the engine-wave snapshot merge left
two copies of the helper in the same file.
Restore silently deleted kosong and protocol trees, drop orphaned
pre-machine AgentRuntime and turn-budget services, and reapply the
local auth surface so the stripped oauth package matches callers.
Align config, kosong, session, and workspace surfaces after the
engine-wave vendor merge, strip managed oauth callers, and quarantine
obsolete tests that still target deleted pre-machine APIs.
Drop Chinese PR/workflow shipping text brought in by the engine-wave
snapshot so the sync branch can clear the English-only brand gate.
Replace Chinese comments and docs residue from the engine-wave snapshot
with English so the sync branch does not grow the English-only surface.
Clear remaining Han fixture text and rewrite product CDN/site hosts
away from kimi.com product surfaces while keeping provider API hosts.
Strip Han from migration snapshot fixtures and drop product-host
parentheticals from login region flags.
Align docs, install URLs, telemetry endpoints, and tests with the
Pythinker product hostnames after the engine-wave import.
Bring the loop-machine and human-layer engine snapshot onto main after
typecheck and brand gates cleared on the sync branch.
Port Settings Plugins marketplace, permission labels, Esc panel behavior,
diff wrap, mention Tab-complete, splash stages, file-preview refresh,
skill reload, thicker scrollbar, media ordinals/mention/reorder, and
tower mode UI; restage dist-web via build:web without adopting reference
bundles.
Large tunnel payloads no longer trip V8 Irregexp stack overflow. Also
point credential load at resolveOAuthTokenStorageName after the engine-wave
import drift.
Rewritten assets keep a weak ETag so unchanged GETs/HEADs can return 304
instead of retransferring the body.
Strategies return self-describing records with optional prepare effects;
the engine chains credentials recovery ahead of configured strategies.
PYTHINKER_CODE_PERMISSION_MODE_REMINDER=0 skips injecting the auto-mode
reminder into model context.
Notify the turn via llm.request.retrying so interrupted tool-call streams
cannot leak into the next attempt; settle duplicate tool call ids safely.
…agents

Rebuild wire persistence so the machine journal projects the same file,
flip agent DI ownership to session-spawned actors, and add human-domain
compaction. Share model-catalog error codes between kosong and llm-adapter
so barrel imports no longer double-register provider.not_found.
Remove the experimental auto_session_title flag so title generation is always
available as a surface. Keep the local stub backend (no managed title call).
Restore the oauth toolkit modules required by the SDK harness and fix engine
import paths broken by the human-wire rename.
Moon waiting-state unit tests expect MOON_SPINNER_FRAMES. SDK/host
header tests assert User-Agent only under the product boundary.
Avoid hard-coded pythinker providerType gate in env overlay. Align
unsigned feedback signup URL and moon-loader verb label assertions.
…feedback

Restore oauth formatDuration seconds→human, register codex login routes,
relax brittle compaction token fixtures, restore feedback attachments/codebase.
Local-only until 0122b22 matrix settles.
Complete storage stub size hooks, isolate workspace add-dir fixtures,
restore ellipsis assertions stripped as brand false-positives.
Align RC setup copy assertion with the three-step product text. Add
missing listWorkspaceSkills on the vscode bridge harness mock.
…logout

Unpack LocalPlatformSelection and route non-device login through SDK
runLogin. Default thinking for non-current model picks. Isolate logout
target fixture. Swallow Codex OAuth abort on cancel mid-config.
… API

Reinstate FeedbackAttachmentLevel prompts and input dialog. Add auth
create/complete feedback upload methods. Coerce feedbackId to number.
…fixtures

Guard catalogProviderId and normalize string/object platform selection.
Restore TUI banner ● logo path, Remote Control relayKey resolution, V2
export harness, drainStdio exit, fd no auto-download, task/roster routing
wire, Chinese search fixtures, and related test/fixture alignment.
Mock rg/spawn for toolExecutor Glob spill recovery so CI without system
rg stays green. Align klient model catalog set_default URL with fixture
model id. Stub getActiveToolNames on tower profile mocks; optional-call
in tower service.
Use mkdtemp for doctor/config/reload/tower fixtures. Unbiased task-id
alphabet sampling. Strict Vertex host parse. Cache-dir originals with
O_EXCL write. Config domain allowlist. Inspect joinApiUrl + loopback-only
base/WS. CodeQL paths-ignore for committed dist-web vendor bundles.
Drop catch comments in towerFeature tests. Always spawn
C:\Windows\System32\cmd.exe for .bat/.cmd (never ComSpec).
Comment thread packages/agent-core-v2/test/mcpCore/connection-manager.test.ts
Keep a single braille activity spinner for waiting, tool, and progress UI.
@elkaix
elkaix merged commit 96d1a0c into main Sep 19, 2026
25 checks passed
@elkaix
elkaix deleted the sync/upstream-reconcile-2026-09-19 branch September 19, 2026 22:34
elkaix pushed a commit that referenced this pull request Sep 20, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @pymodel/pythinker-code@0.43.0

### Minor Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Delete sessions from the
session picker: press Ctrl+X on a session, then y to confirm.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Drop the experimental
flat entity message protocol and history API; session inspect returns to
the transcript surface.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Add per-server deferred
MCP tool disclosure so large tool lists stay hidden until selected.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Delete sessions with
serialized cleanup so journal and live listeners finish before the
session is removed, and broadcast a workspace-scoped deletion event.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Rebuild telemetry as a
scope-bound layered context registry so session, agent, and turn fields
flow into every event without a separate agent telemetry context
service.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Serve workspace @ file
suggestions through the engine fs suggest API so the editor extension
matches gateway search scoring.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Turn filesystem watch off
by default. Set `[watch] enabled = true` or `PYTHINKER_CODE_WATCH=1` to
attach watchers.

### Patch Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Make the compaction
attempt limit configurable with loop_control.compaction_max_attempts
(default 5).

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Stop telling the model
that the current working directory is always the project root.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Honor
dynamically_loaded_tools from official model catalogs when building tool
lists.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Preserve original MCP
attachments that are omitted from model output, and let Read open those
session attachment references.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Exclude time spent with
the session closed from goal time budgets.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Rebaseline the terminal
UI library native platform modules and mouse/search behavior.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Include the server token
in the Remote Control Local UI link so it opens already signed in.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Remove the 24-hour limit
on goal time budgets.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Skip the confirmation
prompt for rm -rf commands that target only /tmp or /temp paths.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Track session-index
freshness with a dirty journal so restarts reconcile only changed
sessions instead of rescanning every mtime.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Deliver session approval
and question events past agent filters, and stamp the requesting agent
on the wire shape.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Session title generation
no longer requires the experimental auto_session_title flag.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Allow steering messages
to interrupt waits for background tasks.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Do not record a steer
event when an unconsumed steer seeds the next turn after cancel.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Suppress background-task
terminal notifications as soon as an agent starts closing, so teardown
no longer wakes the model with late task settlements.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Tower mode (experimental,
`PYTHINKER_CODE_EXPERIMENTAL_TOWER=1`): fix tower mode never starting
when enabled through `[experimental] tower = true` in `config.toml`
instead of the environment variable. When tower mode cannot be enabled,
the error now names the actual blocker — the disabled experiment, a
required restart, or the owning session. When another live session owns
the workspace tower, the message also names the owning session's title
alongside its id. /tower now also works in a directory that is not a git
repository — it runs git init and commits what is there (an empty
initial commit for empty directories).

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Tower mode reliability
fixes across messaging, worktrees, and the review-to-merge gate.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Compress Remote Control
tunnel responses with gzip.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Keep live turn ids above
the wire-wide maximum and fold cold transcript rebuilds over the active
branch chain.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Add `-y, --yes` to
`pythinker upgrade` (alias `pythinker update`) to skip the confirmation
prompt and install the update directly.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Warn at startup when a
[models] entry in config.toml is missing the model field and cannot be
used.
## @pymodel/pythinker-desktop@1.2.0

### Minor Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Delete sessions from the
session picker: press Ctrl+X on a session, then y to confirm.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Drop the experimental
flat entity message protocol and history API; session inspect returns to
the transcript surface.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Add per-server deferred
MCP tool disclosure so large tool lists stay hidden until selected.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Delete sessions with
serialized cleanup so journal and live listeners finish before the
session is removed, and broadcast a workspace-scoped deletion event.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Rebuild telemetry as a
scope-bound layered context registry so session, agent, and turn fields
flow into every event without a separate agent telemetry context
service.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Serve workspace @ file
suggestions through the engine fs suggest API so the editor extension
matches gateway search scoring.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Turn filesystem watch off
by default. Set `[watch] enabled = true` or `PYTHINKER_CODE_WATCH=1` to
attach watchers.

### Patch Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Make the compaction
attempt limit configurable with loop_control.compaction_max_attempts
(default 5).

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Stop telling the model
that the current working directory is always the project root.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Honor
dynamically_loaded_tools from official model catalogs when building tool
lists.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Preserve original MCP
attachments that are omitted from model output, and let Read open those
session attachment references.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Exclude time spent with
the session closed from goal time budgets.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Rebaseline the terminal
UI library native platform modules and mouse/search behavior.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Include the server token
in the Remote Control Local UI link so it opens already signed in.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Remove the 24-hour limit
on goal time budgets.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Skip the confirmation
prompt for rm -rf commands that target only /tmp or /temp paths.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Track session-index
freshness with a dirty journal so restarts reconcile only changed
sessions instead of rescanning every mtime.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Deliver session approval
and question events past agent filters, and stamp the requesting agent
on the wire shape.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Session title generation
no longer requires the experimental auto_session_title flag.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Allow steering messages
to interrupt waits for background tasks.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Do not record a steer
event when an unconsumed steer seeds the next turn after cancel.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Suppress background-task
terminal notifications as soon as an agent starts closing, so teardown
no longer wakes the model with late task settlements.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Tower mode (experimental,
`PYTHINKER_CODE_EXPERIMENTAL_TOWER=1`): fix tower mode never starting
when enabled through `[experimental] tower = true` in `config.toml`
instead of the environment variable. When tower mode cannot be enabled,
the error now names the actual blocker — the disabled experiment, a
required restart, or the owning session. When another live session owns
the workspace tower, the message also names the owning session's title
alongside its id. /tower now also works in a directory that is not a git
repository — it runs git init and commits what is there (an empty
initial commit for empty directories).

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Tower mode reliability
fixes across messaging, worktrees, and the review-to-merge gate.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Compress Remote Control
tunnel responses with gzip.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Keep live turn ids above
the wire-wide maximum and fold cold transcript rebuilds over the active
branch chain.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Add `-y, --yes` to
`pythinker upgrade` (alias `pythinker update`) to skip the confirmation
prompt and install the update directly.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Warn at startup when a
[models] entry in config.toml is missing the model field and cannot be
used.
## @pymodel/agent-core-v2@0.5.0

### Minor Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Persist the human runtime
mirror in the agent wire journal and spawn agent DI scopes from the
session layer.

### Patch Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Discard streamed attempt
state when the LLM requester retries below the turn so interrupted
tool-call ids cannot leak into the next attempt.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Compose LLM recovery
strategies outside the turn machine with self-describing proposals and
opaque prepare effects.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Add
`PYTHINKER_CODE_PERMISSION_MODE_REMINDER`: set it to `0` to stop
injecting auto permission-mode reminders into the model context.
## @pymodel/pythinker-code-sdk@0.21.0

### Minor Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Serve workspace @ file
suggestions through the engine fs suggest API so the editor extension
matches gateway search scoring.

### Patch Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Rebuild telemetry as a
scope-bound layered context registry so session, agent, and turn fields
flow into every event without a separate agent telemetry context
service.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Persist the human runtime
mirror in the agent wire journal and spawn agent DI scopes from the
session layer.
## @pymodel/pi-tui@0.85.0

### Minor Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Rebaseline the terminal
UI library native platform modules and mouse/search behavior.
## pythinker@0.7.6

### Patch Changes

- Updated dependencies
[[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c)]:
  - @pymodel/pythinker-code-sdk@0.21.0
  - @pymodel/migration-legacy@0.1.17
## @pymodel/acp-server@0.0.2

### Patch Changes

- Updated dependencies
[[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c)]:
  - @pymodel/agent-core-v2@0.5.0
  - @pymodel/klient@0.1.3
## @pymodel/agent-gateway@0.2.4

### Patch Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Persist the human runtime
mirror in the agent wire journal and spawn agent DI scopes from the
session layer.

- Updated dependencies
[[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c)]:
  - @pymodel/agent-core-v2@0.5.0
  - @pymodel/remote-control@0.0.1
## @pymodel/klient@0.1.3

### Patch Changes

- Updated dependencies
[[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c)]:
  - @pymodel/agent-core-v2@0.5.0
## @pymodel/migration-legacy@0.1.17

### Patch Changes

- Updated dependencies
[[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c)]:
  - @pymodel/agent-core-v2@0.5.0
## @pymodel/remote-control@0.0.1

### Patch Changes

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Validate Remote Control
tunnel request base64 with a linear scan so large payloads no longer
crash the tunnel as a generic connection failure.

- [#323](#323)
[`96d1a0c`](96d1a0c)
Thanks [@elkaix](https://github.com/elkaix)! - Reuse unchanged Remote
Control assets across page loads with ETag validation instead of
retransferring them.

- Updated dependencies
[[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c),
[`96d1a0c`](96d1a0c)]:
  - @pymodel/agent-core-v2@0.5.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
elkaix added a commit that referenced this pull request Sep 20, 2026
## Related Issue

No issue. Unblocks Release on `main`.

## Problem

`#323` copied upstream identity onto `main`:

- CLI version `2.1.0` (already on npm) rewound to `0.42.0`, then `#324`
published a fake `0.43.0` GitHub tag that npm never received
- VS Code publisher `pymodel` (live Marketplace id `pymodel.pythinker`
0.9.7) became `moonshot-ai`, so vsce refused every target: extension
name `pythinker` already exists
- Homebrew 404'd on `pythinker-code-0.42.0.tgz`; native upload looked
for a `@pymodel/pythinker-code@0.42.0` release that does not exist;
consistency check compared local `0.42.0` to npm `latest` `2.1.0`

Required CI (build/test/lint/typecheck/nix) was already green. Only
Release was red.

## What changed

- Restore CLI to `2.1.0` and the 2.x changelog
- Restore VS Code to publisher `pymodel`, id `pymodel.pythinker`,
version `0.9.7`, and the 0.9.x changelog
- Put the `#323` changesets back so the next `ci: release packages` PR
bumps from `2.1.0` (likely `2.2.0`) instead of from `0.43.0`
- Replace leftover `moonshot-ai` branding (capability marker, vis filter
comment, migration doc). Left the Moonshot LLM provider id in
`packages/oauth` — that names `api.moonshot.ai`, not our publisher

After merge, Release should skip-publish npm `2.1.0`, skip-or-no-op
native assets already on that tag, and skip Marketplace `0.9.7`. Then
merge the regenerated version PR to ship the reconcile work on the 2.x
line.

## Checklist

- [x] I have read the CONTRIBUTING document.
- [ ] I have linked a related issue (external PRs: the issue must have a
maintainer's `/approve`.
- [x] I have added tests that prove my feature works.
- [x] Ran  skill, or this PR needs no changeset.
- [x] Ran  skill, or this PR needs no doc update.
EOF
)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added session deletion, workspace file suggestions, configurable
compaction attempts, automatic session titles, noninteractive upgrades,
and improved Tower mode.
- Added deferred MCP tools, attachment access, Remote Control
authentication and performance improvements, and configurable filesystem
watching.
  - Rebranded the VS Code extension as Pythinker.

- **Improvements**
- Improved steering, session cleanup, telemetry, recovery, session
indexing, diagnostics, and model configuration warnings.
- Closed sessions no longer consume goal time, and the goal-time cap was
removed.

- **Breaking Changes**
  - Removed the experimental flat entity protocol and history API.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
elkaix added a commit that referenced this pull request Oct 1, 2026
## Requirement or Bug

Remove the legacy `packages/agent-core-v2/src/kosong` request layer.
Port the features that existed only there and fix the bugs it caused.
Stacked on #349.

## Bug Reproduction Steps

1. Configure a provider with `type = "openai"` and `env = {
OPENAI_API_KEY = "sk-..." }`, with no `apiKey` and no process env key.
2. Start a session and send a turn.
3. The auth check fails with `AuthTokenMissingError`, even though the
request path would find the key.

## Root Cause

20 of the 62 files under `src/kosong` still loaded at runtime through
`app/auth/*` and `app/kosongConfig/*`. kosong keeps its own
provider-definition map, and at runtime that map held only the pythinker
definitions, because the standard definitions never loaded. So
`resolveModelAuthMaterial` / `resolveModelForReady` (auth check) and
`envOverlay` (vendor `*_BASE_URL`) ignored provider-`env` values for
every non-pythinker provider. Real requests use `llm-adapter` and find
them. This is a fundamental fix: one provider-definition registry, the
live one.

## Code Changes

- Delete `src/kosong` (61 files, about 11k lines). The 19 importers now
import the same symbols from `#/llm-adapter/*` (types are identical
apart from the import path).
- Port features that existed only in the deleted copy, each with a test
that fails before and passes after:
- **OpenCode billing errors:** a 401/402/403 whose body says
"insufficient balance", "insufficient credit", "credits exhausted" or
"please recharge" is a provider error, not `provider.auth_error`.
Ordinary 401s stay auth errors.
- **DSML / Hermes tool calls:** tool calls that some models write as
text tags on the chat-completions stream are parsed into real tool
calls.
  - **`modelRecordProviderId`** moved to `llm-adapter/model/model.ts`.
- `apps/vis` imports
`@pymodel/agent-core-v2/llm-adapter/contract/tokens` instead of the
`kosong` subpath.
- `scripts/check-identity-freeze.mjs` drops the deleted kosong path.
- Test fix: an MCP registry test set the wrong home variable, so it did
not isolate the home directory. It now sets `PYTHINKER_CODE_HOME`.

The default-model fallback that also lived only in kosong is **not**
restored: since #323 the gateway tests require that `default_model` is
never rewritten. That is a product decision, tracked in #351.

## Behavior Changes and Affected Users

| Behavior | Before | After | Who relies on the old behavior | Escape
hatch |
|---|---|---|---|---|
| Vendor API key in a provider's `env` table | Auth check ignores it,
turn fails with `AuthTokenMissingError` | Key is found, turn runs |
Nobody (the old behavior was a bug) | n/a |
| Vendor `*_BASE_URL` in an `env` provider of non-pythinker type |
Ignored by `envOverlay` | Applied | Nobody (bug) | Remove the variable
from `env` |
| OpenCode 401/402/403 with a billing message | `provider.auth_error`
("not logged in") | Provider error with the billing message, not retried
| Clients that map `provider.auth_error` to a re-login prompt for this
case | None needed; the old message was wrong |
| Chat-completions stream with DSML/Hermes tool tags | Tags shown as
assistant text, no tool call | Parsed into tool calls. Text that could
start a tag is held back until it is known not to be a tag, and
`llm.streaming.finish` arrives after the stream ends | Nobody relies on
raw tags | None |
| `@pymodel/agent-core-v2/kosong/*` subpath import | Resolves | Gone |
`apps/vis` (updated in this PR); no other consumer found in the repo |
Import from `.../llm-adapter/*` |

Affected modules and coverage:
- `app/auth`: `test/app/auth/auth.test.ts` (provider-env key case, fails
on `main`).
- `app/kosongConfig/envOverlay`: new non-pythinker base-url case (fails
on `main`).
- `human/llm` openai format and stream: billing-error tests and DSML
parser/recovery tests.
- Full suites: agent-core-v2 6,491 pass, agent-gateway 1,407 pass,
vis-server 173 pass; `tsc` and `tsgo` clean; no-comments and
identity-freeze checks pass.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md)
document.
- [x] I have linked a related issue (external PRs: issue must have a
maintainer's `/approve`).
- [x] I have added tests that prove my feature works.
- [x] The behavior-change table above is complete, and every removed
behavior or flipped default is named in the changeset and either has an
escape hatch or was explicitly approved by a maintainer in this PR.
- [x] Ran `gen-changesets` skill, or this PR needs no changeset.
- [x] Ran `gen-docs` skill, or this PR needs no doc update.
elkaix added a commit that referenced this pull request Oct 2, 2026
…355)

## Requirement or Bug

Native `pythinker update` fails with `native binary download returned
HTTP 404`, and the 2.4.0 and 2.4.1 release runs on main end red.
Related: #354.

## Bug Reproduction Steps

1. Install 2.1.0 with the native installer.
2. Run `pythinker update` and accept 2.4.0 (or 2.4.1).
3. `error: failed to download update 2.4.0: native binary download
returned HTTP 404`.

On 2.2.0–2.4.1 the same command fails earlier, because `manifest.json`
comes back as an HTML page.

Release runs 36917123003 (2.4.0) and 36942551995 (2.4.1) fail in `Update
Homebrew tap` and `Release lane summary`.

## Root Cause

Three separate defects. All three are fixed at the root, with no
workarounds.

1. **Missing release asset (affects 2.1.0 clients).**
`produce-manifest.mjs` writes `manifest.json` and `latest.json` entries
that point at the bare binary `pythinker-code-<target>[.exe]`. It hashes
that binary and then deletes it, so only `.zip`, `.zst` and `.tar.gz`
get uploaded. A 2.1.0 client downloads the bare file and gets a 404.
2. **Wrong download base (affects 2.2.0–2.4.1 clients).** #323 moved the
manifest and binary URLs to
`code.pythinker.com/pythinker-code/binaries/<v>/…`. That route has never
served a file: the site answers it with its SPA page and HTTP 200. These
clients cannot update, and only a reinstall fixes them (#354).
3. **Brew job race (the red X).** `assertPublishedNpmVersion` ran `npm
view` once, 24 s after `changeset publish`. npm showed 2.4.1 at
23:56:55, about 4 minutes after the check at 23:53:10. The job failed,
and `Release lane summary` fails whenever `BREW_RESULT` is not success.

## Code Changes

- `produce-manifest.mjs`: copy the binary into the upload set under the
name the manifest uses, with a `.sha256` sidecar. The existing
`dist-native-release/*` upload step then ships it.
- `constant/app.ts`, `native-manifest.ts`, `native-stage.ts`: replace
`pythinkerCodeCdnBinariesBase()` with
`pythinkerCodeReleaseAssetUrl(version, filename)`. It returns the GitHub
release URL with the encoded tag, which is the same shape as 2.1.0 and
as `latest.json`. The `.zst`-first staging path does not change.
- `update-brew-formula.mjs`: delete the one-shot `npm view` check.
`downloadNpmTarball` already polls for 600 s, retries on 404, and
refuses when the budget runs out, so it is the only gate now.
- `cdn-consistency.mjs` + `verify-release-consistency.mjs`: the release
gate now HEADs every URL that `latest.json` and the release
`manifest.json` advertise, and fails on anything unreachable. It retries
403/429. Against the live 2.4.1 release it reports the 6 missing bare
binaries, so it would have caught defect 1 on 2.3.0.
- `README.md`: a pointer for stuck native installs to #354.

## Behavior Changes and Affected Users

| Behavior | Before | After | Who relies on the old behavior | Escape
hatch |
|---|---|---|---|---|
| Native release assets | `.zip`, `.zst`, `.tar.gz` (+ sidecars) | also
the bare binary + `.sha256` | nobody; it adds an asset that manifests
already named | n/a |
| Native client download URLs (built from this tree) |
`code.pythinker.com/pythinker-code/binaries/<v>/…` |
`github.com/PyModel/pythinker-code/releases/download/<encoded tag>/…` |
nobody: the CDN route never returned a file (SPA HTML, 200) | n/a |
| Brew tap job when npm is slow to show the version | fails at once |
polls the tarball up to 600 s | nobody; the old failure was a false
negative | `RELEASE_LANE_BREW=disabled` (unchanged) |
| Release consistency gate | checks version strings only | also fails on
any unreachable advertised download | release operators | none needed;
it only reports real 404s |

Populations:
- Native installs on 2.1.0 and earlier: they update normally from the
next release on (defect 1).
- Native installs on 2.2.0–2.4.1: they need one reinstall, because their
own updater has the dead URL built in (defect 2, #354).
- npm and Homebrew installs: not affected.

Contract file touched: `apps/pythinker-code/src/cli/update/*` (CLI
tripwire). The removed `/binaries/` base never served data, so no client
from a previous release loses anything.

Test coverage:
- `release-artifacts.test.ts`: every file the manifest names exists in
the upload set. Failed before the fix, passes after.
- `native-manifest.test.ts`: exact encoded GitHub URLs.
- `cdn-consistency.test.mjs`: URL collection, 404s, 403/429 retry.
- `update-brew-formula.test.mjs`: the poll is the only gate.
- `pnpm test:release` 54/54; app update suites 325/325; `tsc` clean;
`pnpm lint` 0 errors.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md)
document.
- [x] I have linked a related issue (external PRs: issue must have a
maintainer's `/approve`).
- [x] I have added tests that prove my feature works.
- [x] The behavior-change table above is complete, and every removed
behavior or flipped default is named in the changeset and either has an
escape hatch or was explicitly approved by a maintainer in this PR.
- [x] Ran `gen-changesets` skill, or this PR needs no changeset.
- [x] Ran `gen-docs` skill, or this PR needs no doc update.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Native `pythinker update` now downloads the latest binary from the
GitHub release, restoring updates for supported native installations.
* If you’re using a native install on version 2.2.0–2.4.1, rerun the
installation command once to receive a version that can update normally.
After reinstalling, use `pythinker update` for future updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
elkaix added a commit that referenced this pull request Oct 2, 2026
When the bound model no longer resolves at turn start, switch to the
best-ranked ready model and emit ModelFallbackSwitched, a model-fallback
warning, and model_fallback_triggered telemetry. #323 dropped this.
elkaix added a commit that referenced this pull request Oct 2, 2026
## Requirement or Bug

Resolve #351

## Bug Reproduction Steps

Bind a session to a model, then delete that model (or its provider) from
the config. The next turn fails with an unresolved-model error. Before
#323 it switched to a ready model.

## Root Cause

#323 (upstream reconcile) removed `ensureResolvableModel` from
`AgentProfileService.resolveModelContext`, together with the
`ModelFallbackSwitched` event, the `model_fallback_triggered` telemetry
event, and the ranking policy. This PR restores them in the current
`llm-adapter` layout. Fundamental fix.

## Code Changes

- `llm-adapter/model/default-model-policy.ts`:
`rankDefaultModelCandidates`, ported unchanged from the pre-#323
`kosong/model/defaultModelPolicy.ts`.
- `profileService.ts`: `resolveModelContext(turnId)` runs
`ensureResolvableModel` at turn bind. If the bound alias does not
resolve, it switches to the first ranked candidate that does resolve,
then emits `ModelFallbackSwitched`, a `model-fallback` `WarningIssued`,
and `model_fallback_triggered` telemetry. If no candidate resolves,
nothing changes.
- `llmRequesterService.ts`: passes `turnId` when it builds the per-turn
config. This is the only caller that passes it. Compaction and
credential lookups stay read-only.
- `profileOps.ts` and `telemetry/events.ts`: event and telemetry
definitions restored.
- Tests: `test/agent/profile/modelFallback.test.ts` ports the five
pre-#323 cases. Three of them fail without the fix. Two test DI setups
get `IModelService` and `IProviderService` stubs.

Not restored: the global default-model fallback (`settleDefaultModel`).
As the issue comment says, #323 made `default_model` never be rewritten,
so bringing that back is a product decision.

## Behavior Changes and Affected Users

| Behavior | Before | After | Who relies on the old behavior | Escape
hatch |
| --- | --- | --- | --- | --- |
| Turn start with a bound model that no longer resolves | Turn fails |
Switches to the best-ranked ready model, with a warning and an event |
None found. This restores the 2.0-era behavior from #299 | Pick the
model again with `/model` |
| New observable event `turn.model_fallback.switched` | Not emitted |
Emitted on a switch | Not durable, so no wire or state manifest change
(regenerated, no drift) | None needed |

Affected module: `agent-core-v2` profile. Full suites pass:
agent-core-v2 6498/6498, agent-gateway 1407/1407.

## Checklist

- [x] I have read the
[CONTRIBUTING](https://github.com/PyModel/pythinker-code/blob/main/CONTRIBUTING.md)
document.
- [x] I have linked a related issue (external PRs: issue must have a
maintainer's `/approve`).
- [x] I have added tests that prove my feature works.
- [x] The behavior-change table above is complete, and every removed
behavior or flipped default is named in the changeset and either has an
escape hatch or was explicitly approved by a maintainer in this PR.
- [x] Ran `gen-changesets` skill, or this PR needs no changeset.
- [x] Ran `gen-docs` skill, or this PR needs no doc update.
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.

2 participants