Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1129b7c
fix(agent-core-v2): allow mid-turn progress notes and calm NotifyUser…
elkaix Oct 15, 2026
889b599
fix(agent-core-v2): restore media tools after session resume
elkaix Oct 15, 2026
cfc34f6
fix(cli): report cumulative survey user turns
elkaix Oct 15, 2026
5156848
fix(tui): show Working… in the status spinner while thinking
elkaix Oct 15, 2026
9daeac6
fix(agent-core-v2): keep fork subagent request prefix identical to pa…
elkaix Oct 15, 2026
bd32c08
fix(tui): show shutdown progress and force exit on Ctrl+C during shut…
elkaix Oct 15, 2026
ba9d22c
feat(agent-core-v2): add PYTHINKER_CODE_LLM_HEADERS_TIMEOUT_MS for th…
elkaix Oct 15, 2026
ec2ca10
fix(remote-control): exit the web server when the relay takes the dev…
elkaix Oct 15, 2026
dd28e59
fix: cancel in-flight video uploads when media reads are interrupted
elkaix Oct 15, 2026
2b74ad5
feat(agent-core-v2): distinguish session-unavailable tools and explai…
elkaix Oct 15, 2026
2c69bc5
refactor(tower): coordinate missions with deterministic orchestration
elkaix Oct 15, 2026
2e527c4
feat(tui): unify skill prompts and steering
elkaix Oct 15, 2026
510ba14
feat(agent-core-v2): report fork request prefix parity in the prompt …
elkaix Oct 15, 2026
fe60e12
feat(agent-core-v2): register plugin skills under qualified names and…
elkaix Oct 15, 2026
0bd2cb0
fix(tui): keep the previous answer visible when a task-notification t…
elkaix Oct 15, 2026
3fa6122
perf(tui): stop blocking the footer on git status
elkaix Oct 15, 2026
6ad4878
feat(agent-core-v2): add a global default for deferred MCP tool discl…
elkaix Oct 15, 2026
53b517c
test(tower): wait for both skip logs instead of a fixed delay
elkaix Oct 15, 2026
fdd7841
fix(google-genai): use thinking level for Gemini 4 models and map off…
elkaix Oct 15, 2026
47bdf42
feat: give NotifyUser updates an optional one-line title
elkaix Oct 15, 2026
b966310
fix(vscode): add back navigation to AskUserQuestion
elkaix Oct 15, 2026
c9c0a3c
fix(agent-core-v2): nudge the agent to keep its todo list current
elkaix Oct 15, 2026
fca7e1d
fix(gateway): run agent hooks in the session's working directory
elkaix Oct 15, 2026
508b88c
fix(agent-core-v2): align subagent tool descriptions with availability
elkaix Oct 15, 2026
cd033b0
fix(cli): skip workspace binaries during command lookup instead of fa…
elkaix Oct 15, 2026
ebe9225
docs(tower): add a Tower multi-agent coordination guide
elkaix Oct 15, 2026
fa24a04
feat: support model migration metadata from provider registries
elkaix Oct 15, 2026
7f1ac78
chore(web): restage the web bundle manifest
elkaix Oct 15, 2026
4e20ad4
docs: say registry instead of managed for model upgrade metadata
elkaix Oct 15, 2026
7fb244e
chore: add changesets for the reconciled fixes
elkaix Oct 15, 2026
5e66b42
chore: remove the unused pyaos package and MCP pyaos executor option
elkaix Oct 15, 2026
8e5f85d
feat(remote-control): send large HTTP responses in chunks
elkaix Oct 15, 2026
c97b96e
fix: clear CodeQL alerts in provider routes and Remote Control rewrite
elkaix Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .agents/skills/gen-changesets/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Use when generating changesets in the pythinker-code repository —

# Generate Changesets

The only user-facing published package is the CLI: `@pymodel/pythinker-code`. All other `@pymodel/*` packages (sdk, agent-core, kosong, pyaos, oauth, telemetry, and so on) are internal.
The only user-facing published package is the CLI: `@pymodel/pythinker-code`. All other `@pymodel/*` packages (sdk, agent-core, kosong, oauth, telemetry, and so on) are internal.

## 1. Whether to Write

