@oh-my-pi/pi-coding-agent 15.10.1 → 15.10.3
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/CHANGELOG.md +113 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +7 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/edit/index.d.ts +0 -1
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/index.d.ts +0 -5
- package/dist/types/main.d.ts +14 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +0 -9
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/late-diagnostics-message.d.ts +20 -0
- package/dist/types/modes/components/read-tool-group.d.ts +6 -0
- package/dist/types/modes/components/session-selector.d.ts +16 -7
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +0 -18
- package/dist/types/modes/controllers/event-controller.d.ts +17 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/types.d.ts +7 -0
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +11 -8
- package/dist/types/session/session-manager.d.ts +5 -2
- package/dist/types/session/yield-queue.d.ts +10 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/tools/eval-render.d.ts +0 -1
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/index.d.ts +31 -0
- package/dist/types/tools/path-utils.d.ts +13 -1
- package/dist/types/tools/read.d.ts +2 -1
- package/dist/types/tools/render-utils.d.ts +3 -1
- package/dist/types/tools/renderers.d.ts +0 -15
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/write.d.ts +0 -2
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/code-cell.d.ts +0 -2
- package/dist/types/tui/hyperlink.d.ts +5 -7
- package/dist/types/tui/output-block.d.ts +0 -18
- package/package.json +9 -9
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +2 -4
- package/src/cli/gallery-cli.ts +4 -0
- package/src/cli/gallery-fixtures/codeintel.ts +0 -1
- package/src/cli/gallery-fixtures/fs.ts +68 -1
- package/src/cli/gallery-fixtures/types.ts +8 -1
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/agentic/agent.ts +1 -0
- package/src/commit/model-selection.ts +3 -2
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +4 -22
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings.ts +86 -41
- package/src/debug/index.ts +8 -0
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/diff.ts +86 -0
- package/src/edit/hashline/execute.ts +14 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/index.ts +31 -17
- package/src/edit/renderer.ts +116 -31
- package/src/eval/__tests__/llm-bridge.test.ts +20 -0
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/js/shared/prelude.txt +26 -10
- package/src/eval/llm-bridge.ts +14 -3
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +189 -61
- package/src/main.ts +144 -78
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +2 -2
- package/src/modes/components/assistant-message.ts +3 -15
- package/src/modes/components/custom-editor.ts +143 -111
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/plan-review-overlay.ts +26 -5
- package/src/modes/components/read-tool-group.ts +415 -35
- package/src/modes/components/session-selector.ts +89 -35
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/tool-execution.ts +7 -49
- package/src/modes/components/transcript-container.ts +108 -32
- package/src/modes/components/user-message.ts +1 -1
- package/src/modes/controllers/event-controller.ts +32 -1
- package/src/modes/controllers/input-controller.ts +56 -9
- package/src/modes/interactive-mode.ts +107 -20
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/types.ts +7 -0
- package/src/modes/utils/ui-helpers.ts +26 -5
- package/src/modes/workflow.ts +10 -10
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/plan-mode-active.md +56 -72
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +85 -10
- package/src/session/agent-session.ts +42 -15
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +21 -14
- package/src/session/session-manager.ts +98 -25
- package/src/session/yield-queue.ts +20 -2
- package/src/task/executor.ts +72 -36
- package/src/task/render.ts +3 -4
- package/src/tiny/title-client.ts +6 -1
- package/src/tools/bash.ts +7 -7
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/eval-render.ts +4 -23
- package/src/tools/eval.ts +13 -2
- package/src/tools/find.ts +148 -99
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/index.ts +32 -0
- package/src/tools/inspect-image.ts +2 -2
- package/src/tools/path-utils.ts +47 -24
- package/src/tools/plan-mode-guard.ts +52 -7
- package/src/tools/read.ts +41 -20
- package/src/tools/render-utils.ts +3 -1
- package/src/tools/renderers.ts +0 -15
- package/src/tools/search.ts +38 -3
- package/src/tools/ssh.ts +0 -1
- package/src/tools/todo.ts +1 -0
- package/src/tools/write.ts +5 -14
- package/src/tools/yield.ts +10 -1
- package/src/tui/code-cell.ts +1 -6
- package/src/tui/hyperlink.ts +13 -23
- package/src/tui/output-block.ts +2 -97
- package/src/utils/commit-message-generator.ts +2 -2
- package/src/utils/enhanced-paste.ts +30 -2
- package/src/web/search/providers/codex.ts +37 -8
|
@@ -1,125 +1,109 @@
|
|
|
1
1
|
<critical>
|
|
2
|
-
Plan mode active. You MUST perform READ-ONLY
|
|
2
|
+
Plan mode is active. You MUST perform READ-ONLY work only:
|
|
3
|
+
- You NEVER create, edit, or delete files — except the single plan file named below.
|
|
4
|
+
- You NEVER run state-changing commands (`git commit`, `npm install`, migrations) or make any other system change.
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
- Create, edit, or delete files (except plan file below)
|
|
6
|
-
- Run state-changing commands (git commit, npm install, etc.)
|
|
7
|
-
- Make any system changes
|
|
6
|
+
To leave plan mode and implement: call `resolve` with `action: "apply"`, a `reason`, and `extra: { title: "<slug>" }`, where `<slug>` matches your `local://<slug>-plan.md`. The user then picks an execution option and full write access is restored. `<slug>` may contain only letters, numbers, underscores, and hyphens.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
You NEVER ask the user to exit plan mode for you; you MUST call `resolve` yourself.
|
|
8
|
+
You NEVER ask the user to exit plan mode, and you NEVER request approval in prose or via `{{askToolName}}` — approval happens ONLY through `resolve`.
|
|
12
9
|
</critical>
|
|
13
10
|
|
|
14
|
-
##
|
|
11
|
+
## What a plan is
|
|
12
|
+
|
|
13
|
+
The plan is an **execution spec**, not a design doc. After approval the planning conversation may be cleared or compacted, and a different engineer or a fresh agent implements straight from the file. The bar is absolute: **a competent implementer who never saw this conversation executes the file top to bottom and makes ZERO design decisions.** Every choice is already made; the file alone carries it.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
Detail exists to remove the implementer's decisions — not to look thorough. A document padded with Non-Goals, Alternatives, or risk matrices yet leaving one real decision open is a FAILED plan. So is a short plan that reads cleanly but forces the implementer to choose. When brevity and decision-completeness collide, completeness wins.
|
|
17
16
|
|
|
18
|
-
## Plan
|
|
17
|
+
## Plan file
|
|
19
18
|
|
|
20
19
|
{{#if planExists}}
|
|
21
|
-
|
|
20
|
+
A plan already exists at `{{planFilePath}}` — read it, then update it incrementally with `{{editToolName}}`. If this request is a different task, leave that plan in place and start a fresh `local://<slug>-plan.md`.
|
|
22
21
|
{{else}}
|
|
23
|
-
Choose a short kebab-case `<slug>`
|
|
22
|
+
Choose a short kebab-case `<slug>` naming this task and write the plan to `local://<slug>-plan.md` (e.g. `local://auth-token-refresh-plan.md`). The file is never renamed on approval, so the name you choose persists — pass that same `<slug>` as `title` when you `resolve`.
|
|
24
23
|
{{/if}}
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
Use `{{editToolName}}` for incremental edits and `{{writeToolName}}` only to create or fully replace the file. You MUST write findings into the plan as you learn them — you NEVER batch all writing to the end.
|
|
27
26
|
|
|
28
|
-
##
|
|
27
|
+
## Ground every claim
|
|
29
28
|
|
|
30
|
-
You
|
|
29
|
+
You eliminate unknowns by discovering facts, not by asking.
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
- **
|
|
34
|
-
- **Preferences and tradeoffs** — intent, UX, scope boundaries, performance-vs-simplicity: not derivable from code. You MUST surface these early via `{{askToolName}}` with 2–4 mutually exclusive options and a recommended default. If left unanswered, proceed with the default and record it under Assumptions.
|
|
31
|
+
- **Discoverable facts** (file locations, current behavior, signatures, configs): you MUST find them yourself with `find`, `search`, `read`, or parallel `explore` subagents. Every path, symbol, signature, and behavior the plan states as fact MUST come from something you actually read this session. Anything you could not confirm you mark inline (`unverified — confirm first`); you NEVER present a guess as settled. Ask only when several real candidates survive exploration — then present them with a recommendation.
|
|
32
|
+
- **Preferences and tradeoffs** (intent, UX, scope edges, performance-vs-simplicity): not derivable from code. Surface these early via `{{askToolName}}` with 2–4 mutually exclusive options and a recommended default. Left unanswered → proceed with the default and record it under Assumptions.
|
|
35
33
|
|
|
36
|
-
Every question MUST
|
|
34
|
+
Every question MUST change the plan or settle a load-bearing choice. Batch them. You NEVER ask what exploration answers, and you NEVER ask filler.
|
|
37
35
|
|
|
38
36
|
{{#if reentry}}
|
|
39
37
|
## Re-entry
|
|
40
38
|
|
|
41
39
|
<procedure>
|
|
42
40
|
1. Read the existing plan.
|
|
43
|
-
2.
|
|
44
|
-
3.
|
|
45
|
-
- **Different task** → overwrite the plan.
|
|
46
|
-
- **Same task, continuing** → update and delete outdated sections.
|
|
41
|
+
2. Compare the new request against it.
|
|
42
|
+
3. Different task → overwrite it. Same task continuing → update it and delete outdated sections.
|
|
47
43
|
4. Call `resolve` with `action: "apply"` and `extra: { title }` when complete.
|
|
48
44
|
</procedure>
|
|
49
45
|
{{/if}}
|
|
50
46
|
|
|
51
47
|
{{#if iterative}}
|
|
52
|
-
## Workflow —
|
|
48
|
+
## Workflow — iterative
|
|
53
49
|
|
|
54
50
|
<procedure>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
You MUST use `{{askToolName}}` to resolve preferences and tradeoffs (see Resolving Unknowns). Batch questions; never ask what exploration answers.
|
|
60
|
-
|
|
61
|
-
### 3. Update incrementally
|
|
62
|
-
You MUST use `{{editToolName}}` to revise the plan file as you learn.
|
|
63
|
-
|
|
64
|
-
### 4. Calibrate
|
|
65
|
-
- Large, unspecified task → multiple interview rounds.
|
|
66
|
-
- Small, well-specified task → few or no questions.
|
|
51
|
+
1. **Explore** — use `find`/`search`/`read` to ground in the real code; hunt for existing functions, utilities, and conventions to reuse before proposing anything new.
|
|
52
|
+
2. **Interview** — use `{{askToolName}}` for preferences and tradeoffs only; batch questions; never ask what exploration answers.
|
|
53
|
+
3. **Update** — revise the plan with `{{editToolName}}` as you learn.
|
|
54
|
+
4. **Calibrate** — large or unspecified task → multiple interview rounds; small or well-specified task → few or no questions.
|
|
67
55
|
</procedure>
|
|
68
56
|
{{else}}
|
|
69
|
-
## Workflow —
|
|
57
|
+
## Workflow — parallel
|
|
70
58
|
|
|
71
59
|
<procedure>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
You MUST draft an approach from your exploration, weigh trade-offs briefly, then commit to one. For large or cross-cutting changes you MAY spawn a planning/critique subagent to pressure-test the approach before you commit.
|
|
77
|
-
|
|
78
|
-
### Phase 3 — Review
|
|
79
|
-
You MUST read the critical files you intend to touch to confirm the approach holds against the real code. You MUST verify the plan still matches the original request. You SHOULD use `{{askToolName}}` to close remaining preference questions.
|
|
80
|
-
|
|
81
|
-
### Phase 4 — Write the plan
|
|
82
|
-
You MUST write the plan file (see **Plan File** above) per **The Plan** below.
|
|
60
|
+
1. **Understand** — focus on the request and the code behind it. Launch parallel `explore` subagents (via `task`) when scope spans areas; give each a distinct focus (existing implementations, related components, test patterns). Hunt for reusable code before proposing new.
|
|
61
|
+
2. **Design** — draft one approach from what you found, weigh tradeoffs briefly, then commit. For large or cross-cutting work you MAY spawn a critique subagent to pressure-test it before committing.
|
|
62
|
+
3. **Review** — read the files you intend to touch and confirm the approach holds against the real code; confirm the plan still answers the literal request; use `{{askToolName}}` to close any remaining preference questions.
|
|
63
|
+
4. **Write** — write the plan per **Plan contents** below.
|
|
83
64
|
</procedure>
|
|
84
65
|
{{/if}}
|
|
85
66
|
|
|
86
|
-
##
|
|
67
|
+
## Plan contents
|
|
87
68
|
|
|
88
|
-
|
|
69
|
+
Write scannable markdown using these sections. Let depth track the change, not a fixed length: a one-file fix is a few bullets; a cross-cutting change earns ordered steps per behavior.
|
|
89
70
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
71
|
+
- **Context** — restate the literal ask, why it is needed, and the intended end state, in 2–4 sentences. Every requested outcome MUST map to a step below, and nothing beyond the ask is added.
|
|
72
|
+
- **Approach** — the load-bearing section: the ordered steps that make the change. Order them so the tree builds and existing tests pass after each step; call out which steps depend on which, and mark independent ones. Group steps by behavior, never one-per-file. For each step:
|
|
73
|
+
- State the concrete edit — verb + exact target + the new behavior — never just an area to "update" or "handle".
|
|
74
|
+
- Name existing functions/utilities to reuse, with paths; introduce new code only with a one-line note that no existing equivalent was found.
|
|
75
|
+
- For a new or changed symbol whose callers must fit it, or whose value is load-bearing (enum member, error/log string, config key, wire/JSON field), give the exact signature or literal.
|
|
76
|
+
- For a rename, signature change, or removal, list every callsite to update (or the exact `search` that returns exactly them) and what to delete — default to a clean cutover with no dead code or compatibility aliases.
|
|
77
|
+
- When rival patterns exist, name the one to copy and the one to avoid.
|
|
78
|
+
- Specify the edge and failure handling for each new path (empty, missing, conflict, error), or state that none is needed and why.
|
|
79
|
+
- **Critical files & anchors** — the ≤5 files that disambiguate non-obvious work, each as path + the symbol or region + a one-line reason. Line numbers are hints; the implementer re-reads before editing. Skip files already obvious from the Approach.
|
|
80
|
+
- **Verification** — how to prove it works end-to-end. Include at least one check that exercises the NEW behavior (concrete input → expected observable output), not only build/typecheck or the existing suite. Give exact commands plus what they need to run: working directory, env vars, fixtures, and how to reach a manual UI or state. Tie a risky step's check to that step.
|
|
81
|
+
- **Assumptions & contingencies** — only the decisions you made that the user might want to override; you NEVER park a decision the implementer must make here — that belongs in Approach. For any load-bearing assumption that could prove false during execution, pre-decide the fallback ("if reality is X, do Y instead") so the implementer never stalls with the conversation gone.
|
|
82
|
+
|
|
83
|
+
Cut anything that removes no decision: restated invariants, unaffected behavior, mechanical repetition, narration. Spell out anything an implementer would otherwise have to invent.
|
|
100
84
|
|
|
101
85
|
<directives>
|
|
102
|
-
- You NEVER include sections
|
|
103
|
-
- You NEVER
|
|
104
|
-
- You NEVER
|
|
86
|
+
- You NEVER include decision-free sections — Non-Goals, Out of Scope, Alternatives Considered, Risks/Mitigations, Future Work. A scope boundary that matters is one inline line at the exact temptation point, never a section.
|
|
87
|
+
- You NEVER reference the planning conversation ("the option we chose above", "as discussed") — the reader will not have it. State the choice and its reason inline.
|
|
88
|
+
- You NEVER invent schema, precedence, or fallback policy the request did not establish, unless it prevents a concrete implementation mistake — then state it as a decision, not an open question.
|
|
105
89
|
</directives>
|
|
106
90
|
|
|
107
91
|
<caution>
|
|
108
|
-
|
|
92
|
+
On approval the user picks one execution mode:
|
|
109
93
|
- **Approve and execute** — execution starts in fresh context (session cleared).
|
|
110
|
-
- **Approve and compact context** — distills this discussion into a summary, then executes
|
|
111
|
-
- **Approve and keep context** — executes
|
|
94
|
+
- **Approve and compact context** — distills this discussion into a summary, then executes here.
|
|
95
|
+
- **Approve and keep context** — executes here, preserving exploration history.
|
|
112
96
|
|
|
113
|
-
All three rely on the
|
|
97
|
+
All three rely on the file being self-contained.
|
|
114
98
|
</caution>
|
|
115
99
|
|
|
116
100
|
<critical>
|
|
117
|
-
|
|
101
|
+
Before you `resolve`, apply the test: an engineer who never saw this conversation executes every step without making one design decision and can tell, at each step, whether it worked. If any step would force a choice or leave "done" ambiguous, deepen it first.
|
|
118
102
|
|
|
119
103
|
Your turn ends ONLY by:
|
|
120
|
-
1. Using `{{askToolName}}` to gather
|
|
121
|
-
2. Calling `resolve` with `action: "apply"`, `reason`, and `extra: { title: "<slug>" }` (the slug of your `local://<slug>-plan.md`)
|
|
104
|
+
1. Using `{{askToolName}}` to gather requirements or choose between approaches, OR
|
|
105
|
+
2. Calling `resolve` with `action: "apply"`, `reason`, and `extra: { title: "<slug>" }` (the slug of your `local://<slug>-plan.md`).
|
|
122
106
|
|
|
123
|
-
You NEVER
|
|
107
|
+
You NEVER request plan approval via prose or `{{askToolName}}`; you MUST use `resolve`.
|
|
124
108
|
You MUST keep going until the plan is decision-complete.
|
|
125
109
|
</critical>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<system-notice>
|
|
2
|
-
The user's message above contains the **
|
|
2
|
+
The user's message above contains the **workflowz** keyword: drive this task as a deterministic multi-subagent workflow. Author the orchestration as Python in the `eval` tool and fan out subagents — to be comprehensive (decompose and cover in parallel), to be confident (independent perspectives and adversarial checks before you commit), or to take on scale one context can't hold (audits, migrations, broad sweeps). This overrides any default tendency to do the whole task inline when fanning out would be more thorough.
|
|
3
3
|
|
|
4
4
|
<when>
|
|
5
5
|
Worth it when the task benefits from decomposition + parallel coverage, or from independent/adversarial cross-checking before you commit. For a quick lookup or single edit, just do it directly — don't spin up agents. Scout inline FIRST (list the files, scope the diff, find the call sites) to discover the work-list, then fan out over it — you don't need to know the shape before the *task*, only before the *fan-out*. Common shapes, each a well-scoped `eval` call you can chain across turns:
|
|
@@ -31,6 +31,15 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
|
|
31
31
|
- `async: true` only defers **reporting** of the result — it does NOT disable, extend, or detach the timeout. A daemon started with `async: true` is still killed when `timeout` elapses, regardless of how long the agent waits before reading the result.
|
|
32
32
|
- For long-running daemons (dev servers, watchers): either pass an explicit large `timeout` (up to `3600`), or fully detach the process from this shell using `nohup … &` / `setsid … &` / `disown` so it survives independent of the bash call's lifecycle.
|
|
33
33
|
{{/if}}
|
|
34
|
+
{{#if autoBackgroundEnabled}}
|
|
35
|
+
|
|
36
|
+
## Auto-background
|
|
37
|
+
|
|
38
|
+
- A foreground (non-`async`) call that has not completed within **{{autoBackgroundThresholdSeconds}}s** is automatically converted into a background job and returns a `Background job <id> started: …` notice with the buffered output so far. The command keeps running; the final result is delivered as a follow-up tool call when it completes.
|
|
39
|
+
- This is NOT a failure or a re-queue. Treat the notice as "still running, will report back" — do not retry the same command, and do not wait synchronously for it.
|
|
40
|
+
- Auto-backgrounding does NOT extend `timeout`: the job is still killed at the original deadline.
|
|
41
|
+
- If you need the result inline (e.g. piping into another command), raise `timeout` above the expected duration so it finishes before the threshold matters{{#if asyncEnabled}}, or set `async: true` up front so the contract is explicit{{/if}}.
|
|
42
|
+
{{/if}}
|
|
34
43
|
|
|
35
44
|
# Output minimizer
|
|
36
45
|
|
|
@@ -26,7 +26,7 @@ Drives real Chromium tab; full puppeteer access via JS execution.
|
|
|
26
26
|
- `tab.waitForResponse(pattern, { timeout? })` — pattern substring, `RegExp`, or `(response) => boolean`. Returns raw puppeteer `HTTPResponse` (call `.text()` / `.json()` / `.status()` / `.headers()` on it).
|
|
27
27
|
- `tab.evaluate(fn, …args)` — sugar for `page.evaluate` with abort signal already wired. Use this instead of dropping to `page.evaluate` for ad-hoc DOM reads.
|
|
28
28
|
- `tab.screenshot({ selector?, fullPage?, save?, silent? })` — captures screenshot and **auto-attaches to tool output for you to view** (unless `silent: true`). `save` is **strictly optional**: OMIT when you just want to look at page — downscaled image shown regardless, full-res capture written to temp file automatically. Pass `save` (a path) ONLY when deliberately need to keep full-res copy on disk for later use; `browser.screenshotDir` does same for every shot. NEVER invent `save` path for throwaway/temporal screenshot.
|
|
29
|
-
- `tab.extract(format = "markdown")` — Readability-extracted page content.
|
|
29
|
+
- `tab.extract(format = "markdown")` — returns Readability-extracted page content as a string (`"markdown"` or `"text"`). Throws if the page yields no readable content.
|
|
30
30
|
- Selectors accept CSS plus puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`. Playwright-style `p-aria/[name="…"]`, `p-text/…` normalized.
|
|
31
31
|
- Default `tab.observe()` over `tab.screenshot()` for page state. Screenshot only when visual appearance matters.
|
|
32
32
|
</instruction>
|
|
@@ -22,7 +22,7 @@ Cell fields:
|
|
|
22
22
|
</instruction>
|
|
23
23
|
|
|
24
24
|
<prelude>
|
|
25
|
-
{{#ifAll py js}}Same helpers in both runtimes with the same positional argument order. Python: trailing options as keyword args. JavaScript: trailing options
|
|
25
|
+
{{#ifAll py js}}Same helpers in both runtimes with the same positional argument order. Python: trailing options as keyword args. JavaScript: trailing options are a single trailing object literal, never positional — passing options positionally (or any extra positional arg) throws. JavaScript helpers are async and `await`able; Python helpers run synchronously.{{else}}{{#if py}}Helpers run synchronously. Trailing options are keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are a single trailing object literal, never positional — passing options positionally (or any extra positional arg) throws.{{/if}}{{/ifAll}}
|
|
26
26
|
```
|
|
27
27
|
display(value) → None
|
|
28
28
|
Render a value in the current cell output.
|
|
@@ -46,8 +46,11 @@ tool.<name>(args) → unknown
|
|
|
46
46
|
Invoke any session tool by name. `args` is the tool's parameter object.
|
|
47
47
|
llm(prompt, model?="default", system?=None, schema?=None) → str | dict
|
|
48
48
|
Oneshot, stateless LLM call (no history, no tools). `model` picks a tier: "smol" (fast), "default" (this session's model), "slow" (most capable). Pass `system` for a system prompt. Pass a JSON-Schema `schema` to force structured output and get the parsed object back; otherwise returns the completion text.
|
|
49
|
-
agent(prompt, agent_type?="task", model?=None, context?=None, label?=None, schema?=None) → str | dict
|
|
49
|
+
{{#if spawns}}agent(prompt, agent_type?="task", model?=None, context?=None, label?=None, schema?=None) → str | dict
|
|
50
50
|
Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back.
|
|
51
|
+
{{#if js}} In JS, pass options as one trailing object — never positional: agent(prompt, { agentType, context, schema }).
|
|
52
|
+
{{/if}}
|
|
53
|
+
{{/if}}
|
|
51
54
|
parallel(thunks) → list
|
|
52
55
|
Run thunks (callables) through a bounded pool, preserving input order. The pool is as wide as a `task` tool batch (tracks the `task.maxConcurrency` setting), so fan out as wide as the work divides — don't pre-shrink it. Barrier: returns once all finish; a thunk that throws propagates.
|
|
53
56
|
pipeline(items, ...stages) → list
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<system-notice>
|
|
2
|
+
{{#if multiple}}Late LSP diagnostics arrived for {{files.length}} files after their edits returned:
|
|
3
|
+
{{else}}Late LSP diagnostics arrived after the edit returned:
|
|
4
|
+
{{/if}}
|
|
5
|
+
{{#each files}}{{this.path}} — {{this.summary}}
|
|
6
|
+
{{#each this.messages}}{{this}}
|
|
7
|
+
{{/each}}{{#unless @last}}
|
|
8
|
+
{{/unless}}{{/each}}</system-notice>
|
|
@@ -18,8 +18,8 @@ Append `:<sel>` to `path`. The bare path falls back to the default mode.
|
|
|
18
18
|
- `:50` / `:50-` — read from line 50 onward.
|
|
19
19
|
- `:50-200` — lines 50–200 inclusive.
|
|
20
20
|
- `:50+150` — 150 lines starting at line 50.
|
|
21
|
-
- `:20+1` —
|
|
22
|
-
- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged).
|
|
21
|
+
- `:20+1` — anchor on line 20 (single-range reads expand by ≤1 leading and ≤3 trailing context lines).
|
|
22
|
+
- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged). Multi-range mode returns exact bounds with no context padding.
|
|
23
23
|
- `:raw` — verbatim text; no anchors, no summary, no line prefixes.
|
|
24
24
|
- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.
|
|
25
25
|
- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.
|
package/src/sdk.ts
CHANGED
|
@@ -41,7 +41,9 @@ import { createApiKeyResolver } from "./config/api-key-resolver";
|
|
|
41
41
|
import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
|
|
42
42
|
import { ModelRegistry } from "./config/model-registry";
|
|
43
43
|
import {
|
|
44
|
+
defaultModelPerProvider,
|
|
44
45
|
formatModelString,
|
|
46
|
+
getModelMatchPreferences,
|
|
45
47
|
parseModelPattern,
|
|
46
48
|
parseModelString,
|
|
47
49
|
resolveAllowedModels,
|
|
@@ -89,6 +91,7 @@ import { discoverAndLoadMCPTools, MCPManager, type MCPToolsLoadResult } from "./
|
|
|
89
91
|
import { resolveMemoryBackend } from "./memory-backend";
|
|
90
92
|
import type { MnemopiSessionState } from "./mnemopi/state";
|
|
91
93
|
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
94
|
+
import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with { type: "text" };
|
|
92
95
|
import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
93
96
|
import {
|
|
94
97
|
collectEnvSecrets,
|
|
@@ -108,7 +111,12 @@ import {
|
|
|
108
111
|
type SnapshotResponse,
|
|
109
112
|
writeAuthBrokerSnapshotCache,
|
|
110
113
|
} from "./session/auth-storage";
|
|
111
|
-
import {
|
|
114
|
+
import {
|
|
115
|
+
type CustomMessage,
|
|
116
|
+
convertToLlm,
|
|
117
|
+
LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
|
|
118
|
+
wrapSteeringForModel,
|
|
119
|
+
} from "./session/messages";
|
|
112
120
|
import { getRestorableSessionModels, SessionManager } from "./session/session-manager";
|
|
113
121
|
import { closeAllConnections } from "./ssh/connection-manager";
|
|
114
122
|
import { unmountAll } from "./ssh/sshfs-mount";
|
|
@@ -141,6 +149,7 @@ import {
|
|
|
141
149
|
BUILTIN_TOOLS,
|
|
142
150
|
computeEssentialBuiltinNames,
|
|
143
151
|
createTools,
|
|
152
|
+
type DeferredDiagnosticsEntry,
|
|
144
153
|
discoverStartupLspServers,
|
|
145
154
|
EditTool,
|
|
146
155
|
EvalTool,
|
|
@@ -227,6 +236,42 @@ function buildAsyncResultBatchMessage(entries: AsyncResultEntry[]): CustomMessag
|
|
|
227
236
|
};
|
|
228
237
|
}
|
|
229
238
|
|
|
239
|
+
type LateDiagnosticsDetails = {
|
|
240
|
+
files: Array<{ path: string; summary: string; errored: boolean; messages: string[] }>;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
function buildLateDiagnosticsBatchMessage(
|
|
244
|
+
entries: DeferredDiagnosticsEntry[],
|
|
245
|
+
): CustomMessage<LateDiagnosticsDetails> | null {
|
|
246
|
+
if (entries.length === 0) return null;
|
|
247
|
+
const files = entries.map(entry => ({
|
|
248
|
+
path: entry.path,
|
|
249
|
+
summary: entry.summary,
|
|
250
|
+
messages: entry.messages,
|
|
251
|
+
errored: entry.errored,
|
|
252
|
+
}));
|
|
253
|
+
const details: LateDiagnosticsDetails = {
|
|
254
|
+
files: files.map(file => ({
|
|
255
|
+
path: file.path,
|
|
256
|
+
summary: file.summary,
|
|
257
|
+
errored: file.errored,
|
|
258
|
+
messages: file.messages,
|
|
259
|
+
})),
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
role: "custom",
|
|
263
|
+
customType: LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
|
|
264
|
+
content: prompt.render(lateDiagnosticTemplate, {
|
|
265
|
+
multiple: files.length > 1,
|
|
266
|
+
files,
|
|
267
|
+
}),
|
|
268
|
+
display: true,
|
|
269
|
+
attribution: "agent",
|
|
270
|
+
details,
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
230
275
|
function buildMcpNotificationBatchMessage(entries: McpNotificationEntry[]): AgentMessage | null {
|
|
231
276
|
const resources: McpNotificationEntry[] = [];
|
|
232
277
|
const seen = new Set<string>();
|
|
@@ -709,6 +754,7 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
|
709
754
|
parameters: tool.parameters,
|
|
710
755
|
hidden: tool.hidden,
|
|
711
756
|
deferrable: tool.deferrable,
|
|
757
|
+
approval: typeof tool.approval === "function" ? tool.approval.bind(tool) : tool.approval,
|
|
712
758
|
mcpServerName: tool.mcpServerName,
|
|
713
759
|
mcpToolName: tool.mcpToolName,
|
|
714
760
|
execute: (toolCallId, params, signal, onUpdate, ctx) =>
|
|
@@ -1030,9 +1076,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1030
1076
|
const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
|
|
1031
1077
|
|
|
1032
1078
|
const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
|
|
1033
|
-
const modelMatchPreferences =
|
|
1034
|
-
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
1035
|
-
};
|
|
1079
|
+
const modelMatchPreferences = getModelMatchPreferences(settings);
|
|
1036
1080
|
const allowedModels = await logger.time("resolveAllowedModels", () =>
|
|
1037
1081
|
resolveAllowedModels(modelRegistry, settings, modelMatchPreferences),
|
|
1038
1082
|
);
|
|
@@ -1266,6 +1310,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1266
1310
|
if (model) return formatModelString(model);
|
|
1267
1311
|
return undefined;
|
|
1268
1312
|
};
|
|
1313
|
+
// Per-path mutation counter shared across edit/write tools. Late-diagnostics
|
|
1314
|
+
// entries capture it at fetch time and are dropped at injection if a newer
|
|
1315
|
+
// mutation (any tool) bumped it in the meantime.
|
|
1316
|
+
const fileMutationVersions = new Map<string, number>();
|
|
1269
1317
|
const toolSession: ToolSession = {
|
|
1270
1318
|
get cwd() {
|
|
1271
1319
|
return sessionManager.getCwd();
|
|
@@ -1311,6 +1359,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1311
1359
|
recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
|
|
1312
1360
|
getClientBridge: () => session?.clientBridge,
|
|
1313
1361
|
getCompactContext: () => session.formatCompactContext(),
|
|
1362
|
+
queueDeferredDiagnostics: entry => session?.yieldQueue.enqueue(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, entry),
|
|
1363
|
+
bumpFileMutationVersion: path => {
|
|
1364
|
+
const next = (fileMutationVersions.get(path) ?? 0) + 1;
|
|
1365
|
+
fileMutationVersions.set(path, next);
|
|
1366
|
+
return next;
|
|
1367
|
+
},
|
|
1368
|
+
getFileMutationVersion: path => fileMutationVersions.get(path) ?? 0,
|
|
1314
1369
|
getTodoPhases: () => session.getTodoPhases(),
|
|
1315
1370
|
setTodoPhases: phases => session.setTodoPhases(phases),
|
|
1316
1371
|
isMCPDiscoveryEnabled: () => session.isMCPDiscoveryEnabled(),
|
|
@@ -1553,9 +1608,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1553
1608
|
// Resolve deferred --model pattern now that extension models are registered.
|
|
1554
1609
|
if (!model && options.modelPattern) {
|
|
1555
1610
|
const availableModels = modelRegistry.getAll();
|
|
1556
|
-
const matchPreferences =
|
|
1557
|
-
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
1558
|
-
};
|
|
1611
|
+
const matchPreferences = getModelMatchPreferences(settings);
|
|
1559
1612
|
const { model: resolved } = parseModelPattern(options.modelPattern, availableModels, matchPreferences, {
|
|
1560
1613
|
modelRegistry,
|
|
1561
1614
|
});
|
|
@@ -1574,12 +1627,30 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1574
1627
|
// Re-resolve the allowed set: extension factories above may have
|
|
1575
1628
|
// registered providers/models that weren't visible at startup.
|
|
1576
1629
|
const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1630
|
+
// Prefer each provider's configured default model
|
|
1631
|
+
// (DEFAULT_MODEL_PER_PROVIDER) over raw catalog order. Without this the
|
|
1632
|
+
// first-run fallback picks whatever model sorts first in models.json for
|
|
1633
|
+
// the winning provider (e.g. anthropic's claude-3-5-sonnet-20240620)
|
|
1634
|
+
// instead of the intended provider default (claude-sonnet-4-6). Mirrors
|
|
1635
|
+
// findInitialModel's precedence.
|
|
1636
|
+
for (const [provider, defaultId] of Object.entries(defaultModelPerProvider)) {
|
|
1637
|
+
const preferred = fallbackCandidates.find(
|
|
1638
|
+
candidate => candidate.provider === provider && candidate.id === defaultId,
|
|
1639
|
+
);
|
|
1640
|
+
if (preferred && (await hasModelApiKey(preferred))) {
|
|
1641
|
+
model = preferred;
|
|
1580
1642
|
break;
|
|
1581
1643
|
}
|
|
1582
1644
|
}
|
|
1645
|
+
// Otherwise, first available model with a valid API key.
|
|
1646
|
+
if (!model) {
|
|
1647
|
+
for (const candidate of fallbackCandidates) {
|
|
1648
|
+
if (await hasModelApiKey(candidate)) {
|
|
1649
|
+
model = candidate;
|
|
1650
|
+
break;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1583
1654
|
if (model) {
|
|
1584
1655
|
if (modelFallbackMessage) {
|
|
1585
1656
|
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
@@ -2150,6 +2221,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2150
2221
|
session.yieldQueue.register<McpNotificationEntry>("mcp-notification", {
|
|
2151
2222
|
build: buildMcpNotificationBatchMessage,
|
|
2152
2223
|
});
|
|
2224
|
+
session.yieldQueue.register<DeferredDiagnosticsEntry>(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, {
|
|
2225
|
+
isStale: entry => entry.isStale(),
|
|
2226
|
+
build: buildLateDiagnosticsBatchMessage,
|
|
2227
|
+
});
|
|
2153
2228
|
|
|
2154
2229
|
// Attach the live session to the pre-registered ref so peers can route IRC
|
|
2155
2230
|
// messages here. Refresh sessionFile in case it was unavailable at pre-register
|
|
@@ -109,6 +109,7 @@ import {
|
|
|
109
109
|
extractExplicitThinkingSelector,
|
|
110
110
|
formatModelSelectorValue,
|
|
111
111
|
formatModelString,
|
|
112
|
+
getModelMatchPreferences,
|
|
112
113
|
parseModelString,
|
|
113
114
|
type ResolvedModelRoleValue,
|
|
114
115
|
resolveModelRoleValue,
|
|
@@ -283,6 +284,11 @@ export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
|
283
284
|
export type AsyncJobSnapshotItem = Pick<AsyncJob, "id" | "type" | "status" | "label" | "startTime">;
|
|
284
285
|
|
|
285
286
|
const EMPTY_STOP_MAX_RETRIES = 3;
|
|
287
|
+
const NON_WHITESPACE_RE = /\S/;
|
|
288
|
+
|
|
289
|
+
function hasNonWhitespace(value: string): boolean {
|
|
290
|
+
return NON_WHITESPACE_RE.test(value);
|
|
291
|
+
}
|
|
286
292
|
|
|
287
293
|
export interface AsyncJobSnapshot {
|
|
288
294
|
running: AsyncJobSnapshotItem[];
|
|
@@ -1168,7 +1174,6 @@ export class AgentSession {
|
|
|
1168
1174
|
this.agent.setRawSseEventInterceptor(this.#onSseEvent);
|
|
1169
1175
|
this.yieldQueue = new YieldQueue({
|
|
1170
1176
|
isStreaming: () => this.isStreaming,
|
|
1171
|
-
injectStreaming: message => this.agent.followUp(message),
|
|
1172
1177
|
injectIdle: async messages => {
|
|
1173
1178
|
const first = messages[0];
|
|
1174
1179
|
if (!first) return;
|
|
@@ -1183,7 +1188,10 @@ export class AgentSession {
|
|
|
1183
1188
|
);
|
|
1184
1189
|
},
|
|
1185
1190
|
});
|
|
1186
|
-
|
|
1191
|
+
// Background-job completions / late diagnostics are pulled into the run at
|
|
1192
|
+
// each step boundary as non-interrupting asides (see Agent.getAsideMessages),
|
|
1193
|
+
// so they reach the model between requests without waiting for a yield.
|
|
1194
|
+
this.agent.setAsideMessageProvider(() => this.yieldQueue.drainLazy());
|
|
1187
1195
|
this.#convertToLlm = config.convertToLlm ?? convertToLlm;
|
|
1188
1196
|
this.#rebuildSystemPrompt = config.rebuildSystemPrompt;
|
|
1189
1197
|
this.#getMcpServerInstructions = config.getMcpServerInstructions;
|
|
@@ -3034,7 +3042,7 @@ export class AgentSession {
|
|
|
3034
3042
|
this.#isDisposed = true;
|
|
3035
3043
|
this.#pendingBackgroundExchanges = [];
|
|
3036
3044
|
this.yieldQueue.clear();
|
|
3037
|
-
this.agent.
|
|
3045
|
+
this.agent.setAsideMessageProvider(undefined);
|
|
3038
3046
|
this.#evalExecutionDisposing = true;
|
|
3039
3047
|
try {
|
|
3040
3048
|
if (this.#extensionRunner?.hasHandlers("session_shutdown")) {
|
|
@@ -5445,7 +5453,7 @@ export class AgentSession {
|
|
|
5445
5453
|
|
|
5446
5454
|
const currentModel = this.model;
|
|
5447
5455
|
if (!currentModel) return undefined;
|
|
5448
|
-
const matchPreferences =
|
|
5456
|
+
const matchPreferences = getModelMatchPreferences(this.settings);
|
|
5449
5457
|
const models: ResolvedRoleModel[] = [];
|
|
5450
5458
|
|
|
5451
5459
|
for (const role of roleOrder) {
|
|
@@ -6539,9 +6547,13 @@ export class AgentSession {
|
|
|
6539
6547
|
this.#retryAttempt = 0;
|
|
6540
6548
|
}
|
|
6541
6549
|
this.#resolveRetry();
|
|
6550
|
+
// Tool-use orphans corrupt Anthropic message history (tool_result without
|
|
6551
|
+
// matching tool_use). Always remove them even when the retry cap is hit.
|
|
6552
|
+
if (assistantMessage.stopReason === "toolUse") {
|
|
6553
|
+
this.#removeEmptyStopFromActiveContext(assistantMessage);
|
|
6554
|
+
}
|
|
6542
6555
|
return true;
|
|
6543
6556
|
}
|
|
6544
|
-
|
|
6545
6557
|
this.#removeEmptyStopFromActiveContext(assistantMessage);
|
|
6546
6558
|
this.agent.appendMessage({
|
|
6547
6559
|
role: "developer",
|
|
@@ -6554,12 +6566,26 @@ export class AgentSession {
|
|
|
6554
6566
|
}
|
|
6555
6567
|
|
|
6556
6568
|
#isEmptyAssistantStop(assistantMessage: AssistantMessage): boolean {
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6569
|
+
switch (assistantMessage.stopReason) {
|
|
6570
|
+
case "stop":
|
|
6571
|
+
for (const content of assistantMessage.content) {
|
|
6572
|
+
if (content.type === "toolCall") return false;
|
|
6573
|
+
if (content.type === "text" && hasNonWhitespace(content.text)) return false;
|
|
6574
|
+
if (content.type === "thinking" && hasNonWhitespace(content.thinking)) return false;
|
|
6575
|
+
}
|
|
6576
|
+
return true;
|
|
6577
|
+
case "toolUse":
|
|
6578
|
+
// An orphaned toolUse stop (no tool_use block) corrupts Anthropic history:
|
|
6579
|
+
// a later tool_result has nothing to anchor to. Thinking alone cannot anchor
|
|
6580
|
+
// a tool_result, so it does not rescue a toolUse stop here.
|
|
6581
|
+
for (const content of assistantMessage.content) {
|
|
6582
|
+
if (content.type === "toolCall") return false;
|
|
6583
|
+
if (content.type === "text" && hasNonWhitespace(content.text)) return false;
|
|
6584
|
+
}
|
|
6585
|
+
return true;
|
|
6586
|
+
default:
|
|
6587
|
+
return false;
|
|
6588
|
+
}
|
|
6563
6589
|
}
|
|
6564
6590
|
|
|
6565
6591
|
#emptyStopRetryReminder(): string {
|
|
@@ -7143,7 +7169,7 @@ export class AgentSession {
|
|
|
7143
7169
|
|
|
7144
7170
|
return resolveModelRoleValue(roleModelStr, availableModels, {
|
|
7145
7171
|
settings: this.settings,
|
|
7146
|
-
matchPreferences:
|
|
7172
|
+
matchPreferences: getModelMatchPreferences(this.settings),
|
|
7147
7173
|
modelRegistry: this.#modelRegistry,
|
|
7148
7174
|
});
|
|
7149
7175
|
}
|
|
@@ -7874,11 +7900,12 @@ export class AgentSession {
|
|
|
7874
7900
|
#isTransientTransportErrorMessage(errorMessage: string): boolean {
|
|
7875
7901
|
// Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504,
|
|
7876
7902
|
// service unavailable, provider-suggested retry, network/connection/socket errors, fetch failed,
|
|
7877
|
-
// terminated, retry delay exceeded, Bun HTTP/2 stream resets
|
|
7878
|
-
// ENHANCE_YOUR_CALM, surfaced verbatim from
|
|
7903
|
+
// gateway upstream failures, terminated, retry delay exceeded, Bun HTTP/2 stream resets
|
|
7904
|
+
// (RST_STREAM / REFUSED_STREAM / ENHANCE_YOUR_CALM, surfaced verbatim from
|
|
7905
|
+
// src/http/h2_client/dispatch.zig)
|
|
7879
7906
|
return (
|
|
7880
7907
|
isUnexpectedSocketCloseMessage(errorMessage) ||
|
|
7881
|
-
/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|retry your request|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay|stream stall|no error details in response|HTTP2(?:StreamReset|RefusedStream|EnhanceYourCalm)/i.test(
|
|
7908
|
+
/overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|retry your request|network.?error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|upstream.?request.?failed|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay|stream stall|no error details in response|HTTP2(?:StreamReset|RefusedStream|EnhanceYourCalm)/i.test(
|
|
7882
7909
|
errorMessage,
|
|
7883
7910
|
)
|
|
7884
7911
|
);
|