@gajae-code/coding-agent 0.11.3 → 0.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/types/cli/notify-cli.d.ts +5 -1
- package/dist/types/cli/worktree-cli.d.ts +8 -25
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/worktree.d.ts +7 -32
- package/dist/types/config/settings-schema.d.ts +20 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
- package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/owner.d.ts +3 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -0
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +8 -3
- package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
- package/dist/types/modes/controllers/input-controller.d.ts +8 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
- package/dist/types/plan-mode/approved-plan.d.ts +1 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
- package/dist/types/sdk/broker/broker.d.ts +1 -0
- package/dist/types/sdk/broker/discovery.d.ts +10 -0
- package/dist/types/sdk/broker/ensure.d.ts +24 -1
- package/dist/types/sdk/broker/internal.d.ts +3 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -0
- package/dist/types/sdk/broker/state-version.d.ts +2 -1
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
- package/dist/types/sdk/bus/config-commands.d.ts +8 -0
- package/dist/types/sdk/bus/config.d.ts +22 -0
- package/dist/types/sdk/bus/index.d.ts +162 -2
- package/dist/types/sdk/bus/notification-service.d.ts +71 -8
- package/dist/types/sdk/bus/session-control.d.ts +11 -8
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
- package/dist/types/sdk/session.d.ts +16 -1
- package/dist/types/secrets/index.d.ts +4 -2
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +31 -2
- package/dist/types/session/artifacts.d.ts +30 -8
- package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
- package/dist/types/session/session-manager.d.ts +88 -18
- package/dist/types/session/session-storage.d.ts +27 -0
- package/dist/types/session/streaming-output.d.ts +4 -0
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/tool-discovery/tool-index.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +87 -12
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
- package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/path-utils.d.ts +9 -14
- package/dist/types/tools/render-utils.d.ts +1 -8
- package/dist/types/tools/resource-gc.d.ts +14 -0
- package/dist/types/utils/image-loading.d.ts +23 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/pasted-image-loading.d.ts +39 -0
- package/dist/types/utils/pasted-image-path.d.ts +21 -19
- package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +1 -5
- package/package.json +25 -9
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-threat-model-check.ts +60 -0
- package/src/cli/notify-cli.ts +40 -18
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +10 -9
- package/src/commands/gc.ts +11 -1
- package/src/commands/sdk.ts +10 -4
- package/src/commands/worktree.ts +10 -53
- package/src/config/model-profiles.ts +15 -15
- package/src/config/settings-schema.ts +17 -0
- package/src/daemon/runtime.ts +2 -2
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
- package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
- package/src/defaults/gjc/skills/team/SKILL.md +7 -9
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/plugins/marketplace/manager.ts +94 -10
- package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
- package/src/gjc-runtime/deep-interview-state.ts +127 -3
- package/src/gjc-runtime/gc-render.ts +19 -1
- package/src/gjc-runtime/gc-runtime.ts +68 -3
- package/src/gjc-runtime/launch-tmux.ts +61 -6
- package/src/gjc-runtime/managed-owner-admission.ts +352 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
- package/src/gjc-runtime/state-argv.ts +292 -0
- package/src/gjc-runtime/state-runtime.ts +80 -222
- package/src/gjc-runtime/team-runtime.ts +1 -1
- package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
- package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
- package/src/harness-control-plane/owner.ts +59 -3
- package/src/hindsight/client.ts +65 -2
- package/src/internal-urls/agent-protocol.ts +76 -5
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/internal-urls/local-protocol.ts +416 -18
- package/src/lsp/index.ts +4 -88
- package/src/main.ts +37 -13
- package/src/modes/acp/acp-agent.ts +8 -0
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/custom-editor.ts +103 -32
- package/src/modes/components/custom-provider-wizard.ts +26 -1
- package/src/modes/components/notifications-settings-editor.ts +26 -2
- package/src/modes/components/session-observer-overlay.ts +40 -37
- package/src/modes/components/settings-selector.ts +3 -1
- package/src/modes/components/tool-transcript-format.ts +393 -0
- package/src/modes/components/transcript-viewer-overlay.ts +63 -1
- package/src/modes/controllers/goal-mode-controller.ts +20 -1
- package/src/modes/controllers/input-controller.ts +98 -37
- package/src/modes/controllers/plan-mode-controller.ts +2 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +71 -9
- package/src/modes/interactive-mode.ts +14 -2
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
- package/src/plan-mode/approved-plan.ts +3 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/lsp.md +3 -3
- package/src/runtime-mcp/manager.ts +16 -1
- package/src/runtime-mcp/oauth-discovery.ts +116 -26
- package/src/sdk/broker/broker.ts +139 -32
- package/src/sdk/broker/discovery.ts +125 -0
- package/src/sdk/broker/ensure.ts +103 -19
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/session-index.ts +309 -38
- package/src/sdk/broker/state-version.ts +5 -2
- package/src/sdk/bus/chat-daemon-cli.ts +72 -10
- package/src/sdk/bus/chat-daemon-control.ts +526 -130
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/config-commands.ts +22 -0
- package/src/sdk/bus/config.ts +37 -0
- package/src/sdk/bus/index.ts +384 -92
- package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
- package/src/sdk/bus/notification-service.ts +284 -62
- package/src/sdk/bus/recent-activity.ts +14 -5
- package/src/sdk/bus/session-control.ts +150 -44
- package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
- package/src/sdk/bus/telegram-daemon-control.ts +294 -76
- package/src/sdk/bus/telegram-daemon.ts +1842 -281
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +155 -27
- package/src/secrets/index.ts +53 -10
- package/src/secrets/obfuscator.ts +21 -18
- package/src/secrets/regex.ts +2 -0
- package/src/session/agent-session.ts +495 -253
- package/src/session/artifacts.ts +188 -23
- package/src/session/context-estimation.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +215 -39
- package/src/session/internal/managed-session-storage.ts +1152 -46
- package/src/session/session-manager.ts +1055 -148
- package/src/session/session-storage.ts +109 -11
- package/src/session/streaming-output.ts +6 -0
- package/src/task/executor.ts +71 -26
- package/src/task/index.ts +63 -20
- package/src/tool-discovery/tool-index.ts +18 -0
- package/src/tools/ask.ts +329 -57
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/browser/launch.ts +3 -1
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +3 -0
- package/src/tools/browser/tab-supervisor.ts +125 -122
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +7 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/read.ts +7 -1
- package/src/tools/render-utils.ts +1 -27
- package/src/tools/resource-gc.ts +123 -34
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent.ts +2 -0
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/providers/openai-compatible.ts +76 -23
|
@@ -245,7 +245,6 @@ import {
|
|
|
245
245
|
persistCoordinatorRuntimeStateFromEvent,
|
|
246
246
|
registerCoordinatorRuntimeStateFinalizer,
|
|
247
247
|
} from "../gjc-runtime/session-state-sidecar";
|
|
248
|
-
import { writeArtifact } from "../gjc-runtime/state-writer";
|
|
249
248
|
import { requestGjcWorkerIntegrationAttempt } from "../gjc-runtime/team-runtime";
|
|
250
249
|
import { GoalRuntime } from "../goals/runtime";
|
|
251
250
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
@@ -295,8 +294,10 @@ import {
|
|
|
295
294
|
isMCPBridgeTool,
|
|
296
295
|
isMCPToolName,
|
|
297
296
|
selectDiscoverableToolNamesByServer,
|
|
297
|
+
selectRestorableDiscoveredBuiltinToolNames,
|
|
298
298
|
} from "../tool-discovery/tool-index";
|
|
299
299
|
import type { AskAnswerSource, ToolSession } from "../tools";
|
|
300
|
+
import { computeEssentialBuiltinNames } from "../tools";
|
|
300
301
|
import { AskTool } from "../tools/ask";
|
|
301
302
|
import {
|
|
302
303
|
getAskAnswerSource as getAskAnswerSourceFromRegistry,
|
|
@@ -366,6 +367,8 @@ import type {
|
|
|
366
367
|
CompactionEntry,
|
|
367
368
|
DefaultModelSelectionStage,
|
|
368
369
|
NewSessionOptions,
|
|
370
|
+
RecoveryHydrationContext,
|
|
371
|
+
RecoveryHydrationPromotionFence,
|
|
369
372
|
SessionContext,
|
|
370
373
|
SessionEntry,
|
|
371
374
|
SessionManager,
|
|
@@ -432,11 +435,6 @@ export type AgentSessionEvent =
|
|
|
432
435
|
| { type: "thinking_level_changed"; thinkingLevel: ThinkingLevel | undefined }
|
|
433
436
|
| { type: "goal_updated"; goal: Goal | null; state?: GoalModeState };
|
|
434
437
|
|
|
435
|
-
function isUnderProjectGjc(cwd: string, targetPath: string): boolean {
|
|
436
|
-
const relative = path.relative(path.join(path.resolve(cwd), ".gjc"), path.resolve(targetPath));
|
|
437
|
-
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
438
|
-
}
|
|
439
|
-
|
|
440
438
|
/** Listener function for agent session events */
|
|
441
439
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
442
440
|
export type AsyncJobSnapshotItem = Pick<
|
|
@@ -529,12 +527,31 @@ export interface AgentSessionConfig {
|
|
|
529
527
|
discoveryMode?: "off" | "mcp-only" | "all";
|
|
530
528
|
/** MCP tool names to activate for the current session when discovery mode is enabled. */
|
|
531
529
|
initialSelectedMCPToolNames?: string[];
|
|
532
|
-
/**
|
|
530
|
+
/** Built-in discoverable tool names restored for the current all-discovery session. */
|
|
531
|
+
initialSelectedDiscoveredBuiltinToolNames?: string[];
|
|
532
|
+
/** Discoverable built-ins active for configured or explicit reasons independently of persisted discovery selection. */
|
|
533
|
+
initialBaselineDiscoveredBuiltinToolNames?: string[];
|
|
534
|
+
/** Whether an MCP selection was explicitly supplied to the constructor, including an empty selection. */
|
|
535
|
+
initialMCPToolSelectionIsExplicit?: boolean;
|
|
536
|
+
/** Whether a discoverable built-in selection was explicitly supplied to the constructor, including an empty selection. */
|
|
537
|
+
initialDiscoveredBuiltinToolSelectionIsExplicit?: boolean;
|
|
538
|
+
|
|
539
|
+
/** Whether constructor-provided MCP selections should be persisted immediately. */
|
|
533
540
|
persistInitialMCPToolSelection?: boolean;
|
|
541
|
+
/** Whether constructor-provided discovered-built-in selections should be persisted immediately. */
|
|
542
|
+
persistInitialDiscoveredBuiltinToolSelection?: boolean;
|
|
543
|
+
/** Explicit MCP authority to write for a new session; distinct from active fallback tools. */
|
|
544
|
+
initialPersistedMCPToolNames?: string[];
|
|
545
|
+
/** Explicit discovered built-in authority to write for a new session; distinct from active fallback tools. */
|
|
546
|
+
initialPersistedDiscoveredBuiltinToolNames?: string[];
|
|
547
|
+
/** Immutable predecessor authority while a recovery host is read-only. */
|
|
548
|
+
recoveryHydrationContext?: RecoveryHydrationContext;
|
|
534
549
|
/** MCP server names whose tools should seed discovery-mode sessions whenever those servers are connected. */
|
|
535
550
|
defaultSelectedMCPServerNames?: string[];
|
|
536
551
|
/** MCP tool names that should seed brand-new sessions created from this AgentSession. */
|
|
537
552
|
defaultSelectedMCPToolNames?: string[];
|
|
553
|
+
/** MCP capabilities that are always active and never part of persisted user selection. */
|
|
554
|
+
mandatoryMCPToolNames?: string[];
|
|
538
555
|
/** TTSR manager for time-traveling stream rules */
|
|
539
556
|
ttsrManager?: TtsrManager;
|
|
540
557
|
/** Secret obfuscator for deobfuscating streaming edit content */
|
|
@@ -754,6 +771,30 @@ type RetryErrorClassification =
|
|
|
754
771
|
| "local_unavailable"
|
|
755
772
|
| "unknown";
|
|
756
773
|
|
|
774
|
+
const BARE_DEFAULT_WATCHDOG_ERROR =
|
|
775
|
+
/^(?:[A-Za-z][A-Za-z0-9-]*(?: [A-Za-z][A-Za-z0-9-]*){0,3} )stream (?:timed out while waiting for the first event|stalled while waiting for the next event)$/;
|
|
776
|
+
|
|
777
|
+
function hasBareDefaultRetryDisqualifyingFacts(message: AssistantMessage): boolean {
|
|
778
|
+
if (message.errorKind !== undefined || message.errorStatus !== undefined) return true;
|
|
779
|
+
const facts = message.transportFailure;
|
|
780
|
+
if (!facts) return false;
|
|
781
|
+
if (classifyFallbackTrigger(facts).class !== "other") return true;
|
|
782
|
+
return (
|
|
783
|
+
facts.status !== undefined ||
|
|
784
|
+
facts.providerCode !== undefined ||
|
|
785
|
+
facts.anthropicErrorType !== undefined ||
|
|
786
|
+
facts.openaiErrorCode !== undefined ||
|
|
787
|
+
(facts.headers !== undefined && Object.keys(facts.headers).length > 0)
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function assistantMessageHasVisibleOrToolContent(message: AssistantMessage): boolean {
|
|
792
|
+
return message.content.some(content => {
|
|
793
|
+
if (content.type === "text") return content.text.length > 0;
|
|
794
|
+
return content.type === "thinking" || content.type === "redactedThinking" || content.type === "toolCall";
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
757
798
|
function isLocalModelEndpoint(model: Model | undefined): boolean {
|
|
758
799
|
if (!model) return false;
|
|
759
800
|
if (model.provider === "ollama" || model.provider === "lm-studio" || model.provider === "llama.cpp") {
|
|
@@ -949,11 +990,6 @@ function createHandoffContext(document: string): string {
|
|
|
949
990
|
return `<handoff-context>\n${document}\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.`;
|
|
950
991
|
}
|
|
951
992
|
|
|
952
|
-
function createHandoffFileName(date = new Date()): string {
|
|
953
|
-
const fileTimestamp = date.toISOString().replace(/[:.]/g, "-");
|
|
954
|
-
return `handoff-${fileTimestamp}.md`;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
993
|
// ============================================================================
|
|
958
994
|
// ACP Permission Gate
|
|
959
995
|
// ============================================================================
|
|
@@ -1474,6 +1510,30 @@ export class AgentSession {
|
|
|
1474
1510
|
#lastMidRunMaintenanceAnchorSignature: string | undefined = undefined;
|
|
1475
1511
|
#resourceSampler: () => EmergencyCompactionSample = () => this.#defaultResourceSample();
|
|
1476
1512
|
#retainedMemorySampler: (() => RetainedMemorySample) | undefined;
|
|
1513
|
+
|
|
1514
|
+
/** Replay safety for the currently admitted top-level prompt/custom-message run. */
|
|
1515
|
+
#retryReplayEpoch = 0;
|
|
1516
|
+
#retryReplayUnsafeEpoch: number | undefined;
|
|
1517
|
+
|
|
1518
|
+
#resetRetryReplaySafety(): void {
|
|
1519
|
+
this.#retryReplayEpoch++;
|
|
1520
|
+
this.#retryReplayUnsafeEpoch = undefined;
|
|
1521
|
+
if (
|
|
1522
|
+
this.#extensionRunner?.hasHandlers("context") ||
|
|
1523
|
+
this.#extensionRunner?.hasHandlers("before_provider_request") ||
|
|
1524
|
+
this.#extensionRunner?.hasHandlers("after_provider_response")
|
|
1525
|
+
) {
|
|
1526
|
+
this.#markRetryReplayUnsafe();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
#markRetryReplayUnsafe(): void {
|
|
1531
|
+
if (this.#retryReplayEpoch > 0) this.#retryReplayUnsafeEpoch = this.#retryReplayEpoch;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
get #hasCleanRetryReplaySafety(): boolean {
|
|
1535
|
+
return this.#retryReplayEpoch > 0 && this.#retryReplayUnsafeEpoch !== this.#retryReplayEpoch;
|
|
1536
|
+
}
|
|
1477
1537
|
#prePromptContextCheckPromise: Promise<void> | undefined = undefined;
|
|
1478
1538
|
/** Display-only context snapshot; pre-prompt compaction estimates deliberately remain uncached. */
|
|
1479
1539
|
#contextUsageCache: { key: string; value: ContextUsage } | undefined;
|
|
@@ -1612,12 +1672,17 @@ export class AgentSession {
|
|
|
1612
1672
|
// Generic tool discovery (covers built-in + MCP + extension when tools.discoveryMode === "all")
|
|
1613
1673
|
#discoverableToolSearchIndex: DiscoverableToolSearchIndex | null = null;
|
|
1614
1674
|
#selectedDiscoveredToolNames = new Set<string>();
|
|
1675
|
+
#baselineDiscoveredBuiltinToolNames = new Set<string>();
|
|
1615
1676
|
#discoverableToolAllowedNames: ReadonlySet<string> | undefined;
|
|
1616
1677
|
#gjcSubskillToolNames = new Set<string>();
|
|
1617
1678
|
#gjcSubskillToolSignature: string | undefined;
|
|
1618
1679
|
#defaultSelectedMCPServerNames = new Set<string>();
|
|
1619
1680
|
#defaultSelectedMCPToolNames = new Set<string>();
|
|
1620
|
-
#
|
|
1681
|
+
#mandatoryMCPToolNames = new Set<string>();
|
|
1682
|
+
/** Constructor authority applies only while this AgentSession instance remains alive. */
|
|
1683
|
+
#constructorMCPToolSelection: string[] | undefined;
|
|
1684
|
+
#constructorDiscoveredBuiltinToolSelection: string[] | undefined;
|
|
1685
|
+
#recoveryHydrationContext: RecoveryHydrationContext | undefined;
|
|
1621
1686
|
|
|
1622
1687
|
// TTSR manager for time-traveling stream rules
|
|
1623
1688
|
#ttsrManager: TtsrManager | undefined = undefined;
|
|
@@ -2021,6 +2086,7 @@ export class AgentSession {
|
|
|
2021
2086
|
});
|
|
2022
2087
|
// Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
|
|
2023
2088
|
this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
|
|
2089
|
+
this.#recoveryHydrationContext = config.recoveryHydrationContext;
|
|
2024
2090
|
this.#ownedAsyncJobManager = config.ownedAsyncJobManager;
|
|
2025
2091
|
this.#retainedMemorySampler = config.retainedMemorySampler;
|
|
2026
2092
|
this.#ownedMcpManager = config.ownedMcpManager;
|
|
@@ -2112,24 +2178,53 @@ export class AgentSession {
|
|
|
2112
2178
|
: undefined;
|
|
2113
2179
|
this.#setDiscoverableMCPTools(this.#collectDiscoverableMCPToolsFromRegistry());
|
|
2114
2180
|
this.#selectedMCPToolNames = new Set(config.initialSelectedMCPToolNames ?? []);
|
|
2181
|
+
this.#selectedDiscoveredToolNames = new Set(
|
|
2182
|
+
this.#selectRestorableDiscoveredBuiltinToolNames(config.initialSelectedDiscoveredBuiltinToolNames ?? []),
|
|
2183
|
+
);
|
|
2184
|
+
this.#baselineDiscoveredBuiltinToolNames = new Set(
|
|
2185
|
+
selectRestorableDiscoveredBuiltinToolNames(
|
|
2186
|
+
config.initialBaselineDiscoveredBuiltinToolNames ?? [],
|
|
2187
|
+
this.#toolRegistry,
|
|
2188
|
+
this.#discoverableToolAllowedNames,
|
|
2189
|
+
),
|
|
2190
|
+
);
|
|
2115
2191
|
this.#defaultSelectedMCPServerNames = new Set(config.defaultSelectedMCPServerNames ?? []);
|
|
2116
2192
|
this.#defaultSelectedMCPToolNames = new Set(config.defaultSelectedMCPToolNames ?? []);
|
|
2193
|
+
this.#mandatoryMCPToolNames = new Set(
|
|
2194
|
+
(config.mandatoryMCPToolNames ?? [])
|
|
2195
|
+
.map(name => name.toLowerCase())
|
|
2196
|
+
.filter(name => this.#toolRegistry.has(name)),
|
|
2197
|
+
);
|
|
2198
|
+
this.#constructorMCPToolSelection =
|
|
2199
|
+
config.initialMCPToolSelectionIsExplicit === true
|
|
2200
|
+
? this.#filterSelectableMCPToolNames(config.initialPersistedMCPToolNames ?? [])
|
|
2201
|
+
: undefined;
|
|
2202
|
+
this.#constructorDiscoveredBuiltinToolSelection =
|
|
2203
|
+
config.initialDiscoveredBuiltinToolSelectionIsExplicit === true
|
|
2204
|
+
? this.#selectRestorableDiscoveredBuiltinToolNames(config.initialPersistedDiscoveredBuiltinToolNames ?? [])
|
|
2205
|
+
: undefined;
|
|
2117
2206
|
this.#pruneSelectedMCPToolNames();
|
|
2118
|
-
const persistedSelectedMCPToolNames = this.buildDisplaySessionContext().selectedMCPToolNames;
|
|
2119
|
-
const currentSelectedMCPToolNames = this.getSelectedMCPToolNames();
|
|
2120
2207
|
const persistInitialMCPToolSelection =
|
|
2121
|
-
config.persistInitialMCPToolSelection
|
|
2208
|
+
config.persistInitialMCPToolSelection === true && config.initialMCPToolSelectionIsExplicit !== false;
|
|
2209
|
+
const persistInitialDiscoveredBuiltinToolSelection =
|
|
2210
|
+
config.persistInitialDiscoveredBuiltinToolSelection === true &&
|
|
2211
|
+
config.initialDiscoveredBuiltinToolSelectionIsExplicit !== false;
|
|
2122
2212
|
if (
|
|
2123
|
-
this.#
|
|
2124
|
-
|
|
2125
|
-
|
|
2213
|
+
!this.#recoveryHydrationContext &&
|
|
2214
|
+
(this.#mcpDiscoveryEnabled || this.#resolveEffectiveDiscoveryMode() === "all") &&
|
|
2215
|
+
persistInitialMCPToolSelection
|
|
2126
2216
|
) {
|
|
2127
|
-
this.sessionManager.appendMCPToolSelection(
|
|
2217
|
+
this.sessionManager.appendMCPToolSelection(config.initialPersistedMCPToolNames ?? []);
|
|
2218
|
+
}
|
|
2219
|
+
if (
|
|
2220
|
+
!this.#recoveryHydrationContext &&
|
|
2221
|
+
this.#resolveEffectiveDiscoveryMode() === "all" &&
|
|
2222
|
+
persistInitialDiscoveredBuiltinToolSelection
|
|
2223
|
+
) {
|
|
2224
|
+
this.sessionManager.appendDiscoveredBuiltinToolSelection(
|
|
2225
|
+
config.initialPersistedDiscoveredBuiltinToolNames ?? [],
|
|
2226
|
+
);
|
|
2128
2227
|
}
|
|
2129
|
-
this.#rememberSessionDefaultSelectedMCPToolNames(
|
|
2130
|
-
this.sessionManager.getSessionFile(),
|
|
2131
|
-
this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
2132
|
-
);
|
|
2133
2228
|
this.#ttsrManager = config.ttsrManager;
|
|
2134
2229
|
this.#obfuscator = config.obfuscator;
|
|
2135
2230
|
this.#agentId = config.agentId;
|
|
@@ -2187,11 +2282,37 @@ export class AgentSession {
|
|
|
2187
2282
|
// Re-evaluate append-only context mode when the setting changes at runtime.
|
|
2188
2283
|
this.#unsubscribeAppendOnly = onAppendOnlyModeChanged(_value => this.#syncAppendOnlyContext(this.model));
|
|
2189
2284
|
// SDK ToolSession callbacks capture the just-constructed session. Defer the
|
|
2190
|
-
// initial ask-tool
|
|
2285
|
+
// initial ask-tool registration until that capture has been assigned by the
|
|
2191
2286
|
// session factory, while retaining the durable emitter created above.
|
|
2192
|
-
|
|
2193
|
-
|
|
2287
|
+
this.#workflowGateToolRestoration = new Promise<void>((resolve, reject) => {
|
|
2288
|
+
queueMicrotask(() => {
|
|
2289
|
+
if (this.#isDisposed) {
|
|
2290
|
+
resolve();
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
try {
|
|
2294
|
+
this.#registerWorkflowGateAskTool();
|
|
2295
|
+
this.#attachAskToolIfWorkflowActive().then(resolve, reject);
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2194
2300
|
});
|
|
2301
|
+
// Non-SDK embedders may never observe the getter; a swallowed handler
|
|
2302
|
+
// prevents an unhandled rejection while later awaits still reject.
|
|
2303
|
+
this.#workflowGateToolRestoration.catch(() => {});
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
#workflowGateToolRestoration: Promise<void> = Promise.resolve();
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* Resolves when constructor-time workflow-gate tool restoration (ask
|
|
2310
|
+
* registration plus durable active-workflow attachment) has settled. The
|
|
2311
|
+
* SDK factory awaits this so a resumed canonical workflow session is
|
|
2312
|
+
* returned with `ask` already resident.
|
|
2313
|
+
*/
|
|
2314
|
+
get workflowGateToolRestoration(): Promise<void> {
|
|
2315
|
+
return this.#workflowGateToolRestoration;
|
|
2195
2316
|
}
|
|
2196
2317
|
|
|
2197
2318
|
/** Model registry for API key resolution and model discovery */
|
|
@@ -2900,17 +3021,16 @@ export class AgentSession {
|
|
|
2900
3021
|
if (thresholdTokens <= 0 || estimateTextTokensHeuristic(fullText) <= thresholdTokens) return;
|
|
2901
3022
|
|
|
2902
3023
|
try {
|
|
2903
|
-
const
|
|
2904
|
-
if (!
|
|
2905
|
-
await Bun.write(artifact.path, fullText);
|
|
3024
|
+
const artifactId = await this.sessionManager.saveArtifact(fullText, "tool-result");
|
|
3025
|
+
if (!artifactId) return;
|
|
2906
3026
|
const digest = crypto.createHash("sha256").update(fullText).digest("hex");
|
|
2907
|
-
const preview = createPreAdmissionArtifactSpillPreview(fullText,
|
|
3027
|
+
const preview = createPreAdmissionArtifactSpillPreview(fullText, artifactId, digest);
|
|
2908
3028
|
const spillMeta = outputMeta()
|
|
2909
3029
|
.truncationFromText(preview, {
|
|
2910
3030
|
direction: "middle",
|
|
2911
3031
|
totalLines: fullText.split("\n").length,
|
|
2912
3032
|
totalBytes: Buffer.byteLength(fullText, "utf-8"),
|
|
2913
|
-
artifactId
|
|
3033
|
+
artifactId,
|
|
2914
3034
|
})
|
|
2915
3035
|
.get();
|
|
2916
3036
|
const existingDetails = message.details;
|
|
@@ -2950,6 +3070,38 @@ export class AgentSession {
|
|
|
2950
3070
|
|
|
2951
3071
|
/** Internal handler for agent events - shared by subscribe and reconnect */
|
|
2952
3072
|
#handleAgentEvent = async (event: AgentEvent): Promise<void> => {
|
|
3073
|
+
if (this.#extensionRunner?.hasHandlers(event.type)) this.#markRetryReplayUnsafe();
|
|
3074
|
+
if (
|
|
3075
|
+
event.type === "tool_execution_start" ||
|
|
3076
|
+
event.type === "tool_execution_update" ||
|
|
3077
|
+
event.type === "tool_execution_end"
|
|
3078
|
+
) {
|
|
3079
|
+
this.#markRetryReplayUnsafe();
|
|
3080
|
+
} else if (event.type === "message_end") {
|
|
3081
|
+
if (
|
|
3082
|
+
event.message.role === "toolResult" ||
|
|
3083
|
+
(event.message.role === "assistant" && assistantMessageHasVisibleOrToolContent(event.message))
|
|
3084
|
+
) {
|
|
3085
|
+
this.#markRetryReplayUnsafe();
|
|
3086
|
+
}
|
|
3087
|
+
} else if (event.type === "message_update") {
|
|
3088
|
+
const update = event.assistantMessageEvent;
|
|
3089
|
+
if (
|
|
3090
|
+
update.type === "toolcall_start" ||
|
|
3091
|
+
update.type === "toolcall_delta" ||
|
|
3092
|
+
update.type === "toolcall_end" ||
|
|
3093
|
+
((update.type === "text_delta" ||
|
|
3094
|
+
update.type === "thinking_delta" ||
|
|
3095
|
+
update.type === "reasoning_summary_delta") &&
|
|
3096
|
+
update.delta.length > 0) ||
|
|
3097
|
+
((update.type === "text_end" ||
|
|
3098
|
+
update.type === "thinking_end" ||
|
|
3099
|
+
update.type === "reasoning_summary_end") &&
|
|
3100
|
+
update.content.length > 0)
|
|
3101
|
+
) {
|
|
3102
|
+
this.#markRetryReplayUnsafe();
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
2953
3105
|
// Record a successful final yield before any asynchronous extension work so a
|
|
2954
3106
|
// concurrently delivered agent_end cannot start post-turn maintenance first.
|
|
2955
3107
|
if (event.type === "tool_execution_end" && event.toolName === "yield" && !event.isError) {
|
|
@@ -3096,7 +3248,7 @@ export class AgentSession {
|
|
|
3096
3248
|
// Deobfuscate assistant message content for display emission — the LLM echoes back
|
|
3097
3249
|
// obfuscated placeholders, but listeners (TUI, extensions, exporters) must see real
|
|
3098
3250
|
// values. The original event.message stays obfuscated so the canonical persistence path above
|
|
3099
|
-
// writes
|
|
3251
|
+
// writes authenticated placeholder tokens to the session file; convertToLlm re-obfuscates outbound
|
|
3100
3252
|
// traffic on the next turn. Walks text, thinking, and toolCall arguments/intent.
|
|
3101
3253
|
let displayEvent: AgentEvent = event;
|
|
3102
3254
|
const obfuscator = this.#obfuscator;
|
|
@@ -3621,6 +3773,7 @@ export class AgentSession {
|
|
|
3621
3773
|
suppressPredecessorAgentEnd?: boolean;
|
|
3622
3774
|
shouldContinue?: () => boolean;
|
|
3623
3775
|
onSkip?: (reason: "generation_changed" | "aborted_signal" | "queue_drained") => void;
|
|
3776
|
+
allowDuringCancelAndSubmit?: boolean;
|
|
3624
3777
|
onError?: (error: unknown) => void;
|
|
3625
3778
|
}): Promise<void> {
|
|
3626
3779
|
const predecessorAgentEndHold = options?.suppressPredecessorAgentEnd
|
|
@@ -3655,7 +3808,7 @@ export class AgentSession {
|
|
|
3655
3808
|
skip("generation_changed");
|
|
3656
3809
|
return false;
|
|
3657
3810
|
}
|
|
3658
|
-
if (this.#cancelAndSubmitInProgress) {
|
|
3811
|
+
if (this.#cancelAndSubmitInProgress && !options?.allowDuringCancelAndSubmit) {
|
|
3659
3812
|
skip("queue_drained");
|
|
3660
3813
|
return false;
|
|
3661
3814
|
}
|
|
@@ -4291,6 +4444,7 @@ export class AgentSession {
|
|
|
4291
4444
|
#localProtocolOptions(): LocalProtocolOptions {
|
|
4292
4445
|
return {
|
|
4293
4446
|
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
4447
|
+
isManagedDestination: () => this.sessionManager.isManagedDestination(),
|
|
4294
4448
|
getSessionId: () => this.sessionManager.getSessionId(),
|
|
4295
4449
|
};
|
|
4296
4450
|
}
|
|
@@ -4583,6 +4737,7 @@ export class AgentSession {
|
|
|
4583
4737
|
message: event.message,
|
|
4584
4738
|
contentIndex: event.assistantMessageEvent.contentIndex,
|
|
4585
4739
|
};
|
|
4740
|
+
if (this.#extensionRunner.hasHandlers("reasoning_summary_start")) this.#markRetryReplayUnsafe();
|
|
4586
4741
|
await this.#extensionRunner.emit(reasoningEvent, continueWhile);
|
|
4587
4742
|
} else if (event.assistantMessageEvent.type === "reasoning_summary_delta") {
|
|
4588
4743
|
const reasoningEvent: ReasoningSummaryDeltaEvent = {
|
|
@@ -4591,6 +4746,7 @@ export class AgentSession {
|
|
|
4591
4746
|
contentIndex: event.assistantMessageEvent.contentIndex,
|
|
4592
4747
|
delta: event.assistantMessageEvent.delta,
|
|
4593
4748
|
};
|
|
4749
|
+
if (this.#extensionRunner.hasHandlers("reasoning_summary_delta")) this.#markRetryReplayUnsafe();
|
|
4594
4750
|
await this.#extensionRunner.emit(reasoningEvent, continueWhile);
|
|
4595
4751
|
} else if (event.assistantMessageEvent.type === "reasoning_summary_end") {
|
|
4596
4752
|
const reasoningEvent: ReasoningSummaryEndEvent = {
|
|
@@ -4599,6 +4755,7 @@ export class AgentSession {
|
|
|
4599
4755
|
contentIndex: event.assistantMessageEvent.contentIndex,
|
|
4600
4756
|
content: event.assistantMessageEvent.content,
|
|
4601
4757
|
};
|
|
4758
|
+
if (this.#extensionRunner.hasHandlers("reasoning_summary_end")) this.#markRetryReplayUnsafe();
|
|
4602
4759
|
await this.#extensionRunner.emit(reasoningEvent, continueWhile);
|
|
4603
4760
|
}
|
|
4604
4761
|
} else if (event.type === "message_end") {
|
|
@@ -4652,6 +4809,7 @@ export class AgentSession {
|
|
|
4652
4809
|
continuationSkipReason: event.continuationSkipReason,
|
|
4653
4810
|
});
|
|
4654
4811
|
} else if (event.type === "auto_retry_start") {
|
|
4812
|
+
if (this.#extensionRunner.hasHandlers("auto_retry_start")) this.#markRetryReplayUnsafe();
|
|
4655
4813
|
await this.#extensionRunner.emit({
|
|
4656
4814
|
type: "auto_retry_start",
|
|
4657
4815
|
attempt: event.attempt,
|
|
@@ -5090,29 +5248,66 @@ export class AgentSession {
|
|
|
5090
5248
|
return left.length === right.length && left.every((name, index) => name === right[index]);
|
|
5091
5249
|
}
|
|
5092
5250
|
|
|
5093
|
-
#
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5251
|
+
#resolveConstructorMCPToolSelection(): string[] | undefined {
|
|
5252
|
+
return this.#constructorMCPToolSelection
|
|
5253
|
+
? this.#filterSelectableMCPToolNames(this.#constructorMCPToolSelection)
|
|
5254
|
+
: undefined;
|
|
5255
|
+
}
|
|
5256
|
+
|
|
5257
|
+
#selectRestorableDiscoveredBuiltinToolNames(toolNames: Iterable<string>): string[] {
|
|
5258
|
+
return selectRestorableDiscoveredBuiltinToolNames(
|
|
5259
|
+
toolNames,
|
|
5260
|
+
this.#toolRegistry,
|
|
5261
|
+
this.#discoverableToolAllowedNames,
|
|
5262
|
+
new Set(computeEssentialBuiltinNames(this.settings)),
|
|
5101
5263
|
);
|
|
5102
5264
|
}
|
|
5103
5265
|
|
|
5104
|
-
#
|
|
5105
|
-
|
|
5106
|
-
|
|
5266
|
+
#resolveConstructorDiscoveredBuiltinToolSelection(): string[] | undefined {
|
|
5267
|
+
return this.#constructorDiscoveredBuiltinToolSelection
|
|
5268
|
+
? this.#selectRestorableDiscoveredBuiltinToolNames(this.#constructorDiscoveredBuiltinToolSelection)
|
|
5269
|
+
: undefined;
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5272
|
+
#clearConstructorToolSelectionAuthority(): void {
|
|
5273
|
+
this.#constructorMCPToolSelection = undefined;
|
|
5274
|
+
this.#constructorDiscoveredBuiltinToolSelection = undefined;
|
|
5107
5275
|
}
|
|
5108
5276
|
|
|
5109
|
-
#
|
|
5110
|
-
|
|
5277
|
+
#getSelectedDiscoveredBuiltinToolNames(): string[] {
|
|
5278
|
+
return this.#selectRestorableDiscoveredBuiltinToolNames(this.#selectedDiscoveredToolNames).filter(name =>
|
|
5279
|
+
this.getActiveToolNames().includes(name),
|
|
5280
|
+
);
|
|
5281
|
+
}
|
|
5282
|
+
|
|
5283
|
+
#persistSelectedMCPToolNamesIfChanged(
|
|
5284
|
+
previousSelectedMCPToolNames: string[],
|
|
5285
|
+
previousSelectedDiscoveredBuiltinToolNames: string[],
|
|
5286
|
+
): void {
|
|
5287
|
+
if (!this.#mcpDiscoveryEnabled && this.#resolveEffectiveDiscoveryMode() !== "all") return;
|
|
5111
5288
|
const nextSelectedMCPToolNames = this.getSelectedMCPToolNames();
|
|
5112
|
-
|
|
5113
|
-
|
|
5289
|
+
const nextSelectedDiscoveredBuiltinToolNames = this.#getSelectedDiscoveredBuiltinToolNames();
|
|
5290
|
+
const mcpSelectionChanged = !this.#selectedMCPToolNamesMatch(
|
|
5291
|
+
previousSelectedMCPToolNames,
|
|
5292
|
+
nextSelectedMCPToolNames,
|
|
5293
|
+
);
|
|
5294
|
+
const discoveredBuiltinSelectionChanged =
|
|
5295
|
+
this.#resolveEffectiveDiscoveryMode() === "all" &&
|
|
5296
|
+
!this.#selectedMCPToolNamesMatch(
|
|
5297
|
+
previousSelectedDiscoveredBuiltinToolNames,
|
|
5298
|
+
nextSelectedDiscoveredBuiltinToolNames,
|
|
5299
|
+
);
|
|
5300
|
+
const mutationCorrelationId =
|
|
5301
|
+
mcpSelectionChanged && discoveredBuiltinSelectionChanged ? crypto.randomUUID() : undefined;
|
|
5302
|
+
if (mcpSelectionChanged) {
|
|
5303
|
+
this.sessionManager.appendMCPToolSelection(nextSelectedMCPToolNames, mutationCorrelationId);
|
|
5304
|
+
}
|
|
5305
|
+
if (discoveredBuiltinSelectionChanged) {
|
|
5306
|
+
this.sessionManager.appendDiscoveredBuiltinToolSelection(
|
|
5307
|
+
nextSelectedDiscoveredBuiltinToolNames,
|
|
5308
|
+
mutationCorrelationId,
|
|
5309
|
+
);
|
|
5114
5310
|
}
|
|
5115
|
-
this.sessionManager.appendMCPToolSelection(nextSelectedMCPToolNames);
|
|
5116
5311
|
}
|
|
5117
5312
|
|
|
5118
5313
|
#getActiveNonMCPToolNames(): string[] {
|
|
@@ -5291,30 +5486,13 @@ export class AgentSession {
|
|
|
5291
5486
|
|
|
5292
5487
|
getSelectedMCPToolNames(): string[] {
|
|
5293
5488
|
if (!this.#mcpDiscoveryEnabled) {
|
|
5294
|
-
return this.getActiveToolNames().filter(
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
}
|
|
5298
|
-
|
|
5299
|
-
async #activateDiscoveredMCPTools(toolNames: string[]): Promise<string[]> {
|
|
5300
|
-
const nextSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
|
|
5301
|
-
const activated: string[] = [];
|
|
5302
|
-
for (const name of toolNames) {
|
|
5303
|
-
if (!this.#discoverableMCPTools.has(name) || !this.#toolRegistry.has(name)) {
|
|
5304
|
-
continue;
|
|
5305
|
-
}
|
|
5306
|
-
nextSelectedMCPToolNames.add(name);
|
|
5307
|
-
activated.push(name);
|
|
5308
|
-
}
|
|
5309
|
-
if (activated.length === 0) {
|
|
5310
|
-
return [];
|
|
5489
|
+
return this.getActiveToolNames().filter(
|
|
5490
|
+
name => isMCPToolName(name) && this.#toolRegistry.has(name) && !this.#mandatoryMCPToolNames.has(name),
|
|
5491
|
+
);
|
|
5311
5492
|
}
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
];
|
|
5316
|
-
await this.setActiveToolsByName(nextActive);
|
|
5317
|
-
return [...new Set(activated)];
|
|
5493
|
+
return this.#filterSelectableMCPToolNames(this.#selectedMCPToolNames).filter(
|
|
5494
|
+
name => !this.#mandatoryMCPToolNames.has(name),
|
|
5495
|
+
);
|
|
5318
5496
|
}
|
|
5319
5497
|
|
|
5320
5498
|
// ── Generic tool discovery (covers built-in + MCP + extension) ────────────
|
|
@@ -5362,54 +5540,44 @@ export class AgentSession {
|
|
|
5362
5540
|
return this.#discoverableToolSearchIndex;
|
|
5363
5541
|
}
|
|
5364
5542
|
|
|
5365
|
-
/** Invalidate the generic search index cache (call after tool set changes).
|
|
5366
|
-
* Delegates to {@link #invalidateDiscoveryCaches} so all discovery-related caches stay in sync. */
|
|
5367
|
-
#invalidateDiscoverableToolSearchIndex(): void {
|
|
5368
|
-
this.#invalidateDiscoveryCaches();
|
|
5369
|
-
}
|
|
5370
|
-
|
|
5371
5543
|
getSelectedDiscoveredToolNames(): string[] {
|
|
5372
|
-
// Union of MCP-selected and generic non-MCP selected. Non-MCP selections are only
|
|
5373
|
-
// selected while they are still active; otherwise BM25 must be able to rediscover them.
|
|
5374
|
-
const activeNames = new Set(this.getActiveToolNames());
|
|
5375
5544
|
const mcpSelected = this.getSelectedMCPToolNames();
|
|
5376
|
-
|
|
5377
|
-
name => activeNames.has(name) && this.#toolRegistry.has(name) && !isMCPToolName(name),
|
|
5378
|
-
);
|
|
5379
|
-
return [...new Set([...mcpSelected, ...nonMcpSelected])];
|
|
5545
|
+
return [...new Set([...mcpSelected, ...this.#getSelectedDiscoveredBuiltinToolNames()])];
|
|
5380
5546
|
}
|
|
5381
5547
|
|
|
5382
5548
|
async activateDiscoveredTools(toolNames: string[]): Promise<string[]> {
|
|
5383
|
-
const
|
|
5384
|
-
const
|
|
5549
|
+
const previousSelectedMCPToolNames = this.getSelectedMCPToolNames();
|
|
5550
|
+
const previousSelectedDiscoveredBuiltinToolNames = this.#getSelectedDiscoveredBuiltinToolNames();
|
|
5551
|
+
const nextActiveToolNames = this.getActiveToolNames();
|
|
5552
|
+
const nextActiveNameSet = new Set(nextActiveToolNames);
|
|
5553
|
+
const nextSelectedDiscoveredBuiltinToolNames = new Set(this.#selectedDiscoveredToolNames);
|
|
5385
5554
|
const activated: string[] = [];
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
}
|
|
5392
|
-
|
|
5393
|
-
// Activate non-MCP tools (built-ins that are in the registry but not currently active)
|
|
5394
|
-
if (nonMcpNames.length > 0) {
|
|
5395
|
-
const currentActiveNames = new Set(this.getActiveToolNames());
|
|
5396
|
-
const newlyAdded: string[] = [];
|
|
5397
|
-
for (const name of nonMcpNames) {
|
|
5398
|
-
if (this.#discoverableToolAllowedNames && !this.#discoverableToolAllowedNames.has(name)) continue;
|
|
5399
|
-
if (this.#toolRegistry.has(name) && !currentActiveNames.has(name)) {
|
|
5400
|
-
newlyAdded.push(name);
|
|
5401
|
-
this.#selectedDiscoveredToolNames.add(name);
|
|
5555
|
+
for (const name of new Set(toolNames)) {
|
|
5556
|
+
if (this.#discoverableMCPTools.has(name) && this.#toolRegistry.has(name)) {
|
|
5557
|
+
if (!nextActiveNameSet.has(name)) {
|
|
5558
|
+
nextActiveToolNames.push(name);
|
|
5559
|
+
nextActiveNameSet.add(name);
|
|
5402
5560
|
activated.push(name);
|
|
5403
5561
|
}
|
|
5562
|
+
continue;
|
|
5404
5563
|
}
|
|
5405
|
-
if (
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5564
|
+
if (this.#discoverableToolAllowedNames && !this.#discoverableToolAllowedNames.has(name)) continue;
|
|
5565
|
+
const tool = this.#toolRegistry.get(name);
|
|
5566
|
+
if (tool?.loadMode === "discoverable" && !nextActiveNameSet.has(name)) {
|
|
5567
|
+
nextActiveToolNames.push(name);
|
|
5568
|
+
nextActiveNameSet.add(name);
|
|
5569
|
+
nextSelectedDiscoveredBuiltinToolNames.add(name);
|
|
5570
|
+
activated.push(name);
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
if (activated.length > 0) {
|
|
5574
|
+
await this.#applyActiveToolsByName(nextActiveToolNames, {
|
|
5575
|
+
previousSelectedMCPToolNames,
|
|
5576
|
+
previousSelectedDiscoveredBuiltinToolNames,
|
|
5577
|
+
nextSelectedDiscoveredBuiltinToolNames: [...nextSelectedDiscoveredBuiltinToolNames],
|
|
5578
|
+
});
|
|
5410
5579
|
}
|
|
5411
|
-
|
|
5412
|
-
return [...new Set(activated)];
|
|
5580
|
+
return activated;
|
|
5413
5581
|
}
|
|
5414
5582
|
|
|
5415
5583
|
/** Wrap guarded tools so SDK permission modes remain fail-closed without a reverse provider. */
|
|
@@ -5597,10 +5765,17 @@ export class AgentSession {
|
|
|
5597
5765
|
|
|
5598
5766
|
async #applyActiveToolsByName(
|
|
5599
5767
|
toolNames: string[],
|
|
5600
|
-
options?: {
|
|
5768
|
+
options?: {
|
|
5769
|
+
persistMCPSelection?: boolean;
|
|
5770
|
+
previousSelectedMCPToolNames?: string[];
|
|
5771
|
+
previousSelectedDiscoveredBuiltinToolNames?: string[];
|
|
5772
|
+
nextSelectedDiscoveredBuiltinToolNames?: string[];
|
|
5773
|
+
},
|
|
5601
5774
|
): Promise<void> {
|
|
5602
|
-
toolNames = [...new Set(toolNames.map(name => name.toLowerCase()))];
|
|
5775
|
+
toolNames = [...new Set([...toolNames.map(name => name.toLowerCase()), ...this.#mandatoryMCPToolNames])];
|
|
5603
5776
|
const previousSelectedMCPToolNames = options?.previousSelectedMCPToolNames ?? this.getSelectedMCPToolNames();
|
|
5777
|
+
const previousSelectedDiscoveredBuiltinToolNames =
|
|
5778
|
+
options?.previousSelectedDiscoveredBuiltinToolNames ?? this.#getSelectedDiscoveredBuiltinToolNames();
|
|
5604
5779
|
const tools: AgentTool[] = [];
|
|
5605
5780
|
const validToolNames: string[] = [];
|
|
5606
5781
|
for (const name of toolNames) {
|
|
@@ -5610,37 +5785,29 @@ export class AgentSession {
|
|
|
5610
5785
|
validToolNames.push(name);
|
|
5611
5786
|
}
|
|
5612
5787
|
}
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5788
|
+
const nextSelectedMCPToolNames = this.#mcpDiscoveryEnabled
|
|
5789
|
+
? new Set(
|
|
5790
|
+
validToolNames.filter(
|
|
5791
|
+
name =>
|
|
5792
|
+
isMCPToolName(name) &&
|
|
5793
|
+
!this.#mandatoryMCPToolNames.has(name) &&
|
|
5794
|
+
this.#discoverableMCPTools.has(name) &&
|
|
5795
|
+
this.#toolRegistry.has(name),
|
|
5796
|
+
),
|
|
5797
|
+
)
|
|
5798
|
+
: this.#selectedMCPToolNames;
|
|
5620
5799
|
const activeNameSet = new Set(validToolNames);
|
|
5621
|
-
|
|
5800
|
+
const nextSelectedDiscoveredBuiltinToolNames = new Set(
|
|
5801
|
+
options?.nextSelectedDiscoveredBuiltinToolNames ?? this.#selectedDiscoveredToolNames,
|
|
5802
|
+
);
|
|
5803
|
+
for (const name of nextSelectedDiscoveredBuiltinToolNames) {
|
|
5622
5804
|
if (!activeNameSet.has(name) || this.#discoverableMCPTools.has(name) || !this.#toolRegistry.has(name)) {
|
|
5623
|
-
|
|
5805
|
+
nextSelectedDiscoveredBuiltinToolNames.delete(name);
|
|
5624
5806
|
}
|
|
5625
5807
|
}
|
|
5626
5808
|
const signature = this.#computeAppliedToolSignature(validToolNames, tools);
|
|
5627
5809
|
const promptRelevantToolsChanged =
|
|
5628
5810
|
signature !== (this.#pendingAppliedToolSignature ?? this.#lastAppliedToolSignature);
|
|
5629
|
-
if (promptRelevantToolsChanged) {
|
|
5630
|
-
this.#defaultModelSelectionMutationRevision++;
|
|
5631
|
-
this.#pendingAppliedToolSignature = signature;
|
|
5632
|
-
}
|
|
5633
|
-
this.#setGuardedAgentTools(tools);
|
|
5634
|
-
|
|
5635
|
-
// Active tool set changed → discoverable tool list (which excludes already-active tools)
|
|
5636
|
-
// is now stale. Invalidate before any prompt-template hook reads the discovery list.
|
|
5637
|
-
this.#invalidateDiscoveryCaches();
|
|
5638
|
-
|
|
5639
|
-
// Rebuild base system prompt with new tool set, but only when the tool set
|
|
5640
|
-
// actually changed. MCP servers can reconnect at arbitrary times and call
|
|
5641
|
-
// `refreshMCPTools` -> `#applyActiveToolsByName` even though the resulting
|
|
5642
|
-
// tool list is byte-identical. Skipping the rebuild keeps the system prompt
|
|
5643
|
-
// stable, which is required for Anthropic prompt caching to keep hitting.
|
|
5644
5811
|
if (promptRelevantToolsChanged && this.#rebuildSystemPrompt) {
|
|
5645
5812
|
const generation = this.#reserveBaseSystemPromptGeneration();
|
|
5646
5813
|
try {
|
|
@@ -5663,8 +5830,16 @@ export class AgentSession {
|
|
|
5663
5830
|
this.#lastAppliedToolSignature = signature;
|
|
5664
5831
|
this.#pendingAppliedToolSignature = undefined;
|
|
5665
5832
|
}
|
|
5833
|
+
if (promptRelevantToolsChanged) this.#defaultModelSelectionMutationRevision++;
|
|
5834
|
+
this.#selectedMCPToolNames = nextSelectedMCPToolNames;
|
|
5835
|
+
this.#selectedDiscoveredToolNames = nextSelectedDiscoveredBuiltinToolNames;
|
|
5836
|
+
this.#setGuardedAgentTools(tools);
|
|
5837
|
+
this.#invalidateDiscoveryCaches();
|
|
5666
5838
|
if (options?.persistMCPSelection !== false) {
|
|
5667
|
-
this.#persistSelectedMCPToolNamesIfChanged(
|
|
5839
|
+
this.#persistSelectedMCPToolNamesIfChanged(
|
|
5840
|
+
previousSelectedMCPToolNames,
|
|
5841
|
+
previousSelectedDiscoveredBuiltinToolNames,
|
|
5842
|
+
);
|
|
5668
5843
|
}
|
|
5669
5844
|
}
|
|
5670
5845
|
|
|
@@ -5717,24 +5892,33 @@ export class AgentSession {
|
|
|
5717
5892
|
await this.#applyActiveToolsByName(toolNames);
|
|
5718
5893
|
}
|
|
5719
5894
|
|
|
5720
|
-
async #restoreMCPSelectionsForSessionContext(
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
const
|
|
5726
|
-
|
|
5727
|
-
|
|
5895
|
+
async #restoreMCPSelectionsForSessionContext(sessionContext: SessionContext): Promise<void> {
|
|
5896
|
+
if (!this.#mcpDiscoveryEnabled && this.#resolveEffectiveDiscoveryMode() !== "all") {
|
|
5897
|
+
await this.#attachAskToolIfWorkflowActive();
|
|
5898
|
+
return;
|
|
5899
|
+
}
|
|
5900
|
+
const selectionOnlyDiscoveredBuiltinToolNames = new Set(
|
|
5901
|
+
this.#getSelectedDiscoveredBuiltinToolNames().filter(
|
|
5902
|
+
name => !this.#baselineDiscoveredBuiltinToolNames.has(name),
|
|
5903
|
+
),
|
|
5904
|
+
);
|
|
5905
|
+
const nextActiveNonMCPToolNames = this.#getActiveNonMCPToolNames().filter(
|
|
5906
|
+
name => name !== "ask" && !selectionOnlyDiscoveredBuiltinToolNames.has(name),
|
|
5907
|
+
);
|
|
5908
|
+
const constructorMCPToolNames = this.#resolveConstructorMCPToolSelection();
|
|
5728
5909
|
const restoredMCPToolNames = sessionContext.hasPersistedMCPToolSelection
|
|
5729
5910
|
? this.#filterSelectableMCPToolNames(sessionContext.selectedMCPToolNames)
|
|
5730
|
-
: this.#
|
|
5731
|
-
this.#
|
|
5732
|
-
|
|
5733
|
-
this.#
|
|
5911
|
+
: (constructorMCPToolNames ?? this.#getConfiguredDefaultSelectedMCPToolNames());
|
|
5912
|
+
const constructorDiscoveredBuiltinToolNames = this.#resolveConstructorDiscoveredBuiltinToolSelection();
|
|
5913
|
+
const restoredDiscoveredBuiltinToolNames = sessionContext.hasPersistedDiscoveredBuiltinToolSelection
|
|
5914
|
+
? this.#selectRestorableDiscoveredBuiltinToolNames(sessionContext.selectedDiscoveredBuiltinToolNames ?? [])
|
|
5915
|
+
: (constructorDiscoveredBuiltinToolNames ?? []);
|
|
5916
|
+
this.#selectedDiscoveredToolNames = new Set(restoredDiscoveredBuiltinToolNames);
|
|
5917
|
+
await this.#applyActiveToolsByName(
|
|
5918
|
+
[...nextActiveNonMCPToolNames, ...restoredMCPToolNames, ...restoredDiscoveredBuiltinToolNames],
|
|
5919
|
+
{ persistMCPSelection: false },
|
|
5734
5920
|
);
|
|
5735
|
-
await this.#
|
|
5736
|
-
persistMCPSelection: false,
|
|
5737
|
-
});
|
|
5921
|
+
await this.#attachAskToolIfWorkflowActive();
|
|
5738
5922
|
}
|
|
5739
5923
|
/** Rebuild the base system prompt using the current active tool set. */
|
|
5740
5924
|
async refreshBaseSystemPrompt(): Promise<void> {
|
|
@@ -5878,19 +6062,17 @@ export class AgentSession {
|
|
|
5878
6062
|
|
|
5879
6063
|
this.#setDiscoverableMCPTools(this.#collectDiscoverableMCPToolsFromRegistry());
|
|
5880
6064
|
this.#pruneSelectedMCPToolNames();
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
6065
|
+
const hasPersistedMCPToolSelection = this.buildDisplaySessionContext().hasPersistedMCPToolSelection;
|
|
6066
|
+
if (!hasPersistedMCPToolSelection) {
|
|
6067
|
+
this.#selectedMCPToolNames = new Set(
|
|
6068
|
+
this.#resolveConstructorMCPToolSelection() ?? this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
6069
|
+
);
|
|
5886
6070
|
}
|
|
5887
|
-
this.#rememberSessionDefaultSelectedMCPToolNames(
|
|
5888
|
-
this.sessionFile,
|
|
5889
|
-
this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
5890
|
-
);
|
|
5891
|
-
|
|
5892
6071
|
const nextActive = [...this.#getActiveNonMCPToolNames(), ...this.getSelectedMCPToolNames()];
|
|
5893
|
-
await this.#applyActiveToolsByName(nextActive, {
|
|
6072
|
+
await this.#applyActiveToolsByName(nextActive, {
|
|
6073
|
+
previousSelectedMCPToolNames,
|
|
6074
|
+
persistMCPSelection: hasPersistedMCPToolSelection,
|
|
6075
|
+
});
|
|
5894
6076
|
}
|
|
5895
6077
|
|
|
5896
6078
|
async #hasActiveGjcSubskillTools(parent: string, sessionId: string | undefined): Promise<boolean> {
|
|
@@ -6035,8 +6217,29 @@ export class AgentSession {
|
|
|
6035
6217
|
return this.#promptGeneration;
|
|
6036
6218
|
}
|
|
6037
6219
|
|
|
6220
|
+
/** The immutable recovery authority, present only before external ownership promotion. */
|
|
6221
|
+
get recoveryHydrationContext(): RecoveryHydrationContext | undefined {
|
|
6222
|
+
return this.#recoveryHydrationContext;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
/** Enables normal session mutations after the owner has published its durable fence and writer lease. */
|
|
6226
|
+
async promoteRecoveryHydrationAfterOwnershipReadyFence(fence: RecoveryHydrationPromotionFence): Promise<void> {
|
|
6227
|
+
const context = this.#recoveryHydrationContext;
|
|
6228
|
+
if (!context) throw new Error("Agent session is not awaiting recovery hydration promotion.");
|
|
6229
|
+
await this.sessionManager.promoteRecoveryHydrationAfterOwnershipReadyFence(context, fence);
|
|
6230
|
+
this.#recoveryHydrationContext = undefined;
|
|
6231
|
+
}
|
|
6232
|
+
|
|
6233
|
+
/** Recovery hydration must not start a continuation before ownership promotion. */
|
|
6234
|
+
#assertRecoveryHydrationPromoted(): void {
|
|
6235
|
+
if (this.#recoveryHydrationContext) {
|
|
6236
|
+
throw new Error("Recovery hydration has not been promoted to a writer-owning session.");
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
|
|
6038
6240
|
/** Main startup calls this exactly once, after a strict open returned `kind: "opened"`. */
|
|
6039
6241
|
async continuePersistedHistory(): Promise<void> {
|
|
6242
|
+
this.#assertRecoveryHydrationPromoted();
|
|
6040
6243
|
this.#removeEphemeralCustomMessages();
|
|
6041
6244
|
|
|
6042
6245
|
if (!canContinuePersistedHistory(this.agent.state.messages)) {
|
|
@@ -6392,33 +6595,70 @@ export class AgentSession {
|
|
|
6392
6595
|
this.#workflowGateEmitter = emitter;
|
|
6393
6596
|
notifyWorkflowGateEmitterChanged(this.sessionId, emitter);
|
|
6394
6597
|
if (emitter) {
|
|
6395
|
-
this.#
|
|
6598
|
+
this.#registerWorkflowGateAskTool();
|
|
6396
6599
|
}
|
|
6397
6600
|
}
|
|
6398
6601
|
|
|
6399
|
-
#
|
|
6400
|
-
if (this.#toolRegistry.has("ask")) return;
|
|
6602
|
+
#registerWorkflowGateAskTool(): void {
|
|
6401
6603
|
if (!this.#workflowGateToolSession) return;
|
|
6402
6604
|
|
|
6403
|
-
|
|
6404
|
-
if (!askTool)
|
|
6605
|
+
let askTool = this.#toolRegistry.get("ask");
|
|
6606
|
+
if (!askTool) {
|
|
6607
|
+
const createdAskTool = AskTool.createIf(this.#workflowGateToolSession);
|
|
6608
|
+
if (!createdAskTool) return;
|
|
6609
|
+
const wrappedTool = wrapToolWithMetaNotice(createdAskTool as unknown as AgentTool);
|
|
6610
|
+
askTool = this.#extensionRunner ? new ExtensionToolWrapper(wrappedTool, this.#extensionRunner) : wrappedTool;
|
|
6611
|
+
this.#toolRegistry.set(askTool.name, askTool);
|
|
6612
|
+
}
|
|
6405
6613
|
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6614
|
+
try {
|
|
6615
|
+
if ((this.#workflowGateEmitter?.listPendingGates?.().length ?? 0) > 0) {
|
|
6616
|
+
this.#attachAskTool();
|
|
6617
|
+
}
|
|
6618
|
+
} catch (error) {
|
|
6619
|
+
logger.warn("Failed to inspect pending workflow gates; activating ask tool conservatively", {
|
|
6620
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6621
|
+
});
|
|
6622
|
+
this.#attachAskTool();
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6411
6625
|
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
this.#
|
|
6416
|
-
|
|
6417
|
-
|
|
6626
|
+
async #attachAskToolIfWorkflowActive(): Promise<void> {
|
|
6627
|
+
const sessionId = this.sessionManager.getSessionId();
|
|
6628
|
+
const inMemoryActiveSkill =
|
|
6629
|
+
this.#activeSkillState && (!this.#activeSkillState.sessionId || this.#activeSkillState.sessionId === sessionId)
|
|
6630
|
+
? this.#activeSkillState.skill
|
|
6631
|
+
: undefined;
|
|
6632
|
+
let activeSkill = inMemoryActiveSkill;
|
|
6633
|
+
if (!activeSkill) {
|
|
6634
|
+
try {
|
|
6635
|
+
const activeState = await readVisibleSkillActiveState(this.sessionManager.getCwd(), sessionId);
|
|
6636
|
+
activeSkill =
|
|
6637
|
+
activeState?.skill ?? activeState?.active_skills?.find(entry => entry.active !== false)?.skill;
|
|
6638
|
+
} catch (error) {
|
|
6639
|
+
logger.warn("Failed to read durable workflow skill state while restoring ask tool", {
|
|
6418
6640
|
error: error instanceof Error ? error.message : String(error),
|
|
6419
6641
|
});
|
|
6420
|
-
|
|
6642
|
+
return;
|
|
6643
|
+
}
|
|
6644
|
+
// Identity fence: the durable read is async — the session may have been
|
|
6645
|
+
// disposed or switched to a different identity meanwhile. Never attach
|
|
6646
|
+
// a predecessor session's workflow state to the current identity.
|
|
6647
|
+
if (this.#isDisposed || this.sessionManager.getSessionId() !== sessionId) return;
|
|
6421
6648
|
}
|
|
6649
|
+
if (activeSkill && isCanonicalGjcWorkflowSkill(activeSkill.trim())) this.#attachAskTool();
|
|
6650
|
+
}
|
|
6651
|
+
|
|
6652
|
+
#attachAskTool(): void {
|
|
6653
|
+
const askTool = this.#toolRegistry.get("ask");
|
|
6654
|
+
if (!askTool || this.getActiveToolNames().includes(askTool.name)) return;
|
|
6655
|
+
this.#setGuardedAgentTools([...this.agent.state.tools, askTool]);
|
|
6656
|
+
this.#invalidateDiscoveryCaches();
|
|
6657
|
+
void this.refreshBaseSystemPrompt().catch(error => {
|
|
6658
|
+
logger.warn("Failed to refresh system prompt after workflow gate ask tool activation", {
|
|
6659
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6660
|
+
});
|
|
6661
|
+
});
|
|
6422
6662
|
}
|
|
6423
6663
|
|
|
6424
6664
|
get goalRuntime(): GoalRuntime {
|
|
@@ -6894,6 +7134,7 @@ export class AgentSession {
|
|
|
6894
7134
|
...options,
|
|
6895
7135
|
prependMessages: eagerTodoPrelude ? [eagerTodoPrelude.message] : undefined,
|
|
6896
7136
|
admissionLease: admission,
|
|
7137
|
+
resetRetryReplaySafety: true,
|
|
6897
7138
|
});
|
|
6898
7139
|
} finally {
|
|
6899
7140
|
// Clean up residual eager-todo directive if the prompt never consumed it
|
|
@@ -6916,6 +7157,11 @@ export class AgentSession {
|
|
|
6916
7157
|
const name = (details as { name?: unknown }).name;
|
|
6917
7158
|
if (typeof name !== "string" || !name.trim()) return;
|
|
6918
7159
|
const skill = name.trim();
|
|
7160
|
+
// Functional tool availability must not depend on the best-effort
|
|
7161
|
+
// observational state-sync below (whose failures are swallowed by
|
|
7162
|
+
// #syncSkillPromptActiveStateSafely): attach ask first so canonical
|
|
7163
|
+
// workflow skills can always call it.
|
|
7164
|
+
if (active && isCanonicalGjcWorkflowSkill(skill)) this.#attachAskTool();
|
|
6919
7165
|
const sessionId = this.sessionManager.getSessionId();
|
|
6920
7166
|
// Canonical GJC workflow skills (deep-interview, ralplan, ultragoal, team)
|
|
6921
7167
|
// own their `.gjc/state/skill-active-state.json` row through the
|
|
@@ -7021,7 +7267,11 @@ export class AgentSession {
|
|
|
7021
7267
|
|
|
7022
7268
|
await this.#syncSkillPromptActiveStateSafely(customMessage, true);
|
|
7023
7269
|
try {
|
|
7024
|
-
await this.#promptWithMessage(customMessage, textContent, {
|
|
7270
|
+
await this.#promptWithMessage(customMessage, textContent, {
|
|
7271
|
+
...options,
|
|
7272
|
+
admissionLease: admission,
|
|
7273
|
+
resetRetryReplaySafety: true,
|
|
7274
|
+
});
|
|
7025
7275
|
} finally {
|
|
7026
7276
|
await this.#syncSkillPromptActiveStateSafely(customMessage, false);
|
|
7027
7277
|
}
|
|
@@ -7037,6 +7287,7 @@ export class AgentSession {
|
|
|
7037
7287
|
predecessorAgentEndHold?: symbol;
|
|
7038
7288
|
admissionLease?: SessionAdmissionLease;
|
|
7039
7289
|
onRunAccepted?: () => void;
|
|
7290
|
+
resetRetryReplaySafety?: boolean;
|
|
7040
7291
|
},
|
|
7041
7292
|
): Promise<void> {
|
|
7042
7293
|
await this.#agentEndPublicationPromise;
|
|
@@ -7049,6 +7300,7 @@ export class AgentSession {
|
|
|
7049
7300
|
let hindsightRecall: string | undefined;
|
|
7050
7301
|
try {
|
|
7051
7302
|
this.#throwIfPromptPreflightCancelled(generation, preflightSignal);
|
|
7303
|
+
if (options?.resetRetryReplaySafety) this.#resetRetryReplaySafety();
|
|
7052
7304
|
if (message.role === "user") {
|
|
7053
7305
|
await this.#resetDefaultFallbackForNewTurn();
|
|
7054
7306
|
await this.#ensureDefaultFallbackResolution();
|
|
@@ -7188,6 +7440,8 @@ export class AgentSession {
|
|
|
7188
7440
|
|
|
7189
7441
|
// Emit before_agent_start extension event. Race hook completion with prompt
|
|
7190
7442
|
// cancellation so a wedged hook cannot retain SDK prompt authority.
|
|
7443
|
+
if (this.#extensionRunner?.hasHandlers("before_agent_start")) this.#markRetryReplayUnsafe();
|
|
7444
|
+
|
|
7191
7445
|
if (this.#extensionRunner) {
|
|
7192
7446
|
const result = await this.#awaitPromptPreflight(
|
|
7193
7447
|
generation,
|
|
@@ -7458,6 +7712,8 @@ export class AgentSession {
|
|
|
7458
7712
|
} as unknown as HookCommandContext;
|
|
7459
7713
|
|
|
7460
7714
|
try {
|
|
7715
|
+
this.#markRetryReplayUnsafe();
|
|
7716
|
+
|
|
7461
7717
|
const args = parseCommandArgs(argsString);
|
|
7462
7718
|
const result = await loaded.command.execute(args, ctx);
|
|
7463
7719
|
// If result is a string, it's a prompt to send to LLM
|
|
@@ -8188,6 +8444,7 @@ export class AgentSession {
|
|
|
8188
8444
|
: undefined;
|
|
8189
8445
|
this.#abortActiveMidRunBarriers();
|
|
8190
8446
|
this.#silentAbortPending = options?.silent === true;
|
|
8447
|
+
this.#markRetryReplayUnsafe();
|
|
8191
8448
|
this.abortRetry();
|
|
8192
8449
|
this.#promptGeneration++;
|
|
8193
8450
|
this.#promptPreflightAbortController.abort();
|
|
@@ -8426,6 +8683,7 @@ export class AgentSession {
|
|
|
8426
8683
|
try {
|
|
8427
8684
|
await this.#promptWithMessage(message, messageText, {
|
|
8428
8685
|
admissionLease: admission,
|
|
8686
|
+
resetRetryReplaySafety: true,
|
|
8429
8687
|
onRunAccepted: () => {
|
|
8430
8688
|
runAccepted = true;
|
|
8431
8689
|
if (selected) {
|
|
@@ -8481,10 +8739,17 @@ export class AgentSession {
|
|
|
8481
8739
|
async #runNewSessionTransition(options?: NewSessionOptions): Promise<boolean> {
|
|
8482
8740
|
const previousSessionFile = this.sessionFile;
|
|
8483
8741
|
const previousWorkflowGateSessionId = this.sessionId;
|
|
8742
|
+
const selectionOnlyDiscoveredBuiltinToolNames = new Set(
|
|
8743
|
+
this.#getSelectedDiscoveredBuiltinToolNames().filter(
|
|
8744
|
+
name => !this.#baselineDiscoveredBuiltinToolNames.has(name),
|
|
8745
|
+
),
|
|
8746
|
+
);
|
|
8484
8747
|
const nextDiscoverySessionToolNames = this.#mcpDiscoveryEnabled
|
|
8485
8748
|
? [
|
|
8486
|
-
...this.#getActiveNonMCPToolNames()
|
|
8487
|
-
|
|
8749
|
+
...this.#getActiveNonMCPToolNames().filter(
|
|
8750
|
+
name => name !== "ask" && !selectionOnlyDiscoveredBuiltinToolNames.has(name),
|
|
8751
|
+
),
|
|
8752
|
+
...this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
8488
8753
|
]
|
|
8489
8754
|
: undefined;
|
|
8490
8755
|
|
|
@@ -8626,6 +8891,7 @@ export class AgentSession {
|
|
|
8626
8891
|
nextDiscoverySessionToolNames: string[] | undefined,
|
|
8627
8892
|
previousSessionFile: string | undefined,
|
|
8628
8893
|
): Promise<void> {
|
|
8894
|
+
this.#clearConstructorToolSelectionAuthority();
|
|
8629
8895
|
const inheritedThinkingLevel = resolveThinkingLevelForModel(this.model, this.#getInheritedThinkingLevel());
|
|
8630
8896
|
this.#thinkingLevel = inheritedThinkingLevel;
|
|
8631
8897
|
this.agent.setThinkingLevel(toReasoningEffort(inheritedThinkingLevel));
|
|
@@ -8635,15 +8901,11 @@ export class AgentSession {
|
|
|
8635
8901
|
}
|
|
8636
8902
|
this.sessionManager.appendServiceTierChange(this.serviceTier ?? null);
|
|
8637
8903
|
if (nextDiscoverySessionToolNames) {
|
|
8638
|
-
await this.#applyActiveToolsByName(nextDiscoverySessionToolNames, {
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
}
|
|
8904
|
+
await this.#applyActiveToolsByName(nextDiscoverySessionToolNames, {
|
|
8905
|
+
persistMCPSelection: false,
|
|
8906
|
+
nextSelectedDiscoveredBuiltinToolNames: [],
|
|
8907
|
+
});
|
|
8642
8908
|
}
|
|
8643
|
-
this.#rememberSessionDefaultSelectedMCPToolNames(
|
|
8644
|
-
this.sessionFile,
|
|
8645
|
-
this.#getConfiguredDefaultSelectedMCPToolNames(),
|
|
8646
|
-
);
|
|
8647
8909
|
this.#todoReminderCount = 0;
|
|
8648
8910
|
this.#planReferenceSent = false;
|
|
8649
8911
|
this.#planReferencePath = "local://PLAN.md";
|
|
@@ -8731,25 +8993,6 @@ export class AgentSession {
|
|
|
8731
8993
|
return false;
|
|
8732
8994
|
}
|
|
8733
8995
|
|
|
8734
|
-
// Copy artifacts directory if it exists
|
|
8735
|
-
const oldArtifactDir = forkResult.oldSessionFile.slice(0, -6);
|
|
8736
|
-
const newArtifactDir = forkResult.newSessionFile.slice(0, -6);
|
|
8737
|
-
|
|
8738
|
-
try {
|
|
8739
|
-
const oldDirStat = await fs.promises.stat(oldArtifactDir);
|
|
8740
|
-
if (oldDirStat.isDirectory()) {
|
|
8741
|
-
await fs.promises.cp(oldArtifactDir, newArtifactDir, { recursive: true });
|
|
8742
|
-
}
|
|
8743
|
-
} catch (err) {
|
|
8744
|
-
if (!isEnoent(err)) {
|
|
8745
|
-
logger.warn("Failed to copy artifacts during fork", {
|
|
8746
|
-
oldArtifactDir,
|
|
8747
|
-
newArtifactDir,
|
|
8748
|
-
error: err instanceof Error ? err.message : String(err),
|
|
8749
|
-
});
|
|
8750
|
-
}
|
|
8751
|
-
}
|
|
8752
|
-
|
|
8753
8996
|
// Update agent session ID
|
|
8754
8997
|
this.#syncAgentSessionId();
|
|
8755
8998
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
@@ -10092,27 +10335,13 @@ export class AgentSession {
|
|
|
10092
10335
|
await this.sessionManager.ensureOnDisk();
|
|
10093
10336
|
let savedPath: string | undefined;
|
|
10094
10337
|
if (options?.autoTriggered && this.settings.get("compaction.handoffSaveToDisk")) {
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
audit: { category: "artifact", verb: "write", owner: "gjc-runtime" },
|
|
10103
|
-
});
|
|
10104
|
-
} else {
|
|
10105
|
-
await Bun.write(handoffFilePath, `${handoffText}\n`);
|
|
10106
|
-
}
|
|
10107
|
-
savedPath = handoffFilePath;
|
|
10108
|
-
} catch (error) {
|
|
10109
|
-
logger.warn("Failed to save handoff document to disk", {
|
|
10110
|
-
path: handoffFilePath,
|
|
10111
|
-
error: error instanceof Error ? error.message : String(error),
|
|
10112
|
-
});
|
|
10113
|
-
}
|
|
10114
|
-
} else {
|
|
10115
|
-
logger.debug("Skipping handoff document save because session is not persisted");
|
|
10338
|
+
try {
|
|
10339
|
+
const artifactId = await this.sessionManager.saveArtifact(`${handoffText}\n`, "handoff");
|
|
10340
|
+
savedPath = `artifact://${artifactId}`;
|
|
10341
|
+
} catch (error) {
|
|
10342
|
+
logger.warn("Failed to save handoff document", {
|
|
10343
|
+
error: error instanceof Error ? error.message : String(error),
|
|
10344
|
+
});
|
|
10116
10345
|
}
|
|
10117
10346
|
}
|
|
10118
10347
|
|
|
@@ -10638,6 +10867,8 @@ export class AgentSession {
|
|
|
10638
10867
|
return;
|
|
10639
10868
|
}
|
|
10640
10869
|
|
|
10870
|
+
this.#attachAskTool();
|
|
10871
|
+
|
|
10641
10872
|
const reminder = prompt.render(planModeToolDecisionReminderPrompt, {
|
|
10642
10873
|
askToolName: "ask",
|
|
10643
10874
|
});
|
|
@@ -12489,23 +12720,33 @@ export class AgentSession {
|
|
|
12489
12720
|
): Promise<boolean | ManagedAttemptDecision> {
|
|
12490
12721
|
const controller = this.#defaultFallbackChain();
|
|
12491
12722
|
const managedFallback = controller.chain.entries.length > 1;
|
|
12492
|
-
const trigger = this.#fallbackTriggerFor(message, !managedFallback, transportFailure);
|
|
12493
|
-
if (!trigger) {
|
|
12494
|
-
return managedOutcome
|
|
12495
|
-
? this.#managedFallbackExhaustionDecision(message, message.errorMessage || "Model fallback attempt failed")
|
|
12496
|
-
: false;
|
|
12497
|
-
}
|
|
12498
12723
|
const retrySettings = this.settings.getGroup("retry");
|
|
12499
12724
|
const legacyRetryConfigured =
|
|
12500
12725
|
this.settings.has("retry.enabled") ||
|
|
12501
12726
|
this.settings.has("retry.maxRetries") ||
|
|
12502
12727
|
this.settings.has("retry.baseDelayMs") ||
|
|
12503
12728
|
this.settings.has("retry.maxDelayMs");
|
|
12504
|
-
//
|
|
12505
|
-
//
|
|
12506
|
-
|
|
12507
|
-
if (!managedFallback && (!retrySettings.enabled || !legacyRetryConfigured)) return false;
|
|
12729
|
+
// retry.enabled=false always surfaces immediately, matching the explicit
|
|
12730
|
+
// user opt-out.
|
|
12731
|
+
if (!managedFallback && !retrySettings.enabled) return false;
|
|
12508
12732
|
const classification = managedFallback ? undefined : this.#classifyErrorForRetry(message);
|
|
12733
|
+
// Bare defaults admit only clean, side-effect-free canonical stream watchdog failures.
|
|
12734
|
+
if (!managedFallback && !legacyRetryConfigured) {
|
|
12735
|
+
if (
|
|
12736
|
+
hasBareDefaultRetryDisqualifyingFacts(message) ||
|
|
12737
|
+
(classification !== "transient" && classification !== "first_event_timeout") ||
|
|
12738
|
+
!BARE_DEFAULT_WATCHDOG_ERROR.test(message.errorMessage ?? "") ||
|
|
12739
|
+
!this.#hasCleanRetryReplaySafety
|
|
12740
|
+
) {
|
|
12741
|
+
return false;
|
|
12742
|
+
}
|
|
12743
|
+
}
|
|
12744
|
+
const trigger = this.#fallbackTriggerFor(message, !managedFallback, transportFailure);
|
|
12745
|
+
if (!trigger) {
|
|
12746
|
+
return managedOutcome
|
|
12747
|
+
? this.#managedFallbackExhaustionDecision(message, message.errorMessage || "Model fallback attempt failed")
|
|
12748
|
+
: false;
|
|
12749
|
+
}
|
|
12509
12750
|
const legacyUnbounded = classification === "transient";
|
|
12510
12751
|
const attemptsUsed = managedFallback ? controller.attemptsUsed || 1 : this.#retryAttempt + 1;
|
|
12511
12752
|
const failedSelector = managedFallback ? controller.currentSelector() : undefined;
|
|
@@ -12678,6 +12919,7 @@ export class AgentSession {
|
|
|
12678
12919
|
this.#scheduleAgentContinue({
|
|
12679
12920
|
delayMs: 1,
|
|
12680
12921
|
generation,
|
|
12922
|
+
allowDuringCancelAndSubmit: true,
|
|
12681
12923
|
onError: () => this.#failRetryRecovery("Retry continuation failed to start"),
|
|
12682
12924
|
onSkip: () => this.#failRetryRecovery("Retry continuation was superseded"),
|
|
12683
12925
|
});
|
|
@@ -12860,6 +13102,8 @@ export class AgentSession {
|
|
|
12860
13102
|
options?: { excludeFromContext?: boolean },
|
|
12861
13103
|
): Promise<BashResult> {
|
|
12862
13104
|
const excludeFromContext = options?.excludeFromContext === true;
|
|
13105
|
+
this.#markRetryReplayUnsafe();
|
|
13106
|
+
|
|
12863
13107
|
const cwd = this.sessionManager.getCwd();
|
|
12864
13108
|
|
|
12865
13109
|
if (this.#extensionRunner?.hasHandlers("user_bash")) {
|
|
@@ -12889,7 +13133,7 @@ export class AgentSession {
|
|
|
12889
13133
|
cwd,
|
|
12890
13134
|
timeout: clampTimeout("bash") * 1000,
|
|
12891
13135
|
env: buildGjcRuntimeSessionEnv({
|
|
12892
|
-
sessionFile:
|
|
13136
|
+
sessionFile: null,
|
|
12893
13137
|
sessionId: this.sessionId,
|
|
12894
13138
|
cwd,
|
|
12895
13139
|
}),
|
|
@@ -12991,6 +13235,7 @@ export class AgentSession {
|
|
|
12991
13235
|
options?: { excludeFromContext?: boolean },
|
|
12992
13236
|
): Promise<PythonResult> {
|
|
12993
13237
|
const excludeFromContext = options?.excludeFromContext === true;
|
|
13238
|
+
this.#markRetryReplayUnsafe();
|
|
12994
13239
|
const cwd = this.sessionManager.getCwd();
|
|
12995
13240
|
this.assertEvalExecutionAllowed();
|
|
12996
13241
|
|
|
@@ -13755,9 +14000,6 @@ export class AgentSession {
|
|
|
13755
14000
|
const previousTools = [...this.agent.state.tools];
|
|
13756
14001
|
const previousBaseSystemPrompt = this.#baseSystemPrompt;
|
|
13757
14002
|
const previousSystemPrompt = this.agent.state.systemPrompt;
|
|
13758
|
-
const previousFallbackSelectedMCPToolNames = previousSessionFile
|
|
13759
|
-
? this.#getSessionDefaultSelectedMCPToolNames(previousSessionFile)
|
|
13760
|
-
: undefined;
|
|
13761
14003
|
const previousAgentSteeringQueue = this.agent.snapshotSteering();
|
|
13762
14004
|
const previousAgentFollowUpQueue = this.agent.snapshotFollowUp();
|
|
13763
14005
|
|
|
@@ -13779,8 +14021,7 @@ export class AgentSession {
|
|
|
13779
14021
|
const didReloadConversationChange =
|
|
13780
14022
|
!switchingToDifferentSession &&
|
|
13781
14023
|
this.#didSessionMessagesChange(previousSessionContext.messages, sessionContext.messages);
|
|
13782
|
-
|
|
13783
|
-
await this.#restoreMCPSelectionsForSessionContext(sessionContext, { fallbackSelectedMCPToolNames });
|
|
14024
|
+
await this.#restoreMCPSelectionsForSessionContext(sessionContext);
|
|
13784
14025
|
|
|
13785
14026
|
// The target session is loaded and MCP selections are restored: discard
|
|
13786
14027
|
// pre-switch delivery queues before completing the restored agent state.
|
|
@@ -13852,6 +14093,11 @@ export class AgentSession {
|
|
|
13852
14093
|
}
|
|
13853
14094
|
|
|
13854
14095
|
this.#reconnectToAgent();
|
|
14096
|
+
// Fence predecessor continuations before session_switch starts SDK runtime
|
|
14097
|
+
// teardown. The previous runtime waits for those continuations to settle;
|
|
14098
|
+
// waiting to transfer authority until after hooks creates a circular wait.
|
|
14099
|
+
if (suspendedWorkflowGateEmitter)
|
|
14100
|
+
this.#bindWorkflowGateEmitter(previousSessionState.sessionId, suspendedWorkflowGateEmitter);
|
|
13855
14101
|
// session_switch is the post-commit identity signal. SDK authority and
|
|
13856
14102
|
// other identity-bound integrations must not observe the successor until
|
|
13857
14103
|
// messages, model state, MCP selections, and the agent subscription are live.
|
|
@@ -13862,8 +14108,6 @@ export class AgentSession {
|
|
|
13862
14108
|
previousSessionFile,
|
|
13863
14109
|
});
|
|
13864
14110
|
}
|
|
13865
|
-
if (suspendedWorkflowGateEmitter)
|
|
13866
|
-
this.#bindWorkflowGateEmitter(previousSessionState.sessionId, suspendedWorkflowGateEmitter);
|
|
13867
14111
|
return true;
|
|
13868
14112
|
} catch (error) {
|
|
13869
14113
|
this.sessionManager.restoreState(previousSessionState);
|
|
@@ -13873,9 +14117,7 @@ export class AgentSession {
|
|
|
13873
14117
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
13874
14118
|
let restoreMcpError: unknown;
|
|
13875
14119
|
try {
|
|
13876
|
-
await this.#restoreMCPSelectionsForSessionContext(previousSessionContext
|
|
13877
|
-
fallbackSelectedMCPToolNames: previousFallbackSelectedMCPToolNames,
|
|
13878
|
-
});
|
|
14120
|
+
await this.#restoreMCPSelectionsForSessionContext(previousSessionContext);
|
|
13879
14121
|
} catch (mcpError) {
|
|
13880
14122
|
restoreMcpError = mcpError;
|
|
13881
14123
|
logger.warn("Failed to restore MCP selections after switch error", {
|