@bubblebrain-ai/bubble 0.0.58 → 0.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -6
- package/dist/agent/child-runner.js +5 -1
- package/dist/agent/internal-reminder-sanitizer.d.ts +1 -1
- package/dist/agent/internal-reminder-sanitizer.js +7 -1
- package/dist/agent/profiles.js +4 -4
- package/dist/agent/routing-catalog.d.ts +19 -1
- package/dist/agent/routing-catalog.js +32 -13
- package/dist/agent/subagent/runtime.d.ts +28 -0
- package/dist/agent/subagent/runtime.js +34 -30
- package/dist/agent/subagent-control.d.ts +7 -0
- package/dist/agent/subagent-control.js +2 -0
- package/dist/agent/subagent-lifecycle-reminder.js +2 -1
- package/dist/agent/subagent-wait-guidance.d.ts +2 -0
- package/dist/agent/subagent-wait-guidance.js +8 -0
- package/dist/agent/subtask-policy.js +4 -4
- package/dist/agent.d.ts +20 -11
- package/dist/agent.js +200 -117
- package/dist/anthropic-thinking.d.ts +4 -0
- package/dist/anthropic-thinking.js +11 -0
- package/dist/approval/controller.d.ts +20 -6
- package/dist/approval/controller.js +67 -8
- package/dist/approval/protected-paths.d.ts +1 -0
- package/dist/approval/protected-paths.js +35 -0
- package/dist/approval/session-cache.d.ts +12 -26
- package/dist/approval/session-cache.js +20 -50
- package/dist/approval/types.d.ts +27 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +25 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +7 -0
- package/dist/context/checkpoint.d.ts +18 -0
- package/dist/context/checkpoint.js +117 -0
- package/dist/context/compact-llm.js +7 -0
- package/dist/context/compact.d.ts +8 -0
- package/dist/context/compact.js +40 -7
- package/dist/context/llm-compactor.d.ts +36 -2
- package/dist/context/llm-compactor.js +227 -44
- package/dist/context/session-write-lock.d.ts +20 -0
- package/dist/context/session-write-lock.js +162 -0
- package/dist/context/tool-images.d.ts +3 -0
- package/dist/context/tool-images.js +35 -0
- package/dist/context/usage.js +22 -5
- package/dist/feishu/agent-host/run-driver.js +56 -9
- package/dist/lsp/service.d.ts +13 -1
- package/dist/lsp/service.js +163 -43
- package/dist/main.js +82 -16
- package/dist/mcp/config.d.ts +4 -0
- package/dist/mcp/config.js +17 -0
- package/dist/mcp/manager.d.ts +13 -1
- package/dist/mcp/manager.js +32 -1
- package/dist/mcp/transports.js +11 -0
- package/dist/memory/db.d.ts +1 -0
- package/dist/memory/db.js +20 -4
- package/dist/memory/phase1.js +122 -16
- package/dist/memory/prompts.js +3 -0
- package/dist/model-catalog.js +19 -3
- package/dist/model-config.d.ts +1 -0
- package/dist/model-config.js +5 -5
- package/dist/model-pricing.js +26 -9
- package/dist/network/provider-transport-log.d.ts +12 -0
- package/dist/network/provider-transport-log.js +32 -0
- package/dist/network/provider-transport.js +6 -1
- package/dist/network/retry.d.ts +1 -1
- package/dist/network/retry.js +1 -1
- package/dist/oauth/grok.d.ts +1 -0
- package/dist/oauth/grok.js +20 -16
- package/dist/oauth/openai-codex.d.ts +1 -0
- package/dist/oauth/openai-codex.js +29 -25
- package/dist/oauth/refresh-control.d.ts +12 -0
- package/dist/oauth/refresh-control.js +42 -0
- package/dist/oauth/storage.d.ts +55 -1
- package/dist/oauth/storage.js +281 -16
- package/dist/permissions/rule.d.ts +6 -3
- package/dist/permissions/rule.js +71 -12
- package/dist/permissions/settings.d.ts +28 -1
- package/dist/permissions/settings.js +93 -3
- package/dist/permissions/shell-command.d.ts +36 -0
- package/dist/permissions/shell-command.js +226 -0
- package/dist/permissions/trust-prompt.d.ts +10 -0
- package/dist/permissions/trust-prompt.js +34 -0
- package/dist/permissions/trust.d.ts +26 -0
- package/dist/permissions/trust.js +113 -0
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +24 -7
- package/dist/prompt/environment.js +2 -2
- package/dist/prompt/reminders.js +1 -1
- package/dist/prompt/runtime.js +1 -1
- package/dist/provider-anthropic.d.ts +6 -2
- package/dist/provider-anthropic.js +98 -18
- package/dist/provider-error-record.d.ts +9 -0
- package/dist/provider-error-record.js +42 -14
- package/dist/provider-grok.js +5 -1
- package/dist/provider-openai-codex.d.ts +8 -0
- package/dist/provider-openai-codex.js +107 -24
- package/dist/provider-registry.d.ts +45 -2
- package/dist/provider-registry.js +300 -88
- package/dist/provider-transform.js +8 -0
- package/dist/provider-zen-errors.d.ts +9 -0
- package/dist/provider-zen-errors.js +36 -0
- package/dist/provider.js +36 -5
- package/dist/sdk/index.d.ts +69 -8
- package/dist/sdk/index.js +252 -39
- package/dist/sdk/replay-event-log.d.ts +7 -0
- package/dist/sdk/replay-event-log.js +66 -16
- package/dist/session-context-fence.d.ts +16 -0
- package/dist/session-context-fence.js +26 -0
- package/dist/session-log.d.ts +11 -2
- package/dist/session-log.js +187 -39
- package/dist/session-types.d.ts +5 -1
- package/dist/session.d.ts +39 -7
- package/dist/session.js +341 -93
- package/dist/skills/format.d.ts +31 -1
- package/dist/skills/format.js +45 -24
- package/dist/slash-commands/commands.d.ts +3 -0
- package/dist/slash-commands/commands.js +29 -14
- package/dist/system-prompt.d.ts +3 -1
- package/dist/tasks/manager.d.ts +7 -0
- package/dist/tasks/manager.js +97 -18
- package/dist/tools/agent-lifecycle.js +11 -7
- package/dist/tools/child-tools.d.ts +1 -1
- package/dist/tools/child-tools.js +4 -4
- package/dist/tools/edit-apply.d.ts +0 -3
- package/dist/tools/edit-apply.js +45 -249
- package/dist/tools/edit.js +3 -3
- package/dist/tools/exit-plan-mode.d.ts +5 -0
- package/dist/tools/exit-plan-mode.js +3 -2
- package/dist/tools/file-state.d.ts +4 -0
- package/dist/tools/file-state.js +12 -3
- package/dist/tools/grep.js +20 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/ls.d.ts +3 -0
- package/dist/tools/ls.js +85 -0
- package/dist/tools/path-utils.d.ts +6 -0
- package/dist/tools/path-utils.js +38 -1
- package/dist/tools/sensitive-paths.d.ts +8 -0
- package/dist/tools/sensitive-paths.js +85 -3
- package/dist/tools/skill.d.ts +2 -0
- package/dist/tools/skill.js +34 -2
- package/dist/tools/task-tools.js +11 -7
- package/dist/tui/app.js +22 -12
- package/dist/tui/components/approval-dialog.d.ts +5 -5
- package/dist/tui/components/approval-dialog.js +16 -51
- package/dist/tui/components/skills-panel.d.ts +4 -0
- package/dist/tui/components/skills-panel.js +15 -1
- package/dist/tui/components/streaming-message.js +7 -2
- package/dist/tui/components/tasks-pane.d.ts +38 -0
- package/dist/tui/components/tasks-pane.js +182 -32
- package/dist/tui/components/transcript.d.ts +11 -4
- package/dist/tui/components/transcript.js +158 -71
- package/dist/tui/composer-autocomplete.d.ts +17 -2
- package/dist/tui/composer-autocomplete.js +79 -2
- package/dist/tui/controller/controller.d.ts +9 -4
- package/dist/tui/controller/controller.js +87 -95
- package/dist/tui/controller/goal-runtime-controller.js +1 -1
- package/dist/tui/controller/session-transition.js +1 -1
- package/dist/tui/model/subagent-view.d.ts +11 -19
- package/dist/tui/model/subagent-view.js +56 -85
- package/dist/tui/model/task-lifecycle.d.ts +57 -0
- package/dist/tui/model/task-lifecycle.js +124 -0
- package/dist/tui/model/trace-groups.js +10 -9
- package/dist/tui/trace-groups.js +10 -9
- package/dist/types.d.ts +20 -0
- package/dist/variant/variant-resolver.js +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +1 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +6 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +2 -0
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -1
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +8 -4
- package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -1
- package/package.json +11 -3
- package/dist/prompt/skills.d.ts +0 -2
- package/dist/prompt/skills.js +0 -4
- package/dist/tools/glob.d.ts +0 -5
- package/dist/tools/glob.js +0 -199
package/README.md
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
# Bubble
|
|
2
2
|
|
|
3
|
-
Bubble is a
|
|
3
|
+
Bubble is a local coding agent with a desktop app and a terminal interface. It works inside a local project folder: it reads and edits files, runs commands behind configurable approval controls, searches and navigates code with language-server intelligence, browses the web, loads reusable skills, connects MCP tools, fans work out to subagents, and keeps persistent memory across sessions.
|
|
4
4
|
|
|
5
5
|
It is provider-agnostic. Bring an API key for OpenAI, Anthropic, Google, DeepSeek, Moonshot/Kimi, Zhipu, Z.AI, MiniMax, Groq, Together, Fireworks, a local OpenAI-compatible endpoint, and more; sign in to ChatGPT with OAuth; or sign in with OAuth to use a ChatGPT or Grok subscription directly.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## Desktop app
|
|
10
|
+
|
|
11
|
+
The desktop app is the primary direction for interactive development. It uses the existing Bubble SDK, providers and session history, with the Aegis desktop layout, working trace, KanBan, automations, pull requests and settings adapted for a standalone Bubble agent. Skills and model selection use Bubble directly.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm ci
|
|
15
|
+
npm run desktop:install
|
|
16
|
+
npm run dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`npm run dev` (or `npm run desktop:dev`) starts Electron with Vite hot updates. UI edits refresh in place; Electron/preload/shared code and Agent SDK edits compile and restart the desktop automatically. The first launch compiles the SDK and host, without a renderer production build. Stop everything with Ctrl+C. Use `npm run dev:tui` for terminal development.
|
|
20
|
+
|
|
21
|
+
`npm run desktop` still builds and starts the desktop without hot updates; `npm --prefix desktop start` opens the last compiled build. For a local macOS dogfood app and DMG, run `npm run desktop:package`; it includes the Agent runtime and never publishes. Use `npm run desktop:build` to check a build and `npm run desktop:test` to run desktop checks. See [desktop architecture and migration notes](docs/desktop-architecture.md) for runtime boundaries, data migration and development commands. The CLI remains available.
|
|
22
|
+
|
|
23
|
+
In **Settings → Providers → Bubble**, OpenAI offers **ChatGPT subscription** browser sign-in and a separate **API key** mode. **Grok Subscription** uses browser sign-in only. You can cancel a pending login or sign out from the same panel; OAuth tokens stay in the selected profile's Agent home and are never shown in the API key editor.
|
|
24
|
+
|
|
25
|
+
Desktop data is separated into three profiles (macOS paths shown):
|
|
26
|
+
|
|
27
|
+
| Command | Purpose | Desktop data | Agent data |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `npm run dev` | Your persistent development/test sessions | `~/Library/Application Support/Bubble Dev` | `~/.bubble-dev` |
|
|
30
|
+
| `npm run desktop:qa` | Agent verification; a fresh profile each launch | System temporary directory `bubble-desktop-qa-*/desktop` | Same temporary root, `agent/` |
|
|
31
|
+
| `npm run desktop` / `npm --prefix desktop start` | Normal use with production data | `~/Library/Application Support/Bubble` | `~/.bubble` |
|
|
32
|
+
|
|
33
|
+
Sessions, board state, automations, UI preferences, model/account configuration, OAuth data, memory and MCP settings follow the selected profile. History import reads only that profile's Agent home. Dev sessions survive restarts; QA uses a different temporary root for every launch (and keeps that root across hot restarts). Configure development models separately: production history and credentials are not automatically copied. Existing production data is left in place. These profiles separate app data, not the project files you ask the Agent to edit.
|
|
34
|
+
|
|
35
|
+
Run automated verification with `npm run desktop:test`; manual/agent desktop verification must use `npm run desktop:qa`. If your development server is already running, use `PORT=4337 npm run desktop:qa`. QA paths are logged at startup and retained in the system temporary directory for diagnostics. The normal launch commands select their own profile and ignore inherited data-directory overrides.
|
|
36
|
+
|
|
9
37
|
## Requirements
|
|
10
38
|
|
|
11
39
|
- Node.js 22.19 or newer — Bubble's only runtime
|
|
@@ -50,7 +78,7 @@ bubble
|
|
|
50
78
|
|
|
51
79
|
On first launch, connect a provider:
|
|
52
80
|
|
|
53
|
-
- Run `/login`
|
|
81
|
+
- Run `/login` and pick an account: ChatGPT (OAuth, Codex models) or Grok Subscription, or
|
|
54
82
|
- Run `/provider` to select Grok Subscription or add a provider with an API key.
|
|
55
83
|
|
|
56
84
|
Then just type what you want done. Bubble plans, edits files, and runs commands, asking for approval where the current permission mode requires it.
|
|
@@ -139,7 +167,13 @@ Turns are driven by the SDK itself, not by iterator consumption: a returned
|
|
|
139
167
|
iterator is a replay subscription, so dropping it never stalls or cancels the
|
|
140
168
|
turn. `openSession(sessionId)` returns a durable handle whose `events` iterable
|
|
141
169
|
replays the whole session log from sequence 1 and `eventsFrom(n)` resumes after
|
|
142
|
-
the last sequence the host durably processed. `
|
|
170
|
+
the last sequence the host durably processed. Snapshot `handle.latestSequence`
|
|
171
|
+
before sending to subscribe only to new work, including automatic fallback
|
|
172
|
+
turns. Each turn ends with a `terminal` record (`completed`, `failed`, or
|
|
173
|
+
`cancelled`) after its execution slot is released. A completed turn may have
|
|
174
|
+
already started a queued successor; hosts should drain buffered records and
|
|
175
|
+
check `getSessionRunState(sessionId).active` before showing the session as idle.
|
|
176
|
+
`stop(sessionId)` is
|
|
143
177
|
Claude-style: it interrupts the active turn and keeps the queue; pass
|
|
144
178
|
`{ cancelQueued: true }` to clear both. `clearQueue(sessionId)` cancels only
|
|
145
179
|
queued turns, and `deleteSession(sessionId)` awaits teardown before the JSONL
|
|
@@ -179,6 +213,18 @@ Built-in providers include OpenAI, Anthropic, Google, DeepSeek, Moonshot (CN and
|
|
|
179
213
|
|
|
180
214
|
For Doubao Seed models on Volcengine Ark, run `/provider --add doubao` and paste your Ark API key. The built-in endpoint is `https://ark.cn-beijing.volces.com/api/v3` and uses Ark's Responses API. The model picker exposes `minimal`, `low`, `medium`, and `high`, defaulting to `high`; `minimal` disables Ark thinking, while the other levels enable it.
|
|
181
215
|
|
|
216
|
+
For **Xiaomi MiMo Token Plan**, choose **Xiaomi MiMo Token Plan** in desktop
|
|
217
|
+
Settings → Providers, or run `/provider --add mimo-token-plan` in the CLI.
|
|
218
|
+
Enter the Token Plan key (`tp-…` for personal plans, `ttp-…` for team plans),
|
|
219
|
+
then select `/model mimo-token-plan:mimo-v2.6-pro` or `mimo-v2.6-flash`.
|
|
220
|
+
Bubble uses Anthropic Messages at
|
|
221
|
+
`https://token-plan-cn.xiaomimimo.com/anthropic/v1/messages`, with streaming,
|
|
222
|
+
tools, and a thinking on/off switch (on by default). Both models have a 1M
|
|
223
|
+
context window and a 128K output cap. V2.5 Pro and V2.5 are also available;
|
|
224
|
+
their announced retirement date is October 21, 2026. Use the plan-specific key,
|
|
225
|
+
not a pay-as-you-go API key. See the
|
|
226
|
+
[official integration guide](https://mimo.mi.com/docs/zh-CN/tokenplan/integration/tools-overview).
|
|
227
|
+
|
|
182
228
|
### Custom providers and models
|
|
183
229
|
|
|
184
230
|
For StepFun's standard API, run `/provider --add stepfun-api`, enter your API
|
|
@@ -216,7 +262,7 @@ Bubble gates risky actions behind a permission mode. Press `Tab` to cycle modes
|
|
|
216
262
|
|
|
217
263
|
| Mode | Behavior |
|
|
218
264
|
| --- | --- |
|
|
219
|
-
| Default (Build) |
|
|
265
|
+
| Default (Build) | Workspace file edits and writes auto-approve, except protected files (`.git/`, `.bubble/settings*.json`, `.claude/settings*.json`). Bash, MCP tools and edits outside the workspace prompt unless covered by an allow rule or an "allow for this session" approval. |
|
|
220
266
|
| Plan | Read-only investigation. The agent proposes a plan and waits for your approval before making changes. |
|
|
221
267
|
| Bypass | Auto-approves every tool and disables all safety prompts. Enable deliberately with `--dangerously-skip-permissions`. |
|
|
222
268
|
|
|
@@ -226,6 +272,10 @@ Allow/deny rules are configured per scope and persisted across sessions. Manage
|
|
|
226
272
|
- `<project>/.bubble/settings.json` — project scope (commit to share with your team)
|
|
227
273
|
- `<project>/.bubble/settings.local.json` — local overrides (gitignore)
|
|
228
274
|
|
|
275
|
+
The two project files arrive with the repository, so what they grant — allow rules, `mcpServers` (which Bubble would start) and LSP server definitions — only loads once you trust the folder. Like Kimi Code, Bubble asks when it opens such a folder (at startup in the terminal, on the first message in the desktop app), listing what would be enabled; any later change to those settings asks again. Deny rules, and switches that only turn LSP servers off, always apply; deny rules apply even in Bypass mode.
|
|
276
|
+
|
|
277
|
+
Bash allow rules understand compound commands: every command in `a && b | c` must be covered by some allow rule, and commands with `$(...)`, backticks or file redirections always prompt. Deny rules match any part of the line. "Don't ask again this session" remembers the exact command only. MCP tools are named `mcp__<server>__<tool>`; `mcp__<server>` allows every tool of that server.
|
|
278
|
+
|
|
229
279
|
Rules use a simple pattern syntax, for example:
|
|
230
280
|
|
|
231
281
|
```json
|
|
@@ -245,7 +295,7 @@ Rules use a simple pattern syntax, for example:
|
|
|
245
295
|
|
|
246
296
|
## What Bubble can do
|
|
247
297
|
|
|
248
|
-
**Files and code.** Read, write, and make targeted edits; find files
|
|
298
|
+
**Files and code.** Read, write, and make targeted edits; list directories with ls; find files through Bash with find or rg --files; search contents with ripgrep; and navigate with language-server operations (go-to-definition, find references, hover, document/workspace symbols, call hierarchy).
|
|
249
299
|
|
|
250
300
|
**Shell and dev servers.** Run bounded bash commands with streaming output, and start/stop/inspect long-running dev servers (`npm run dev`, Vite, Next, etc.) with readiness checks and captured logs.
|
|
251
301
|
|
|
@@ -363,7 +413,7 @@ npm test # run the test suite (vitest)
|
|
|
363
413
|
npm start # run the built agent
|
|
364
414
|
```
|
|
365
415
|
|
|
366
|
-
`npm run dev` compiles and launches in one step.
|
|
416
|
+
`npm run dev:tui` compiles and launches the terminal interface in one step.
|
|
367
417
|
|
|
368
418
|
## Feishu host (optional)
|
|
369
419
|
|
|
@@ -101,7 +101,7 @@ export class ChildRunner {
|
|
|
101
101
|
options.abortSignal,
|
|
102
102
|
record.abortController.signal,
|
|
103
103
|
]);
|
|
104
|
-
for await (const event of subAgent.run(input, runCwd, { abortSignal: childAbortSignal, resumeWithoutInput })) {
|
|
104
|
+
for await (const event of subAgent.run(input, runCwd, { abortSignal: childAbortSignal, resumeWithoutInput, inputController: record.inputQueue, continueOnPendingInput: true })) {
|
|
105
105
|
if (event.type === "turn_start") {
|
|
106
106
|
// Leftovers here belong to a half-built attempt the agent discarded
|
|
107
107
|
// (stream-interruption retry re-issues the whole request); keeping
|
|
@@ -135,6 +135,10 @@ export class ChildRunner {
|
|
|
135
135
|
turnSummaryBuffer = "";
|
|
136
136
|
turnHadToolCall = false;
|
|
137
137
|
}
|
|
138
|
+
if (event.type === "input_applied")
|
|
139
|
+
record.inputDelivery = "applied";
|
|
140
|
+
if (event.type === "input_rejected")
|
|
141
|
+
record.inputDelivery = "rejected";
|
|
138
142
|
record.updatedAt = Date.now();
|
|
139
143
|
emit("running", event);
|
|
140
144
|
}
|
|
@@ -18,7 +18,7 @@ export declare function formatInternalReminderBlock(kind: string, content: strin
|
|
|
18
18
|
export declare function formatInternalContextBlock(kind: string, content: string): string;
|
|
19
19
|
export declare function sanitizeInternalReminderBlocks(text: string): string;
|
|
20
20
|
export declare function sanitizeInternalReasoningText(text: string): string;
|
|
21
|
-
export declare function sanitizeAssistantProviderMetadata(metadata: AssistantProviderMetadata | undefined): AssistantProviderMetadata | undefined;
|
|
21
|
+
export declare function sanitizeAssistantProviderMetadata(metadata: AssistantProviderMetadata | undefined, model?: string): AssistantProviderMetadata | undefined;
|
|
22
22
|
export declare function createStreamingInternalReminderSanitizer(): {
|
|
23
23
|
push(delta: string): string;
|
|
24
24
|
flush(): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isClaudeWithBoundThinking } from "../anthropic-thinking.js";
|
|
1
2
|
const INTERNAL_TAG_PREFIX = "<bubble_internal_";
|
|
2
3
|
const MEMORY_CITATION_TAG = "<oai-mem-citation";
|
|
3
4
|
const INTERNAL_TAG_NAMES = ["reminder", "context"];
|
|
@@ -71,7 +72,12 @@ export function sanitizeInternalReasoningText(text) {
|
|
|
71
72
|
.filter((paragraph) => !containsInternalReminderReference(paragraph))
|
|
72
73
|
.join("\n\n");
|
|
73
74
|
}
|
|
74
|
-
export function sanitizeAssistantProviderMetadata(metadata) {
|
|
75
|
+
export function sanitizeAssistantProviderMetadata(metadata, model) {
|
|
76
|
+
// These blocks are wire data, not display text. Removing thinking or editing
|
|
77
|
+
// earlier text invalidates later signatures. The separate content/reasoning
|
|
78
|
+
// fields remain sanitized for display; context checkpoints reset wire thinking.
|
|
79
|
+
if (isClaudeWithBoundThinking(model))
|
|
80
|
+
return metadata;
|
|
75
81
|
const anthropic = metadata?.anthropic;
|
|
76
82
|
const blocks = anthropic?.contentBlocks;
|
|
77
83
|
if (!metadata || !anthropic || !blocks?.length)
|
package/dist/agent/profiles.js
CHANGED
|
@@ -5,7 +5,7 @@ import { randomInt } from "node:crypto";
|
|
|
5
5
|
import { getSubtaskPolicy } from "./subtask-policy.js";
|
|
6
6
|
const READONLY_PRESET = [
|
|
7
7
|
"read",
|
|
8
|
-
"
|
|
8
|
+
"ls",
|
|
9
9
|
"grep",
|
|
10
10
|
"lsp",
|
|
11
11
|
"web_search",
|
|
@@ -123,12 +123,12 @@ export function builtinAgentProfiles() {
|
|
|
123
123
|
"You are an explorer subagent for codebase reconnaissance.",
|
|
124
124
|
"Answer the specific question by inspecting the repository directly. Prefer precise file paths and line-level evidence.",
|
|
125
125
|
"Keep the answer compact and avoid broad refactors or implementation plans unless asked.",
|
|
126
|
-
].join("\n"), ["read", "
|
|
126
|
+
].join("\n"), ["read", "ls", "grep", "lsp", "memory", "skill"]),
|
|
127
127
|
roleProfile("worker", "Implementation-planning worker subagent", [
|
|
128
128
|
"You are a worker subagent. In this Phase 1 runtime you are read-only, so you must not modify files.",
|
|
129
129
|
"Analyze the assigned implementation slice, identify exact files to change, and return a concrete patch plan or findings.",
|
|
130
130
|
"If write-capable worker mode is needed, say so explicitly.",
|
|
131
|
-
].join("\n"), ["read", "
|
|
131
|
+
].join("\n"), ["read", "ls", "grep", "lsp", "memory", "skill"]),
|
|
132
132
|
toProfile("search"),
|
|
133
133
|
toProfile("security_investigation"),
|
|
134
134
|
toProfile("evidence_correlation"),
|
|
@@ -145,7 +145,7 @@ export function builtinAgentProfiles() {
|
|
|
145
145
|
model: "inherit",
|
|
146
146
|
tools: {
|
|
147
147
|
preset: "explicit",
|
|
148
|
-
include: ["read", "
|
|
148
|
+
include: ["read", "ls", "grep", "edit", "write", "bash", "lsp"],
|
|
149
149
|
exclude: [],
|
|
150
150
|
},
|
|
151
151
|
approval: "fail",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { type ModelTier } from "../model-catalog.js";
|
|
12
12
|
import type { ProviderRegistry } from "../provider-registry.js";
|
|
13
|
-
import { selectTierCandidates, type AgentCategoriesConfig, type TierRoutingContext } from "./categories.js";
|
|
13
|
+
import { selectTierCandidates, type AgentCategoriesConfig, type TierCatalogEntry, type TierRoutingContext } from "./categories.js";
|
|
14
14
|
import type { ThinkingLevel } from "../types.js";
|
|
15
15
|
export interface AgentRoutingConfig {
|
|
16
16
|
/** Automatic builtin-tier downgrade routing (§3.2). */
|
|
@@ -36,6 +36,15 @@ export interface RoutingSnapshot {
|
|
|
36
36
|
model: string;
|
|
37
37
|
tier?: ModelTier;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Subscription (OAuth) catalogs are account-scoped: the server decides which
|
|
41
|
+
* models this account may use, so only models the CURRENT identity's
|
|
42
|
+
* discovery snapshot (or the user's models.json) confirmed may be chosen by
|
|
43
|
+
* automatic tier routing. API-key providers keep their builtin tiers routable.
|
|
44
|
+
*/
|
|
45
|
+
accountScopedCatalog: boolean;
|
|
46
|
+
/** Models automatic tier routing may pick from (see accountScopedCatalog). */
|
|
47
|
+
tierCatalog: TierCatalogEntry[];
|
|
39
48
|
/** Catalog-effective provider id (openai OAuth -> "openai-codex" alias). */
|
|
40
49
|
effectiveProviderId: string;
|
|
41
50
|
membershipSource: RoutingMembershipSource;
|
|
@@ -74,6 +83,15 @@ export declare function buildRoutingSnapshot(registry: ProviderRegistry, parent:
|
|
|
74
83
|
providerId: string;
|
|
75
84
|
model: string;
|
|
76
85
|
}, agentCategories: AgentCategoriesConfig, agentRouting: AgentRoutingConfig): RoutingSnapshot;
|
|
86
|
+
/**
|
|
87
|
+
* Catalog visible to automatic tier routing. On an account-scoped catalog only
|
|
88
|
+
* models confirmed by the current identity's discovery snapshot, or listed by
|
|
89
|
+
* the user in models.json, are candidates. A builtin-only entry (e.g. a static
|
|
90
|
+
* fast-tier model the ChatGPT plan does not include) or an overlay entry left
|
|
91
|
+
* by another account's cache would otherwise be routed to and rejected
|
|
92
|
+
* server-side. Without a snapshot nothing but user-listed models qualifies.
|
|
93
|
+
*/
|
|
94
|
+
export declare function tierCatalogEntries(models: readonly RoutingModelEntry[], accountScopedCatalog: boolean, confirmedIds?: ReadonlySet<string>): TierCatalogEntry[];
|
|
77
95
|
/** Tier-resolution context derived from a snapshot (consumed by categories §3.2). */
|
|
78
96
|
export declare function tierContextFromSnapshot(snapshot: RoutingSnapshot, agentRouting: AgentRoutingConfig): TierRoutingContext;
|
|
79
97
|
/**
|
|
@@ -37,6 +37,7 @@ export function buildRoutingSnapshot(registry, parent, agentCategories, agentRou
|
|
|
37
37
|
const effectiveProviderId = parent.providerId === "openai" && configured?.authType === "oauth"
|
|
38
38
|
? "openai-codex"
|
|
39
39
|
: parent.providerId;
|
|
40
|
+
const accountScopedCatalog = configured?.authType === "oauth";
|
|
40
41
|
const builtins = listBuiltinModels(effectiveProviderId);
|
|
41
42
|
const builtinIndex = new Map(builtins.map((model, index) => [model.id, index]));
|
|
42
43
|
const dynamic = listDynamicModelMetadata(effectiveProviderId);
|
|
@@ -101,8 +102,16 @@ export function buildRoutingSnapshot(registry, parent, agentCategories, agentRou
|
|
|
101
102
|
const parentTier = models.find((model) => model.id === parent.model)?.tier
|
|
102
103
|
?? getBuiltinModel(effectiveProviderId, parent.model)?.tier;
|
|
103
104
|
const runnableProviderIds = registry.getEnabled().map((provider) => provider.id);
|
|
105
|
+
// The dynamic overlay is provider-wide and is rebuilt from EVERY unexpired
|
|
106
|
+
// disk-cache entry at startup (other accounts, older client pins included),
|
|
107
|
+
// so "dynamic" membership alone does not prove this account can use a model.
|
|
108
|
+
// Only the current identity's own COMPLETE discovery does: a failed fetch is
|
|
109
|
+
// cached briefly as a builtin fallback and must not confirm anything.
|
|
110
|
+
const tierCatalog = tierCatalogEntries(models, accountScopedCatalog, discovery?.complete ? new Set(discovery.models.map((model) => model.id)) : undefined);
|
|
104
111
|
return {
|
|
105
112
|
parent: { ...parent, tier: parentTier },
|
|
113
|
+
accountScopedCatalog,
|
|
114
|
+
tierCatalog,
|
|
106
115
|
effectiveProviderId,
|
|
107
116
|
membershipSource,
|
|
108
117
|
models,
|
|
@@ -110,19 +119,34 @@ export function buildRoutingSnapshot(registry, parent, agentCategories, agentRou
|
|
|
110
119
|
registryRevision: registry.getRoutingRevision(),
|
|
111
120
|
discoveryExpiresAt: discovery?.expiresAt,
|
|
112
121
|
runnableProviderIds,
|
|
113
|
-
resolvedCategories: resolveCategoriesForMenu(parent, parentTier,
|
|
122
|
+
resolvedCategories: resolveCategoriesForMenu(parent, parentTier, tierCatalog, agentCategories, agentRouting),
|
|
114
123
|
};
|
|
115
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Catalog visible to automatic tier routing. On an account-scoped catalog only
|
|
127
|
+
* models confirmed by the current identity's discovery snapshot, or listed by
|
|
128
|
+
* the user in models.json, are candidates. A builtin-only entry (e.g. a static
|
|
129
|
+
* fast-tier model the ChatGPT plan does not include) or an overlay entry left
|
|
130
|
+
* by another account's cache would otherwise be routed to and rejected
|
|
131
|
+
* server-side. Without a snapshot nothing but user-listed models qualifies.
|
|
132
|
+
*/
|
|
133
|
+
export function tierCatalogEntries(models, accountScopedCatalog, confirmedIds) {
|
|
134
|
+
return models
|
|
135
|
+
.filter((model) => (!accountScopedCatalog
|
|
136
|
+
|| model.source === "custom"
|
|
137
|
+
|| (confirmedIds?.has(model.id) ?? false)))
|
|
138
|
+
.map((model) => ({
|
|
139
|
+
id: model.id,
|
|
140
|
+
tier: model.tier,
|
|
141
|
+
routingPriority: model.routingPriority,
|
|
142
|
+
builtinIndex: model.builtinIndex,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
116
145
|
/** Tier-resolution context derived from a snapshot (consumed by categories §3.2). */
|
|
117
146
|
export function tierContextFromSnapshot(snapshot, agentRouting) {
|
|
118
147
|
return {
|
|
119
148
|
parentTier: snapshot.parent.tier,
|
|
120
|
-
models: snapshot.
|
|
121
|
-
id: model.id,
|
|
122
|
-
tier: model.tier,
|
|
123
|
-
routingPriority: model.routingPriority,
|
|
124
|
-
builtinIndex: model.builtinIndex,
|
|
125
|
-
})),
|
|
149
|
+
models: snapshot.tierCatalog,
|
|
126
150
|
autoTier: agentRouting.autoTier,
|
|
127
151
|
};
|
|
128
152
|
}
|
|
@@ -150,12 +174,7 @@ function resolveCategoriesForMenu(parent, parentTier, models, agentCategories, a
|
|
|
150
174
|
const merged = mergeAgentCategoriesWithProvenance(agentCategories);
|
|
151
175
|
const tierContext = {
|
|
152
176
|
parentTier,
|
|
153
|
-
models
|
|
154
|
-
id: model.id,
|
|
155
|
-
tier: model.tier,
|
|
156
|
-
routingPriority: model.routingPriority,
|
|
157
|
-
builtinIndex: model.builtinIndex,
|
|
158
|
-
})),
|
|
177
|
+
models,
|
|
159
178
|
autoTier: agentRouting.autoTier,
|
|
160
179
|
};
|
|
161
180
|
return Object.entries(merged).map(([name, entry]) => {
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent thread lifecycle + dynamic-workflow execution.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from Agent: spawn/wait/send/close, the scheduler dispatch and its
|
|
5
|
+
* terminal callbacks, admission, ingestion, worktree-backed child construction
|
|
6
|
+
* and the QuickJS workflow driver all lived on the Agent class alongside the
|
|
7
|
+
* conversation loop, sharing its private fields.
|
|
8
|
+
*
|
|
9
|
+
* Two rules keep the boundary honest:
|
|
10
|
+
* - Everything the runtime needs from the parent arrives through
|
|
11
|
+
* `SubagentRuntimeParent` as a LIVE accessor, never a construction-time
|
|
12
|
+
* snapshot: `/model` reassigns the parent's provider/model/thinking
|
|
13
|
+
* mid-session and children must inherit the current values.
|
|
14
|
+
* - Conversation state is never pushed back into the parent. Tool updates
|
|
15
|
+
* and ingestion notices are PULLED (`drainToolUpdates` /
|
|
16
|
+
* `drainIngestionNotices`). The only direct sink is a terminal,
|
|
17
|
+
* fail-closed provider diagnostic that contains no child transcript.
|
|
18
|
+
*
|
|
19
|
+
* `new Agent(...)` stays on Agent, reached through `parent.createChild`. That
|
|
20
|
+
* is the only genuine call back into the parent, and keeping it there is what
|
|
21
|
+
* guarantees the child-construction argument set — including the fields that
|
|
22
|
+
* must stay ABSENT (onMessageAppend, sessionID, routing catalog) — has exactly
|
|
23
|
+
* one definition site.
|
|
24
|
+
*/
|
|
25
|
+
import type { SkillSummary } from "../../skills/types.js";
|
|
1
26
|
import { type AgentProfile, type SubagentRunResult } from "../profiles.js";
|
|
2
27
|
import { type SubagentThreadRecord, type SubagentThreadSnapshot } from "../subagent-control.js";
|
|
3
28
|
import { SubagentStore } from "../subagent-store.js";
|
|
@@ -36,6 +61,9 @@ export interface SubagentRuntimeParent {
|
|
|
36
61
|
readonly apiModel: string;
|
|
37
62
|
readonly thinkingLevel: ThinkingLevel;
|
|
38
63
|
readonly memoryPrompt: string | undefined;
|
|
64
|
+
/** Optional so lightweight parents (tests, embedders) need not provide a catalog. */
|
|
65
|
+
getSkillSummaries?(): SkillSummary[];
|
|
66
|
+
getSkillCatalogChars?(): number | undefined;
|
|
39
67
|
readonly providerFactory: ((route: ResolvedSubagentRoute) => Provider | Promise<Provider>) | undefined;
|
|
40
68
|
/**
|
|
41
69
|
* The UNFILTERED tool map — deferred-but-unlocked entries included. Profile
|
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Subagent thread lifecycle + dynamic-workflow execution.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from Agent: spawn/wait/send/close, the scheduler dispatch and its
|
|
5
|
-
* terminal callbacks, admission, ingestion, worktree-backed child construction
|
|
6
|
-
* and the QuickJS workflow driver all lived on the Agent class alongside the
|
|
7
|
-
* conversation loop, sharing its private fields.
|
|
8
|
-
*
|
|
9
|
-
* Two rules keep the boundary honest:
|
|
10
|
-
* - Everything the runtime needs from the parent arrives through
|
|
11
|
-
* `SubagentRuntimeParent` as a LIVE accessor, never a construction-time
|
|
12
|
-
* snapshot: `/model` reassigns the parent's provider/model/thinking
|
|
13
|
-
* mid-session and children must inherit the current values.
|
|
14
|
-
* - Conversation state is never pushed back into the parent. Tool updates
|
|
15
|
-
* and ingestion notices are PULLED (`drainToolUpdates` /
|
|
16
|
-
* `drainIngestionNotices`). The only direct sink is a terminal,
|
|
17
|
-
* fail-closed provider diagnostic that contains no child transcript.
|
|
18
|
-
*
|
|
19
|
-
* `new Agent(...)` stays on Agent, reached through `parent.createChild`. That
|
|
20
|
-
* is the only genuine call back into the parent, and keeping it there is what
|
|
21
|
-
* guarantees the child-construction argument set — including the fields that
|
|
22
|
-
* must stay ABSENT (onMessageAppend, sessionID, routing catalog) — has exactly
|
|
23
|
-
* one definition site.
|
|
24
|
-
*/
|
|
1
|
+
import { AgentRunInputQueue } from "../input-controller.js";
|
|
25
2
|
import { randomUUID } from "node:crypto";
|
|
26
3
|
import { buildSystemPrompt } from "../../system-prompt.js";
|
|
27
4
|
import { buildToolPromptOptions } from "../../tools/index.js";
|
|
@@ -78,6 +55,12 @@ export class SubagentRuntime {
|
|
|
78
55
|
recordProviderError: (record, error, request) => this.recordSubagentProviderError(record, error, request),
|
|
79
56
|
notifyWaiters: (record) => this.store.notifyWaiters(record),
|
|
80
57
|
onFinal: (record, options) => {
|
|
58
|
+
if ((record.inputQueue?.pendingInputCount() ?? 0) > 0) {
|
|
59
|
+
record.inputQueue?.closePendingInputs();
|
|
60
|
+
record.inputDelivery = "rejected";
|
|
61
|
+
record.toolNotes.push("Pending supplementary input was not delivered because the run ended.");
|
|
62
|
+
this.emitSubagentLifecycle(record, options, record.status === "closed" ? "cancelled" : record.status);
|
|
63
|
+
}
|
|
81
64
|
this.reclaimWorktree(record);
|
|
82
65
|
// Workflow-internal agents are not persisted (they never re-import into
|
|
83
66
|
// the store on restart) and never ingest into parent context (option C).
|
|
@@ -240,17 +223,31 @@ export class SubagentRuntime {
|
|
|
240
223
|
}
|
|
241
224
|
if (record.status === "running" || record.status === "queued") {
|
|
242
225
|
if (!options.interrupt) {
|
|
243
|
-
|
|
226
|
+
if (typeof input !== "string")
|
|
227
|
+
throw new Error("Running subagents currently accept text follow-ups only.");
|
|
228
|
+
record.inputQueue ??= new AgentRunInputQueue(`child-${agentId}`);
|
|
229
|
+
if (record.inputQueue.tryEnqueue(input)) {
|
|
230
|
+
record.inputDelivery = "queued";
|
|
231
|
+
record.updatedAt = Date.now();
|
|
232
|
+
this.queueSubagentUpdate(record, record.status, undefined, "Supplementary input queued for the next safe boundary.");
|
|
233
|
+
return this.snapshotSubagent(record);
|
|
234
|
+
}
|
|
235
|
+
// Admission closed at the final boundary. Resume only after the old
|
|
236
|
+
// run has fully settled; never run the same child concurrently.
|
|
237
|
+
await record.promise?.catch(() => undefined);
|
|
238
|
+
}
|
|
239
|
+
if (options.interrupt) {
|
|
240
|
+
record.abortController.abort(new SubagentAbortError(`Subagent ${agentId} interrupted.`, "interrupt"));
|
|
241
|
+
await record.promise?.catch(() => undefined);
|
|
244
242
|
}
|
|
245
|
-
record.abortController.abort(new SubagentAbortError(`Subagent ${agentId} interrupted.`, "interrupt"));
|
|
246
|
-
await record.promise?.catch(() => undefined);
|
|
247
|
-
record.abortController = new AbortController();
|
|
248
243
|
}
|
|
249
244
|
if (record.status === "closed") {
|
|
250
245
|
throw new Error(`Subagent ${agentId} is closed.`);
|
|
251
246
|
}
|
|
252
|
-
|
|
253
|
-
record.
|
|
247
|
+
// Identity and trace anchor belong to the child, not a follow-up tool call.
|
|
248
|
+
record.abortController = new AbortController();
|
|
249
|
+
record.inputQueue = new AgentRunInputQueue(`child-${agentId}-${Date.now()}`);
|
|
250
|
+
record.inputDelivery = undefined;
|
|
254
251
|
record.task = typeof input === "string" ? input : "(multimodal task)";
|
|
255
252
|
record.summary = "";
|
|
256
253
|
record.toolNotes = [];
|
|
@@ -638,6 +635,7 @@ export class SubagentRuntime {
|
|
|
638
635
|
toolNotes: [],
|
|
639
636
|
createdAt: now,
|
|
640
637
|
updatedAt: now,
|
|
638
|
+
inputQueue: new AgentRunInputQueue(`child-input-${randomUUID()}`),
|
|
641
639
|
abortController: new AbortController(),
|
|
642
640
|
waiters: new Set(),
|
|
643
641
|
};
|
|
@@ -692,6 +690,10 @@ export class SubagentRuntime {
|
|
|
692
690
|
mode: childMode,
|
|
693
691
|
workingDir: childCwd,
|
|
694
692
|
...buildToolPromptOptions(tools),
|
|
693
|
+
// A child that can load skills sees the same catalog as its parent.
|
|
694
|
+
...(childToolNames.includes("skill")
|
|
695
|
+
? { skills: this.parent.getSkillSummaries?.(), skillCatalogChars: this.parent.getSkillCatalogChars?.() }
|
|
696
|
+
: {}),
|
|
695
697
|
memoryPrompt: childToolNames.some((name) => name === "memory")
|
|
696
698
|
? this.parent.memoryPrompt
|
|
697
699
|
: undefined,
|
|
@@ -779,6 +781,8 @@ export class SubagentRuntime {
|
|
|
779
781
|
error: record.error,
|
|
780
782
|
createdAt: record.createdAt,
|
|
781
783
|
updatedAt: record.updatedAt,
|
|
784
|
+
pendingInputCount: record.inputQueue?.pendingInputCount() ?? 0,
|
|
785
|
+
inputDelivery: record.inputDelivery,
|
|
782
786
|
}],
|
|
783
787
|
},
|
|
784
788
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentRunInputQueue } from "./input-controller.js";
|
|
1
2
|
import type { AgentProfile, AgentProfileSource, SubagentRunResult } from "./profiles.js";
|
|
2
3
|
import type { ResolvedSubagentRoute } from "./categories.js";
|
|
3
4
|
import type { SubagentWorktree } from "./worktree.js";
|
|
@@ -31,6 +32,8 @@ export interface SubagentThreadSnapshot {
|
|
|
31
32
|
createdAt: number;
|
|
32
33
|
updatedAt: number;
|
|
33
34
|
deliveredAt?: number;
|
|
35
|
+
pendingInputCount?: number;
|
|
36
|
+
inputDelivery?: "queued" | "applied" | "rejected";
|
|
34
37
|
/** 1-based position in the scheduler queue while status is "queued". */
|
|
35
38
|
queuePosition?: number;
|
|
36
39
|
/** Present for write_worktree children: where the isolated checkout lives. */
|
|
@@ -72,12 +75,16 @@ export interface SubagentThreadRecord {
|
|
|
72
75
|
hookStopPending?: boolean;
|
|
73
76
|
abortController: AbortController;
|
|
74
77
|
waiters: Set<() => void>;
|
|
78
|
+
inputQueue?: AgentRunInputQueue;
|
|
79
|
+
inputDelivery?: "queued" | "applied" | "rejected";
|
|
75
80
|
agent?: {
|
|
76
81
|
messages: Message[];
|
|
77
82
|
injectSystemReminder(content: string): void;
|
|
78
83
|
run(input: string | ContentPart[], cwd: string, options?: {
|
|
79
84
|
abortSignal?: AbortSignal;
|
|
80
85
|
resumeWithoutInput?: boolean;
|
|
86
|
+
inputController?: import("../types.js").AgentInputController;
|
|
87
|
+
continueOnPendingInput?: boolean;
|
|
81
88
|
}): AsyncIterable<AgentEvent>;
|
|
82
89
|
};
|
|
83
90
|
messages?: Message[];
|
|
@@ -33,6 +33,8 @@ export function snapshotSubagentThread(record) {
|
|
|
33
33
|
createdAt: record.createdAt,
|
|
34
34
|
updatedAt: record.updatedAt,
|
|
35
35
|
deliveredAt: record.deliveredAt,
|
|
36
|
+
pendingInputCount: record.inputQueue?.pendingInputCount() ?? 0,
|
|
37
|
+
inputDelivery: record.inputDelivery,
|
|
36
38
|
worktree: record.worktree ? { ...record.worktree } : undefined,
|
|
37
39
|
};
|
|
38
40
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SUBAGENT_WAIT_GUIDANCE } from "./subagent-wait-guidance.js";
|
|
1
2
|
const FINAL_STATUSES = new Set(["completed", "failed", "blocked", "cancelled", "closed"]);
|
|
2
3
|
const STATUS_ORDER = ["queued", "running", "completed", "blocked", "failed", "cancelled", "closed"];
|
|
3
4
|
export function buildSubagentLifecycleReminder(snapshots, toolResults) {
|
|
@@ -17,7 +18,7 @@ export function buildSubagentLifecycleReminder(snapshots, toolResults) {
|
|
|
17
18
|
...subagents.map(formatSubagentLine),
|
|
18
19
|
"- Count unique agent_id values only; do not count repeated spawn_agent/wait_agent tool calls or repeated UI Subagents blocks as additional subagents.",
|
|
19
20
|
"- Do not describe a subagent as running or still working if its status above is completed, failed, blocked, cancelled, or closed.",
|
|
20
|
-
|
|
21
|
+
...SUBAGENT_WAIT_GUIDANCE.map((guidance) => `- ${guidance}`),
|
|
21
22
|
"- When writing a synthesis, use the exact unique subagent count and statuses above.",
|
|
22
23
|
];
|
|
23
24
|
return lines.join("\n");
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Scheduling advice, not a timeout policy: the model keeps control of each wait. */
|
|
2
|
+
export declare const SUBAGENT_WAIT_GUIDANCE: readonly ["Coordinate around dependencies: after delegation, continue useful non-overlapping work that does not require the child's result; wait_agent when that result is needed for your next meaningful step or final synthesis.", "Choose a wait timeout according to the task and your next decision. It is an upper bound, not a mandatory delay: wait_agent returns early when a target finishes, so a generous timeout does not slow down a short task.", "A short wait is appropriate when its outcome informs a concrete scheduling decision. Avoid repeated short checks solely to see whether time has passed; use list_agents for a status snapshot when that information is useful.", "The same decision-value principle applies to list_agents: do not replace short waits with repeated status polling. With multiple children, wait_agent may return as soon as any target finishes; use explicit IDs for the remaining children whose results you still need, rather than repeatedly collecting an already-finished child.", "A timeout ends only that wait, not the child's work. Reassess available independent work and whether the result still blocks progress before waiting again; do not duplicate the delegated task."];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Scheduling advice, not a timeout policy: the model keeps control of each wait. */
|
|
2
|
+
export const SUBAGENT_WAIT_GUIDANCE = [
|
|
3
|
+
"Coordinate around dependencies: after delegation, continue useful non-overlapping work that does not require the child's result; wait_agent when that result is needed for your next meaningful step or final synthesis.",
|
|
4
|
+
"Choose a wait timeout according to the task and your next decision. It is an upper bound, not a mandatory delay: wait_agent returns early when a target finishes, so a generous timeout does not slow down a short task.",
|
|
5
|
+
"A short wait is appropriate when its outcome informs a concrete scheduling decision. Avoid repeated short checks solely to see whether time has passed; use list_agents for a status snapshot when that information is useful.",
|
|
6
|
+
"The same decision-value principle applies to list_agents: do not replace short waits with repeated status polling. With multiple children, wait_agent may return as soon as any target finishes; use explicit IDs for the remaining children whose results you still need, rather than repeatedly collecting an already-finished child.",
|
|
7
|
+
"A timeout ends only that wait, not the child's work. Reassess available independent work and whether the result still blocks progress before waiting again; do not duplicate the delegated task.",
|
|
8
|
+
];
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const POLICY_MAP = {
|
|
2
2
|
search: {
|
|
3
3
|
type: "search",
|
|
4
|
-
allowedTools: ["read", "
|
|
4
|
+
allowedTools: ["read", "ls", "grep", "web_search", "web_fetch", "skill"],
|
|
5
5
|
reminder: [
|
|
6
6
|
"Subtask policy: search",
|
|
7
7
|
"- Focus on locating relevant files, symbols, and evidence quickly.",
|
|
8
|
-
"- Use
|
|
8
|
+
"- Use ls for directory contents and grep for content search.",
|
|
9
9
|
"- Return a concise summary of what you found and where.",
|
|
10
10
|
].join("\n"),
|
|
11
11
|
resultStatus: "success",
|
|
@@ -13,7 +13,7 @@ const POLICY_MAP = {
|
|
|
13
13
|
},
|
|
14
14
|
security_investigation: {
|
|
15
15
|
type: "security_investigation",
|
|
16
|
-
allowedTools: ["read", "
|
|
16
|
+
allowedTools: ["read", "ls", "grep", "web_search", "web_fetch", "skill"],
|
|
17
17
|
reminder: [
|
|
18
18
|
"Subtask policy: security_investigation",
|
|
19
19
|
"- Investigate only in read-only mode.",
|
|
@@ -37,7 +37,7 @@ const POLICY_MAP = {
|
|
|
37
37
|
},
|
|
38
38
|
general_readonly: {
|
|
39
39
|
type: "general_readonly",
|
|
40
|
-
allowedTools: ["read", "
|
|
40
|
+
allowedTools: ["read", "ls", "grep", "web_search", "web_fetch", "skill"],
|
|
41
41
|
reminder: [
|
|
42
42
|
"Subtask policy: general_readonly",
|
|
43
43
|
"- Stay in read-only mode.",
|