Expand Down
5 changes: 5 additions & 0 deletions .changeset/calm-progress-nudges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Stop discouraging short progress notes between tool calls, and count NotifyUser reminders per round of tool calls.
5 changes: 5 additions & 0 deletions .changeset/cancel-video-uploads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Cancel in-flight video uploads when a media read is interrupted.
5 changes: 5 additions & 0 deletions .changeset/command-lookup-workspace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Skip executables inside the workspace during command lookup instead of failing, including when the working directory is the filesystem root.
5 changes: 5 additions & 0 deletions .changeset/footer-git-refresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Stop the footer from waiting on git status; it now refreshes when the repository changes.
5 changes: 5 additions & 0 deletions .changeset/fork-subagent-cache-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Keep forked subagent requests on the same prefix as the parent so they reuse the prompt cache.
5 changes: 5 additions & 0 deletions .changeset/gemini-thinking-level.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Use thinking levels for Gemini 4 models, and send the lowest level when thinking is off.
5 changes: 5 additions & 0 deletions .changeset/hooks-session-cwd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Run agent hooks in the session's working directory in the web and desktop apps.
5 changes: 5 additions & 0 deletions .changeset/inline-skill-prompts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Show skills invoked in a prompt as highlighted tokens in the message, and let Ctrl+S steer a skill prompt into a running turn.
5 changes: 5 additions & 0 deletions .changeset/llm-headers-timeout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Add PYTHINKER_CODE_LLM_HEADERS_TIMEOUT_MS to set how long a request waits for LLM response headers, for slow non-streaming thinking.
5 changes: 5 additions & 0 deletions .changeset/mcp-default-deferred.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Add `default_deferred` under `[mcp]` in config.toml to load the tools of every MCP server on demand; it requires the experimental tool-select feature.
5 changes: 5 additions & 0 deletions .changeset/media-tools-after-resume.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Restore the image and video reading tools after resuming a session.
5 changes: 5 additions & 0 deletions .changeset/model-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": minor
---

When a provider registry retires your default model, switch to the replacement it declares and show its migration notes.
5 changes: 5 additions & 0 deletions .changeset/notify-user-title.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Give NotifyUser updates an optional one-line title in the Updates panel. Enable NotifyUser with PYTHINKER_CODE_EXPERIMENTAL_NOTIFY_USER=1.
5 changes: 5 additions & 0 deletions .changeset/plugin-skill-qualified-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Register plugin skills as `<plugin-id>:<skill-name>`; a bare skill name no longer runs a plugin skill and there is no setting to restore that, so use `/skill:<plugin-id>:<skill-name>`. Plugin names can now contain uppercase letters, dots, and an `@` suffix.
5 changes: 5 additions & 0 deletions .changeset/registry-reimport-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Keep the default model and provider when re-importing a provider registry.
5 changes: 5 additions & 0 deletions .changeset/relay-offline-exit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Exit `pythinker web` when the Remote Control relay takes the device offline.
5 changes: 5 additions & 0 deletions .changeset/remote-control-large-transfers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Remote Control now sends HTTP responses larger than 10 MiB in 5 MiB chunks, up to about 100 MiB, and cancels a transfer when the relay aborts it.
5 changes: 5 additions & 0 deletions .changeset/remove-mcp-pyaos-executor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Remove the never-implemented `executor = "pyaos"` option (and its old `"kaos"` alias) from MCP stdio server configuration; configs that use it now fail validation at load time.
5 changes: 5 additions & 0 deletions .changeset/select-tools-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Explain why select_tools cannot load a tool, and report tools that are not available in the current session separately.
5 changes: 5 additions & 0 deletions .changeset/shutdown-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Show shutdown progress while shutdown hooks run, and exit at once when you press Ctrl+C again.
5 changes: 5 additions & 0 deletions .changeset/subagent-tool-descriptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Describe only the tools a subagent can actually use.
5 changes: 5 additions & 0 deletions .changeset/task-notification-answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Keep the previous answer visible when a background task notification turn ends, and show cancelled background agents as stopped.
5 changes: 5 additions & 0 deletions .changeset/todo-list-nudge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Remind the agent to update its todo list when items stay open for several steps.
5 changes: 5 additions & 0 deletions .changeset/tower-deterministic-missions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": minor
---

Tower now merges and reviews missions through deterministic git checks, adds the TowerComplete and TowerRebase tools, and blocks destructive shell commands in the main checkout. Enable Tower with PYTHINKER_CODE_EXPERIMENTAL_TOWER=1.
5 changes: 5 additions & 0 deletions .changeset/vscode-question-back.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"pythinker": patch
---

