@gajae-code/coding-agent 0.11.9 → 0.11.11
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 +56 -1
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +57 -2
- package/dist/types/config/settings.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +60 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +14 -9
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +16 -8
- package/dist/types/modes/interactive-mode.d.ts +5 -1
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +11 -2
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +14 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +97 -8
- package/dist/types/sdk/host/control/operations.d.ts +1 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/session.d.ts +7 -0
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +11 -11
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +28 -1
- package/dist/types/session/session-manager.d.ts +34 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/provider-retry-status.d.ts +13 -0
- package/dist/types/task/types.d.ts +39 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/resource-gc.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +1 -0
- package/src/cli.ts +48 -2
- package/src/commands/sdk.ts +91 -7
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +31 -7
- package/src/config/model-registry.ts +4 -2
- package/src/config/settings-schema.ts +61 -2
- package/src/config/settings.ts +8 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/tool-bridge.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +14 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +16 -2
- package/src/gjc-runtime/deep-interview-draft.ts +2 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +16 -1
- package/src/gjc-runtime/deep-interview-state.ts +25 -3
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/ralplan-runtime.ts +252 -1
- package/src/gjc-runtime/state-runtime.ts +48 -11
- package/src/gjc-runtime/state-writer.ts +1 -1
- package/src/hooks/skill-state.ts +23 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/modes/acp/acp-agent.ts +554 -60
- package/src/modes/acp/acp-event-mapper.ts +93 -6
- package/src/modes/components/model-selector.ts +15 -1
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +44 -3
- package/src/modes/controllers/selector-controller.ts +38 -8
- package/src/modes/interactive-mode.ts +65 -26
- package/src/modes/runtime-init.ts +1 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/architect.md +8 -1
- package/src/prompts/agents/critic.md +8 -1
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/identity.ts +22 -12
- package/src/sdk/broker/lifecycle.ts +82 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/index.ts +222 -27
- package/src/sdk/bus/notification-service.ts +19 -5
- package/src/sdk/bus/operator-runtime.ts +31 -5
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/telegram-cli.ts +6 -13
- package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +15 -7
- package/src/sdk/bus/telegram-daemon-control.ts +14 -5
- package/src/sdk/bus/telegram-daemon.ts +1191 -164
- package/src/sdk/host/control/dispatch.ts +2 -0
- package/src/sdk/host/control/operations.ts +1 -0
- package/src/sdk/host/host.ts +2 -2
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/protocol/operation-inventory.generated.json +58 -0
- package/src/sdk/protocol/operation-registry.ts +1 -0
- package/src/sdk/session.ts +24 -1
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +290 -132
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +173 -127
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/session-manager.ts +608 -25
- package/src/skill-state/workflow-mutation-guard.ts +41 -7
- package/src/task/executor.ts +152 -42
- package/src/task/index.ts +2 -0
- package/src/task/provider-retry-status.ts +88 -0
- package/src/task/render.ts +45 -26
- package/src/task/types.ts +25 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/bash-allowed-prefixes.ts +45 -1
- package/src/tools/index.ts +3 -1
- package/src/tools/resource-gc.ts +496 -120
- package/src/tools/subagent-render.ts +74 -16
- package/src/tools/subagent.ts +2 -0
- package/src/workflow/workflow-intent-diff.ts +4 -1
- package/vendor/insane-search/MANIFEST.json +4 -1
- package/vendor/insane-search/engine/bias_check.py +5 -0
- package/vendor/insane-search/engine/fetch_chain.py +67 -1
- package/vendor/insane-search/engine/transport.py +13 -2
- package/vendor/insane-search/engine/validators.py +4 -1
|
@@ -172,6 +172,7 @@ import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../a
|
|
|
172
172
|
import { reset as resetCapabilities } from "../capability";
|
|
173
173
|
import type { Rule } from "../capability/rule";
|
|
174
174
|
import type { CasReceipt } from "../config/atomic-yaml-patch";
|
|
175
|
+
import { activateModelProfile } from "../config/model-profile-activation";
|
|
175
176
|
import {
|
|
176
177
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
177
178
|
isAuthenticated,
|
|
@@ -233,7 +234,7 @@ import type {
|
|
|
233
234
|
TurnEndEvent,
|
|
234
235
|
TurnStartEvent,
|
|
235
236
|
} from "../extensibility/extensions";
|
|
236
|
-
import type { CompactOptions, ContextUsage } from "../extensibility/extensions/types";
|
|
237
|
+
import type { CompactOptions, ContextUsage, ExtensionTranscriptEntry } from "../extensibility/extensions/types";
|
|
237
238
|
import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
|
|
238
239
|
import {
|
|
239
240
|
type LoadedSubskillActivation,
|
|
@@ -243,7 +244,13 @@ import { resolveCurrentPhaseForParent } from "../extensibility/gjc-plugins/injec
|
|
|
243
244
|
import { readActiveSubskillsForParent, toActiveSubskillEntry } from "../extensibility/gjc-plugins/state";
|
|
244
245
|
import { loadActiveSubskillTools } from "../extensibility/gjc-plugins/tools";
|
|
245
246
|
import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
246
|
-
import {
|
|
247
|
+
import {
|
|
248
|
+
buildSkillPromptMessage,
|
|
249
|
+
getSkillSlashCommandName,
|
|
250
|
+
parseSkillInvocations,
|
|
251
|
+
type Skill,
|
|
252
|
+
type SkillWarning,
|
|
253
|
+
} from "../extensibility/skills";
|
|
247
254
|
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
248
255
|
import { assertDeepInterviewIntentManifest } from "../gjc-runtime/deep-interview-state";
|
|
249
256
|
import { buildGjcRuntimeSessionEnv, consumePendingGoalModeRequest } from "../gjc-runtime/goal-mode-request";
|
|
@@ -341,6 +348,7 @@ import {
|
|
|
341
348
|
import {
|
|
342
349
|
type ConfiguredFallbackChain,
|
|
343
350
|
cappedExponentialWithFullJitter,
|
|
351
|
+
compactionRetryDelay,
|
|
344
352
|
effectiveFallbackDelay,
|
|
345
353
|
FallbackChainController,
|
|
346
354
|
} from "./fallback-chain-controller";
|
|
@@ -379,6 +387,7 @@ import type {
|
|
|
379
387
|
CompactionEntry,
|
|
380
388
|
DefaultModelSelectionStage,
|
|
381
389
|
NewSessionOptions,
|
|
390
|
+
PreparedNewSession,
|
|
382
391
|
RecoveryHydrationContext,
|
|
383
392
|
RecoveryHydrationPromotionFence,
|
|
384
393
|
SessionContext,
|
|
@@ -798,8 +807,14 @@ const KIMI_CODE_FIRST_EVENT_TIMEOUT_MESSAGES = {
|
|
|
798
807
|
|
|
799
808
|
const ALIBABA_TOKEN_PLAN_PROVIDER = "alibaba-token-plan";
|
|
800
809
|
const ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MESSAGES = {
|
|
801
|
-
"openai-responses":
|
|
802
|
-
|
|
810
|
+
"openai-responses": new Set([
|
|
811
|
+
"Provider stream timed out while waiting for the first event",
|
|
812
|
+
"OpenAI responses stream timed out while waiting for the first event",
|
|
813
|
+
]),
|
|
814
|
+
"openai-completions": new Set([
|
|
815
|
+
"Provider stream timed out while waiting for the first event",
|
|
816
|
+
"OpenAI completions stream timed out while waiting for the first event",
|
|
817
|
+
]),
|
|
803
818
|
} as const;
|
|
804
819
|
|
|
805
820
|
function hasBareDefaultRetryDisqualifyingFacts(message: AssistantMessage): boolean {
|
|
@@ -1043,17 +1058,37 @@ function createHandoffContext(document: string): string {
|
|
|
1043
1058
|
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.`;
|
|
1044
1059
|
}
|
|
1045
1060
|
|
|
1061
|
+
async function discardPreparedNewSessionAfterFailure(
|
|
1062
|
+
sessionManager: SessionManager,
|
|
1063
|
+
prepared: PreparedNewSession,
|
|
1064
|
+
transitionError: unknown,
|
|
1065
|
+
): Promise<unknown> {
|
|
1066
|
+
try {
|
|
1067
|
+
await sessionManager.discardPreparedNewSession(prepared);
|
|
1068
|
+
} catch (cleanupError) {
|
|
1069
|
+
throw new AggregateError(
|
|
1070
|
+
[transitionError, cleanupError],
|
|
1071
|
+
"Session transition and staged successor cleanup both failed.",
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return transitionError;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1046
1077
|
// ============================================================================
|
|
1047
1078
|
// ACP Permission Gate
|
|
1048
1079
|
// ============================================================================
|
|
1049
1080
|
|
|
1050
1081
|
/** Tools that require user permission before execution when an ACP client is connected. */
|
|
1051
|
-
const PERMISSION_REQUIRED_TOOLS = new Set(["bash", "monitor", "edit", "delete", "move"]);
|
|
1082
|
+
const PERMISSION_REQUIRED_TOOLS = new Set(["bash", "monitor", "eval", "edit", "delete", "move"]);
|
|
1052
1083
|
|
|
1053
1084
|
function isShellExecutionPermissionTool(toolName: string): boolean {
|
|
1054
1085
|
return toolName === "bash" || toolName === "monitor";
|
|
1055
1086
|
}
|
|
1056
1087
|
|
|
1088
|
+
function isExecutionPermissionTool(toolName: string): boolean {
|
|
1089
|
+
return isShellExecutionPermissionTool(toolName) || toolName === "eval";
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1057
1092
|
/** Permission options presented to the client on each gated tool call. */
|
|
1058
1093
|
const PERMISSION_OPTIONS: ClientBridgePermissionOption[] = [
|
|
1059
1094
|
{ optionId: "allow_once", name: "Allow once", kind: "allow_once" },
|
|
@@ -1121,6 +1156,18 @@ function getPermissionIntent(
|
|
|
1121
1156
|
const cmd = getStringProperty(a, "command")?.slice(0, 80);
|
|
1122
1157
|
return { toolName, title: cmd || toolName, cacheKey: toolName };
|
|
1123
1158
|
}
|
|
1159
|
+
if (toolName === "eval") {
|
|
1160
|
+
const cells = Array.isArray(a.cells) ? a.cells : [];
|
|
1161
|
+
const firstCell = cells.find(cell => cell && typeof cell === "object" && !Array.isArray(cell));
|
|
1162
|
+
const cell = firstCell as Record<string, unknown> | undefined;
|
|
1163
|
+
const title = cell ? getStringProperty(cell, "title") : undefined;
|
|
1164
|
+
const language = cell ? getStringProperty(cell, "language") : undefined;
|
|
1165
|
+
return {
|
|
1166
|
+
toolName,
|
|
1167
|
+
title: title ?? (language ? `Eval ${language}` : "eval"),
|
|
1168
|
+
cacheKey: toolName,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1124
1171
|
if (toolName === "delete") {
|
|
1125
1172
|
const p = getStringProperty(a, "path");
|
|
1126
1173
|
return { toolName, title: p ? `Delete ${p}` : toolName, paths: p ? [p] : undefined, cacheKey: toolName };
|
|
@@ -3992,8 +4039,21 @@ export class AgentSession {
|
|
|
3992
4039
|
return;
|
|
3993
4040
|
}
|
|
3994
4041
|
const predecessorAgentEnd = this.#claimDeferredAgentEndForContinuation(predecessorAgentEndHold);
|
|
4042
|
+
const startsQueuedSuccessor =
|
|
4043
|
+
this.agent.state.messages.at(-1)?.role === "assistant" && this.agent.hasQueuedMessages();
|
|
3995
4044
|
try {
|
|
3996
|
-
await this.agent.continue(
|
|
4045
|
+
await this.agent.continue({
|
|
4046
|
+
...this.#managedFallbackPromptOptions(),
|
|
4047
|
+
// Reset only after continue() has claimed the queued turn. Skipped or stale
|
|
4048
|
+
// continuations retain predecessor accounting, and resetAttemptBudget keeps
|
|
4049
|
+
// the sticky fallback cursor unchanged.
|
|
4050
|
+
onRunAccepted: startsQueuedSuccessor
|
|
4051
|
+
? () => {
|
|
4052
|
+
this.#defaultFallbackChain().resetAttemptBudget();
|
|
4053
|
+
this.#overflowMaintenanceAttempts = 0;
|
|
4054
|
+
}
|
|
4055
|
+
: undefined,
|
|
4056
|
+
});
|
|
3997
4057
|
} catch (error) {
|
|
3998
4058
|
this.#restoreDeferredAgentEndAfterContinuationFailure(predecessorAgentEnd);
|
|
3999
4059
|
throw error;
|
|
@@ -4596,13 +4656,13 @@ export class AgentSession {
|
|
|
4596
4656
|
return resolveToCwd(normalized, this.sessionManager.getCwd());
|
|
4597
4657
|
}
|
|
4598
4658
|
|
|
4599
|
-
#localProtocolOptions(): LocalProtocolOptions {
|
|
4659
|
+
#localProtocolOptions(prepared?: PreparedNewSession): LocalProtocolOptions {
|
|
4600
4660
|
return {
|
|
4601
|
-
getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
|
|
4661
|
+
getArtifactsDir: () => prepared?.artifactsDir ?? this.sessionManager.getArtifactsDir(),
|
|
4602
4662
|
isManagedDestination: () => this.sessionManager.isManagedDestination(),
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
getSessionId: () => this.sessionManager.getSessionId(),
|
|
4663
|
+
getManagedLegacyLocalMigrationSource: () =>
|
|
4664
|
+
prepared?.managedLegacyLocalMigrationSource ?? this.sessionManager.getManagedLegacyLocalMigrationSource(),
|
|
4665
|
+
getSessionId: () => prepared?.sessionId ?? this.sessionManager.getSessionId(),
|
|
4606
4666
|
};
|
|
4607
4667
|
}
|
|
4608
4668
|
|
|
@@ -5502,6 +5562,12 @@ export class AgentSession {
|
|
|
5502
5562
|
return undefined;
|
|
5503
5563
|
}
|
|
5504
5564
|
|
|
5565
|
+
/** Get a registered tool with the same guards used for model-facing execution. */
|
|
5566
|
+
getToolForExecution(name: string): AgentTool | undefined {
|
|
5567
|
+
const tool = this.getToolByName(name);
|
|
5568
|
+
return tool ? this.#prepareToolForExecution(tool) : undefined;
|
|
5569
|
+
}
|
|
5570
|
+
|
|
5505
5571
|
/**
|
|
5506
5572
|
* Register a UI/control-plane request handler for a currently foregrounded
|
|
5507
5573
|
* managed bash execution. This is intentionally narrower than generic
|
|
@@ -5766,6 +5832,7 @@ export class AgentSession {
|
|
|
5766
5832
|
return await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
|
|
5767
5833
|
}
|
|
5768
5834
|
const isShellExecutionTool = isShellExecutionPermissionTool(target.name);
|
|
5835
|
+
const isExecutionTool = isExecutionPermissionTool(target.name);
|
|
5769
5836
|
const command =
|
|
5770
5837
|
isShellExecutionTool && args && typeof args === "object" && !Array.isArray(args)
|
|
5771
5838
|
? getStringProperty(args as Record<string, unknown>, "command")
|
|
@@ -5808,7 +5875,7 @@ export class AgentSession {
|
|
|
5808
5875
|
toolCallId,
|
|
5809
5876
|
toolName: target.name,
|
|
5810
5877
|
title: permissionIntent.title,
|
|
5811
|
-
...(
|
|
5878
|
+
...(isExecutionTool ? { kind: "execute" } : {}),
|
|
5812
5879
|
status: "pending",
|
|
5813
5880
|
rawInput: args,
|
|
5814
5881
|
...(commandContent ? { content: commandContent } : {}),
|
|
@@ -6456,6 +6523,15 @@ export class AgentSession {
|
|
|
6456
6523
|
return { ...context, messages: this.#withoutEphemeralCustomMessages(context.messages) };
|
|
6457
6524
|
}
|
|
6458
6525
|
|
|
6526
|
+
/** Build display context from an unpublished successor without changing active session state. @internal */
|
|
6527
|
+
buildPreparedDisplaySessionContext(prepared: PreparedNewSession): SessionContext {
|
|
6528
|
+
const context = deobfuscateSessionContext(
|
|
6529
|
+
this.sessionManager.buildPreparedNewSessionContext(prepared),
|
|
6530
|
+
this.#obfuscator,
|
|
6531
|
+
);
|
|
6532
|
+
return { ...context, messages: this.#withoutEphemeralCustomMessages(context.messages) };
|
|
6533
|
+
}
|
|
6534
|
+
|
|
6459
6535
|
/** Convert session messages using the same pre-LLM pipeline as the active session. */
|
|
6460
6536
|
async convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]> {
|
|
6461
6537
|
const transformedMessages = await this.#transformContext(messages, signal);
|
|
@@ -6563,9 +6639,11 @@ export class AgentSession {
|
|
|
6563
6639
|
/** Live SDK configuration values exposed through the session query surface. */
|
|
6564
6640
|
getSdkConfigItems(): Record<string, string> {
|
|
6565
6641
|
const model = this.model;
|
|
6642
|
+
const modelPreset = this.getActiveModelProfile() ?? this.settings.get("modelProfile.default");
|
|
6566
6643
|
return {
|
|
6567
6644
|
mode: this.#planModeState?.enabled ? "plan" : "default",
|
|
6568
6645
|
...(model ? { model: `${model.provider}/${model.id}` } : {}),
|
|
6646
|
+
...(modelPreset ? { modelPreset } : {}),
|
|
6569
6647
|
thinking: this.#thinkingLevel ?? "off",
|
|
6570
6648
|
steeringMode: this.steeringMode,
|
|
6571
6649
|
followUpMode: this.followUpMode,
|
|
@@ -6584,6 +6662,7 @@ export class AgentSession {
|
|
|
6584
6662
|
async invokeSkill(
|
|
6585
6663
|
name: string,
|
|
6586
6664
|
args = "",
|
|
6665
|
+
options?: Pick<PromptOptions, "onPreflightAccepted">,
|
|
6587
6666
|
): Promise<{ name: string; path: string; args?: string; lineCount?: number }> {
|
|
6588
6667
|
const skillName = name.trim();
|
|
6589
6668
|
if (!skillName) throw Object.assign(new Error("skill.invoke requires a skill name."), { code: "invalid_input" });
|
|
@@ -6616,7 +6695,7 @@ export class AgentSession {
|
|
|
6616
6695
|
attribution: "user" as const,
|
|
6617
6696
|
};
|
|
6618
6697
|
this.#deepInterviewPreclaimedCustomInputEpochs.set(skillPromptMessage, deepInterviewUserIntentEpoch);
|
|
6619
|
-
await this.promptCustomMessage(skillPromptMessage);
|
|
6698
|
+
await this.promptCustomMessage(skillPromptMessage, options);
|
|
6620
6699
|
return {
|
|
6621
6700
|
name: skill.name,
|
|
6622
6701
|
path: skill.filePath,
|
|
@@ -6662,22 +6741,49 @@ export class AgentSession {
|
|
|
6662
6741
|
}
|
|
6663
6742
|
}
|
|
6664
6743
|
|
|
6665
|
-
getTranscript():
|
|
6744
|
+
getTranscript(): ExtensionTranscriptEntry[] {
|
|
6666
6745
|
return getEntriesForInternalRead(this.sessionManager).flatMap(entry => {
|
|
6667
6746
|
if (entry.type !== "message") return [];
|
|
6668
|
-
const message = entry.message as unknown as {
|
|
6747
|
+
const message = entry.message as unknown as {
|
|
6748
|
+
role?: unknown;
|
|
6749
|
+
content?: unknown;
|
|
6750
|
+
toolCallId?: unknown;
|
|
6751
|
+
toolName?: unknown;
|
|
6752
|
+
isError?: unknown;
|
|
6753
|
+
};
|
|
6754
|
+
const content: NonNullable<ExtensionTranscriptEntry["content"]> | undefined = Array.isArray(message.content)
|
|
6755
|
+
? message.content.reduce<NonNullable<ExtensionTranscriptEntry["content"]>>((blocks, part) => {
|
|
6756
|
+
if (typeof part !== "object" || part === null || !("type" in part)) return blocks;
|
|
6757
|
+
if (part.type === "text" && "text" in part && typeof part.text === "string") {
|
|
6758
|
+
blocks.push({ type: "text", text: part.text });
|
|
6759
|
+
return blocks;
|
|
6760
|
+
}
|
|
6761
|
+
if (part.type === "thinking" && "thinking" in part && typeof part.thinking === "string") {
|
|
6762
|
+
blocks.push({ type: "thinking", thinking: part.thinking });
|
|
6763
|
+
return blocks;
|
|
6764
|
+
}
|
|
6765
|
+
if (
|
|
6766
|
+
part.type === "toolCall" &&
|
|
6767
|
+
"id" in part &&
|
|
6768
|
+
typeof part.id === "string" &&
|
|
6769
|
+
"name" in part &&
|
|
6770
|
+
typeof part.name === "string"
|
|
6771
|
+
) {
|
|
6772
|
+
blocks.push({
|
|
6773
|
+
type: "toolCall",
|
|
6774
|
+
id: part.id,
|
|
6775
|
+
name: part.name,
|
|
6776
|
+
arguments: "arguments" in part ? part.arguments : {},
|
|
6777
|
+
});
|
|
6778
|
+
}
|
|
6779
|
+
return blocks;
|
|
6780
|
+
}, [])
|
|
6781
|
+
: undefined;
|
|
6669
6782
|
const body =
|
|
6670
6783
|
typeof message.content === "string"
|
|
6671
6784
|
? message.content
|
|
6672
|
-
:
|
|
6673
|
-
?
|
|
6674
|
-
.map(part =>
|
|
6675
|
-
typeof part === "object" && part !== null && "text" in part && typeof part.text === "string"
|
|
6676
|
-
? part.text
|
|
6677
|
-
: "",
|
|
6678
|
-
)
|
|
6679
|
-
.filter(Boolean)
|
|
6680
|
-
.join("\n")
|
|
6785
|
+
: content
|
|
6786
|
+
? content.flatMap(part => (part.type === "text" ? [part.text] : [])).join("\n")
|
|
6681
6787
|
: "";
|
|
6682
6788
|
return [
|
|
6683
6789
|
{
|
|
@@ -6686,6 +6792,10 @@ export class AgentSession {
|
|
|
6686
6792
|
textSummary: body.slice(0, 500),
|
|
6687
6793
|
ts: entry.timestamp,
|
|
6688
6794
|
body,
|
|
6795
|
+
...(content ? { content } : {}),
|
|
6796
|
+
...(typeof message.toolCallId === "string" ? { toolCallId: message.toolCallId } : {}),
|
|
6797
|
+
...(typeof message.toolName === "string" ? { toolName: message.toolName } : {}),
|
|
6798
|
+
...(typeof message.isError === "boolean" ? { isError: message.isError } : {}),
|
|
6689
6799
|
},
|
|
6690
6800
|
];
|
|
6691
6801
|
});
|
|
@@ -6723,8 +6833,7 @@ export class AgentSession {
|
|
|
6723
6833
|
return getAskAnswerSourceFromRegistry(this.sessionId);
|
|
6724
6834
|
}
|
|
6725
6835
|
|
|
6726
|
-
#constructWorkflowGateEmitter(): WorkflowGateEmitter {
|
|
6727
|
-
const sessionId = this.sessionManager.getSessionId();
|
|
6836
|
+
#constructWorkflowGateEmitter(sessionId = this.sessionManager.getSessionId()): WorkflowGateEmitter {
|
|
6728
6837
|
assertNonEmptyGjcSessionId(sessionId, "AgentSession workflow-gate session");
|
|
6729
6838
|
const gateStore = this.sessionManager.isPersisted()
|
|
6730
6839
|
? new FileGateStore(path.join(sessionStateDir(this.sessionManager.getCwd(), sessionId), "workflow-gates.json"))
|
|
@@ -7243,6 +7352,22 @@ export class AgentSession {
|
|
|
7243
7352
|
async prompt(text: string, options?: PromptOptions): Promise<void> {
|
|
7244
7353
|
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
7245
7354
|
|
|
7355
|
+
if (expandPromptTemplates && text.startsWith("/skill:") && !options?.images?.length) {
|
|
7356
|
+
const skillCommands = new Map(this.skills.map(skill => [getSkillSlashCommandName(skill), skill]));
|
|
7357
|
+
const invocations = parseSkillInvocations(text, skillCommands);
|
|
7358
|
+
if (invocations.length === 1) {
|
|
7359
|
+
const invocation = invocations[0];
|
|
7360
|
+
if (invocation) {
|
|
7361
|
+
await this.invokeSkill(
|
|
7362
|
+
invocation.skill.name,
|
|
7363
|
+
invocation.args,
|
|
7364
|
+
options?.onPreflightAccepted ? { onPreflightAccepted: options.onPreflightAccepted } : undefined,
|
|
7365
|
+
);
|
|
7366
|
+
return;
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
|
|
7246
7371
|
// Handle extension commands first (execute immediately, even during streaming)
|
|
7247
7372
|
if (expandPromptTemplates && text.startsWith("/")) {
|
|
7248
7373
|
const handled = await this.#tryExecuteExtensionCommand(text);
|
|
@@ -7422,7 +7547,7 @@ export class AgentSession {
|
|
|
7422
7547
|
|
|
7423
7548
|
async promptCustomMessage<T = unknown>(
|
|
7424
7549
|
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">,
|
|
7425
|
-
options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy">,
|
|
7550
|
+
options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy" | "onPreflightAccepted">,
|
|
7426
7551
|
): Promise<void> {
|
|
7427
7552
|
const textContent =
|
|
7428
7553
|
typeof message.content === "string"
|
|
@@ -7530,7 +7655,11 @@ export class AgentSession {
|
|
|
7530
7655
|
}
|
|
7531
7656
|
|
|
7532
7657
|
// Validate API key
|
|
7533
|
-
const apiKey = await this.#
|
|
7658
|
+
const apiKey = await this.#awaitPromptPreflight(
|
|
7659
|
+
generation,
|
|
7660
|
+
preflightSignal,
|
|
7661
|
+
this.#modelRegistry.getApiKey(this.model, this.sessionId, { signal: preflightSignal }),
|
|
7662
|
+
);
|
|
7534
7663
|
if (!apiKey) {
|
|
7535
7664
|
throw new Error(formatNoCredentialOnboardingError(this.model.provider));
|
|
7536
7665
|
}
|
|
@@ -7817,6 +7946,7 @@ export class AgentSession {
|
|
|
7817
7946
|
return tool ? { safeSummary: tool.safeSummary, safeSummaryFields: tool.safeSummaryFields } : undefined;
|
|
7818
7947
|
},
|
|
7819
7948
|
cycleModel: () => this.cycleModel(),
|
|
7949
|
+
setModelProfile: name => this.activateModelProfileForControl(name),
|
|
7820
7950
|
cycleThinkingLevel: () => this.cycleThinkingLevel(),
|
|
7821
7951
|
setQueueMode: (kind, mode) => {
|
|
7822
7952
|
if (kind === "steering" && (mode === "all" || mode === "one-at-a-time")) {
|
|
@@ -7844,6 +7974,7 @@ export class AgentSession {
|
|
|
7844
7974
|
getJobs: () => undefined,
|
|
7845
7975
|
sdkBindings: () => [
|
|
7846
7976
|
"cycleModel",
|
|
7977
|
+
"setModelProfile",
|
|
7847
7978
|
"cycleThinkingLevel",
|
|
7848
7979
|
"setQueueMode",
|
|
7849
7980
|
"getSkillState",
|
|
@@ -9012,7 +9143,14 @@ export class AgentSession {
|
|
|
9012
9143
|
this.#rebindProviderSessionState(new Map());
|
|
9013
9144
|
this.agent.reset();
|
|
9014
9145
|
if (!options?.drop) await this.sessionManager.flush();
|
|
9015
|
-
await this.sessionManager.
|
|
9146
|
+
const prepared = await this.sessionManager.prepareNewSession(options);
|
|
9147
|
+
try {
|
|
9148
|
+
// Last fallible gate while public getters still show the predecessor (#3138).
|
|
9149
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
9150
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
9151
|
+
} catch (error) {
|
|
9152
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
9153
|
+
}
|
|
9016
9154
|
this.setTodoPhases([]);
|
|
9017
9155
|
this.#syncAgentSessionId();
|
|
9018
9156
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
@@ -9074,12 +9212,18 @@ export class AgentSession {
|
|
|
9074
9212
|
if (!(await manager.cancelAndSettleOwnerJobs(ownerId))) {
|
|
9075
9213
|
throw new Error("Owned async jobs did not settle before session replacement.");
|
|
9076
9214
|
}
|
|
9077
|
-
|
|
9215
|
+
const prepared = await this.sessionManager.prepareNewSession(options);
|
|
9216
|
+
try {
|
|
9217
|
+
// Last fallible gate while public getters still show the predecessor (#3138).
|
|
9218
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
9219
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
9220
|
+
} catch (error) {
|
|
9221
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
9222
|
+
}
|
|
9078
9223
|
this.#disconnectFromAgent();
|
|
9079
9224
|
this.#closeAllProviderSessions("new session");
|
|
9080
9225
|
this.#rebindProviderSessionState(new Map());
|
|
9081
9226
|
this.agent.reset();
|
|
9082
|
-
await this.sessionManager.newSession(options);
|
|
9083
9227
|
this.setTodoPhases([]);
|
|
9084
9228
|
this.#syncAgentSessionId();
|
|
9085
9229
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
@@ -9122,10 +9266,17 @@ export class AgentSession {
|
|
|
9122
9266
|
}
|
|
9123
9267
|
this.sessionManager.appendServiceTierChange(this.serviceTier ?? null);
|
|
9124
9268
|
if (nextDiscoverySessionToolNames) {
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9269
|
+
// Avoid a full system-prompt rebuild when the active set is unchanged; rebuild
|
|
9270
|
+
// can block on shared workspace-tree work across repeated /new transitions.
|
|
9271
|
+
const current = this.getActiveToolNames().slice().sort().join("\0");
|
|
9272
|
+
const next = [...new Set(nextDiscoverySessionToolNames.map(n => n.toLowerCase()))].sort().join("\0");
|
|
9273
|
+
if (current !== next) {
|
|
9274
|
+
await this.#applyActiveToolsByName(nextDiscoverySessionToolNames, {
|
|
9275
|
+
persistMCPSelection: false,
|
|
9276
|
+
nextSelectedDiscoveredBuiltinToolNames: [],
|
|
9277
|
+
});
|
|
9278
|
+
} else {
|
|
9279
|
+
}
|
|
9129
9280
|
}
|
|
9130
9281
|
this.#todoReminderCount = 0;
|
|
9131
9282
|
this.#planReferenceSent = false;
|
|
@@ -9138,6 +9289,7 @@ export class AgentSession {
|
|
|
9138
9289
|
reason: "new",
|
|
9139
9290
|
previousSessionFile,
|
|
9140
9291
|
});
|
|
9292
|
+
} else {
|
|
9141
9293
|
}
|
|
9142
9294
|
}
|
|
9143
9295
|
|
|
@@ -9217,13 +9369,18 @@ export class AgentSession {
|
|
|
9217
9369
|
// Flush current session to ensure all entries are written
|
|
9218
9370
|
await this.sessionManager.flush();
|
|
9219
9371
|
|
|
9220
|
-
//
|
|
9221
|
-
|
|
9222
|
-
|
|
9372
|
+
// Prepare the copied successor and complete local:// readiness while all
|
|
9373
|
+
// public manager getters remain bound to the predecessor.
|
|
9374
|
+
const prepared = await this.sessionManager.prepareFork();
|
|
9375
|
+
if (!prepared) {
|
|
9223
9376
|
return false;
|
|
9224
9377
|
}
|
|
9225
|
-
|
|
9226
|
-
|
|
9378
|
+
try {
|
|
9379
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
9380
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
9381
|
+
} catch (error) {
|
|
9382
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
9383
|
+
}
|
|
9227
9384
|
this.#syncAgentSessionId();
|
|
9228
9385
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|
|
9229
9386
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
@@ -9312,6 +9469,17 @@ export class AgentSession {
|
|
|
9312
9469
|
return this.#activeModelProfile;
|
|
9313
9470
|
}
|
|
9314
9471
|
|
|
9472
|
+
/** Activate a complete model profile through a nonvisual session control. */
|
|
9473
|
+
async activateModelProfileForControl(profileName: string): Promise<boolean> {
|
|
9474
|
+
await activateModelProfile({
|
|
9475
|
+
session: this,
|
|
9476
|
+
modelRegistry: this.#modelRegistry,
|
|
9477
|
+
settings: this.settings,
|
|
9478
|
+
profileName,
|
|
9479
|
+
});
|
|
9480
|
+
return this.getActiveModelProfile() === profileName;
|
|
9481
|
+
}
|
|
9482
|
+
|
|
9315
9483
|
/** Return the persisted configured fallback selectors for a model role. */
|
|
9316
9484
|
getConfiguredModelChain(role: string): readonly string[] | undefined {
|
|
9317
9485
|
return getSessionContextForInternalRead(this.sessionManager).configuredModelChains[role]?.entries;
|
|
@@ -10614,22 +10782,36 @@ export class AgentSession {
|
|
|
10614
10782
|
// user work that agent.reset() would otherwise clear.
|
|
10615
10783
|
const rollbackAgentSteeringQueue = this.agent.snapshotSteering();
|
|
10616
10784
|
const rollbackAgentFollowUpQueue = this.agent.snapshotFollowUp();
|
|
10617
|
-
let successorSessionFile: string | undefined;
|
|
10618
10785
|
let savedPath: string | undefined;
|
|
10619
10786
|
let committed = false;
|
|
10620
|
-
|
|
10621
|
-
// commit or resumed on rollback. Suspension runs inside the transaction so a
|
|
10622
|
-
// listener fault during suspension is rolled back like any other prepare step.
|
|
10623
|
-
let suspendedWorkflowGateEmitter: WorkflowGateEmitter | undefined;
|
|
10787
|
+
let prepared: PreparedNewSession | undefined;
|
|
10624
10788
|
try {
|
|
10625
|
-
|
|
10626
|
-
//
|
|
10627
|
-
//
|
|
10628
|
-
|
|
10629
|
-
await this.sessionManager.newSession(
|
|
10789
|
+
// Prepare successor entries, persistence, display state, and gate
|
|
10790
|
+
// construction without publishing manager identity. Managed local-root
|
|
10791
|
+
// readiness is the last fallible step before the sole commit (#3138).
|
|
10792
|
+
prepared = await this.sessionManager.prepareNewSession(
|
|
10630
10793
|
previousSessionFile ? { parentSession: previousSessionFile } : undefined,
|
|
10631
10794
|
);
|
|
10632
|
-
|
|
10795
|
+
if (model) this.sessionManager.appendPreparedModelChange(prepared, `${model.provider}/${model.id}`);
|
|
10796
|
+
this.sessionManager.appendPreparedThinkingLevelChange(prepared, this.thinkingLevel);
|
|
10797
|
+
this.sessionManager.appendPreparedServiceTierChange(prepared, this.serviceTier ?? null);
|
|
10798
|
+
this.sessionManager.appendPreparedCustomMessageEntry(
|
|
10799
|
+
prepared,
|
|
10800
|
+
"handoff",
|
|
10801
|
+
createHandoffContext(handoffText),
|
|
10802
|
+
true,
|
|
10803
|
+
undefined,
|
|
10804
|
+
"agent",
|
|
10805
|
+
);
|
|
10806
|
+
await this.sessionManager.ensurePreparedNewSessionOnDisk(prepared);
|
|
10807
|
+
const sessionContext = this.buildPreparedDisplaySessionContext(prepared);
|
|
10808
|
+
const successorGateEmitter = this.#constructWorkflowGateEmitter(prepared.sessionId);
|
|
10809
|
+
// Last fallible action: verified local:// readiness from immutable staged options.
|
|
10810
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
10811
|
+
|
|
10812
|
+
// --- Commit boundary: synchronous adoption is the sole identity publication.
|
|
10813
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
10814
|
+
committed = true;
|
|
10633
10815
|
this.agent.reset();
|
|
10634
10816
|
this.#syncAgentSessionId();
|
|
10635
10817
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
@@ -10638,16 +10820,8 @@ export class AgentSession {
|
|
|
10638
10820
|
this.#pendingNextTurnMessages = [];
|
|
10639
10821
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
10640
10822
|
this.#todoReminderCount = 0;
|
|
10641
|
-
|
|
10642
|
-
|
|
10643
|
-
}
|
|
10644
|
-
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
|
|
10645
|
-
this.sessionManager.appendServiceTierChange(this.serviceTier ?? null);
|
|
10646
|
-
|
|
10647
|
-
// Inject the handoff document as a custom message
|
|
10648
|
-
const handoffContent = createHandoffContext(handoffText);
|
|
10649
|
-
this.sessionManager.appendCustomMessageEntry("handoff", handoffContent, true, undefined, "agent");
|
|
10650
|
-
await this.sessionManager.ensureOnDisk();
|
|
10823
|
+
this.agent.replaceMessages(sessionContext.messages);
|
|
10824
|
+
this.#syncTodoPhasesFromBranch();
|
|
10651
10825
|
if (options?.autoTriggered && this.settings.get("compaction.handoffSaveToDisk")) {
|
|
10652
10826
|
try {
|
|
10653
10827
|
const artifactId = await this.sessionManager.saveArtifact(`${handoffText}\n`, "handoff");
|
|
@@ -10659,30 +10833,9 @@ export class AgentSession {
|
|
|
10659
10833
|
}
|
|
10660
10834
|
}
|
|
10661
10835
|
|
|
10662
|
-
//
|
|
10663
|
-
const sessionContext = this.buildDisplaySessionContext();
|
|
10664
|
-
this.agent.replaceMessages(sessionContext.messages);
|
|
10665
|
-
this.#syncTodoPhasesFromBranch();
|
|
10666
|
-
|
|
10667
|
-
// Construct the successor workflow-gate emitter in the reversible window:
|
|
10668
|
-
// FileGateStore load / broker init can throw, and a failure here must roll
|
|
10669
|
-
// back rather than corrupt an already-committed switch. Publication is
|
|
10670
|
-
// deferred to the (no-throw) commit step below.
|
|
10671
|
-
const successorGateEmitter = this.#constructWorkflowGateEmitter();
|
|
10672
|
-
|
|
10673
|
-
// --- Commit boundary: the successor is durably persisted and every
|
|
10674
|
-
// reversible, fallible step above has succeeded. Cross the irreversible
|
|
10675
|
-
// commit BEFORE any identity rotation. Fencing the predecessor gate
|
|
10676
|
-
// emitter and rotating provider sessions cannot be undone, so once we
|
|
10677
|
-
// begin them a failure must RETAIN the committed successor rather than
|
|
10678
|
-
// attempt a (now-impossible) rollback that would resume a fenced emitter.
|
|
10679
|
-
committed = true;
|
|
10836
|
+
// The forward-only suffix starts only after the committed successor is live.
|
|
10680
10837
|
this.#resetInjectedContextSignatures();
|
|
10681
|
-
this.#publishWorkflowGateEmitter(
|
|
10682
|
-
successorGateEmitter,
|
|
10683
|
-
rollbackSessionState.sessionId,
|
|
10684
|
-
suspendedWorkflowGateEmitter,
|
|
10685
|
-
);
|
|
10838
|
+
this.#publishWorkflowGateEmitter(successorGateEmitter, rollbackSessionState.sessionId);
|
|
10686
10839
|
this.#closeAllProviderSessions("session handoff");
|
|
10687
10840
|
this.#rebindProviderSessionState(new Map());
|
|
10688
10841
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
@@ -10718,12 +10871,12 @@ export class AgentSession {
|
|
|
10718
10871
|
return { document: handoffText, savedPath };
|
|
10719
10872
|
} catch (switchError) {
|
|
10720
10873
|
if (committed) {
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10874
|
+
throw Object.assign(new Error("Handoff committed but successor initialization was degraded."), {
|
|
10875
|
+
code: "handoff_committed_degraded",
|
|
10876
|
+
cause: switchError,
|
|
10877
|
+
handoffDocument: handoffText,
|
|
10878
|
+
savedPath,
|
|
10725
10879
|
});
|
|
10726
|
-
return { document: handoffText, savedPath };
|
|
10727
10880
|
}
|
|
10728
10881
|
// Reversible window: roll back to the pre-handoff session so the
|
|
10729
10882
|
// failure is non-destructive. Predecessor gate emitter, provider
|
|
@@ -10731,7 +10884,6 @@ export class AgentSession {
|
|
|
10731
10884
|
// were never mutated before commit, so they survive intact.
|
|
10732
10885
|
this.sessionManager.restoreState(rollbackSessionState);
|
|
10733
10886
|
this.#syncAgentSessionId(rollbackSessionState.sessionId);
|
|
10734
|
-
this.#restoreWorkflowGateEmitter(suspendedWorkflowGateEmitter);
|
|
10735
10887
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
10736
10888
|
this.agent.replaceMessages(rollbackAgentMessages);
|
|
10737
10889
|
this.agent.clearAllQueues();
|
|
@@ -10743,25 +10895,17 @@ export class AgentSession {
|
|
|
10743
10895
|
this.#scheduledHiddenNextTurnGeneration = rollbackScheduledHiddenNextTurnGeneration;
|
|
10744
10896
|
this.#todoReminderCount = rollbackTodoReminderCount;
|
|
10745
10897
|
this.#syncTodoPhasesFromBranch();
|
|
10746
|
-
//
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
await this.sessionManager.discardUncommittedSession(successorSessionFile);
|
|
10751
|
-
} catch (cleanupError) {
|
|
10752
|
-
logger.warn("Failed to remove orphaned handoff session after rollback", {
|
|
10753
|
-
path: successorSessionFile,
|
|
10754
|
-
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
10755
|
-
});
|
|
10756
|
-
}
|
|
10757
|
-
}
|
|
10898
|
+
// Exact-discard only the staged successor; predecessor state was never adopted.
|
|
10899
|
+
const rollbackError = prepared
|
|
10900
|
+
? await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, switchError)
|
|
10901
|
+
: switchError;
|
|
10758
10902
|
// Map to cancellation only for a genuine handoff-signal abort; a
|
|
10759
10903
|
// downstream error keeps its cause and the generated document.
|
|
10760
10904
|
if (handoffSignal.aborted) {
|
|
10761
10905
|
throw new Error("Handoff cancelled");
|
|
10762
10906
|
}
|
|
10763
10907
|
// Preserve the generated handoff document for copy/retry.
|
|
10764
|
-
throw Object.assign(
|
|
10908
|
+
throw Object.assign(rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)), {
|
|
10765
10909
|
handoffDocument: handoffText,
|
|
10766
10910
|
});
|
|
10767
10911
|
}
|
|
@@ -10769,7 +10913,7 @@ export class AgentSession {
|
|
|
10769
10913
|
// Genuine handoff-signal cancellation maps to a cancellation error.
|
|
10770
10914
|
// Errors surfaced by the inner transaction (which already rolled back
|
|
10771
10915
|
// and, for non-aborts, attached the generated document) pass through.
|
|
10772
|
-
if (handoffSignal.aborted) {
|
|
10916
|
+
if (handoffSignal.aborted && !(error instanceof AggregateError)) {
|
|
10773
10917
|
throw new Error("Handoff cancelled");
|
|
10774
10918
|
}
|
|
10775
10919
|
throw error;
|
|
@@ -12459,8 +12603,14 @@ export class AgentSession {
|
|
|
12459
12603
|
break;
|
|
12460
12604
|
}
|
|
12461
12605
|
|
|
12462
|
-
|
|
12463
|
-
|
|
12606
|
+
// Legacy parsed Retry-After is capped at retry.maxDelayMs (see
|
|
12607
|
+
// compactionRetryDelay); only managed fallback is uncapped.
|
|
12608
|
+
const delayMs = compactionRetryDelay(
|
|
12609
|
+
retrySettings.baseDelayMs,
|
|
12610
|
+
retrySettings.maxDelayMs,
|
|
12611
|
+
attempt,
|
|
12612
|
+
retryAfterMs,
|
|
12613
|
+
);
|
|
12464
12614
|
|
|
12465
12615
|
// If retry delay is too long (>30s), try next candidate instead of waiting
|
|
12466
12616
|
const maxAcceptableDelayMs = 30_000;
|
|
@@ -12653,7 +12803,7 @@ export class AgentSession {
|
|
|
12653
12803
|
);
|
|
12654
12804
|
}
|
|
12655
12805
|
|
|
12656
|
-
#
|
|
12806
|
+
#alibabaTokenPlanTimeoutMessagesForApi(api: string): ReadonlySet<string> | undefined {
|
|
12657
12807
|
if (api === "openai-responses" || api === "openai-completions") {
|
|
12658
12808
|
return ALIBABA_TOKEN_PLAN_FIRST_EVENT_TIMEOUT_MESSAGES[api];
|
|
12659
12809
|
}
|
|
@@ -12662,18 +12812,23 @@ export class AgentSession {
|
|
|
12662
12812
|
|
|
12663
12813
|
#isAlibabaTokenPlanFirstEventTimeout(message: AssistantMessage): boolean {
|
|
12664
12814
|
if (message.stopReason !== "error" || message.provider !== ALIBABA_TOKEN_PLAN_PROVIDER) return false;
|
|
12665
|
-
const
|
|
12666
|
-
return
|
|
12815
|
+
const timeoutMessages = this.#alibabaTokenPlanTimeoutMessagesForApi(message.api);
|
|
12816
|
+
return timeoutMessages?.has(message.errorMessage ?? "") ?? false;
|
|
12667
12817
|
}
|
|
12668
12818
|
|
|
12669
12819
|
#isAlibabaTokenPlanCompactionTimeout(candidate: Model, errorMessage: string): boolean {
|
|
12670
12820
|
if (candidate.provider !== ALIBABA_TOKEN_PLAN_PROVIDER) return false;
|
|
12671
|
-
const
|
|
12672
|
-
if (
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12821
|
+
const timeoutMessages = this.#alibabaTokenPlanTimeoutMessagesForApi(candidate.api);
|
|
12822
|
+
if (!timeoutMessages) return false;
|
|
12823
|
+
for (const timeoutMessage of timeoutMessages) {
|
|
12824
|
+
if (
|
|
12825
|
+
errorMessage === `Summarization failed: ${timeoutMessage}` ||
|
|
12826
|
+
errorMessage === `Turn prefix summarization failed: ${timeoutMessage}`
|
|
12827
|
+
) {
|
|
12828
|
+
return true;
|
|
12829
|
+
}
|
|
12830
|
+
}
|
|
12831
|
+
return false;
|
|
12677
12832
|
}
|
|
12678
12833
|
|
|
12679
12834
|
#isTerminalProviderFirstEventTimeout(message: AssistantMessage): boolean {
|
|
@@ -13025,7 +13180,7 @@ export class AgentSession {
|
|
|
13025
13180
|
}
|
|
13026
13181
|
if (this.#isTerminalProviderFirstEventTimeout(outcome.failure.message)) {
|
|
13027
13182
|
// The managed transport discarded this attempt before session policy saw it.
|
|
13028
|
-
// Remove its provisional
|
|
13183
|
+
// Remove its provisional charge without changing sticky fallback selection.
|
|
13029
13184
|
this.#defaultFallbackChain().discardStartedAttempt();
|
|
13030
13185
|
return {
|
|
13031
13186
|
type: "terminal",
|
|
@@ -14557,6 +14712,12 @@ export class AgentSession {
|
|
|
14557
14712
|
|
|
14558
14713
|
try {
|
|
14559
14714
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
14715
|
+
// The successor identity is already rotated in the manager but not yet
|
|
14716
|
+
// published; gate its local:// root before publication so the agent,
|
|
14717
|
+
// workflow-gate emitter, and hooks cannot resolve against an ungated
|
|
14718
|
+
// root. The manager-rotation window itself is tracked in #3138
|
|
14719
|
+
// (#2797 / #2925).
|
|
14720
|
+
if (switchingToDifferentSession) await initializeLocalRoot(this.#localProtocolOptions());
|
|
14560
14721
|
this.#syncAgentSessionId();
|
|
14561
14722
|
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
14562
14723
|
|
|
@@ -14633,13 +14794,8 @@ export class AgentSession {
|
|
|
14633
14794
|
await this.sessionManager.ensureOnDisk();
|
|
14634
14795
|
|
|
14635
14796
|
if (switchingToDifferentSession) {
|
|
14636
|
-
//
|
|
14637
|
-
//
|
|
14638
|
-
// post-commit session_switch / local path resolution. createAgentSession
|
|
14639
|
-
// already does this at cold start (#2797); switchSession previously omitted
|
|
14640
|
-
// it, so resolving local:// after /resume could fail-closed with
|
|
14641
|
-
// "legacy migration must complete before path resolution" (#2925).
|
|
14642
|
-
await initializeLocalRoot(this.#localProtocolOptions());
|
|
14797
|
+
// The local:// migration gate for this successor already ran above,
|
|
14798
|
+
// before the identity was published (#2797 / #2925).
|
|
14643
14799
|
this.#resetHindsightConversationTrackingIfHindsight();
|
|
14644
14800
|
this.#resetIrcRosterDeliveryState();
|
|
14645
14801
|
}
|
|
@@ -14758,19 +14914,21 @@ export class AgentSession {
|
|
|
14758
14914
|
skipConversationRestore = result?.skipConversationRestore ?? false;
|
|
14759
14915
|
}
|
|
14760
14916
|
|
|
14761
|
-
//
|
|
14917
|
+
// Flush pending writes before preparing the successor.
|
|
14918
|
+
await this.sessionManager.flush();
|
|
14919
|
+
const prepared = selectedEntry.parentId
|
|
14920
|
+
? await this.sessionManager.prepareBranchedSession(selectedEntry.parentId)
|
|
14921
|
+
: await this.sessionManager.prepareNewSession({ parentSession: previousSessionFile });
|
|
14922
|
+
try {
|
|
14923
|
+
await initializeLocalRoot(this.#localProtocolOptions(prepared));
|
|
14924
|
+
this.sessionManager.commitPreparedNewSession(prepared);
|
|
14925
|
+
} catch (error) {
|
|
14926
|
+
throw await discardPreparedNewSessionAfterFailure(this.sessionManager, prepared, error);
|
|
14927
|
+
}
|
|
14762
14928
|
this.#pendingNextTurnMessages = [];
|
|
14763
14929
|
this.#scheduledHiddenNextTurnGeneration = undefined;
|
|
14764
|
-
|
|
14765
|
-
// Flush pending writes before branching
|
|
14766
|
-
await this.sessionManager.flush();
|
|
14767
14930
|
this.#cancelOwnAsyncJobs();
|
|
14768
14931
|
|
|
14769
|
-
if (!selectedEntry.parentId) {
|
|
14770
|
-
await this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
14771
|
-
} else {
|
|
14772
|
-
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
14773
|
-
}
|
|
14774
14932
|
this.#syncTodoPhasesFromBranch();
|
|
14775
14933
|
this.#syncAgentSessionId();
|
|
14776
14934
|
this.#bindWorkflowGateEmitter(previousWorkflowGateSessionId);
|