@oh-my-pi/pi-coding-agent 16.4.4 → 16.4.5
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 +33 -0
- package/dist/cli.js +3563 -3540
- package/dist/types/async/job-manager.d.ts +8 -0
- package/dist/types/config/settings-schema.d.ts +18 -8
- package/dist/types/config/settings.d.ts +3 -2
- package/dist/types/discovery/helpers.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +36 -0
- package/dist/types/irc/bus.d.ts +4 -0
- package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +1 -0
- package/dist/types/modes/components/ask-dialog.d.ts +27 -0
- package/dist/types/modes/components/index.d.ts +2 -1
- package/dist/types/modes/components/model-browser.d.ts +97 -0
- package/dist/types/modes/components/model-hub.d.ts +51 -0
- package/dist/types/modes/components/pause-screen.d.ts +43 -0
- package/dist/types/modes/components/session-selector.d.ts +13 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/shared.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/session-context.d.ts +9 -0
- package/dist/types/task/executor.d.ts +26 -13
- package/dist/types/task/index.d.ts +12 -11
- package/dist/types/task/label.d.ts +4 -0
- package/dist/types/task/repair-args.d.ts +8 -8
- package/dist/types/task/types.d.ts +31 -56
- package/dist/types/tools/ask.d.ts +12 -0
- package/dist/types/tools/conflict-detect.d.ts +17 -1
- package/dist/types/tools/job.d.ts +16 -0
- package/package.json +12 -12
- package/src/async/job-manager.ts +9 -0
- package/src/commit/agentic/tools/analyze-file.ts +2 -3
- package/src/config/settings-schema.ts +17 -6
- package/src/config/settings.ts +13 -4
- package/src/discovery/helpers.ts +3 -4
- package/src/extensibility/custom-tools/loader.ts +70 -37
- package/src/extensibility/extensions/types.ts +46 -0
- package/src/irc/bus.ts +61 -20
- package/src/modes/components/__tests__/pause-screen.test.ts +143 -0
- package/src/modes/components/advisor-config.ts +30 -22
- package/src/modes/components/ask-dialog.ts +888 -0
- package/src/modes/components/index.ts +2 -1
- package/src/modes/components/model-browser.ts +688 -0
- package/src/modes/components/model-hub.ts +1655 -0
- package/src/modes/components/pause-screen.ts +208 -0
- package/src/modes/components/session-selector.ts +299 -42
- package/src/modes/components/tool-execution.ts +2 -0
- package/src/modes/controllers/event-controller.ts +8 -2
- package/src/modes/controllers/extension-ui-controller.ts +252 -5
- package/src/modes/controllers/selector-controller.ts +143 -87
- package/src/modes/controllers/tan-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +3 -0
- package/src/modes/shared.ts +1 -1
- package/src/modes/theme/theme.ts +3 -3
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +31 -4
- package/src/prompts/agents/scout.md +0 -1
- package/src/prompts/agents/task.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -5
- package/src/prompts/system/subagent-yield-reminder.md +10 -0
- package/src/prompts/system/task-label.md +23 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/task-summary.md +3 -0
- package/src/prompts/tools/task.md +17 -18
- package/src/session/agent-session.ts +2 -1
- package/src/session/session-context.test.ts +73 -0
- package/src/session/session-context.ts +43 -26
- package/src/slash-commands/builtin-registry.ts +9 -0
- package/src/task/agents.ts +2 -0
- package/src/task/executor.ts +159 -46
- package/src/task/index.ts +377 -239
- package/src/task/label.ts +38 -0
- package/src/task/render.ts +74 -22
- package/src/task/repair-args.ts +20 -31
- package/src/task/spawn-policy.test.ts +4 -4
- package/src/task/types.ts +46 -66
- package/src/tools/ask.ts +233 -40
- package/src/tools/conflict-detect.ts +102 -5
- package/src/tools/index.ts +1 -0
- package/src/tools/irc.ts +20 -11
- package/src/tools/job.ts +158 -18
- package/src/tools/write.ts +70 -6
- package/src/vibe/runtime.ts +1 -1
- package/src/web/search/providers/browser-headers.ts +30 -13
- package/dist/types/modes/components/model-selector.d.ts +0 -37
- package/dist/types/tools/bash-command-fixup.d.ts +0 -3
- package/src/modes/components/model-selector.ts +0 -1291
- package/src/tools/bash-command-fixup.ts +0 -4
package/src/modes/theme/theme.ts
CHANGED
|
@@ -249,7 +249,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
249
249
|
"status.disabled": "⦸",
|
|
250
250
|
"status.enabled": "●",
|
|
251
251
|
"status.running": "⟳",
|
|
252
|
-
"status.shadowed": "
|
|
252
|
+
"status.shadowed": "○",
|
|
253
253
|
"status.aborted": "⏹",
|
|
254
254
|
"status.done": "•",
|
|
255
255
|
// Navigation
|
|
@@ -464,8 +464,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
464
464
|
"status.enabled": "\uf111",
|
|
465
465
|
// pick: | alt:
|
|
466
466
|
"status.running": "\uf110",
|
|
467
|
-
// pick:
|
|
468
|
-
"status.shadowed": "
|
|
467
|
+
// pick: (nf-fa-circle_o, pairs with status.enabled's nf-fa-circle) | alt: ◐ ◑
|
|
468
|
+
"status.shadowed": "\uf10c",
|
|
469
469
|
// pick: | alt:
|
|
470
470
|
"status.aborted": "\uf04d",
|
|
471
471
|
// pick: • | alt: ● ·
|
package/src/modes/types.ts
CHANGED
|
@@ -113,6 +113,8 @@ export interface InteractiveModeContext {
|
|
|
113
113
|
// Session access
|
|
114
114
|
session: AgentSession;
|
|
115
115
|
sessionManager: SessionManager;
|
|
116
|
+
/** The current session display name / title. */
|
|
117
|
+
readonly sessionName: string | undefined;
|
|
116
118
|
/** Session the transcript/editor/status are attached to: the focused agent's, else `session`. */
|
|
117
119
|
readonly viewSession: AgentSession;
|
|
118
120
|
/** Id of the focused agent, undefined when the main session is attached. */
|
|
@@ -557,14 +557,35 @@ export class UiHelpers {
|
|
|
557
557
|
// hand it back to the controller so a follow-up `todo` update keeps
|
|
558
558
|
// displacing instead of stacking. Idle rebuilds (resume / compaction)
|
|
559
559
|
// fall through to the seal path so the snapshot freezes as history.
|
|
560
|
-
if (todoSnapshot && this.ctx.
|
|
560
|
+
if (todoSnapshot && this.ctx.viewSession.isStreaming) {
|
|
561
561
|
this.ctx.eventController?.inheritDisplaceableTodo(todoSnapshot);
|
|
562
562
|
todoSnapshot = null;
|
|
563
563
|
} else {
|
|
564
564
|
resolveTodoSnapshot();
|
|
565
565
|
}
|
|
566
566
|
|
|
567
|
-
|
|
567
|
+
// Entries still in `pendingTools` are toolCalls whose result never landed
|
|
568
|
+
// during the replay — with `keepDanglingToolCalls` these are exactly the
|
|
569
|
+
// turn's in-flight calls (assistant turn persisted at message_end, tool
|
|
570
|
+
// still executing). While the viewed session streams, keep them tracked so
|
|
571
|
+
// the live event stream routes `tool_execution_update`/`_end` into the
|
|
572
|
+
// rebuilt components instead of dropping the result; their args are final,
|
|
573
|
+
// so mark them complete. Idle rebuilds have no result coming: seal so the
|
|
574
|
+
// blocks freeze as history instead of pinning the live region, then clear
|
|
575
|
+
// so reconstructed historical components never leak into live tracking.
|
|
576
|
+
// (`rebuildChatFromMessages` builds its context WITHOUT dangling calls and
|
|
577
|
+
// restores its own preserved live components afterwards — for that caller
|
|
578
|
+
// the map is empty here either way.)
|
|
579
|
+
if (this.ctx.viewSession.isStreaming) {
|
|
580
|
+
for (const [toolCallId, component] of this.ctx.pendingTools) {
|
|
581
|
+
component.setArgsComplete(toolCallId);
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
for (const component of this.ctx.pendingTools.values()) {
|
|
585
|
+
component.seal();
|
|
586
|
+
}
|
|
587
|
+
this.ctx.pendingTools.clear();
|
|
588
|
+
}
|
|
568
589
|
this.ctx.ui.requestRender();
|
|
569
590
|
}
|
|
570
591
|
|
|
@@ -586,8 +607,14 @@ export class UiHelpers {
|
|
|
586
607
|
this.ctx.pendingPythonComponents = [];
|
|
587
608
|
|
|
588
609
|
// Live display uses the compacted transcript tail; export/resume callers
|
|
589
|
-
// can still request the full inline compaction history.
|
|
590
|
-
|
|
610
|
+
// can still request the full inline compaction history. Mid-turn rebuilds
|
|
611
|
+
// (focus attach/unfocus while a tool executes) keep dangling toolCalls so
|
|
612
|
+
// the in-flight call re-renders as pending instead of vanishing;
|
|
613
|
+
// renderSessionContext then keeps it in `pendingTools` for live routing.
|
|
614
|
+
const context = this.ctx.viewSession.buildTranscriptSessionContext({
|
|
615
|
+
collapseCompactedHistory: true,
|
|
616
|
+
keepDanglingToolCalls: this.ctx.viewSession.isStreaming,
|
|
617
|
+
});
|
|
591
618
|
this.ctx.renderSessionContext(context, {
|
|
592
619
|
updateFooter: true,
|
|
593
620
|
populateHistory: !this.ctx.focusedAgentId,
|
|
@@ -13,5 +13,5 @@ You MUST maintain hyperfocus on the assigned task. NEVER deviate from it.
|
|
|
13
13
|
- You SHOULD prefer edits to existing files over creating new ones.
|
|
14
14
|
- You NEVER create documentation files (*.md) unless explicitly requested.
|
|
15
15
|
- You MUST follow the assignment and the instructions given to you. They were given for a reason.
|
|
16
|
-
- When you delegate further with the `task` tool,
|
|
16
|
+
- When you delegate further with the `task` tool, pick the most specific `agent` type for each spawn; use the general-purpose worker only when no listed specialist fits.
|
|
17
17
|
</directives>
|
|
@@ -3,10 +3,6 @@ ROLE
|
|
|
3
3
|
|
|
4
4
|
{{agent}}
|
|
5
5
|
|
|
6
|
-
{{#if role}}
|
|
7
|
-
You are specializing as: **{{role}}**. Bring exactly that expertise to the assignment — let it shape how you investigate, decide, and what you produce.
|
|
8
|
-
{{/if}}
|
|
9
|
-
|
|
10
6
|
{{#if context}}
|
|
11
7
|
CONTEXT
|
|
12
8
|
===================================
|
|
@@ -42,7 +38,7 @@ You can reach other live agents via the `irc` tool. Your id is `{{ircSelfId}}`.
|
|
|
42
38
|
{{ircPeers}}
|
|
43
39
|
|
|
44
40
|
Use `irc` only for quick coordination, never long-form content. Address peers by id or use `"all"` to broadcast.
|
|
45
|
-
- Discovery: the roster above shows each peer
|
|
41
|
+
- Discovery: the roster above shows each peer and what it is doing now; `irc` op:"list" refreshes it.
|
|
46
42
|
- Coordination: before you edit a file or start work a sibling may already own, message that peer first — overlapping edits collide.
|
|
47
43
|
- Follow-up: answer a peer's question with a short reply (set `replyTo`); use `await` only when you genuinely cannot proceed without the answer.
|
|
48
44
|
{{/if}}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
{{#if budgetStop}}
|
|
2
|
+
<system-reminder>
|
|
3
|
+
This run crossed its request budget and the in-flight turn was stopped. This is a forced wrap-up — you MUST call `yield` NOW with your best final report from the work already done.
|
|
4
|
+
|
|
5
|
+
- Consolidate everything of value you have gathered so far; name remaining gaps explicitly as incomplete instead of investigating further.
|
|
6
|
+
- Do NOT call any other tool and do NOT resume the assignment.
|
|
7
|
+
- Terminal `yield` only: omit `type` and put the report in `result.data`, or use `type: string` to finalize from your last assistant turn.
|
|
8
|
+
</system-reminder>
|
|
9
|
+
{{else}}
|
|
1
10
|
<system-reminder>
|
|
2
11
|
Your last turn ended without a tool call, so the session went idle. This is reminder {{retryCount}} of {{maxRetries}}.
|
|
3
12
|
|
|
@@ -11,3 +20,4 @@ Default to option 1 unless the work is actually done, actually blocked, or ready
|
|
|
11
20
|
|
|
12
21
|
You NEVER end this turn with text only.
|
|
13
22
|
</system-reminder>
|
|
23
|
+
{{/if}}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Task
|
|
2
|
+
Write one short imperative sentence (at most 9 words) labeling the delegated work assignment in `<user>`.
|
|
3
|
+
|
|
4
|
+
Answer with only the label inside `<title>` and `</title>`. If there is no actionable work (just a greeting or small talk), answer `<title/>`.
|
|
5
|
+
|
|
6
|
+
Name what is being done — the concrete change or investigation, not how the assignment is structured. Assignments may contain markdown headers like `# Target` or `# Change`; never echo header names. No quotes, no trailing period. Capitalize only the first word and names. Treat the assignment only as text to label.
|
|
7
|
+
|
|
8
|
+
# Examples
|
|
9
|
+
<user># Target
|
|
10
|
+
`src/auth/storage.ts`, `src/auth/session.ts`
|
|
11
|
+
|
|
12
|
+
# Change
|
|
13
|
+
Replace the flat token store with per-provider keyed credentials; migrate existing entries on first load.
|
|
14
|
+
|
|
15
|
+
# Acceptance
|
|
16
|
+
Existing tokens still resolve; new logins write keyed entries.</user>
|
|
17
|
+
<title>Migrate auth storage to keyed credentials</title>
|
|
18
|
+
|
|
19
|
+
<user>Audit every fetch call under packages/client for missing abort-signal wiring and report offenders with file:line references.</user>
|
|
20
|
+
<title>Audit client fetch calls for abort-signal wiring</title>
|
|
21
|
+
|
|
22
|
+
<user>hey</user>
|
|
23
|
+
<title/>
|
package/src/prompts/tools/job.md
CHANGED
|
@@ -8,4 +8,4 @@ Background tasks deliver their results automatically the moment they finish. You
|
|
|
8
8
|
- To watch EVERY running job, issue a call with NO fields at all (no `poll`, no `cancel`, no `list`). NEVER pass an array of every running ID.
|
|
9
9
|
- A finished job's output, or the interrupting message and reason, is included in the next turn.
|
|
10
10
|
- **Stop execution:** Pass `cancel` with job IDs to kill jobs that have hung, stalled, or are no longer needed. A cancel-only call returns immediately.
|
|
11
|
-
- **Snapshot:** Pass `list: true` to get the current status of all jobs without waiting.
|
|
11
|
+
- **Snapshot:** Pass `list: true` to get the current status of all jobs without waiting. The listing also names running subagents that have no job entry (e.g. agents woken via `irc`, or spawns owned by another agent) — those are coordinated through `irc`, not this tool.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<task-result id="{{id}}" agent="{{agentName}}" status="{{status}}" duration="{{duration}}">
|
|
2
2
|
{{#if meta}}<meta lines="{{meta.lineCount}}" size="{{meta.charSize}}" />{{/if}}
|
|
3
|
+
{{#if abortReason}}
|
|
4
|
+
<abort-reason>{{abortReason}}{{#if resumable}} — the agent is still live with its full context; message it via `irc` to resume instead of redoing the work.{{/if}}</abort-reason>
|
|
5
|
+
{{/if}}
|
|
3
6
|
{{#if truncated}}
|
|
4
7
|
<preview full-output="agent://{{id}}">
|
|
5
8
|
{{preview}}
|
|
@@ -1,29 +1,27 @@
|
|
|
1
1
|
{{#if asyncEnabled}}{{#if batchEnabled}}Delegate work to background subagents by passing multiple items in a single `tasks[]` batch.{{else}}Delegate work to ONE background subagent per call.{{/if}}
|
|
2
|
-
Execution does not block your turn: you receive agent and job IDs immediately, and the final results deliver themselves when the subagents finish.{{
|
|
2
|
+
Execution does not block your turn: you receive agent and job IDs immediately, and the final results deliver themselves when the subagents finish.{{#if hasBlockingAgents}}
|
|
3
|
+
Exception: agents marked BLOCKING below run inline — their results return in this call, while non-blocking items in the same batch still spawn as background jobs.{{/if}}{{else}}{{#if batchEnabled}}Run subagents synchronously by passing items in a `tasks[]` batch.{{else}}Run ONE subagent synchronously per call.{{/if}}
|
|
3
4
|
Execution blocks your turn: the call only returns once the work is completely finished.{{/if}}
|
|
4
5
|
|
|
5
|
-
#
|
|
6
|
-
- **
|
|
7
|
-
- **No overhead:** Each
|
|
8
|
-
- **One-pass agents:** Prefer agents that investigate **and** edit in a single pass; only spin a read-only discovery step (e.g. `scout`) when the affected files are genuinely unknown.
|
|
6
|
+
# Task Design
|
|
7
|
+
- **Agent typing:** Choose each item's `agent` type first. Read-only research MUST use `agent: "scout"`, which runs on a faster model. Use the default worker only when no listed specialist fits.
|
|
8
|
+
- **No overhead:** Each `task` MUST instruct its agent to skip formatters, linters, and project-wide test suites. You will run those once at the end.
|
|
9
|
+
- **One-pass agents:** Prefer agents that investigate **and** edit in a single pass; only spin a read-only discovery step (e.g. `agent: "scout"`) when the affected files are genuinely unknown.
|
|
9
10
|
|
|
10
11
|
# Inputs
|
|
11
|
-
- `agent` (optional): The base agent type to use (e.g., `scout`, `reviewer`). Defaults to `{{defaultAgent}}`{{#if defaultAgentIsGeneric}} (the general-purpose worker){{/if}} — omit it for the default worker instead of passing `agent: "{{defaultAgent}}"`.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
12
12
|
{{#if batchEnabled}}
|
|
13
13
|
- `context`: Shared project state, constraints, and contracts. Applies to the entire batch; do not duplicate this background into individual tasks.
|
|
14
14
|
- `tasks[]`: Array of subagents to spawn.
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
18
|
-
- `role`: The specialist this subagent embodies. Tailor per spawn; do not clone a generic worker.
|
|
15
|
+
- `name`: A stable CamelCase identifier (≤32 chars), used to address the agent (IRC, job ids). Generated automatically if omitted.
|
|
16
|
+
- `agent`: The agent type running this item (e.g. `scout`, `reviewer`). Omitting it gives you the general-purpose worker (`{{defaultAgent}}`) — NEVER pass that name explicitly. Only omit it after checking the agent list below and finding no specialist that fits.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
17
|
+
- `task`: Complete, self-contained instructions. One-liners or missing acceptance criteria are PROHIBITED.
|
|
19
18
|
{{#if isolationEnabled}}
|
|
20
19
|
- `isolated`: Run in a dedicated worktree and return patches. Isolated agents are destroyed upon completion and cannot be addressed afterward.
|
|
21
20
|
{{/if}}
|
|
22
21
|
{{else}}
|
|
23
|
-
- `
|
|
24
|
-
- `
|
|
25
|
-
- `
|
|
26
|
-
- `role`: The specialist this subagent embodies. Tailor per spawn; do not clone a generic worker.
|
|
22
|
+
- `name`: A stable CamelCase identifier (≤32 chars), used to address the agent (IRC, job ids). Generated automatically if omitted.
|
|
23
|
+
- `agent`: The agent type to spawn (e.g. `scout`, `reviewer`). Omitting it gives you the general-purpose worker (`{{defaultAgent}}`) — NEVER pass that name explicitly. Only omit it after checking the agent list below and finding no specialist that fits.{{#if allowedAgentsText}} Current spawn policy allows: {{allowedAgentsText}}.{{/if}}
|
|
24
|
+
- `task`: Complete, self-contained instructions. One-liners or missing acceptance criteria are PROHIBITED.
|
|
27
25
|
{{#if isolationEnabled}}
|
|
28
26
|
- `isolated`: Run in a dedicated worktree and return patches. Isolated agents are destroyed upon completion and cannot be addressed afterward.
|
|
29
27
|
{{/if}}
|
|
@@ -33,9 +31,9 @@ Execution blocks your turn: the call only returns once the work is completely fi
|
|
|
33
31
|
Subagents start blank. They have no access to your conversation history.
|
|
34
32
|
{{#if ircEnabled}}- **Steering delivery:** Parent-to-subagent IRC is delivered immediately as steering; subagents blocked in `job poll` / `irc wait` do not need to poll separately for it.{{/if}}
|
|
35
33
|
{{#if batchEnabled}}
|
|
36
|
-
- Pass large payloads using `local://<path>` URIs,
|
|
34
|
+
- Pass large payloads using `local://<path>` URIs, NEVER inline text.
|
|
37
35
|
{{else}}
|
|
38
|
-
- Write shared project state ONCE to a `local://` file (e.g., `local://ctx.md`) and reference that URL in
|
|
36
|
+
- Write shared project state ONCE to a `local://` file (e.g., `local://ctx.md`) and reference that URL in each `task`.
|
|
39
37
|
{{/if}}
|
|
40
38
|
|
|
41
39
|
# Format Contracts
|
|
@@ -46,7 +44,7 @@ The `context` field MUST follow this format:
|
|
|
46
44
|
# Contract ← shared interfaces
|
|
47
45
|
{{/if}}
|
|
48
46
|
|
|
49
|
-
The `
|
|
47
|
+
The `task` field MUST follow this format:
|
|
50
48
|
# Target ← exact files and symbols; explicit non-goals
|
|
51
49
|
# Change ← step-by-step add/remove/rename; APIs and patterns
|
|
52
50
|
# Acceptance ← observable result; no project-wide commands
|
|
@@ -55,8 +53,9 @@ The `assignment` field MUST follow this format:
|
|
|
55
53
|
{{#if spawningDisabled}}
|
|
56
54
|
Agent spawning is currently disabled.
|
|
57
55
|
{{else}}
|
|
56
|
+
Pick the most specific agent for each task. Use the default worker only when no specialist below fits.
|
|
58
57
|
{{#list agents join="\n"}}
|
|
59
|
-
### {{name}}{{#if readOnly}} (READ-ONLY: no edit/write/command tools){{/if}}
|
|
58
|
+
### {{name}}{{#if readOnly}} (READ-ONLY: no edit/write/command tools){{/if}}{{#if blocking}} (BLOCKING: runs inline; its result returns in this call){{/if}}
|
|
60
59
|
{{description}}
|
|
61
60
|
{{#if readOnly}}Use ONLY for investigation and reporting; do the edits yourself or assign them to a writing agent.{{/if}}
|
|
62
61
|
{{/list}}
|
|
@@ -6891,12 +6891,13 @@ export class AgentSession {
|
|
|
6891
6891
|
* `agent.replaceMessages` or a provider.
|
|
6892
6892
|
*/
|
|
6893
6893
|
buildTranscriptSessionContext(
|
|
6894
|
-
options?: Pick<BuildSessionContextOptions, "collapseCompactedHistory">,
|
|
6894
|
+
options?: Pick<BuildSessionContextOptions, "collapseCompactedHistory" | "keepDanglingToolCalls">,
|
|
6895
6895
|
): SessionContext {
|
|
6896
6896
|
return deobfuscateSessionContext(
|
|
6897
6897
|
this.sessionManager.buildSessionContext({
|
|
6898
6898
|
transcript: true,
|
|
6899
6899
|
collapseCompactedHistory: options?.collapseCompactedHistory,
|
|
6900
|
+
keepDanglingToolCalls: options?.keepDanglingToolCalls,
|
|
6900
6901
|
}),
|
|
6901
6902
|
this.#obfuscator,
|
|
6902
6903
|
);
|
|
@@ -81,3 +81,76 @@ describe("buildSessionContext snapcompact archives", () => {
|
|
|
81
81
|
expect(summary.blocks?.map(block => block.type)).toEqual(["text", "image", "text"]);
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
// A turn whose tool is still executing at rebuild time: the assistant message
|
|
86
|
+
// (with its toolCall) is persisted at message_end, the toolResult is not.
|
|
87
|
+
const danglingToolCallEntries = [
|
|
88
|
+
{
|
|
89
|
+
type: "message",
|
|
90
|
+
id: "m1",
|
|
91
|
+
parentId: null,
|
|
92
|
+
timestamp,
|
|
93
|
+
message: { role: "user", content: [{ type: "text", text: "run it" }], timestamp: 1 },
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: "message",
|
|
97
|
+
id: "m2",
|
|
98
|
+
parentId: "m1",
|
|
99
|
+
timestamp,
|
|
100
|
+
message: {
|
|
101
|
+
role: "assistant",
|
|
102
|
+
content: [{ type: "toolCall", id: "call-1", name: "bash", arguments: { command: "sleep 60" } }],
|
|
103
|
+
api: "anthropic-messages",
|
|
104
|
+
provider: "anthropic",
|
|
105
|
+
model: "claude-sonnet-4-5",
|
|
106
|
+
usage: {
|
|
107
|
+
input: 1,
|
|
108
|
+
output: 1,
|
|
109
|
+
cacheRead: 0,
|
|
110
|
+
cacheWrite: 0,
|
|
111
|
+
totalTokens: 2,
|
|
112
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
113
|
+
},
|
|
114
|
+
stopReason: "toolUse",
|
|
115
|
+
timestamp: 2,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
] satisfies SessionEntry[];
|
|
119
|
+
|
|
120
|
+
function danglingCallIds(messages: AgentMessage[]): string[] {
|
|
121
|
+
const ids: string[] = [];
|
|
122
|
+
for (const message of messages) {
|
|
123
|
+
if (message.role !== "assistant") continue;
|
|
124
|
+
for (const block of message.content) {
|
|
125
|
+
if (block.type === "toolCall") ids.push(block.id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return ids;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
describe("buildSessionContext dangling toolCalls", () => {
|
|
132
|
+
it("strips a dangling toolCall-only assistant turn from the transcript by default", () => {
|
|
133
|
+
const context = buildSessionContext(danglingToolCallEntries, undefined, undefined, { transcript: true });
|
|
134
|
+
|
|
135
|
+
expect(danglingCallIds(context.messages)).toEqual([]);
|
|
136
|
+
// The turn had nothing but the dangling call, so the whole message drops.
|
|
137
|
+
expect(context.messages.some(message => message.role === "assistant")).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("keeps a dangling toolCall in transcript mode with keepDanglingToolCalls", () => {
|
|
141
|
+
const context = buildSessionContext(danglingToolCallEntries, undefined, undefined, {
|
|
142
|
+
transcript: true,
|
|
143
|
+
keepDanglingToolCalls: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(danglingCallIds(context.messages)).toEqual(["call-1"]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("always strips dangling toolCalls from the LLM context", () => {
|
|
150
|
+
const context = buildSessionContext(danglingToolCallEntries, undefined, undefined, {
|
|
151
|
+
keepDanglingToolCalls: true,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(danglingCallIds(context.messages)).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -120,6 +120,15 @@ export interface BuildSessionContextOptions {
|
|
|
120
120
|
transcript?: boolean;
|
|
121
121
|
/** In transcript mode, elide entries replaced by the latest compaction. */
|
|
122
122
|
collapseCompactedHistory?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Transcript mode only: keep `toolCall` blocks that have no matching
|
|
125
|
+
* `toolResult` on the path instead of stripping them. Pass this when the
|
|
126
|
+
* session is mid-turn (a tool is still executing, its result not yet
|
|
127
|
+
* persisted) so the rebuilt transcript renders the in-flight call as
|
|
128
|
+
* pending; without it a focus/unfocus or overlay-close rebuild silently
|
|
129
|
+
* hides the call the agent is still waiting on.
|
|
130
|
+
*/
|
|
131
|
+
keepDanglingToolCalls?: boolean;
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
/**
|
|
@@ -446,34 +455,42 @@ export function buildSessionContext(
|
|
|
446
455
|
// plaintext to keep) and clear `thinking` signatures so the provider encoder
|
|
447
456
|
// downgrades them to plain text (verified accepted by the live API), preserving the
|
|
448
457
|
// visible reasoning while removing the immutability/invalid-signature hazard. Drop a
|
|
449
|
-
// turn left with no content. (Live turns
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const message
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
block =>
|
|
465
|
-
!(block.type === "toolCall" && !pairedToolResultIds.has(block.id)) && block.type !== "redactedThinking",
|
|
466
|
-
)
|
|
467
|
-
.map(block =>
|
|
468
|
-
block.type === "thinking" && block.thinkingSignature ? { ...block, thinkingSignature: undefined } : block,
|
|
458
|
+
// turn left with no content. (Live turns only qualify mid-turn: a transcript rebuild
|
|
459
|
+
// while the tool still executes sees the persisted assistant turn without its result.
|
|
460
|
+
// Those callers pass `keepDanglingToolCalls` so the in-flight call stays visible as
|
|
461
|
+
// a pending block instead of vanishing from the chat.)
|
|
462
|
+
const keepDangling = options?.transcript === true && options.keepDanglingToolCalls === true;
|
|
463
|
+
if (!keepDangling) {
|
|
464
|
+
const pairedToolResultIds = new Set<string>();
|
|
465
|
+
for (const message of messages) {
|
|
466
|
+
if (message.role === "toolResult") pairedToolResultIds.add(message.toolCallId);
|
|
467
|
+
}
|
|
468
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
469
|
+
const message = messages[i];
|
|
470
|
+
if (message.role !== "assistant") continue;
|
|
471
|
+
const hasDangling = message.content.some(
|
|
472
|
+
block => block.type === "toolCall" && !pairedToolResultIds.has(block.id),
|
|
469
473
|
);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
+
if (!hasDangling) continue;
|
|
475
|
+
const normalized = message.content
|
|
476
|
+
.filter(
|
|
477
|
+
block =>
|
|
478
|
+
!(block.type === "toolCall" && !pairedToolResultIds.has(block.id)) &&
|
|
479
|
+
block.type !== "redactedThinking",
|
|
480
|
+
)
|
|
481
|
+
.map(block =>
|
|
482
|
+
block.type === "thinking" && block.thinkingSignature
|
|
483
|
+
? { ...block, thinkingSignature: undefined }
|
|
484
|
+
: block,
|
|
485
|
+
);
|
|
486
|
+
if (normalized.length === 0) {
|
|
487
|
+
messages.splice(i, 1);
|
|
488
|
+
if (options?.transcript) {
|
|
489
|
+
cacheMissExplainedAt.splice(i, 1);
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
messages[i] = { ...message, content: normalized };
|
|
474
493
|
}
|
|
475
|
-
} else {
|
|
476
|
-
messages[i] = { ...message, content: normalized };
|
|
477
494
|
}
|
|
478
495
|
}
|
|
479
496
|
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
MarketplaceManager,
|
|
25
25
|
} from "../extensibility/plugins/marketplace";
|
|
26
26
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
27
|
+
import { runPauseScreen } from "../modes/components/pause-screen";
|
|
27
28
|
import { describeLoopLimitRuntime } from "../modes/loop-limit";
|
|
28
29
|
import { theme } from "../modes/theme/theme";
|
|
29
30
|
import type { InteractiveModeContext } from "../modes/types";
|
|
@@ -2269,6 +2270,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
2269
2270
|
if (prompt) return { prompt };
|
|
2270
2271
|
},
|
|
2271
2272
|
},
|
|
2273
|
+
{
|
|
2274
|
+
name: "pause",
|
|
2275
|
+
description: "Freeze all agents (main, subagents, advisor) until resumed",
|
|
2276
|
+
handleTui: async (_command, runtime) => {
|
|
2277
|
+
runtime.ctx.editor.setText("");
|
|
2278
|
+
await runPauseScreen(runtime.ctx);
|
|
2279
|
+
},
|
|
2280
|
+
},
|
|
2272
2281
|
{
|
|
2273
2282
|
name: "quit",
|
|
2274
2283
|
description: "Quit the application",
|
package/src/task/agents.ts
CHANGED
|
@@ -13,6 +13,7 @@ import librarianMd from "../prompts/agents/librarian.md" with { type: "text" };
|
|
|
13
13
|
import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
|
|
14
14
|
import scoutMd from "../prompts/agents/scout.md" with { type: "text" };
|
|
15
15
|
import taskMd from "../prompts/agents/task.md" with { type: "text" };
|
|
16
|
+
import { AUTO_THINKING } from "../thinking";
|
|
16
17
|
|
|
17
18
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
18
19
|
|
|
@@ -50,6 +51,7 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
50
51
|
description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
|
|
51
52
|
spawns: "*",
|
|
52
53
|
model: "pi/task",
|
|
54
|
+
thinkingLevel: AUTO_THINKING,
|
|
53
55
|
},
|
|
54
56
|
template: taskMd,
|
|
55
57
|
},
|