Add Back navigation to multi-question prompts and keep earlier answers while moving between questions.
5 changes: 5 additions & 0 deletions .changeset/working-spinner-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pymodel/pythinker-code": patch
---

Show Working… instead of Thinking… in the status spinner while the model thinks.
1 change: 0 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ Adding an OpenAI-compatible provider requires **zero code changes** — just add
| `packages/agent-core-v2` | DI × Scope agent engine (the v2 port behind agent-gateway) | Three `LifecycleScope` tiers — `App` / `Session` / `Agent` (`app/scopes.ts`). Workspace resources use App-owned `WorkspaceInstance` / `Program` lifetimes, not a DI scope. Also includes the L3 unit layer (`Service`/`Fiber` units, collection contribution points, the Feature seam in `src/features/`). See its `AGENTS.md` and use the `agent-core-dev` skill. |
| `packages/node-sdk` | Public TS SDK & harness | |
| `packages/kosong` | LLM provider abstraction | Wire types, catalog, capability registry. |
| `packages/pyaos` | Execution environment | File/process abstractions. |
| `packages/agent-gateway` | Pythinker Code server | Fastify server backed by `@pymodel/agent-core-v2`; sessions over REST + WebSocket (`/api/v1` + `/api/v1/ws`), plus `/api/v1/debug/*` reflection RPC (`--debug-endpoints`, loopback bind + bearer auth). See its `AGENTS.md`. |
| `packages/klient` | Client SDK | Contract-driven facade over agent-core-v2 (`global.*` / `session(id).*` / `agent(id).*`, zod-validated); transport via subpath entry (`@pymodel/klient/ipc|memory`); hosts the e2e suites. See its `AGENTS.md`. |
| `packages/transcript` | Isomorphic transcript rendering data layer | L1 agent-granular store, L2 idempotent operations, L3 `off/turn/block/delta` subscription granularity, L4 framework-free view registry, turn-cursor pagination. Pure TypeScript (browser-safe, no engine imports); sole owner of the transcript contract types (`src/contract/`). See its `AGENTS.md`. |
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This is a pnpm monorepo. The most relevant entry points are:
- `apps/vis` — session debug visualizer
- `packages/node-sdk` — public TypeScript SDK (`@pymodel/pythinker-code-sdk`)
- `packages/agent-core-v2` — the agent engine (v2, DI Scope architecture); `packages/agent-core` is v1 and being phased out
- `packages/klient`, `agent-gateway`, `protocol`, `transcript`, `kosong`, `pyaos`, `oauth`, `telemetry` — internal engine packages
- `packages/klient`, `agent-gateway`, `protocol`, `transcript`, `kosong`, `oauth`, `telemetry` — internal engine packages
- `docs/` — VitePress bilingual docs site

For the full project map, see [AGENTS.md](AGENTS.md).
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ Pythinker Code is a pnpm monorepo. The desktop app and the CLI both talk to the
| `apps/pythinker-web` | Browser interface that the desktop app renders |
| `packages/agent-core` | Agent engine: sessions, tools, skills, permissions, plans |
| `packages/kosong` | LLM and provider abstraction |
| `packages/pyaos` | Execution environment, file and process abstractions |
| `packages/server` | REST and WebSocket session host (`/api/v1`) |
| `packages/node-sdk` | Public TypeScript SDK |

Expand Down
2 changes: 1 addition & 1 deletion apps/pythinker-code/dist-web/.web-bundle-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"sourceHash": "b03226b74b69912ccace38e0a803ee98077478208348852dc4857429e06abc33",
"sourceHash": "85ca51758dad0358848603e07154c341133ae7d7f92aca71ecc47039255b4072",
"sourceFileCount": 497
}
1 change: 1 addition & 0 deletions apps/pythinker-code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"node": ">=22.19.0"
},
"dependencies": {
"chokidar": "^4.0.3",
"qrcode": "^1.5.4",
"ws": "^8.21.3"
}
Expand Down
8 changes: 4 additions & 4 deletions apps/pythinker-code/src/cli/sub/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
* a provider, or inspect what is configured without launching the TUI.
*
* `add` writes the same `source = { kind: 'apiJson', url, apiKey }` blob the
* TUI does; the next launch's `refreshAllProviderModels`
* (apps/pythinker-code/src/tui/utils/refresh-providers.ts) groups by URL, retries
* available API-key candidates, and re-fetches the model list, so periodic
* refresh is automatic.
* TUI does; the next launch's provider-model refresh
* (`PythinkerHarness.refreshProviderModels`, backed by the engine's discovery
* service) groups by URL, retries available API-key candidates, and
* re-fetches the model list, so periodic refresh is automatic.
*/

