@oh-my-pi/pi-coding-agent 17.2.4 → 17.2.6
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 +69 -0
- package/dist/{CHANGELOG-bpmhv26t.md → CHANGELOG-gs76k6wc.md} +69 -0
- package/dist/cli.js +4438 -3962
- package/dist/{template-8vdv6xb4.js → template-c2hyaytt.js} +8 -26
- package/dist/types/cli/args.d.ts +4 -1
- package/dist/types/cli/browser-relay-cli.d.ts +15 -0
- package/dist/types/cli/command-help.d.ts +99 -0
- package/dist/types/cli/gc-cli.d.ts +1 -0
- package/dist/types/cli/help-extra.d.ts +2 -0
- package/dist/types/cli/thinking-levels.d.ts +5 -0
- package/dist/types/cli/update-cli.d.ts +13 -0
- package/dist/types/commands/browser-relay.d.ts +38 -0
- package/dist/types/commands/commit.d.ts +3 -0
- package/dist/types/commands/complete.d.ts +1 -1
- package/dist/types/commands/completions.d.ts +6 -0
- package/dist/types/commands/grep.d.ts +3 -0
- package/dist/types/commands/join.d.ts +4 -0
- package/dist/types/commands/launch-help.d.ts +171 -0
- package/dist/types/commands/launch.d.ts +5 -1
- package/dist/types/commands/models.d.ts +3 -0
- package/dist/types/commands/say.d.ts +10 -0
- package/dist/types/commands/worktree.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-discovery.d.ts +2 -2
- package/dist/types/config/models-config-schema-bundle.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/models-config.d.ts +1 -0
- package/dist/types/config/service-tier.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +69 -21
- package/dist/types/cursor-bridge-tools.d.ts +1 -1
- package/dist/types/cursor.d.ts +7 -4
- package/dist/types/edit/index.d.ts +3 -3
- package/dist/types/edit/modes/replace.d.ts +22 -15
- package/dist/types/edit/renderer.d.ts +1 -1
- package/dist/types/eval/py/kernel.d.ts +3 -1
- package/dist/types/eval/py/spawn-options.d.ts +12 -16
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +8 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
- package/dist/types/launch/broker.d.ts +1 -1
- package/dist/types/launch/client.d.ts +15 -4
- package/dist/types/launch/ensure.d.ts +8 -0
- package/dist/types/launch/paths.d.ts +1 -1
- package/dist/types/launch/protocol.d.ts +19 -2
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/mux/daemon.d.ts +15 -0
- package/dist/types/lsp/mux/protocol.d.ts +57 -0
- package/dist/types/lsp/mux/server.d.ts +18 -0
- package/dist/types/lsp/types.d.ts +22 -2
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mnemopi/embed-client.d.ts +1 -1
- package/dist/types/mnemopi/state.d.ts +0 -23
- package/dist/types/modes/components/assistant-message.d.ts +2 -0
- package/dist/types/modes/components/btw-panel.d.ts +3 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/stripped-tool-calls-placeholder.d.ts +12 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/modes/workflow.d.ts +2 -1
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/security/contracts/schemas.d.ts +405 -403
- package/dist/types/session/agent-session-types.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +26 -2
- package/dist/types/session/indexed-session-storage.d.ts +1 -0
- package/dist/types/session/launch-completion.d.ts +10 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-context.d.ts +2 -1
- package/dist/types/session/session-entries.d.ts +15 -1
- package/dist/types/session/session-maintenance.d.ts +1 -0
- package/dist/types/session/session-manager.d.ts +23 -11
- package/dist/types/session/session-stats.d.ts +10 -0
- package/dist/types/session/session-storage.d.ts +15 -3
- package/dist/types/session/yield-queue.d.ts +6 -1
- package/dist/types/system-prompt.d.ts +4 -1
- package/dist/types/task/index.d.ts +2 -1
- package/dist/types/task/spawn-policy.d.ts +5 -0
- package/dist/types/thinking.d.ts +1 -7
- package/dist/types/tools/ast-grep.d.ts +1 -1
- package/dist/types/tools/browser/registry.d.ts +2 -1
- package/dist/types/tools/browser/relay/bridge.d.ts +35 -0
- package/dist/types/tools/browser/relay/daemon.d.ts +16 -0
- package/dist/types/tools/browser/relay/kind.d.ts +19 -0
- package/dist/types/tools/browser/relay/protocol.d.ts +97 -0
- package/dist/types/tools/browser/relay/server.d.ts +35 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -0
- package/dist/types/tools/computer/exposure.d.ts +9 -7
- package/dist/types/tools/computer/protocol.d.ts +70 -17
- package/dist/types/tools/computer/supervisor.d.ts +23 -10
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +42 -7
- package/dist/types/tools/computer-renderer.d.ts +7 -5
- package/dist/types/tools/computer.d.ts +25 -44
- package/dist/types/tools/context.d.ts +3 -0
- package/dist/types/tools/glob.d.ts +1 -1
- package/dist/types/tools/grep.d.ts +1 -1
- package/dist/types/tools/index.d.ts +9 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/{browser/run-cancellation.d.ts → run-scope.d.ts} +8 -8
- package/dist/types/tools/tool-timeouts.d.ts +5 -0
- package/dist/types/tools/xdev.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +2 -2
- package/dist/types/web/kagi.d.ts +1 -0
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/providers/anthropic.d.ts +1 -0
- package/dist/types/web/search/providers/base.d.ts +2 -0
- package/dist/types/web/search/providers/brave.d.ts +1 -0
- package/dist/types/web/search/providers/browser-page.d.ts +1 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -0
- package/dist/types/web/search/providers/exa.d.ts +1 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +1 -0
- package/dist/types/web/search/providers/gemini.d.ts +1 -0
- package/dist/types/web/search/providers/jina.d.ts +1 -0
- package/dist/types/web/search/providers/kagi.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -0
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +1 -0
- package/dist/types/web/search/providers/searxng.d.ts +1 -0
- package/dist/types/web/search/providers/tavily.d.ts +1 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +1 -0
- package/dist/types/web/search/providers/utils.d.ts +6 -9
- package/dist/types/web/search/providers/zai.d.ts +1 -0
- package/dist/types/web/search/types.d.ts +4 -0
- package/package.json +12 -12
- package/src/cli/args.ts +7 -87
- package/src/cli/browser-relay-cli.ts +119 -0
- package/src/cli/command-help.ts +103 -0
- package/src/cli/completion-gen.ts +5 -5
- package/src/cli/flag-tables.ts +9 -0
- package/src/cli/gc-cli.ts +641 -21
- package/src/cli/help-extra.ts +89 -0
- package/src/cli/setup-cli.ts +13 -21
- package/src/cli/thinking-levels.ts +7 -0
- package/src/cli/update-cli.ts +26 -7
- package/src/cli-commands.ts +170 -33
- package/src/cli.ts +17 -5
- package/src/commands/acp.ts +3 -1
- package/src/commands/agents.ts +3 -2
- package/src/commands/auth-broker.ts +3 -2
- package/src/commands/auth-gateway.ts +3 -2
- package/src/commands/bench.ts +2 -3
- package/src/commands/browser-relay.ts +53 -0
- package/src/commands/cleanse.ts +2 -2
- package/src/commands/commit.ts +3 -2
- package/src/commands/complete.ts +2 -1
- package/src/commands/completions.ts +3 -2
- package/src/commands/config.ts +3 -2
- package/src/commands/dry-balance.ts +2 -2
- package/src/commands/gallery.ts +3 -2
- package/src/commands/gc.ts +3 -2
- package/src/commands/grep.ts +3 -2
- package/src/commands/grievances.ts +3 -2
- package/src/commands/install.ts +2 -2
- package/src/commands/join.ts +3 -2
- package/src/commands/launch-help.ts +116 -0
- package/src/commands/launch.ts +7 -191
- package/src/commands/models.ts +3 -2
- package/src/commands/plugin.ts +3 -2
- package/src/commands/read.ts +3 -2
- package/src/commands/say.ts +3 -2
- package/src/commands/setup.ts +3 -2
- package/src/commands/shell.ts +3 -2
- package/src/commands/ssh.ts +3 -2
- package/src/commands/stats.ts +3 -2
- package/src/commands/tiny-models.ts +2 -2
- package/src/commands/token.ts +2 -2
- package/src/commands/ttsr.ts +2 -2
- package/src/commands/update.ts +3 -2
- package/src/commands/usage.ts +3 -2
- package/src/commands/web-search.ts +3 -2
- package/src/commands/worktree.ts +3 -2
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-discovery.ts +44 -27
- package/src/config/model-registry.ts +19 -13
- package/src/config/models-config-schema-bundle.ts +9 -0
- package/src/config/resolve-config-value.ts +2 -1
- package/src/config/service-tier.ts +5 -0
- package/src/config/settings-schema.ts +72 -20
- package/src/config/settings.ts +1 -1
- package/src/cursor-bridge-tools.ts +1 -1
- package/src/cursor.ts +10 -14
- package/src/edit/diff.ts +17 -8
- package/src/edit/index.ts +20 -7
- package/src/edit/modes/replace.ts +93 -40
- package/src/edit/renderer.ts +1 -1
- package/src/edit/streaming.ts +14 -17
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/spawn-options.ts +24 -29
- package/src/export/html/index.ts +10 -3
- package/src/export/html/template.js +8 -26
- package/src/export/share.ts +4 -0
- package/src/extensibility/extensions/wrapper.ts +1 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +22 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -6
- package/src/extensibility/plugins/legacy-pi-compat.ts +124 -57
- package/src/extensibility/skills.ts +11 -0
- package/src/extensibility/utils.ts +10 -2
- package/src/launch/broker.ts +223 -7
- package/src/launch/client.ts +192 -21
- package/src/launch/ensure.ts +73 -0
- package/src/launch/paths.ts +1 -1
- package/src/launch/protocol.ts +54 -2
- package/src/lsp/client.ts +23 -2
- package/src/lsp/index.ts +8 -0
- package/src/lsp/mux/daemon.ts +348 -0
- package/src/lsp/mux/protocol.ts +96 -0
- package/src/lsp/mux/server.ts +797 -0
- package/src/lsp/types.ts +28 -2
- package/src/main.ts +20 -8
- package/src/mcp/config-writer.ts +1 -1
- package/src/mcp/oauth-flow.ts +11 -1
- package/src/mnemopi/embed-client.ts +49 -2
- package/src/mnemopi/state.ts +57 -23
- package/src/modes/components/assistant-message.ts +11 -0
- package/src/modes/components/btw-panel.ts +41 -4
- package/src/modes/components/chat-transcript-builder.ts +4 -0
- package/src/modes/components/custom-editor.ts +9 -0
- package/src/modes/components/read-tool-group.ts +10 -0
- package/src/modes/components/status-line/component.ts +1 -1
- package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
- package/src/modes/components/tool-execution.ts +8 -0
- package/src/modes/components/tree-selector.ts +46 -48
- package/src/modes/controllers/btw-controller.ts +55 -7
- package/src/modes/controllers/command-controller.ts +31 -0
- package/src/modes/controllers/event-controller.ts +17 -0
- package/src/modes/controllers/input-controller.ts +63 -8
- package/src/modes/controllers/mcp-command-controller.ts +11 -5
- package/src/modes/controllers/selector-controller.ts +20 -0
- package/src/modes/interactive-mode.ts +18 -2
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/interactive-context-helpers.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +6 -10
- package/src/modes/workflow.ts +8 -2
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +26 -52
- package/src/prompts/system/workflow-notice.md +3 -3
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/computer.md +21 -21
- package/src/prompts/tools/glob.md +1 -1
- package/src/prompts/tools/grep.md +1 -1
- package/src/prompts/tools/replace.md +5 -4
- package/src/prompts/tools/task.md +4 -4
- package/src/sdk.ts +49 -6
- package/src/secrets/index.ts +1 -1
- package/src/security/contracts/schemas.ts +205 -183
- package/src/security/contracts/validation.ts +5 -1
- package/src/security/store.ts +2 -2
- package/src/session/agent-session-types.ts +9 -1
- package/src/session/agent-session.ts +204 -15
- package/src/session/indexed-session-storage.ts +9 -0
- package/src/session/launch-completion.ts +37 -0
- package/src/session/prewalk.ts +24 -2
- package/src/session/session-advisors.ts +2 -2
- package/src/session/session-context.ts +42 -12
- package/src/session/session-dump-format.ts +1 -1
- package/src/session/session-entries.ts +17 -1
- package/src/session/session-maintenance.ts +38 -4
- package/src/session/session-manager.ts +123 -67
- package/src/session/session-paths.ts +118 -55
- package/src/session/session-stats.ts +48 -3
- package/src/session/session-storage.ts +30 -22
- package/src/session/stream-guards.ts +1 -1
- package/src/session/yield-queue.ts +121 -16
- package/src/slash-commands/builtin-registry.ts +37 -24
- package/src/system-prompt.ts +8 -3
- package/src/task/index.ts +23 -8
- package/src/task/spawn-policy.ts +14 -0
- package/src/thinking.ts +2 -8
- package/src/tools/ast-grep.ts +10 -4
- package/src/tools/browser/cmux/cmux-tab.ts +97 -14
- package/src/tools/browser/cmux/rpc.ts +2 -8
- package/src/tools/browser/registry.ts +53 -2
- package/src/tools/browser/relay/bridge.ts +945 -0
- package/src/tools/browser/relay/daemon.ts +117 -0
- package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
- package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
- package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
- package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
- package/src/tools/browser/relay/kind.ts +41 -0
- package/src/tools/browser/relay/protocol.ts +54 -0
- package/src/tools/browser/relay/server.ts +141 -0
- package/src/tools/browser/render.ts +4 -1
- package/src/tools/browser/shared-daemon.ts +7 -58
- package/src/tools/browser/tab-supervisor.ts +5 -4
- package/src/tools/browser/tab-worker.ts +33 -12
- package/src/tools/browser.ts +26 -1
- package/src/tools/computer/exposure.ts +11 -35
- package/src/tools/computer/protocol.ts +51 -10
- package/src/tools/computer/supervisor.ts +227 -124
- package/src/tools/computer/worker-entry.ts +11 -6
- package/src/tools/computer/worker.ts +695 -81
- package/src/tools/computer-renderer.ts +104 -65
- package/src/tools/computer.ts +134 -428
- package/src/tools/context.ts +3 -0
- package/src/tools/glob.ts +9 -2
- package/src/tools/grep.ts +12 -6
- package/src/tools/hub/launch.ts +122 -6
- package/src/tools/index.ts +9 -0
- package/src/tools/inspect-image.ts +32 -6
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/{browser/run-cancellation.ts → run-scope.ts} +11 -11
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/xdev.ts +29 -2
- package/src/utils/clipboard.ts +40 -14
- package/src/utils/command-args.ts +26 -28
- package/src/utils/tool-choice.ts +0 -13
- package/src/web/kagi.ts +2 -1
- package/src/web/search/index.ts +18 -2
- package/src/web/search/providers/anthropic.ts +4 -1
- package/src/web/search/providers/base.ts +2 -0
- package/src/web/search/providers/brave.ts +3 -1
- package/src/web/search/providers/browser-page.ts +6 -4
- package/src/web/search/providers/codex.ts +4 -1
- package/src/web/search/providers/duckduckgo.ts +2 -1
- package/src/web/search/providers/ecosia.ts +2 -1
- package/src/web/search/providers/exa.ts +4 -2
- package/src/web/search/providers/firecrawl.ts +3 -1
- package/src/web/search/providers/gemini.ts +8 -2
- package/src/web/search/providers/google.ts +2 -1
- package/src/web/search/providers/jina.ts +12 -2
- package/src/web/search/providers/kagi.ts +3 -0
- package/src/web/search/providers/kimi.ts +5 -1
- package/src/web/search/providers/mojeek.ts +2 -1
- package/src/web/search/providers/parallel.ts +5 -1
- package/src/web/search/providers/perplexity.ts +6 -3
- package/src/web/search/providers/public.ts +1 -1
- package/src/web/search/providers/searxng.ts +11 -5
- package/src/web/search/providers/startpage.ts +10 -4
- package/src/web/search/providers/synthetic.ts +11 -5
- package/src/web/search/providers/tavily.ts +3 -1
- package/src/web/search/providers/tinyfish.ts +3 -1
- package/src/web/search/providers/utils.ts +12 -10
- package/src/web/search/providers/xai.ts +2 -2
- package/src/web/search/providers/zai.ts +18 -3
- package/src/web/search/types.ts +6 -0
- package/dist/types/config/file-lock.d.ts +0 -29
- package/src/config/file-lock.ts +0 -164
|
@@ -109,6 +109,8 @@ export interface AgentSessionConfig {
|
|
|
109
109
|
agent: Agent;
|
|
110
110
|
sessionManager: SessionManager;
|
|
111
111
|
settings: Settings;
|
|
112
|
+
/** Whether the session spawn policy permits the read-only `scout` subagent. Defaults to true. */
|
|
113
|
+
scoutAllowedBySpawnPolicy?: boolean;
|
|
112
114
|
/** Whether the caller explicitly requested yolo/auto-approve behavior for this session. */
|
|
113
115
|
autoApprove?: boolean;
|
|
114
116
|
/** Models to cycle through with Ctrl+P (from --models flag). */
|
|
@@ -232,7 +234,7 @@ export interface AgentSessionConfig {
|
|
|
232
234
|
/**
|
|
233
235
|
* Build the `replace`-mode `edit` a Cursor `pi_edit` frame needs, against the
|
|
234
236
|
* advisor-scoped tool session. The advisor's ordinary instance follows the
|
|
235
|
-
* configured `edit.mode` and rejects the frame's `
|
|
237
|
+
* configured `edit.mode` and rejects the frame's `old_string`/`new_string` args.
|
|
236
238
|
*/
|
|
237
239
|
advisorCreateEditTool?(): AgentTool | undefined;
|
|
238
240
|
/**
|
|
@@ -387,5 +389,11 @@ export interface FreshSessionResult {
|
|
|
387
389
|
closedProviderSessions: number;
|
|
388
390
|
}
|
|
389
391
|
|
|
392
|
+
/** Outcome of an in-place `/reset` conversation-context reset. */
|
|
393
|
+
export interface ResetSessionContextResult {
|
|
394
|
+
/** Number of live messages dropped from the model's context. */
|
|
395
|
+
droppedCount: number;
|
|
396
|
+
}
|
|
397
|
+
|
|
390
398
|
/** Queued user content restored to the editor. */
|
|
391
399
|
export type RestoredQueuedMessage = { text: string; images?: ImageContent[] };
|
|
@@ -144,6 +144,7 @@ import type { GoalModeState } from "../goals/state";
|
|
|
144
144
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
145
145
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
146
146
|
import type { IrcMessage } from "../irc/bus";
|
|
147
|
+
import type { DaemonCompletionNotification } from "../launch/protocol";
|
|
147
148
|
import { shutdownMnemopiEmbedClient } from "../mnemopi/embed-client";
|
|
148
149
|
import { getMnemopiSessionState, type MnemopiSessionState, setMnemopiSessionState } from "../mnemopi/state";
|
|
149
150
|
import { containsOrchestrate, ORCHESTRATE_NOTICE } from "../modes/orchestrate";
|
|
@@ -221,6 +222,7 @@ import type {
|
|
|
221
222
|
ModelCycleResult,
|
|
222
223
|
Prewalk,
|
|
223
224
|
PromptOptions,
|
|
225
|
+
ResetSessionContextResult,
|
|
224
226
|
ResolvedRoleModel,
|
|
225
227
|
RestoredQueuedMessage,
|
|
226
228
|
RoleModelCycle,
|
|
@@ -271,6 +273,12 @@ import {
|
|
|
271
273
|
type ToolExecutionStartData,
|
|
272
274
|
} from "./exit-diagnostics";
|
|
273
275
|
import { IrcBridge, type IrcBridgeHost } from "./irc-bridge";
|
|
276
|
+
import {
|
|
277
|
+
buildLaunchCompletionBatchMessage,
|
|
278
|
+
isLaunchCompletionOwner,
|
|
279
|
+
LAUNCH_COMPLETION_MESSAGE_TYPE,
|
|
280
|
+
type LaunchCompletionEntry,
|
|
281
|
+
} from "./launch-completion";
|
|
274
282
|
import {
|
|
275
283
|
type BashExecutionMessage,
|
|
276
284
|
buildReplanTitleContext,
|
|
@@ -340,6 +348,7 @@ import { TodoTracker, type TodoTrackerHost } from "./todo-tracker";
|
|
|
340
348
|
import { TtsrCoordinator, type TtsrCoordinatorHost } from "./ttsr-coordinator";
|
|
341
349
|
|
|
342
350
|
const PLAN_MODE_REMINDER_MAX = 3;
|
|
351
|
+
const POST_PROMPT_DRAIN_TIMEOUT_MS = 5_000;
|
|
343
352
|
|
|
344
353
|
/** Internal marker for hook messages queued through the agent loop */
|
|
345
354
|
// ============================================================================
|
|
@@ -445,6 +454,8 @@ export class AgentSession {
|
|
|
445
454
|
#lastAppendOnlyResolution?: { enable: boolean; providerId: string | undefined };
|
|
446
455
|
#eventListeners: AgentSessionEventListener[] = [];
|
|
447
456
|
#commandMetadataChangedListeners: CommandMetadataChangedListener[] = [];
|
|
457
|
+
#sessionChangeCallbacks = new Set<() => void>();
|
|
458
|
+
#observedSessionId: string | undefined;
|
|
448
459
|
|
|
449
460
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
450
461
|
#pendingNextTurnMessages: CustomMessage[] = [];
|
|
@@ -518,6 +529,7 @@ export class AgentSession {
|
|
|
518
529
|
// Agent identity (registry id) used for IRC routing and job ownership.
|
|
519
530
|
#agentId: string | undefined;
|
|
520
531
|
#agentKind: "main" | "sub" = "main";
|
|
532
|
+
#scoutAllowedBySpawnPolicy = true;
|
|
521
533
|
#providerSessionId: string | undefined;
|
|
522
534
|
#freshProviderSessionId: string | undefined;
|
|
523
535
|
#inheritedProviderPromptCacheKey: string | undefined;
|
|
@@ -657,6 +669,7 @@ export class AgentSession {
|
|
|
657
669
|
if (onSettled) this.#inFlightSettledCallbacks.push(onSettled);
|
|
658
670
|
this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
|
|
659
671
|
if (this.#promptInFlightCount !== 0) return;
|
|
672
|
+
this.yieldQueue.requestIdleFlush();
|
|
660
673
|
this.#releasePowerAssertion();
|
|
661
674
|
this.#flushPendingAgentEnd();
|
|
662
675
|
if (this.#inFlightSettledCallbacks.length === 0) {
|
|
@@ -838,6 +851,7 @@ export class AgentSession {
|
|
|
838
851
|
|
|
839
852
|
#resetInFlight(): void {
|
|
840
853
|
this.#promptInFlightCount = 0;
|
|
854
|
+
this.yieldQueue.requestIdleFlush();
|
|
841
855
|
this.#releasePowerAssertion();
|
|
842
856
|
this.#flushPendingAgentEnd();
|
|
843
857
|
if (this.#inFlightSettledCallbacks.length === 0) {
|
|
@@ -1121,17 +1135,30 @@ export class AgentSession {
|
|
|
1121
1135
|
injectIdle: async messages => {
|
|
1122
1136
|
const first = messages[0];
|
|
1123
1137
|
if (!first) return;
|
|
1124
|
-
|
|
1138
|
+
this.#beginInFlight();
|
|
1139
|
+
try {
|
|
1140
|
+
await this.agent.prompt(messages.length === 1 ? first : messages);
|
|
1141
|
+
} finally {
|
|
1142
|
+
this.#endInFlight();
|
|
1143
|
+
}
|
|
1125
1144
|
},
|
|
1126
1145
|
scheduleIdleFlush: run => {
|
|
1127
1146
|
this.#schedulePostPromptTask(
|
|
1128
1147
|
async () => {
|
|
1129
1148
|
await run();
|
|
1130
1149
|
},
|
|
1131
|
-
{
|
|
1150
|
+
{
|
|
1151
|
+
delayMs: 1,
|
|
1152
|
+
onSkip: () => this.yieldQueue.cancelIdleFlushScheduling(),
|
|
1153
|
+
},
|
|
1132
1154
|
);
|
|
1133
1155
|
},
|
|
1134
1156
|
});
|
|
1157
|
+
this.yieldQueue.register<LaunchCompletionEntry>(LAUNCH_COMPLETION_MESSAGE_TYPE, {
|
|
1158
|
+
isStale: entry =>
|
|
1159
|
+
this.#isDisposed || !isLaunchCompletionOwner(entry.owner, this.sessionManager.getSessionId()),
|
|
1160
|
+
build: buildLaunchCompletionBatchMessage,
|
|
1161
|
+
});
|
|
1135
1162
|
// Background-job completions / late diagnostics are pulled into the run at
|
|
1136
1163
|
// each step boundary as non-interrupting asides. Peer IRCs share the aside
|
|
1137
1164
|
// injection boundary, but also expose a non-consuming interrupt peek so
|
|
@@ -1237,6 +1264,7 @@ export class AgentSession {
|
|
|
1237
1264
|
this.#loopGuards = new LoopGuards(streamGuardsHost);
|
|
1238
1265
|
this.#agentId = config.agentId;
|
|
1239
1266
|
this.#agentKind = config.agentKind ?? "main";
|
|
1267
|
+
this.#scoutAllowedBySpawnPolicy = config.scoutAllowedBySpawnPolicy ?? true;
|
|
1240
1268
|
this.#providerSessionId = config.providerSessionId;
|
|
1241
1269
|
this.#inheritedProviderPromptCacheKey =
|
|
1242
1270
|
config.providerPromptCacheKeySource === "fork" ? this.agent.promptCacheKey : undefined;
|
|
@@ -1418,6 +1446,7 @@ export class AgentSession {
|
|
|
1418
1446
|
syncTodoPhasesFromBranch: () => this.#todo.syncFromBranch(),
|
|
1419
1447
|
resetAdvisorRuntimes: () => this.#advisors.resetAllRuntimes(),
|
|
1420
1448
|
rebaseAfterCompaction: () => this.#stats.rebaseAfterCompaction(),
|
|
1449
|
+
recordAnchoredHistoryRewrite: tokensRemoved => this.#stats.recordAnchoredHistoryRewrite(tokensRemoved),
|
|
1421
1450
|
getContextBreakdown: options => this.getContextBreakdown(options),
|
|
1422
1451
|
getContextUsage: options => this.getContextUsage(options),
|
|
1423
1452
|
shake: (mode, options) => this.shake(mode, options),
|
|
@@ -2897,6 +2926,7 @@ export class AgentSession {
|
|
|
2897
2926
|
try {
|
|
2898
2927
|
await scheduler.wait(delayMs, { signal });
|
|
2899
2928
|
} catch {
|
|
2929
|
+
if (signal.aborted) options?.onSkip?.("aborted");
|
|
2900
2930
|
return;
|
|
2901
2931
|
}
|
|
2902
2932
|
}
|
|
@@ -3397,6 +3427,12 @@ export class AgentSession {
|
|
|
3397
3427
|
};
|
|
3398
3428
|
}
|
|
3399
3429
|
|
|
3430
|
+
/** Register cleanup that runs when this AgentSession adopts a different session ID. */
|
|
3431
|
+
registerSessionChangeCallback(callback: () => void): () => void {
|
|
3432
|
+
this.#sessionChangeCallbacks.add(callback);
|
|
3433
|
+
return () => this.#sessionChangeCallbacks.delete(callback);
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3400
3436
|
subscribeCommandMetadataChanged(listener: CommandMetadataChangedListener): () => void {
|
|
3401
3437
|
this.#commandMetadataChangedListeners.push(listener);
|
|
3402
3438
|
return () => {
|
|
@@ -3471,7 +3507,14 @@ export class AgentSession {
|
|
|
3471
3507
|
* (login/logout, token refresh that surfaces a new account UUID) without
|
|
3472
3508
|
* needing to re-call `#syncAgentSessionId()` on every such event.
|
|
3473
3509
|
*/
|
|
3474
|
-
#syncAgentSessionId(sessionId?: string): void {
|
|
3510
|
+
#syncAgentSessionId(sessionId?: string, notifyChange = true): void {
|
|
3511
|
+
const currentSessionId = this.sessionManager.getSessionId();
|
|
3512
|
+
if (this.#observedSessionId === undefined) {
|
|
3513
|
+
this.#observedSessionId = currentSessionId;
|
|
3514
|
+
} else if (this.#observedSessionId !== currentSessionId) {
|
|
3515
|
+
this.#observedSessionId = currentSessionId;
|
|
3516
|
+
if (notifyChange) this.#notifySessionChangeCallbacks();
|
|
3517
|
+
}
|
|
3475
3518
|
const sid = this.#activeProviderSessionId(sessionId);
|
|
3476
3519
|
this.agent.sessionId = sid;
|
|
3477
3520
|
this.agent.setMetadataResolver((provider: string) =>
|
|
@@ -3493,6 +3536,16 @@ export class AgentSession {
|
|
|
3493
3536
|
if (this.#advisors) this.#advisors.refreshProviderIdentity();
|
|
3494
3537
|
}
|
|
3495
3538
|
|
|
3539
|
+
#notifySessionChangeCallbacks(): void {
|
|
3540
|
+
for (const callback of [...this.#sessionChangeCallbacks]) {
|
|
3541
|
+
try {
|
|
3542
|
+
callback();
|
|
3543
|
+
} catch (error) {
|
|
3544
|
+
logger.warn("Session change callback failed", { error: String(error) });
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3496
3549
|
/** Run one abortable auto-learn capture outside the primary agent loop. */
|
|
3497
3550
|
async runAutolearnCapture(capture: (signal: AbortSignal) => Promise<void>): Promise<void> {
|
|
3498
3551
|
if (this.#autolearnCaptureTask || this.#isDisposed) return;
|
|
@@ -3673,7 +3726,11 @@ export class AgentSession {
|
|
|
3673
3726
|
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
3674
3727
|
this.agent.abort();
|
|
3675
3728
|
try {
|
|
3676
|
-
await withTimeout(
|
|
3729
|
+
await withTimeout(
|
|
3730
|
+
postPromptDrain,
|
|
3731
|
+
POST_PROMPT_DRAIN_TIMEOUT_MS,
|
|
3732
|
+
"Timed out draining post-prompt tasks during dispose",
|
|
3733
|
+
);
|
|
3677
3734
|
} catch (error) {
|
|
3678
3735
|
logger.warn("Post-prompt tasks still draining at dispose deadline", { error: String(error) });
|
|
3679
3736
|
}
|
|
@@ -3720,6 +3777,7 @@ export class AgentSession {
|
|
|
3720
3777
|
this.#unsubscribeModelRoles = undefined;
|
|
3721
3778
|
}
|
|
3722
3779
|
this.#eventListeners = [];
|
|
3780
|
+
this.#sessionChangeCallbacks.clear();
|
|
3723
3781
|
}
|
|
3724
3782
|
|
|
3725
3783
|
#closeAllProviderSessions(reason: string): void {
|
|
@@ -3754,6 +3812,101 @@ export class AgentSession {
|
|
|
3754
3812
|
};
|
|
3755
3813
|
}
|
|
3756
3814
|
|
|
3815
|
+
/**
|
|
3816
|
+
* Reset the current conversation in place: drop every message, queued turn,
|
|
3817
|
+
* and pending tool call from the model's context while keeping the session
|
|
3818
|
+
* itself — its id, title, cwd, model, settings, and on-disk transcript all
|
|
3819
|
+
* survive. The next turn is sent with only the base system prompt plus the
|
|
3820
|
+
* project rules/AGENTS.md.
|
|
3821
|
+
*
|
|
3822
|
+
* This is the in-place sibling of {@link newSession}: it reuses the same
|
|
3823
|
+
* conversation-boundary teardown (drop the conversation, rotate provider-side
|
|
3824
|
+
* session state so providers that keep history server-side resume nothing,
|
|
3825
|
+
* re-prime the advisors, and undo any memory promotion) but skips minting a
|
|
3826
|
+
* new session id and opening a fresh transcript file. Unlike
|
|
3827
|
+
* {@link freshSession} (which only rotates provider stream state) it also
|
|
3828
|
+
* clears the conversation.
|
|
3829
|
+
*
|
|
3830
|
+
* Returns `undefined` without mutating anything while a response is
|
|
3831
|
+
* streaming or a foreground bash/python execution is in flight.
|
|
3832
|
+
*/
|
|
3833
|
+
async resetSessionContext(): Promise<ResetSessionContextResult | undefined> {
|
|
3834
|
+
// Refuse while a response streams OR a foreground user bash/python
|
|
3835
|
+
// execution is in flight: those complete via recordBashResult()/
|
|
3836
|
+
// recordPythonResult(), which append directly to agent.state when not
|
|
3837
|
+
// streaming, so a command finishing after the reset would land its output
|
|
3838
|
+
// after the boundary and re-enter the supposedly empty context. The
|
|
3839
|
+
// sibling boundary op (branchFromBtw) guards on the same predicates.
|
|
3840
|
+
if (this.isStreaming || this.isBashRunning || this.isEvalRunning) return undefined;
|
|
3841
|
+
const droppedCount = this.agent.state.messages.length;
|
|
3842
|
+
|
|
3843
|
+
// Tear down the same per-turn runtime state that newSession() resets across
|
|
3844
|
+
// a conversation boundary, so work scheduled from the pre-reset turn cannot
|
|
3845
|
+
// re-enter the cleared context:
|
|
3846
|
+
// - bump #promptGeneration + drain post-prompt tasks so an already-queued
|
|
3847
|
+
// post-prompt continuation (recovery can be scheduled after agent_end
|
|
3848
|
+
// while isStreaming is false) sees a stale generation and skips
|
|
3849
|
+
// (mirrors abort()).
|
|
3850
|
+
// - cancel this agent's async bash/task jobs so their completions can't
|
|
3851
|
+
// re-deliver stale tool output into the cleared conversation
|
|
3852
|
+
// (mirrors newSession()).
|
|
3853
|
+
this.#promptGeneration++;
|
|
3854
|
+
await this.#cancelPostPromptTasks();
|
|
3855
|
+
this.#cancelOwnAsyncJobs();
|
|
3856
|
+
|
|
3857
|
+
// Drop the conversation: messages, queued steers/follow-ups, pending tool
|
|
3858
|
+
// calls, and error state. agent.reset() keeps the model and system prompt.
|
|
3859
|
+
this.agent.reset();
|
|
3860
|
+
this.#pendingNextTurnMessages = [];
|
|
3861
|
+
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
3862
|
+
// Reset the session_stop continuation chain: the queued continuation
|
|
3863
|
+
// message is gone with the conversation, but the counters would otherwise
|
|
3864
|
+
// carry over, so the next post-reset turn is reported to hooks as part of
|
|
3865
|
+
// the old chain and can hit SESSION_STOP_CONTINUATION_CAP early (mirrors
|
|
3866
|
+
// abort()/newSession()).
|
|
3867
|
+
this.#resetSessionStopContinuationState();
|
|
3868
|
+
|
|
3869
|
+
// Drop checkpoint/rewind runtime state and deferred tool directives
|
|
3870
|
+
// alongside the messages that carried them: the checkpoint tool result is
|
|
3871
|
+
// gone from agent.state, so an intact #checkpointState would otherwise
|
|
3872
|
+
// force a rewind onto the pre-reset transcript on the next turn (mirrors
|
|
3873
|
+
// newSession()).
|
|
3874
|
+
this.#clearCheckpointRuntimeState();
|
|
3875
|
+
this.#clearSessionScopedToolState();
|
|
3876
|
+
|
|
3877
|
+
// Rotate provider-side session state so a provider that keeps conversation
|
|
3878
|
+
// history server-side starts a brand-new exchange rather than resuming the
|
|
3879
|
+
// context we just dropped (mirrors freshSession()).
|
|
3880
|
+
this.#closeAllProviderSessions("reset context");
|
|
3881
|
+
this.#freshProviderSessionId = Bun.randomUUIDv7();
|
|
3882
|
+
this.#syncAgentSessionId();
|
|
3883
|
+
this.#memory.rekeyForCurrentSessionId();
|
|
3884
|
+
this.agent.appendOnlyContext?.invalidateForModelChange();
|
|
3885
|
+
|
|
3886
|
+
// Re-arm the approved-plan reference: the reset dropped the plan-approved
|
|
3887
|
+
// prompt/reference from agent.state, so mark it unsent (preserving the
|
|
3888
|
+
// path — the plan file on disk is still the active plan) to let
|
|
3889
|
+
// #buildPlanReferenceMessage re-read and re-inject it on the next turn.
|
|
3890
|
+
// Mirrors the sent-flag reset newSession() and compaction perform after a
|
|
3891
|
+
// history rewrite (issue #1246).
|
|
3892
|
+
this.#planReferenceSent = false;
|
|
3893
|
+
|
|
3894
|
+
// Re-prime the advisors across the conversation boundary and undo any
|
|
3895
|
+
// memory promotion so the next turn rebuilds from the base system prompt.
|
|
3896
|
+
this.#advisors.resetSessionState();
|
|
3897
|
+
await this.#memory.resetContextForNewTranscript();
|
|
3898
|
+
|
|
3899
|
+
// Record a durable boundary on the persisted branch. The collapsed live
|
|
3900
|
+
// transcript and the model-context rebuild start emission after the latest
|
|
3901
|
+
// boundary, so a rebuild across a `/reset` (theme change, focus attach,
|
|
3902
|
+
// /shake, resume) does not resurrect the pre-reset conversation. The
|
|
3903
|
+
// on-disk record and the plain `transcript:true` export path keep the full
|
|
3904
|
+
// pre-reset history.
|
|
3905
|
+
this.sessionManager.appendResetBoundary();
|
|
3906
|
+
|
|
3907
|
+
return { droppedCount };
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3757
3910
|
// =========================================================================
|
|
3758
3911
|
// Read-only State Access
|
|
3759
3912
|
// =========================================================================
|
|
@@ -4635,6 +4788,11 @@ export class AgentSession {
|
|
|
4635
4788
|
};
|
|
4636
4789
|
}
|
|
4637
4790
|
|
|
4791
|
+
#isScoutAvailable(): boolean {
|
|
4792
|
+
const disabledAgents = this.settings.get("task.disabledAgents") as string[] | undefined;
|
|
4793
|
+
return this.#scoutAllowedBySpawnPolicy && !disabledAgents?.includes("scout");
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4638
4796
|
async #buildPlanModeMessage(): Promise<CustomMessage | null> {
|
|
4639
4797
|
const state = this.#planModeState;
|
|
4640
4798
|
if (!state?.enabled) return null;
|
|
@@ -4658,6 +4816,7 @@ export class AgentSession {
|
|
|
4658
4816
|
isHashlineEditMode: this.#resolveActiveEditMode() === "hashline",
|
|
4659
4817
|
reentry: state.reentry ?? false,
|
|
4660
4818
|
iterative: state.workflow === "iterative",
|
|
4819
|
+
scoutAvailable: this.#isScoutAvailable(),
|
|
4661
4820
|
});
|
|
4662
4821
|
|
|
4663
4822
|
return {
|
|
@@ -4789,7 +4948,10 @@ export class AgentSession {
|
|
|
4789
4948
|
keywordNotices.push({
|
|
4790
4949
|
role: "custom",
|
|
4791
4950
|
customType: "workflow-notice",
|
|
4792
|
-
content: renderWorkflowNotice({
|
|
4951
|
+
content: renderWorkflowNotice({
|
|
4952
|
+
taskBatch: this.settings.get("task.batch"),
|
|
4953
|
+
scoutAvailable: this.#isScoutAvailable(),
|
|
4954
|
+
}),
|
|
4793
4955
|
display: false,
|
|
4794
4956
|
attribution: "user",
|
|
4795
4957
|
timestamp,
|
|
@@ -5522,6 +5684,16 @@ export class AgentSession {
|
|
|
5522
5684
|
this.#queueHiddenNextTurnMessage(message, true);
|
|
5523
5685
|
}
|
|
5524
5686
|
|
|
5687
|
+
queueLaunchCompletion(notification: DaemonCompletionNotification): Promise<void> {
|
|
5688
|
+
if (this.#isDisposed) return Promise.reject(new Error("Session disposed before launch completion delivery"));
|
|
5689
|
+
const delivered = this.yieldQueue.enqueueWithReceipt<LaunchCompletionEntry>(
|
|
5690
|
+
LAUNCH_COMPLETION_MESSAGE_TYPE,
|
|
5691
|
+
notification,
|
|
5692
|
+
);
|
|
5693
|
+
this.yieldQueue.requestIdleFlush();
|
|
5694
|
+
return delivered;
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5525
5697
|
#queueHiddenNextTurnMessage(message: CustomMessage, triggerTurn: boolean): void {
|
|
5526
5698
|
this.#pendingNextTurnMessages.push(message);
|
|
5527
5699
|
if (!triggerTurn) return;
|
|
@@ -7239,7 +7411,7 @@ export class AgentSession {
|
|
|
7239
7411
|
this.#clearInheritedProviderPromptCacheKey();
|
|
7240
7412
|
this.#adoptInheritedProviderPromptCacheKey();
|
|
7241
7413
|
}
|
|
7242
|
-
this.#syncAgentSessionId();
|
|
7414
|
+
this.#syncAgentSessionId(undefined, false);
|
|
7243
7415
|
this.#memory.rekeyForCurrentSessionId();
|
|
7244
7416
|
|
|
7245
7417
|
let sessionContext = this.buildDisplaySessionContext();
|
|
@@ -7376,11 +7548,14 @@ export class AgentSession {
|
|
|
7376
7548
|
this.#advisors.restoreCost(await loadAdvisorTranscriptCosts(this.sessionFile));
|
|
7377
7549
|
}
|
|
7378
7550
|
this.#bash.finishSessionTransition(bashTransition, true);
|
|
7551
|
+
if (previousSessionState.sessionId !== this.sessionManager.getSessionId()) {
|
|
7552
|
+
this.#notifySessionChangeCallbacks();
|
|
7553
|
+
}
|
|
7379
7554
|
return true;
|
|
7380
7555
|
} catch (error) {
|
|
7381
7556
|
this.sessionManager.restoreState(previousSessionState);
|
|
7382
7557
|
this.#freshProviderSessionId = previousFreshProviderSessionId;
|
|
7383
|
-
this.#syncAgentSessionId(previousSessionState.sessionId);
|
|
7558
|
+
this.#syncAgentSessionId(previousSessionState.sessionId, false);
|
|
7384
7559
|
this.#memory.rekeyForCurrentSessionId();
|
|
7385
7560
|
this.agent.setTools(previousTools);
|
|
7386
7561
|
this.#tools.setBaseSystemPrompt(previousBaseSystemPrompt);
|
|
@@ -7494,7 +7669,10 @@ export class AgentSession {
|
|
|
7494
7669
|
await this.#advisors.drainAndDetachRecorders();
|
|
7495
7670
|
try {
|
|
7496
7671
|
if (!selectedEntry.parentId) {
|
|
7672
|
+
const title = this.sessionManager.getSessionName();
|
|
7673
|
+
const titleSource = this.sessionManager.titleSource;
|
|
7497
7674
|
await this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
7675
|
+
if (title) await this.sessionManager.setSessionName(title, titleSource);
|
|
7498
7676
|
} else {
|
|
7499
7677
|
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
7500
7678
|
}
|
|
@@ -7541,21 +7719,24 @@ export class AgentSession {
|
|
|
7541
7719
|
}
|
|
7542
7720
|
}
|
|
7543
7721
|
|
|
7722
|
+
/** Promotes a completed /btw answer from the explicitly authorized session and leaf. */
|
|
7544
7723
|
async branchFromBtw(
|
|
7545
7724
|
question: string,
|
|
7546
7725
|
assistantMessage: AssistantMessage,
|
|
7726
|
+
leafId: string,
|
|
7727
|
+
sessionId: string,
|
|
7547
7728
|
): Promise<{ cancelled: boolean; sessionFile: string | undefined }> {
|
|
7548
7729
|
const previousSessionFile = this.sessionFile;
|
|
7549
7730
|
if (!this.sessionManager.getSessionFile()) {
|
|
7550
7731
|
throw new Error("Cannot branch /btw: session is not persisted");
|
|
7551
7732
|
}
|
|
7552
7733
|
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
throw new Error("Cannot branch /btw: current session has no leaf");
|
|
7734
|
+
if (!leafId || this.sessionManager.getSessionId() !== sessionId || this.sessionManager.getLeafId() !== leafId) {
|
|
7735
|
+
throw new Error("Cannot branch /btw: session changed since /btw started");
|
|
7556
7736
|
}
|
|
7557
7737
|
|
|
7558
7738
|
if (
|
|
7739
|
+
this.isStreaming ||
|
|
7559
7740
|
this.isBashRunning ||
|
|
7560
7741
|
this.isEvalRunning ||
|
|
7561
7742
|
this.isCompacting ||
|
|
@@ -7576,8 +7757,17 @@ export class AgentSession {
|
|
|
7576
7757
|
}
|
|
7577
7758
|
}
|
|
7578
7759
|
|
|
7579
|
-
|
|
7760
|
+
if (this.sessionManager.getSessionId() !== sessionId || this.sessionManager.getLeafId() !== leafId) {
|
|
7761
|
+
throw new Error("Cannot branch /btw: session changed since /btw started");
|
|
7762
|
+
}
|
|
7763
|
+
|
|
7764
|
+
await withTimeout(
|
|
7765
|
+
this.#cancelPostPromptTasks(),
|
|
7766
|
+
POST_PROMPT_DRAIN_TIMEOUT_MS,
|
|
7767
|
+
"Timed out draining post-prompt tasks before /btw branch",
|
|
7768
|
+
);
|
|
7580
7769
|
if (
|
|
7770
|
+
this.isStreaming ||
|
|
7581
7771
|
this.isBashRunning ||
|
|
7582
7772
|
this.isEvalRunning ||
|
|
7583
7773
|
this.isCompacting ||
|
|
@@ -7590,10 +7780,6 @@ export class AgentSession {
|
|
|
7590
7780
|
this.#pendingNextTurnMessages = [];
|
|
7591
7781
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
7592
7782
|
this.agent.replaceQueues([], []);
|
|
7593
|
-
if (this.isStreaming) {
|
|
7594
|
-
await this.abort({ goalReason: "internal", reason: "branching /btw" });
|
|
7595
|
-
this.agent.replaceQueues([], []);
|
|
7596
|
-
}
|
|
7597
7783
|
await this.#bash.flushPending();
|
|
7598
7784
|
await this.sessionManager.flush();
|
|
7599
7785
|
const bashTransition = this.#bash.beginSessionTransition();
|
|
@@ -7607,6 +7793,9 @@ export class AgentSession {
|
|
|
7607
7793
|
advisorRecordersDetached = true;
|
|
7608
7794
|
await this.#advisors.drainAndDetachRecorders();
|
|
7609
7795
|
try {
|
|
7796
|
+
if (this.sessionManager.getSessionId() !== sessionId || this.sessionManager.getLeafId() !== leafId) {
|
|
7797
|
+
throw new Error("Cannot branch /btw: session changed since /btw started");
|
|
7798
|
+
}
|
|
7610
7799
|
this.sessionManager.createBranchedSession(leafId);
|
|
7611
7800
|
this.#bash.markSessionTransition(bashTransition);
|
|
7612
7801
|
this.#advisors.clearCost();
|
|
@@ -511,6 +511,15 @@ class IndexedSessionStorageWriter implements SessionStorageWriter {
|
|
|
511
511
|
return next;
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
+
appendSync(line: string): void {
|
|
515
|
+
if (this.#closed) throw new Error("Writer closed");
|
|
516
|
+
if (this.#error) throw this.#error;
|
|
517
|
+
// Local index is updated immediately; remote publish stays ordered on the
|
|
518
|
+
// path queue. Callers that need remote durability still await append()/flush().
|
|
519
|
+
const mtimeMs = this.#storage._appendForWriter(this.#path, line);
|
|
520
|
+
void this.#trackPromise(this.#storage._queueAppend(this.#path, line, mtimeMs, () => this.#error));
|
|
521
|
+
}
|
|
522
|
+
|
|
514
523
|
async append(line: string): Promise<void> {
|
|
515
524
|
if (this.#closed) throw new Error("Writer closed");
|
|
516
525
|
if (this.#error) throw this.#error;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import type { DaemonCompletionNotification } from "../launch/protocol";
|
|
3
|
+
import launchCompletionTemplate from "../prompts/session/launch-completion.md" with { type: "text" };
|
|
4
|
+
import type { CustomMessage } from "./messages";
|
|
5
|
+
|
|
6
|
+
/** Yield-queue kind for broker-owned supervised process completions. */
|
|
7
|
+
export const LAUNCH_COMPLETION_MESSAGE_TYPE = "launch-completion";
|
|
8
|
+
|
|
9
|
+
/** One broker completion awaiting injection into its owning session. */
|
|
10
|
+
export type LaunchCompletionEntry = DaemonCompletionNotification;
|
|
11
|
+
|
|
12
|
+
/** Whether a broker completion belongs to the primary session or its advisor. */
|
|
13
|
+
export function isLaunchCompletionOwner(owner: string, sessionId: string): boolean {
|
|
14
|
+
return owner === sessionId || owner === `${sessionId}-advisor`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Build one model-visible notification per terminal supervised process exit. */
|
|
18
|
+
export function buildLaunchCompletionBatchMessage(entries: LaunchCompletionEntry[]): CustomMessage {
|
|
19
|
+
return {
|
|
20
|
+
role: "custom",
|
|
21
|
+
customType: LAUNCH_COMPLETION_MESSAGE_TYPE,
|
|
22
|
+
content: entries
|
|
23
|
+
.map(({ daemon }) =>
|
|
24
|
+
prompt.render(launchCompletionTemplate, {
|
|
25
|
+
name: daemon.name,
|
|
26
|
+
state: daemon.state,
|
|
27
|
+
exitCode: daemon.exitCode,
|
|
28
|
+
hasExitCode: daemon.exitCode !== undefined,
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
.join("\n"),
|
|
32
|
+
display: true,
|
|
33
|
+
attribution: "agent",
|
|
34
|
+
details: { daemons: entries.map(entry => entry.daemon) },
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
};
|
|
37
|
+
}
|
package/src/session/prewalk.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Agent, AgentMessage, AgentToolResult, AgentTurnEndContext } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { invalidateMessageCache } from "@oh-my-pi/pi-agent-core/compaction";
|
|
3
|
-
import type { Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { Model, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
6
6
|
import { resolveApprovedPlan } from "../plan-mode/approved-plan";
|
|
@@ -25,6 +25,28 @@ const PREWALK_ACTION_TOOLS: Record<string, true> = {
|
|
|
25
25
|
};
|
|
26
26
|
const PLAN_YOLO_HANDOFF_MESSAGE_TYPE = "plan-yolo-handoff";
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Whether a completed tool result is the first workspace-mutating action that
|
|
30
|
+
* arms the prewalk hand-off. A direct `edit`/`write` call always counts; a
|
|
31
|
+
* `write` that dispatched an `xd://` device (e.g. `lsp`, `ast_edit`, `debug`)
|
|
32
|
+
* counts only when the wrapped tool resolved to a `write`/`exec` approval tier.
|
|
33
|
+
* Read-only device calls — LSP navigation, `debug` inspection, `ast_edit` on
|
|
34
|
+
* internal URLs, help lookups — leave the tier `read` (or absent) and must not
|
|
35
|
+
* switch the model mid-investigation (issue #7312).
|
|
36
|
+
*/
|
|
37
|
+
function isPrewalkImplementationAction(result: ToolResultMessage): boolean {
|
|
38
|
+
if (!PREWALK_ACTION_TOOLS[result.toolName]) return false;
|
|
39
|
+
const details = result.details;
|
|
40
|
+
// A direct filesystem edit/write carries no `xd://` dispatch metadata.
|
|
41
|
+
if (!details || typeof details !== "object" || !("xdev" in details) || !details.xdev) return true;
|
|
42
|
+
const xdev = details.xdev;
|
|
43
|
+
// Device dispatch: switch only on a genuine mutation tier. An absent tier
|
|
44
|
+
// (help lookup, unresolved approval) declines the switch, matching the
|
|
45
|
+
// reporter's "stay on the large model a couple turns longer" preference.
|
|
46
|
+
if (typeof xdev !== "object" || !("tier" in xdev)) return false;
|
|
47
|
+
return xdev.tier === "write" || xdev.tier === "exec";
|
|
48
|
+
}
|
|
49
|
+
|
|
28
50
|
/** Capabilities the prewalk coordinator borrows from its owning session. */
|
|
29
51
|
export interface PrewalkCoordinatorHost {
|
|
30
52
|
agent: Agent;
|
|
@@ -100,7 +122,7 @@ export class PrewalkCoordinator {
|
|
|
100
122
|
|
|
101
123
|
const todoGateOpen = this.#todoSeen || !this.#host.getActiveToolNames().includes("todo");
|
|
102
124
|
const action = todoGateOpen
|
|
103
|
-
? context.toolResults.find(result =>
|
|
125
|
+
? context.toolResults.find(result => isPrewalkImplementationAction(result))
|
|
104
126
|
: undefined;
|
|
105
127
|
if (!action) {
|
|
106
128
|
if (!this.#planInjected) {
|
|
@@ -191,7 +191,7 @@ export interface SessionAdvisorsOptions {
|
|
|
191
191
|
/**
|
|
192
192
|
* Build the `replace`-mode `edit` a Cursor `pi_edit` frame needs. The
|
|
193
193
|
* advisor's own instance follows the configured `edit.mode` (`hashline` by
|
|
194
|
-
* default), whose schema the frame's `
|
|
194
|
+
* default), whose schema the frame's `old_string`/`new_string` args do not
|
|
195
195
|
* match, so without this every native advisor edit fails validation.
|
|
196
196
|
*/
|
|
197
197
|
createEditTool?(): AgentTool | undefined;
|
|
@@ -760,7 +760,7 @@ export class SessionAdvisors {
|
|
|
760
760
|
// to delete workspace files it was never granted (issue #5680 review).
|
|
761
761
|
const advisorCanMutateFiles = advisorToolMap.has("write") || advisorToolMap.has("edit");
|
|
762
762
|
if (advisorCanMutateFiles) availableAdvisorToolNames.add("delete");
|
|
763
|
-
// `pi_edit` speaks `replace`'s `
|
|
763
|
+
// `pi_edit` speaks `replace`'s `old_string`/`new_string` schema, which the
|
|
764
764
|
// advisor's ordinary `EditTool` (built at the session's configured
|
|
765
765
|
// `edit.mode`, `hashline` by default) does not accept. The bridge map
|
|
766
766
|
// swaps in a `replace` instance for the exec channel only — the
|
|
@@ -154,6 +154,21 @@ function snapcompactHistoryBlocksForContext(
|
|
|
154
154
|
return snapcompact.historyBlocks(archive, snapcompactHistoryBlockOptions(archive, options));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
export function getOpenAiRemoteCompactionPayload(
|
|
158
|
+
compaction: CompactionEntry | null | undefined,
|
|
159
|
+
): ProviderPayload | undefined {
|
|
160
|
+
const candidate = compaction?.preserveData?.openaiRemoteCompaction;
|
|
161
|
+
if (!candidate || typeof candidate !== "object") return undefined;
|
|
162
|
+
const remote = candidate as { provider?: unknown; replacementHistory?: unknown };
|
|
163
|
+
if (typeof remote.provider !== "string" || remote.provider.length === 0) return undefined;
|
|
164
|
+
if (!Array.isArray(remote.replacementHistory)) return undefined;
|
|
165
|
+
return {
|
|
166
|
+
type: "openaiResponsesHistory",
|
|
167
|
+
provider: remote.provider,
|
|
168
|
+
items: remote.replacementHistory as Array<Record<string, unknown>>,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
export function buildSessionContext(
|
|
158
173
|
entries: SessionEntry[],
|
|
159
174
|
leafId?: string | null,
|
|
@@ -269,6 +284,12 @@ export function buildSessionContext(
|
|
|
269
284
|
|
|
270
285
|
const injectedTtsrRules = Array.from(injectedTtsrRulesSet);
|
|
271
286
|
|
|
287
|
+
// Index on the path of the latest `/reset` boundary, or -1 when none. The
|
|
288
|
+
// collapsed live transcript and the model-context rebuild start emission
|
|
289
|
+
// after it (see the emission branch below); the full-history export path
|
|
290
|
+
// ignores it.
|
|
291
|
+
const resetBoundaryIdx = path.reduce((latest, entry, i) => (entry.type === "reset_boundary" ? i : latest), -1);
|
|
292
|
+
|
|
272
293
|
// Build messages and collect corresponding entries
|
|
273
294
|
// When there's a compaction, we need to:
|
|
274
295
|
// 1. Emit summary first (entry = compaction)
|
|
@@ -364,19 +385,28 @@ export function buildSessionContext(
|
|
|
364
385
|
appendMessage(entry);
|
|
365
386
|
}
|
|
366
387
|
}
|
|
388
|
+
} else if (
|
|
389
|
+
resetBoundaryIdx >= 0 &&
|
|
390
|
+
resetBoundaryIdx > (compaction ? path.findIndex(e => e.type === "compaction" && e.id === compaction.id) : -1)
|
|
391
|
+
) {
|
|
392
|
+
// A `/reset` boundary durably starts emission after it — for BOTH the
|
|
393
|
+
// collapsed live transcript AND the model context (non-transcript) rebuild
|
|
394
|
+
// that feeds agent.replaceMessages (resume, /shake, reload, image drop).
|
|
395
|
+
// Without honoring it here, those model-context rebuilds walk the full
|
|
396
|
+
// persisted branch and put the pre-reset turns back into the LLM context
|
|
397
|
+
// even though `/reset` reported it empty. The full-history export path
|
|
398
|
+
// (`transcript && !collapseCompactedHistory`) is handled by the first
|
|
399
|
+
// branch above and left untouched, so on-disk history stays recoverable.
|
|
400
|
+
// When a compaction and a reset boundary interact, the later one on the
|
|
401
|
+
// path wins: a boundary after the latest compaction elides that compaction
|
|
402
|
+
// (and its kept tail) too, so only genuinely post-reset entries emit; a
|
|
403
|
+
// boundary before the latest compaction is superseded by it (the
|
|
404
|
+
// `else if (compaction)` branch below handles that case via this guard).
|
|
405
|
+
for (let i = resetBoundaryIdx + 1; i < path.length; i++) {
|
|
406
|
+
appendMessage(path[i]);
|
|
407
|
+
}
|
|
367
408
|
} else if (compaction) {
|
|
368
|
-
const providerPayload
|
|
369
|
-
const candidate = compaction.preserveData?.openaiRemoteCompaction;
|
|
370
|
-
if (!candidate || typeof candidate !== "object") return undefined;
|
|
371
|
-
const remote = candidate as { provider?: unknown; replacementHistory?: unknown };
|
|
372
|
-
if (typeof remote.provider !== "string" || remote.provider.length === 0) return undefined;
|
|
373
|
-
if (!Array.isArray(remote.replacementHistory)) return undefined;
|
|
374
|
-
return {
|
|
375
|
-
type: "openaiResponsesHistory",
|
|
376
|
-
provider: remote.provider,
|
|
377
|
-
items: remote.replacementHistory as Array<Record<string, unknown>>,
|
|
378
|
-
};
|
|
379
|
-
})();
|
|
409
|
+
const providerPayload = getOpenAiRemoteCompactionPayload(compaction);
|
|
380
410
|
const remoteReplacementHistory = providerPayload?.items;
|
|
381
411
|
|
|
382
412
|
// Re-attach any archived snapcompact frames so the model can keep
|
|
@@ -82,7 +82,7 @@ function renderDumpHeader(options: FormatSessionDumpTextOptions, inventoryTools:
|
|
|
82
82
|
const hasSystemPromptToolInventory = options.inlineToolDescriptors === true;
|
|
83
83
|
if (inventoryTools.length > 0 && !hasSystemPromptToolInventory) {
|
|
84
84
|
lines.push("## Available Tools\n");
|
|
85
|
-
lines.push(renderToolInventory(inventoryTools
|
|
85
|
+
lines.push(renderToolInventory(inventoryTools));
|
|
86
86
|
lines.push("\n");
|
|
87
87
|
}
|
|
88
88
|
|