@oh-my-pi/pi-coding-agent 15.12.4 → 15.13.0
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 +304 -6
- package/dist/cli.js +1015 -881
- package/dist/types/async/job-manager.d.ts +15 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/cli/args.d.ts +19 -1
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/collab/protocol.d.ts +1 -1
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/config/keybindings.d.ts +3 -3
- package/dist/types/config/model-registry.d.ts +10 -0
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/models-config.d.ts +8 -2
- package/dist/types/config/settings-schema.d.ts +261 -58
- package/dist/types/export/html/index.d.ts +2 -1
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +47 -1
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/local-protocol.d.ts +4 -2
- package/dist/types/main.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/memories/index.d.ts +7 -0
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/mnemopi/config.d.ts +4 -4
- package/dist/types/modes/components/agent-hub.d.ts +6 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
- package/dist/types/modes/components/custom-editor.d.ts +39 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +26 -16
- package/dist/types/modes/components/transcript-container.d.ts +23 -2
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +27 -3
- package/dist/types/modes/magic-keywords.d.ts +13 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/theme/theme.d.ts +13 -2
- package/dist/types/modes/types.d.ts +8 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +17 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/indexed-session-storage.d.ts +3 -4
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +82 -474
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -12
- package/dist/types/session/snapcompact-inline.d.ts +12 -1
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +4 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/index.d.ts +9 -1
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/tools/bash.d.ts +2 -2
- package/dist/types/tools/eval-render.d.ts +1 -1
- package/dist/types/tools/index.d.ts +11 -1
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/plan-mode-guard.d.ts +10 -0
- package/dist/types/tools/renderers.d.ts +7 -11
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +25 -0
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/utils/tools-manager.test.d.ts +1 -0
- package/dist/types/web/scrapers/github.d.ts +1 -1
- package/package.json +15 -14
- package/src/async/job-manager.ts +49 -0
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/cli/args.ts +56 -2
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +45 -13
- package/src/collab/host.ts +1 -1
- package/src/collab/protocol.ts +1 -1
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commit/agentic/tools/analyze-file.ts +3 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-discovery.ts +11 -5
- package/src/config/model-registry.ts +64 -9
- package/src/config/models-config-schema.ts +4 -1
- package/src/config/models-config.ts +2 -1
- package/src/config/settings-schema.ts +248 -32
- package/src/config/settings.ts +10 -0
- package/src/discovery/builtin.ts +23 -1
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +41 -1
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/js/shared/prelude.txt +69 -17
- package/src/export/html/index.ts +3 -6
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +52 -1
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
- package/src/extensibility/plugins/loader.ts +30 -19
- package/src/extensibility/plugins/manager.ts +221 -90
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +96 -15
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/internal-urls/history-protocol.ts +1 -1
- package/src/internal-urls/local-protocol.ts +29 -7
- package/src/main.ts +27 -7
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/memories/index.ts +146 -11
- package/src/memory-backend/local-backend.ts +11 -5
- package/src/mnemopi/backend.ts +1 -0
- package/src/mnemopi/config.ts +26 -10
- package/src/modes/acp/acp-agent.ts +3 -5
- package/src/modes/components/agent-hub.ts +49 -4
- package/src/modes/components/assistant-message.ts +4 -37
- package/src/modes/components/compaction-summary-message.ts +125 -26
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +164 -8
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/tool-execution.ts +82 -43
- package/src/modes/components/transcript-container.ts +70 -1
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message.ts +4 -2
- package/src/modes/controllers/command-controller.ts +14 -4
- package/src/modes/controllers/event-controller.ts +78 -11
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +258 -27
- package/src/modes/controllers/selector-controller.ts +12 -2
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +20 -0
- package/src/modes/interactive-mode.ts +286 -40
- package/src/modes/magic-keywords.ts +27 -5
- package/src/modes/rpc/rpc-mode.ts +146 -14
- package/src/modes/rpc/rpc-subagents.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +8 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/theme/theme.ts +98 -50
- package/src/modes/types.ts +6 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +34 -6
- package/src/priority.json +5 -1
- package/src/prompts/agents/task.md +1 -0
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/read-path.md +6 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/subagent-system-prompt.md +4 -0
- package/src/prompts/system/system-prompt.md +10 -5
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/tools/job.md +1 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/registry/agent-registry.ts +30 -0
- package/src/sdk.ts +88 -24
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +209 -87
- package/src/session/history-storage.ts +2 -2
- package/src/session/indexed-session-storage.ts +7 -17
- package/src/session/session-context.ts +352 -0
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +933 -3145
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -50
- package/src/session/snapcompact-inline.ts +21 -1
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +25 -3
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +247 -60
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/agents.ts +1 -2
- package/src/task/executor.ts +49 -15
- package/src/task/index.ts +60 -6
- package/src/task/render.ts +83 -8
- package/src/task/types.ts +53 -0
- package/src/tools/ask.ts +8 -0
- package/src/tools/bash.ts +4 -3
- package/src/tools/eval-render.ts +4 -3
- package/src/tools/index.ts +40 -4
- package/src/tools/irc.ts +10 -2
- package/src/tools/job.ts +14 -2
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/plan-mode-guard.ts +53 -19
- package/src/tools/renderers.ts +7 -11
- package/src/tools/ssh.ts +4 -3
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +203 -92
- package/src/tools/write.ts +18 -2
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +497 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/utils/title-generator.ts +48 -5
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.test.ts +25 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/scrapers/github.ts +96 -0
- package/src/web/search/index.ts +13 -0
- package/src/web/search/providers/searxng.ts +13 -1
- package/dist/types/stt/setup.d.ts +0 -18
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
|
@@ -166,6 +166,7 @@ import type {
|
|
|
166
166
|
TurnEndEvent,
|
|
167
167
|
TurnStartEvent,
|
|
168
168
|
} from "../extensibility/extensions";
|
|
169
|
+
import { createExtensionModelQuery } from "../extensibility/extensions/model-api";
|
|
169
170
|
import type { CompactOptions, ContextUsage } from "../extensibility/extensions/types";
|
|
170
171
|
import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
|
|
171
172
|
import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
@@ -187,6 +188,7 @@ import { containsWorkflow, WORKFLOW_NOTICE } from "../modes/workflow";
|
|
|
187
188
|
import { createPlanReadMatcher } from "../plan-mode/plan-protection";
|
|
188
189
|
import type { PlanModeState } from "../plan-mode/state";
|
|
189
190
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
191
|
+
import eagerTaskPrompt from "../prompts/system/eager-task.md" with { type: "text" };
|
|
190
192
|
import eagerTodoPrompt from "../prompts/system/eager-todo.md" with { type: "text" };
|
|
191
193
|
import emptyStopRetryTemplate from "../prompts/system/empty-stop-retry.md" with { type: "text" };
|
|
192
194
|
import ircAutoReplyTemplate from "../prompts/system/irc-autoreply.md" with { type: "text" };
|
|
@@ -238,7 +240,7 @@ import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
|
238
240
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
239
241
|
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
240
242
|
import { normalizeModelContextImages } from "../utils/image-loading";
|
|
241
|
-
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
243
|
+
import { buildNamedToolChoice, isToolChoiceActive } from "../utils/tool-choice";
|
|
242
244
|
import type { AuthStorage } from "./auth-storage";
|
|
243
245
|
import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome } from "./client-bridge";
|
|
244
246
|
import {
|
|
@@ -258,15 +260,12 @@ import {
|
|
|
258
260
|
SKILL_PROMPT_MESSAGE_TYPE,
|
|
259
261
|
stripImagesFromMessage,
|
|
260
262
|
} from "./messages";
|
|
263
|
+
import type { SessionContext } from "./session-context";
|
|
264
|
+
import { getLatestCompactionEntry, getRestorableSessionModels } from "./session-context";
|
|
261
265
|
import { formatSessionDumpText } from "./session-dump-format";
|
|
262
|
-
import type {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
NewSessionOptions,
|
|
266
|
-
SessionContext,
|
|
267
|
-
SessionManager,
|
|
268
|
-
} from "./session-manager";
|
|
269
|
-
import { EPHEMERAL_MODEL_CHANGE_ROLE, getLatestCompactionEntry, getRestorableSessionModels } from "./session-manager";
|
|
266
|
+
import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions } from "./session-entries";
|
|
267
|
+
import { EPHEMERAL_MODEL_CHANGE_ROLE } from "./session-entries";
|
|
268
|
+
import type { SessionManager } from "./session-manager";
|
|
270
269
|
import type { ShakeMode, ShakeResult } from "./shake-types";
|
|
271
270
|
import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
272
271
|
import { YieldQueue } from "./yield-queue";
|
|
@@ -441,6 +440,10 @@ export interface AgentSessionConfig {
|
|
|
441
440
|
asyncJobManager?: AsyncJobManager;
|
|
442
441
|
/** Agent identity (registry id like "Main" or "Alice") used for IRC routing. */
|
|
443
442
|
agentId?: string;
|
|
443
|
+
/** Whether this session is the top-level agent or a subagent. Drives eager-task
|
|
444
|
+
* prelude gating so a top-level session created with a custom `agentId` still
|
|
445
|
+
* receives the always-mode reminder. Defaults to "main". */
|
|
446
|
+
agentKind?: "main" | "sub";
|
|
444
447
|
/**
|
|
445
448
|
* Override the provider-facing session ID for all API requests from this session.
|
|
446
449
|
* When absent, `sessionManager.getSessionId()` is used. Needed when benchmark or
|
|
@@ -931,6 +934,7 @@ export class AgentSession {
|
|
|
931
934
|
/** Messages queued to be included with the next user prompt as context ("asides"). */
|
|
932
935
|
#pendingNextTurnMessages: CustomMessage[] = [];
|
|
933
936
|
#scheduledHiddenNextTurnGeneration: number | undefined = undefined;
|
|
937
|
+
#queuedMessageDrainScheduled = false;
|
|
934
938
|
#planModeState: PlanModeState | undefined;
|
|
935
939
|
#goalModeState: GoalModeState | undefined;
|
|
936
940
|
#goalRuntime: GoalRuntime;
|
|
@@ -994,6 +998,7 @@ export class AgentSession {
|
|
|
994
998
|
#pendingIrcAsides: CustomMessage[] = [];
|
|
995
999
|
// Agent identity (registry id) used for IRC routing and job ownership.
|
|
996
1000
|
#agentId: string | undefined;
|
|
1001
|
+
#agentKind: "main" | "sub" = "main";
|
|
997
1002
|
#providerSessionId: string | undefined;
|
|
998
1003
|
#freshProviderSessionId: string | undefined;
|
|
999
1004
|
#isDisposed = false;
|
|
@@ -1085,6 +1090,7 @@ export class AgentSession {
|
|
|
1085
1090
|
|
|
1086
1091
|
#streamingEditFileCache = new Map<string, string>();
|
|
1087
1092
|
#promptInFlightCount = 0;
|
|
1093
|
+
#abortInProgress = false;
|
|
1088
1094
|
// Wire-level agent_end emission deferred until #promptInFlightCount drops to 0.
|
|
1089
1095
|
// Internal extension hooks and post-emit work (auto-retry, auto-compaction, todo
|
|
1090
1096
|
// checks in #handleAgentEvent) still fire on the original schedule — only the
|
|
@@ -1154,24 +1160,21 @@ export class AgentSession {
|
|
|
1154
1160
|
}
|
|
1155
1161
|
}
|
|
1156
1162
|
|
|
1157
|
-
/** A steer/follow-up can land after the agent loop's final queue poll
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
* prompt. Runs when the session settles; the guard makes it a no-op when
|
|
1163
|
-
* the queue was consumed normally or a new turn already started. */
|
|
1163
|
+
/** A steer/follow-up can land after the agent loop's final queue poll, or
|
|
1164
|
+
* after an abort stops an auto-continued queued turn. In both cases the
|
|
1165
|
+
* agent-core queue still owns the message, but no loop is left to poll it.
|
|
1166
|
+
* Runs whenever the session settles; the guard makes it a no-op when the
|
|
1167
|
+
* queue was consumed normally or a new turn already started. */
|
|
1164
1168
|
#drainStrandedQueuedMessages(): void {
|
|
1165
|
-
if (
|
|
1166
|
-
this.#
|
|
1167
|
-
shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
|
|
1168
|
-
});
|
|
1169
|
+
if (this.#abortInProgress) return;
|
|
1170
|
+
this.#scheduleQueuedMessageDrain();
|
|
1169
1171
|
}
|
|
1170
1172
|
|
|
1171
1173
|
#resetInFlight(): void {
|
|
1172
1174
|
this.#promptInFlightCount = 0;
|
|
1173
1175
|
this.#releasePowerAssertion();
|
|
1174
1176
|
this.#flushPendingAgentEnd();
|
|
1177
|
+
this.#drainStrandedQueuedMessages();
|
|
1175
1178
|
}
|
|
1176
1179
|
|
|
1177
1180
|
#flushPendingAgentEnd(): void {
|
|
@@ -1298,6 +1301,7 @@ export class AgentSession {
|
|
|
1298
1301
|
this.#ttsrManager = config.ttsrManager;
|
|
1299
1302
|
this.#obfuscator = config.obfuscator;
|
|
1300
1303
|
this.#agentId = config.agentId;
|
|
1304
|
+
this.#agentKind = config.agentKind ?? "main";
|
|
1301
1305
|
this.#providerSessionId = config.providerSessionId;
|
|
1302
1306
|
this.agent.setAssistantMessageEventInterceptor((message, assistantMessageEvent) => {
|
|
1303
1307
|
const event: AgentEvent = {
|
|
@@ -1374,7 +1378,12 @@ export class AgentSession {
|
|
|
1374
1378
|
|
|
1375
1379
|
/** Advance the tool-choice queue and return the next directive for the upcoming LLM call. */
|
|
1376
1380
|
nextToolChoice(): ToolChoice | undefined {
|
|
1377
|
-
|
|
1381
|
+
const choice = this.#toolChoiceQueue.nextToolChoice();
|
|
1382
|
+
if (isToolChoiceActive(choice, this.agent.state.tools)) {
|
|
1383
|
+
return choice;
|
|
1384
|
+
}
|
|
1385
|
+
this.#toolChoiceQueue.reject("unavailable");
|
|
1386
|
+
return undefined;
|
|
1378
1387
|
}
|
|
1379
1388
|
|
|
1380
1389
|
/**
|
|
@@ -1912,6 +1921,11 @@ export class AgentSession {
|
|
|
1912
1921
|
return;
|
|
1913
1922
|
}
|
|
1914
1923
|
|
|
1924
|
+
// A deliberate abort should settle the current turn, not trigger queued continuations.
|
|
1925
|
+
if (msg.stopReason === "aborted") {
|
|
1926
|
+
this.#resolveRetry();
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1915
1929
|
// Check for retryable errors first (overloaded, rate limit, server errors)
|
|
1916
1930
|
if (this.#isRetryableError(msg)) {
|
|
1917
1931
|
const didRetry = await this.#handleRetryableError(msg);
|
|
@@ -1934,7 +1948,7 @@ export class AgentSession {
|
|
|
1934
1948
|
if (compactionDeferredHandoff) {
|
|
1935
1949
|
return;
|
|
1936
1950
|
}
|
|
1937
|
-
if (msg.stopReason !== "error"
|
|
1951
|
+
if (msg.stopReason !== "error") {
|
|
1938
1952
|
if (this.#enforceRewindBeforeYield()) {
|
|
1939
1953
|
return;
|
|
1940
1954
|
}
|
|
@@ -2030,13 +2044,13 @@ export class AgentSession {
|
|
|
2030
2044
|
onError?: () => void;
|
|
2031
2045
|
}): void {
|
|
2032
2046
|
this.#schedulePostPromptTask(
|
|
2033
|
-
async
|
|
2047
|
+
async signal => {
|
|
2034
2048
|
// Defense in depth: if compaction/handoff slipped onto the post-prompt queue
|
|
2035
2049
|
// alongside us (e.g. via a scheduler we don't own), refuse to start a fresh
|
|
2036
2050
|
// streaming turn — agent.continue() here would race the handoff's session
|
|
2037
2051
|
// reset. The first-class fix is in #checkCompaction/the agent_end handler,
|
|
2038
2052
|
// but this guard catches anything that bypasses that path.
|
|
2039
|
-
if (this.isCompacting || this.isGeneratingHandoff) {
|
|
2053
|
+
if (signal.aborted || this.#isDisposed || this.isCompacting || this.isGeneratingHandoff) {
|
|
2040
2054
|
options?.onSkip?.();
|
|
2041
2055
|
return;
|
|
2042
2056
|
}
|
|
@@ -2044,14 +2058,21 @@ export class AgentSession {
|
|
|
2044
2058
|
options?.onSkip?.();
|
|
2045
2059
|
return;
|
|
2046
2060
|
}
|
|
2061
|
+
this.#beginInFlight();
|
|
2047
2062
|
try {
|
|
2048
2063
|
await this.#maybeRestoreRetryFallbackPrimary();
|
|
2064
|
+
if (signal.aborted || this.#isDisposed) {
|
|
2065
|
+
options?.onSkip?.();
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2049
2068
|
await this.agent.continue();
|
|
2050
2069
|
} catch (error) {
|
|
2051
2070
|
logger.warn("agent.continue failed after scheduling", {
|
|
2052
2071
|
error: error instanceof Error ? error.message : String(error),
|
|
2053
2072
|
});
|
|
2054
2073
|
options?.onError?.();
|
|
2074
|
+
} finally {
|
|
2075
|
+
this.#endInFlight();
|
|
2055
2076
|
}
|
|
2056
2077
|
},
|
|
2057
2078
|
{
|
|
@@ -2107,8 +2128,13 @@ export class AgentSession {
|
|
|
2107
2128
|
* and fire-and-forget `agent.continue()` may still be streaming after
|
|
2108
2129
|
* the TTSR resume gate resolves.
|
|
2109
2130
|
*/
|
|
2110
|
-
async #waitForPostPromptRecovery(): Promise<void> {
|
|
2131
|
+
async #waitForPostPromptRecovery(generation?: number): Promise<void> {
|
|
2111
2132
|
while (true) {
|
|
2133
|
+
// An abort bumps #promptGeneration. When this wait runs on behalf of a
|
|
2134
|
+
// specific prompt turn, stop as soon as that turn has been superseded:
|
|
2135
|
+
// its promise must resolve on the abort, not block on a queued
|
|
2136
|
+
// steer/follow-up that the post-abort drain starts as a fresh turn.
|
|
2137
|
+
if (generation !== undefined && this.#promptGeneration !== generation) return;
|
|
2112
2138
|
if (this.#retryPromise) {
|
|
2113
2139
|
await this.#retryPromise;
|
|
2114
2140
|
continue;
|
|
@@ -3155,8 +3181,9 @@ export class AgentSession {
|
|
|
3155
3181
|
// session's dispose.
|
|
3156
3182
|
this.abortRetry();
|
|
3157
3183
|
this.abortCompaction();
|
|
3184
|
+
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
3158
3185
|
this.agent.abort();
|
|
3159
|
-
await
|
|
3186
|
+
await postPromptDrain;
|
|
3160
3187
|
// Cancel jobs this agent registered so a subagent's teardown doesn't
|
|
3161
3188
|
// leak its background bash/task work into the parent's manager. Only
|
|
3162
3189
|
// the session that owns the manager goes on to dispose it (which itself
|
|
@@ -4603,10 +4630,12 @@ export class AgentSession {
|
|
|
4603
4630
|
return true;
|
|
4604
4631
|
}
|
|
4605
4632
|
|
|
4606
|
-
// Skip eager
|
|
4633
|
+
// Skip eager preludes when the user has already queued a directive
|
|
4607
4634
|
const hasPendingUserDirective = this.#toolChoiceQueue.inspect().includes("user-force");
|
|
4608
4635
|
const eagerTodoPrelude =
|
|
4609
4636
|
!options?.synthetic && !hasPendingUserDirective ? this.#createEagerTodoPrelude(expandedText) : undefined;
|
|
4637
|
+
const eagerTaskPrelude =
|
|
4638
|
+
!options?.synthetic && !hasPendingUserDirective ? this.#createEagerTaskPrelude(expandedText) : undefined;
|
|
4610
4639
|
const normalizedImages = await this.#normalizeImagesForModel(options?.images);
|
|
4611
4640
|
|
|
4612
4641
|
const userContent: (TextContent | ImageContent)[] = [{ type: "text", text: expandedText }];
|
|
@@ -4619,17 +4648,24 @@ export class AgentSession {
|
|
|
4619
4648
|
? { role: "developer" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() }
|
|
4620
4649
|
: { role: "user" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() };
|
|
4621
4650
|
|
|
4651
|
+
const preludeMessages: AgentMessage[] = [];
|
|
4622
4652
|
if (eagerTodoPrelude) {
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4653
|
+
if (eagerTodoPrelude.toolChoice) {
|
|
4654
|
+
this.#toolChoiceQueue.pushOnce(eagerTodoPrelude.toolChoice, {
|
|
4655
|
+
label: "eager-todo",
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
preludeMessages.push(eagerTodoPrelude.message);
|
|
4659
|
+
}
|
|
4660
|
+
if (eagerTaskPrelude) {
|
|
4661
|
+
preludeMessages.push(eagerTaskPrelude);
|
|
4626
4662
|
}
|
|
4627
4663
|
|
|
4628
4664
|
try {
|
|
4629
4665
|
await this.#promptWithMessage(message, expandedText, {
|
|
4630
4666
|
...options,
|
|
4631
4667
|
images: normalizedImages,
|
|
4632
|
-
prependMessages:
|
|
4668
|
+
prependMessages: preludeMessages.length > 0 ? preludeMessages : undefined,
|
|
4633
4669
|
appendMessages: keywordNotices.length > 0 ? keywordNotices : undefined,
|
|
4634
4670
|
});
|
|
4635
4671
|
} finally {
|
|
@@ -4857,7 +4893,7 @@ export class AgentSession {
|
|
|
4857
4893
|
const agentPromptOptions = options?.toolChoice ? { toolChoice: options.toolChoice } : undefined;
|
|
4858
4894
|
await this.#promptAgentWithIdleRetry(messages, agentPromptOptions);
|
|
4859
4895
|
if (!options?.skipPostPromptRecoveryWait) {
|
|
4860
|
-
await this.#waitForPostPromptRecovery();
|
|
4896
|
+
await this.#waitForPostPromptRecovery(generation);
|
|
4861
4897
|
}
|
|
4862
4898
|
} finally {
|
|
4863
4899
|
this.#endInFlight();
|
|
@@ -4907,6 +4943,7 @@ export class AgentSession {
|
|
|
4907
4943
|
sessionManager: this.sessionManager,
|
|
4908
4944
|
modelRegistry: this.#modelRegistry,
|
|
4909
4945
|
model: this.model ?? undefined,
|
|
4946
|
+
models: createExtensionModelQuery(this.#modelRegistry, this.settings, () => this.model ?? undefined),
|
|
4910
4947
|
isIdle: () => !this.isStreaming,
|
|
4911
4948
|
abort: () => {
|
|
4912
4949
|
void this.abort();
|
|
@@ -5054,9 +5091,25 @@ export class AgentSession {
|
|
|
5054
5091
|
}
|
|
5055
5092
|
|
|
5056
5093
|
#scheduleIdleQueueDrain(): void {
|
|
5057
|
-
|
|
5094
|
+
this.#scheduleQueuedMessageDrain();
|
|
5095
|
+
}
|
|
5096
|
+
|
|
5097
|
+
#scheduleQueuedMessageDrain(): void {
|
|
5098
|
+
if (this.#queuedMessageDrainScheduled || !this.#canAutoContinueForFollowUp() || !this.agent.hasQueuedMessages()) {
|
|
5099
|
+
return;
|
|
5100
|
+
}
|
|
5101
|
+
this.#queuedMessageDrainScheduled = true;
|
|
5058
5102
|
this.#scheduleAgentContinue({
|
|
5059
|
-
shouldContinue: () =>
|
|
5103
|
+
shouldContinue: () => {
|
|
5104
|
+
this.#queuedMessageDrainScheduled = false;
|
|
5105
|
+
return this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages();
|
|
5106
|
+
},
|
|
5107
|
+
onSkip: () => {
|
|
5108
|
+
this.#queuedMessageDrainScheduled = false;
|
|
5109
|
+
},
|
|
5110
|
+
onError: () => {
|
|
5111
|
+
this.#queuedMessageDrainScheduled = false;
|
|
5112
|
+
},
|
|
5060
5113
|
});
|
|
5061
5114
|
}
|
|
5062
5115
|
|
|
@@ -5068,7 +5121,11 @@ export class AgentSession {
|
|
|
5068
5121
|
if (this.isRetrying) return false;
|
|
5069
5122
|
const messages = this.agent.state.messages;
|
|
5070
5123
|
const last = messages[messages.length - 1];
|
|
5071
|
-
|
|
5124
|
+
// A user interrupt during tool execution can leave the transcript ending
|
|
5125
|
+
// with the emitted tool result, not the aborted assistant message. Continuing
|
|
5126
|
+
// from that state is still resumable: Agent.continue() first polls queued
|
|
5127
|
+
// steering before making the next model call.
|
|
5128
|
+
return last?.role === "assistant" || last?.role === "toolResult";
|
|
5072
5129
|
}
|
|
5073
5130
|
|
|
5074
5131
|
queueDeferredMessage(message: CustomMessage): void {
|
|
@@ -5167,11 +5224,17 @@ export class AgentSession {
|
|
|
5167
5224
|
* - Streaming: queue as steer/follow-up or store for next turn
|
|
5168
5225
|
* - Not streaming + triggerTurn: appends to state/session, starts new turn unless the client cannot own it
|
|
5169
5226
|
* - Not streaming + no trigger: appends to state/session, no turn
|
|
5227
|
+
*
|
|
5228
|
+
* @returns true iff this call synchronously started a new turn (awaited
|
|
5229
|
+
* `agent.prompt`); false when the message was queued/appended without a turn
|
|
5230
|
+
* — including when `triggerTurn` is downgraded because the client defers
|
|
5231
|
+
* agent-initiated turns. Callers that must mirror the resulting `agent_end`
|
|
5232
|
+
* use this to avoid acting on a turn that never ran.
|
|
5170
5233
|
*/
|
|
5171
5234
|
async sendCustomMessage<T = unknown>(
|
|
5172
5235
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
5173
5236
|
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn"; queueChipText?: string },
|
|
5174
|
-
): Promise<
|
|
5237
|
+
): Promise<boolean> {
|
|
5175
5238
|
const details =
|
|
5176
5239
|
options?.queueChipText && options.deliverAs !== "nextTurn"
|
|
5177
5240
|
? ({
|
|
@@ -5195,7 +5258,7 @@ export class AgentSession {
|
|
|
5195
5258
|
if (this.isStreaming) {
|
|
5196
5259
|
if (options?.deliverAs === "nextTurn") {
|
|
5197
5260
|
this.#queueHiddenNextTurnMessage(normalizedAppMessage, options?.triggerTurn ?? false);
|
|
5198
|
-
return;
|
|
5261
|
+
return false;
|
|
5199
5262
|
}
|
|
5200
5263
|
|
|
5201
5264
|
if (options?.deliverAs === "followUp") {
|
|
@@ -5204,17 +5267,17 @@ export class AgentSession {
|
|
|
5204
5267
|
this.agent.steer(normalizedAppMessage);
|
|
5205
5268
|
}
|
|
5206
5269
|
this.#scheduleIdleQueueDrain();
|
|
5207
|
-
return;
|
|
5270
|
+
return false;
|
|
5208
5271
|
}
|
|
5209
5272
|
|
|
5210
5273
|
if (options?.deliverAs === "nextTurn") {
|
|
5211
5274
|
if (options?.triggerTurn) {
|
|
5212
5275
|
if (this.#clientBridge?.deferAgentInitiatedTurns && !this.#allowAcpAgentInitiatedTurns) {
|
|
5213
5276
|
this.#queueHiddenNextTurnMessage(normalizedAppMessage, false);
|
|
5214
|
-
return;
|
|
5277
|
+
return false;
|
|
5215
5278
|
}
|
|
5216
5279
|
await this.agent.prompt(normalizedAppMessage);
|
|
5217
|
-
return;
|
|
5280
|
+
return true;
|
|
5218
5281
|
}
|
|
5219
5282
|
this.agent.appendMessage(normalizedAppMessage);
|
|
5220
5283
|
this.sessionManager.appendCustomMessageEntry(
|
|
@@ -5224,16 +5287,16 @@ export class AgentSession {
|
|
|
5224
5287
|
message.details,
|
|
5225
5288
|
message.attribution ?? "agent",
|
|
5226
5289
|
);
|
|
5227
|
-
return;
|
|
5290
|
+
return false;
|
|
5228
5291
|
}
|
|
5229
5292
|
|
|
5230
5293
|
if (options?.triggerTurn) {
|
|
5231
5294
|
if (this.#clientBridge?.deferAgentInitiatedTurns && !this.#allowAcpAgentInitiatedTurns) {
|
|
5232
5295
|
this.#queueHiddenNextTurnMessage(normalizedAppMessage, false);
|
|
5233
|
-
return;
|
|
5296
|
+
return false;
|
|
5234
5297
|
}
|
|
5235
5298
|
await this.agent.prompt(normalizedAppMessage);
|
|
5236
|
-
return;
|
|
5299
|
+
return true;
|
|
5237
5300
|
}
|
|
5238
5301
|
|
|
5239
5302
|
this.agent.appendMessage(normalizedAppMessage);
|
|
@@ -5244,6 +5307,7 @@ export class AgentSession {
|
|
|
5244
5307
|
message.details,
|
|
5245
5308
|
message.attribution ?? "agent",
|
|
5246
5309
|
);
|
|
5310
|
+
return false;
|
|
5247
5311
|
}
|
|
5248
5312
|
|
|
5249
5313
|
/**
|
|
@@ -5380,28 +5444,37 @@ export class AgentSession {
|
|
|
5380
5444
|
* abort. Omit it for internal/lifecycle aborts.
|
|
5381
5445
|
*/
|
|
5382
5446
|
async abort(options?: { goalReason?: "interrupted" | "internal"; reason?: string }): Promise<void> {
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
this
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5447
|
+
// Session switch/compact paths disconnect first; explicit aborts should
|
|
5448
|
+
// leave any queued steer/follow-up visible for the user rather than
|
|
5449
|
+
// auto-starting a fresh turn during cleanup.
|
|
5450
|
+
this.#abortInProgress = true;
|
|
5451
|
+
try {
|
|
5452
|
+
this.abortRetry();
|
|
5453
|
+
this.#promptGeneration++;
|
|
5454
|
+
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
5455
|
+
this.abortCompaction();
|
|
5456
|
+
this.abortHandoff();
|
|
5457
|
+
this.abortBash();
|
|
5458
|
+
this.abortEval();
|
|
5459
|
+
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
5460
|
+
this.agent.abort(options?.reason);
|
|
5461
|
+
await postPromptDrain;
|
|
5462
|
+
await this.agent.waitForIdle();
|
|
5463
|
+
await this.#goalRuntime.onTaskAborted({ reason: options?.goalReason ?? "interrupted" });
|
|
5464
|
+
// Clear prompt-in-flight state: waitForIdle resolves when the agent loop's finally
|
|
5465
|
+
// block runs, but nested prompt setup/finalizers may still be unwinding. Without this,
|
|
5466
|
+
// a subsequent prompt() can incorrectly observe the session as busy after an abort.
|
|
5467
|
+
this.#resetInFlight();
|
|
5468
|
+
// Safety net: if the agent loop aborted without producing an assistant
|
|
5469
|
+
// message (e.g. failed before the first stream), the in-flight yield was
|
|
5470
|
+
// never resolved or rejected by the normal message_end path. Reject it now
|
|
5471
|
+
// so any requeue callback still fires and the queue stays consistent.
|
|
5472
|
+
if (this.#toolChoiceQueue.hasInFlight) {
|
|
5473
|
+
this.#toolChoiceQueue.reject("aborted");
|
|
5474
|
+
}
|
|
5475
|
+
} finally {
|
|
5476
|
+
this.#abortInProgress = false;
|
|
5477
|
+
this.#drainStrandedQueuedMessages();
|
|
5405
5478
|
}
|
|
5406
5479
|
}
|
|
5407
5480
|
|
|
@@ -6007,7 +6080,12 @@ export class AgentSession {
|
|
|
6007
6080
|
// Already on under any scope — keep the user's scoped value.
|
|
6008
6081
|
return;
|
|
6009
6082
|
}
|
|
6010
|
-
|
|
6083
|
+
if (!enabled) {
|
|
6084
|
+
this.setServiceTier(undefined);
|
|
6085
|
+
return;
|
|
6086
|
+
}
|
|
6087
|
+
const scope = this.settings.get("fastModeScope");
|
|
6088
|
+
this.setServiceTier(scope === "openai" ? "openai-only" : scope === "claude" ? "claude-only" : "priority");
|
|
6011
6089
|
}
|
|
6012
6090
|
|
|
6013
6091
|
toggleFastMode(): boolean {
|
|
@@ -7025,10 +7103,28 @@ export class AgentSession {
|
|
|
7025
7103
|
});
|
|
7026
7104
|
}
|
|
7027
7105
|
|
|
7028
|
-
|
|
7029
|
-
|
|
7106
|
+
/**
|
|
7107
|
+
* Render context shared by the eager todo/task preludes. `toolRefs` resolves each
|
|
7108
|
+
* tool's wire name (matching `buildSystemPrompt`'s `toolRefs`) so the reminder names
|
|
7109
|
+
* the tool the model actually sees when an extension renames it; `taskBatch` gates
|
|
7110
|
+
* batch-call guidance that would steer toward a failing call shape when `task.batch`
|
|
7111
|
+
* is off (the flat single-spawn schema rejects `tasks`/`context`).
|
|
7112
|
+
*/
|
|
7113
|
+
#buildEagerPreludeContext(): { toolRefs: Record<string, string>; taskBatch: boolean } {
|
|
7114
|
+
const wireName = (name: string): string => {
|
|
7115
|
+
const tool = this.#toolRegistry.get(name);
|
|
7116
|
+
return typeof tool?.customWireName === "string" ? tool.customWireName : name;
|
|
7117
|
+
};
|
|
7118
|
+
return {
|
|
7119
|
+
toolRefs: { task: wireName("task"), todo: wireName("todo") },
|
|
7120
|
+
taskBatch: this.settings.get("task.batch"),
|
|
7121
|
+
};
|
|
7122
|
+
}
|
|
7123
|
+
|
|
7124
|
+
#createEagerTodoPrelude(promptText: string): { message: AgentMessage; toolChoice?: ToolChoice } | undefined {
|
|
7125
|
+
const mode = this.settings.get("todo.eager");
|
|
7030
7126
|
const todosEnabled = this.settings.get("todo.enabled");
|
|
7031
|
-
if (
|
|
7127
|
+
if (mode === "default" || !todosEnabled) {
|
|
7032
7128
|
return undefined;
|
|
7033
7129
|
}
|
|
7034
7130
|
|
|
@@ -7063,27 +7159,53 @@ export class AgentSession {
|
|
|
7063
7159
|
return undefined;
|
|
7064
7160
|
}
|
|
7065
7161
|
|
|
7162
|
+
const message: AgentMessage = {
|
|
7163
|
+
role: "custom",
|
|
7164
|
+
customType: "eager-todo-prelude",
|
|
7165
|
+
content: prompt.render(eagerTodoPrompt, { ...this.#buildEagerPreludeContext(), forced: mode === "always" }),
|
|
7166
|
+
display: false,
|
|
7167
|
+
attribution: "agent",
|
|
7168
|
+
timestamp: Date.now(),
|
|
7169
|
+
};
|
|
7170
|
+
// `preferred` suggests a todo list (reminder only); `always` also forces the
|
|
7171
|
+
// `todo` tool on the first turn — the previous boolean-on behavior.
|
|
7172
|
+
if (mode === "preferred") {
|
|
7173
|
+
return { message };
|
|
7174
|
+
}
|
|
7066
7175
|
const todoToolChoice = buildNamedToolChoice("todo", this.model);
|
|
7067
7176
|
if (!todoToolChoice) {
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7177
|
+
// `always` on a model that can't be forced degrades to reminder-only (no
|
|
7178
|
+
// tool_choice). For `todo.eager: true` users migrated to `always`, such
|
|
7179
|
+
// models now receive the first-turn reminder where they previously got
|
|
7180
|
+
// nothing (see the CHANGELOG entry); `always ⊇ preferred` is preserved.
|
|
7181
|
+
logger.warn(
|
|
7182
|
+
"Eager todo proceeding with the reminder only because the current model does not support a forced todo tool_choice",
|
|
7183
|
+
{ modelApi: this.model?.api, modelId: this.model?.id },
|
|
7184
|
+
);
|
|
7185
|
+
return { message };
|
|
7186
|
+
}
|
|
7187
|
+
return { message, toolChoice: todoToolChoice };
|
|
7188
|
+
}
|
|
7189
|
+
|
|
7190
|
+
#createEagerTaskPrelude(promptText: string): AgentMessage | undefined {
|
|
7191
|
+
if (this.settings.get("task.eager") !== "always") return undefined;
|
|
7192
|
+
// Main agent only: subagents keep `task` active (the parent only filters `todo`),
|
|
7193
|
+
// so a salient delegate-reminder there would amplify nested fan-out. Gate on the
|
|
7194
|
+
// resolved agent kind, not the id, so a top-level session with a custom `agentId`
|
|
7195
|
+
// still gets the reminder.
|
|
7196
|
+
if (this.#agentKind === "sub") return undefined;
|
|
7197
|
+
if (this.#planModeState?.enabled) return undefined;
|
|
7198
|
+
if (this.agent.state.messages.some(m => m.role === "user")) return undefined;
|
|
7199
|
+
const trimmed = promptText.trimEnd();
|
|
7200
|
+
if (trimmed.endsWith("?") || trimmed.endsWith("!")) return undefined;
|
|
7201
|
+
if (!this.getActiveToolNames().includes("task")) return undefined;
|
|
7077
7202
|
return {
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
timestamp: Date.now(),
|
|
7085
|
-
},
|
|
7086
|
-
toolChoice: todoToolChoice,
|
|
7203
|
+
role: "custom",
|
|
7204
|
+
customType: "eager-task-prelude",
|
|
7205
|
+
content: prompt.render(eagerTaskPrompt, this.#buildEagerPreludeContext()),
|
|
7206
|
+
display: false,
|
|
7207
|
+
attribution: "agent",
|
|
7208
|
+
timestamp: Date.now(),
|
|
7087
7209
|
};
|
|
7088
7210
|
}
|
|
7089
7211
|
/**
|
|
@@ -84,10 +84,10 @@ export class HistoryStorage {
|
|
|
84
84
|
|
|
85
85
|
this.#db = new Database(dbPath);
|
|
86
86
|
|
|
87
|
-
const hasFts = this.#db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
|
|
88
|
-
|
|
89
87
|
// Install the busy handler BEFORE any lock-taking statement. See #2421.
|
|
90
88
|
this.#db.run("PRAGMA busy_timeout = 5000");
|
|
89
|
+
|
|
90
|
+
const hasFts = this.#db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
|
|
91
91
|
this.#db.run(`
|
|
92
92
|
PRAGMA journal_mode=WAL;
|
|
93
93
|
PRAGMA synchronous=NORMAL;
|
|
@@ -173,6 +173,10 @@ export class IndexedSessionStorage implements SessionStorage {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
writeTextAtomic(path: string, content: string): Promise<void> {
|
|
177
|
+
return this.writeText(path, content);
|
|
178
|
+
}
|
|
179
|
+
|
|
176
180
|
async rename(src: string, dst: string): Promise<void> {
|
|
177
181
|
await this.#awaitPath(src);
|
|
178
182
|
await this.#awaitPath(dst);
|
|
@@ -390,14 +394,7 @@ class IndexedSessionStorageWriter implements SessionStorageWriter {
|
|
|
390
394
|
return next;
|
|
391
395
|
}
|
|
392
396
|
|
|
393
|
-
|
|
394
|
-
if (this.#closed) throw new Error("Writer closed");
|
|
395
|
-
if (this.#error) throw this.#error;
|
|
396
|
-
const mtimeMs = this.#storage._appendForWriter(this.#path, line);
|
|
397
|
-
this.#trackPromise(this.#storage._queueAppend(this.#path, line, mtimeMs, () => this.#error));
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async writeLine(line: string): Promise<void> {
|
|
397
|
+
async append(line: string): Promise<void> {
|
|
401
398
|
if (this.#closed) throw new Error("Writer closed");
|
|
402
399
|
if (this.#error) throw this.#error;
|
|
403
400
|
const mtimeMs = this.#storage._appendForWriter(this.#path, line);
|
|
@@ -410,15 +407,8 @@ class IndexedSessionStorageWriter implements SessionStorageWriter {
|
|
|
410
407
|
if (this.#error) throw this.#error;
|
|
411
408
|
}
|
|
412
409
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
fsyncSync(): void {
|
|
418
|
-
// Indexed storage has no real fd to fsync; drain the pending chain
|
|
419
|
-
// synchronously is not possible, so this is a no-op. The async flush()
|
|
420
|
-
// above already ensures durability for the indexed backend.
|
|
421
|
-
if (this.#error) throw this.#error;
|
|
410
|
+
isOpen(): boolean {
|
|
411
|
+
return !this.#closed;
|
|
422
412
|
}
|
|
423
413
|
|
|
424
414
|
async close(): Promise<void> {
|