import {
Expand Down
3 changes: 3 additions & 0 deletions apps/pythinker-code/src/cli/sub/web/remote-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export {
} from '@pymodel/remote-control';
export type {
ParsedRawHttpRequest,
RemoteControlCloseReason,
RemoteControlHandle,
RemoteControlLock,
RemoteControlLockInfo,
Expand Down Expand Up @@ -88,6 +89,8 @@ export function formatRemoteControlStatus(status: RemoteControlStatus): string {
return ` ${value('✓')} ${label('Connected to relay, waiting for remote devices…')}\n`;
case 'relay_disconnected':
return ` ${value('!')} ${label('Relay disconnected; reconnecting…')}\n`;
case 'relay_terminated':
return ` ${value('✕')} ${label('Taken offline from the web; exiting…')}\n`;
case 'device_connected':
return ` ${value('✓')} ${label('Remote device connected (1 active session)')}\n`;
case 'device_disconnected':
Expand Down
26 changes: 23 additions & 3 deletions apps/pythinker-code/src/cli/sub/web/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,14 @@ export interface WebCliOptions extends ServerCliOptions {
remoteControl?: boolean;
}

export interface ForegroundControls {
/** Ask the foreground runner to shut the server down and exit (like SIGINT). */
requestShutdown(reason: string): void;
}

export interface StartForegroundHooks {
/** Fires once the server is listening, before the foreground runner blocks. */
onReady?: (origin: string) => void | Promise<void>;
onReady?: (origin: string, controls?: ForegroundControls) => void | Promise<void>;
onShutdown?: (reason: string) => void | Promise<void>;
}

Expand Down Expand Up @@ -207,7 +212,7 @@ export async function handleWebCommand(
const run = deps.startServerForeground ?? startServerForeground;
let remoteControl: RemoteControlHandle | undefined;
await run(parsed, {
onReady: async (origin) => {
onReady: async (origin, controls) => {
// Resolve the persistent token only once the server is up: a fresh
// server writes `server.token` on first boot, so reading it beforehand
// would miss first-time starts and the browser would hit the auth gate.
Expand Down Expand Up @@ -238,6 +243,19 @@ export async function handleWebCommand(
stderr: deps.stderr,
onStatus,
});
void remoteControl.closed.then(
(reason) => {
if (reason === 'user_requested') {
controls?.requestShutdown('remote_control_user_requested');
}
},
(error) => {
deps.stderr.write(
`Remote Control lock release failed: ${error instanceof Error ? error.message : String(error)}\n`,
);
controls?.requestShutdown('remote_control_closed');
},
);
const qrCode = await generateRemoteControlQr(remoteControl.url, dataDir);
deps.stdout.write(
formatRemoteControlOutput({
Expand Down Expand Up @@ -410,7 +428,9 @@ async function runServerInProcess(
running.logger.info({ address: running.address }, 'server ready');

try {
await hooks.onReady?.(running.address);
await hooks.onReady?.(running.address, {
requestShutdown: (reason) => void shutdown(reason),
});
} catch (error) {
try {
await hooks.onShutdown?.('startup_failed');
Expand Down
1 change: 1 addition & 0 deletions apps/pythinker-code/src/constant/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const PYTHINKER_CODE_BANNER_DIR_NAME = 'banner';
export const PYTHINKER_CODE_BANNER_STATE_FILE_NAME = 'state.json';
export const PYTHINKER_CODE_SURVEY_STATE_FILE_NAME = 'feedback-survey-state.json';
export const PYTHINKER_CODE_RECOMMENDED_EFFORT_STATE_FILE_NAME = 'recommended-effort-state.json';
export const PYTHINKER_CODE_MODEL_MIGRATION_STATE_FILE_NAME = 'model-migrations.json';

// Managed Pythinker auth provider key shared with OAuth/SDK config.
export const DEFAULT_OAUTH_PROVIDER_NAME = 'openai';
Expand Down
Loading
Loading