@gajae-code/coding-agent 0.16.3 → 0.16.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/args.d.ts +2 -2
  4. package/dist/types/cli/customize-doctor.d.ts +22 -0
  5. package/dist/types/cli/root-flags.d.ts +4 -2
  6. package/dist/types/cli.d.ts +1 -1
  7. package/dist/types/commands/launch.d.ts +1 -1
  8. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  9. package/dist/types/config/file-lock.d.ts +31 -3
  10. package/dist/types/config/model-registry.d.ts +4 -0
  11. package/dist/types/config/model-resolver.d.ts +9 -0
  12. package/dist/types/config/settings-schema.d.ts +29 -1
  13. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  15. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  16. package/dist/types/discovery/helpers.d.ts +35 -0
  17. package/dist/types/exec/bash-executor.d.ts +17 -1
  18. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  19. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  20. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  21. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  22. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  23. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  24. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  25. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  26. package/dist/types/exec/isolated-shell.d.ts +19 -0
  27. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  28. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  29. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  30. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  31. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  32. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  33. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  34. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  35. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  36. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  37. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  38. package/dist/types/extensibility/runtime-skill-discovery.d.ts +5 -2
  39. package/dist/types/extensibility/shared-events.d.ts +1 -1
  40. package/dist/types/extensibility/skill-management.d.ts +9 -4
  41. package/dist/types/extensibility/skills.d.ts +2 -0
  42. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  43. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  44. package/dist/types/gjc-runtime/session-state-lock.d.ts +14 -0
  45. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  46. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  47. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  48. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  49. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  50. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  51. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  52. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  53. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  54. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  55. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  56. package/dist/types/modes/interactive-mode.d.ts +3 -0
  57. package/dist/types/modes/types.d.ts +7 -0
  58. package/dist/types/runtime-mcp/client.d.ts +9 -1
  59. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  60. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  61. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  62. package/dist/types/runtime-mcp/types.d.ts +22 -0
  63. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  64. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  65. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  66. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  68. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  69. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  70. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  71. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  72. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  73. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  74. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  75. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  76. package/dist/types/sdk/client/client.d.ts +6 -0
  77. package/dist/types/sdk/host/host.d.ts +3 -1
  78. package/dist/types/sdk/host/session-runtime.d.ts +26 -1
  79. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  80. package/dist/types/sdk/router/session-router.d.ts +10 -2
  81. package/dist/types/sdk/session-api-key.d.ts +26 -0
  82. package/dist/types/sdk/session.d.ts +5 -1
  83. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  84. package/dist/types/session/agent-session.d.ts +11 -0
  85. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  86. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  87. package/dist/types/session/messages.d.ts +1 -0
  88. package/dist/types/session/session-manager.d.ts +3 -1
  89. package/dist/types/setup/paseo/announce.d.ts +112 -0
  90. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  91. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  92. package/dist/types/system-prompt.d.ts +10 -0
  93. package/dist/types/task/executor.d.ts +5 -1
  94. package/dist/types/tools/bash.d.ts +1 -0
  95. package/dist/types/tools/fetch.d.ts +7 -0
  96. package/dist/types/tools/github-cache.d.ts +2 -1
  97. package/dist/types/tools/index.d.ts +10 -0
  98. package/dist/types/tools/path-utils.d.ts +2 -0
  99. package/dist/types/tools/search.d.ts +6 -0
  100. package/package.json +9 -7
  101. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +11 -0
  103. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  104. package/src/capability/index.ts +14 -2
  105. package/src/capability/types.ts +4 -0
  106. package/src/cli/args.ts +20 -9
  107. package/src/cli/customize-doctor.ts +155 -12
  108. package/src/cli/root-flags.ts +5 -3
  109. package/src/cli.ts +22 -0
  110. package/src/commands/sdk.ts +74 -31
  111. package/src/config/autorouting-tier-map.ts +1 -0
  112. package/src/config/file-lock-gc.ts +40 -2
  113. package/src/config/file-lock.ts +379 -31
  114. package/src/config/model-preset-registry.ts +120 -19
  115. package/src/config/model-profiles.ts +40 -0
  116. package/src/config/model-registry.ts +73 -23
  117. package/src/config/model-resolver.ts +48 -0
  118. package/src/config/settings-schema.ts +33 -1
  119. package/src/config/settings.ts +1 -0
  120. package/src/coordinator-mcp/question-state.ts +33 -2
  121. package/src/coordinator-mcp/server.ts +477 -101
  122. package/src/crash/sanitize.ts +1 -1
  123. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  124. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  125. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  126. package/src/discovery/builtin.ts +52 -55
  127. package/src/discovery/claude-plugins.ts +12 -4
  128. package/src/discovery/helpers.ts +307 -34
  129. package/src/exec/bash-executor.ts +330 -50
  130. package/src/exec/bash-shell-guardian.ts +388 -0
  131. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  132. package/src/exec/bash-shell-signals.ts +29 -0
  133. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  134. package/src/exec/bash-shell-supervisor.ts +359 -0
  135. package/src/exec/bash-shell-worker-entry.ts +5 -0
  136. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  137. package/src/exec/bash-shell-worker.ts +116 -0
  138. package/src/exec/isolated-shell.ts +393 -0
  139. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  140. package/src/extensibility/extensions/function-hooks.ts +959 -0
  141. package/src/extensibility/extensions/index.ts +22 -0
  142. package/src/extensibility/extensions/loader.ts +53 -2
  143. package/src/extensibility/extensions/runner.ts +895 -264
  144. package/src/extensibility/extensions/types.ts +42 -0
  145. package/src/extensibility/extensions/wrapper.ts +56 -15
  146. package/src/extensibility/gjc-plugins/compiler.ts +228 -60
  147. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  148. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  149. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  150. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  152. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  153. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  154. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  155. package/src/extensibility/gjc-plugins/types.ts +25 -0
  156. package/src/extensibility/hooks/loader.ts +133 -16
  157. package/src/extensibility/hooks/types.ts +28 -0
  158. package/src/extensibility/runtime-skill-discovery.ts +149 -190
  159. package/src/extensibility/shared-events.ts +1 -1
  160. package/src/extensibility/skill-management.ts +200 -52
  161. package/src/extensibility/skills.ts +73 -25
  162. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  163. package/src/gjc-runtime/gc-runtime.ts +29 -0
  164. package/src/gjc-runtime/linux-proc.ts +14 -0
  165. package/src/gjc-runtime/repository-binding.ts +1 -1
  166. package/src/gjc-runtime/session-state-lock.ts +143 -10
  167. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  168. package/src/gjc-runtime/state-writer.ts +30 -29
  169. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  170. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  171. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  172. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  173. package/src/harness-control-plane/session-lease.ts +2 -2
  174. package/src/internal-urls/docs-index.generated.ts +12 -12
  175. package/src/lsp/config.ts +10 -3
  176. package/src/lsp/index.ts +1 -1
  177. package/src/lsp/path-trust.ts +69 -14
  178. package/src/main.ts +78 -35
  179. package/src/modes/DESIGN.md +54 -0
  180. package/src/modes/acp/acp-agent.ts +16 -3
  181. package/src/modes/components/assistant-message.ts +22 -9
  182. package/src/modes/components/bash-execution.ts +4 -1
  183. package/src/modes/components/execution-shared.ts +4 -1
  184. package/src/modes/components/model-selector.ts +166 -33
  185. package/src/modes/components/session-observer-overlay.ts +1 -1
  186. package/src/modes/components/settings-selector.ts +1075 -235
  187. package/src/modes/components/status-line/command.ts +190 -0
  188. package/src/modes/components/status-line/index.ts +1 -0
  189. package/src/modes/components/status-line/segments.ts +32 -5
  190. package/src/modes/components/status-line/types.ts +5 -0
  191. package/src/modes/components/tool-status-header.ts +396 -22
  192. package/src/modes/controllers/command-controller.ts +3 -0
  193. package/src/modes/controllers/event-controller.ts +151 -7
  194. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  195. package/src/modes/controllers/input-controller.ts +6 -3
  196. package/src/modes/controllers/selector-controller.ts +21 -0
  197. package/src/modes/interactive-mode.ts +53 -6
  198. package/src/modes/print-mode.ts +5 -1
  199. package/src/modes/prompt-action-autocomplete.ts +54 -27
  200. package/src/modes/runtime-init.ts +2 -0
  201. package/src/modes/types.ts +17 -3
  202. package/src/modes/utils/ui-helpers.ts +2 -0
  203. package/src/prompts/system/system-prompt.md +8 -0
  204. package/src/prompts/tools/search.md +2 -0
  205. package/src/prompts/tools/subagent.md +1 -0
  206. package/src/runtime-mcp/client.ts +60 -17
  207. package/src/runtime-mcp/manager.ts +189 -14
  208. package/src/runtime-mcp/pool.test.ts +218 -0
  209. package/src/runtime-mcp/pool.ts +224 -23
  210. package/src/runtime-mcp/transports/stdio.ts +158 -24
  211. package/src/runtime-mcp/types.ts +24 -0
  212. package/src/sdk/broker/broker.ts +124 -32
  213. package/src/sdk/broker/ensure.ts +248 -41
  214. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  215. package/src/sdk/broker/lifecycle.ts +56 -6
  216. package/src/sdk/broker/startup-failure.ts +15 -6
  217. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  218. package/src/sdk/bus/chat-daemon-runtime.ts +72 -6
  219. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  220. package/src/sdk/bus/conversation-store.ts +29 -0
  221. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  222. package/src/sdk/bus/slack-conversation.ts +1 -0
  223. package/src/sdk/bus/slack-daemon.ts +478 -49
  224. package/src/sdk/bus/slack-live-provider.ts +12 -0
  225. package/src/sdk/bus/slack-provider.ts +5 -0
  226. package/src/sdk/bus/telegram-daemon.ts +16 -2
  227. package/src/sdk/bus/topic-registry.ts +19 -0
  228. package/src/sdk/cli/master-cli.ts +1 -1
  229. package/src/sdk/cli/session-cli.ts +11 -8
  230. package/src/sdk/client/client.ts +66 -17
  231. package/src/sdk/host/host.ts +29 -9
  232. package/src/sdk/host/query/revision-store.ts +19 -13
  233. package/src/sdk/host/reverse-leases.ts +7 -4
  234. package/src/sdk/host/session-runtime.test.ts +52 -10
  235. package/src/sdk/host/session-runtime.ts +383 -44
  236. package/src/sdk/lifecycle/service.ts +4 -0
  237. package/src/sdk/protocol/operation-inventory.generated.json +77 -0
  238. package/src/sdk/router/session-router.ts +89 -38
  239. package/src/sdk/session-api-key.ts +94 -0
  240. package/src/sdk/session.ts +228 -55
  241. package/src/sdk/transport/relay.ts +35 -13
  242. package/src/sdk/transport/serve-cli.ts +3 -3
  243. package/src/session/agent-session.ts +240 -90
  244. package/src/session/contribution-prep.ts +214 -21
  245. package/src/session/fallback-chain-controller.ts +38 -0
  246. package/src/session/internal/managed-session-storage.ts +19 -5
  247. package/src/session/messages.ts +5 -0
  248. package/src/session/session-manager.ts +29 -5
  249. package/src/session-import/redact.ts +5 -1
  250. package/src/setup/paseo/announce.ts +456 -0
  251. package/src/setup/paseo/setup-deps.ts +15 -3
  252. package/src/skill-state/canonical-skills.ts +12 -0
  253. package/src/skills/skills.test.ts +41 -21
  254. package/src/slash-commands/builtin-registry.ts +1 -0
  255. package/src/system-prompt.ts +29 -3
  256. package/src/task/executor.ts +7 -1
  257. package/src/task/index.ts +21 -4
  258. package/src/tools/bash.ts +63 -19
  259. package/src/tools/cron.ts +1 -1
  260. package/src/tools/fetch.ts +29 -1
  261. package/src/tools/gh.ts +26 -3
  262. package/src/tools/github-cache.ts +68 -16
  263. package/src/tools/index.ts +10 -0
  264. package/src/tools/job.ts +21 -4
  265. package/src/tools/monitor.ts +8 -2
  266. package/src/tools/path-utils.ts +7 -0
  267. package/src/tools/read.ts +14 -2
  268. package/src/tools/render-utils.ts +1 -3
  269. package/src/tools/resource-gc.ts +28 -8
  270. package/src/tools/search.ts +116 -58
  271. package/src/tools/skill-discovery.ts +5 -0
  272. package/src/tools/skill.ts +37 -2
  273. package/src/tools/subagent.ts +6 -3
  274. package/src/tools/tool-catalog.generated.ts +10 -3
  275. package/src/tools/ultragoal-ask-guard.ts +2 -2
  276. package/src/web/insane/bridge.ts +113 -23
  277. package/src/web/search/providers/perplexity.ts +102 -41
@@ -36,6 +36,7 @@ import {
36
36
  canContinuePersistedHistory,
37
37
  dispatchedToolIdentity,
38
38
  getAgentTerminalOwnerContext,
39
+ isContinuingMidRunMaintenanceOutcome,
39
40
  isNonDispatchedToolEvent,
40
41
  type ManagedAttemptContinuationOwnership,
41
42
  type ManagedAttemptDecision,
@@ -438,6 +439,7 @@ import {
438
439
  type ConfiguredFallbackChain,
439
440
  cappedExponentialWithFullJitter,
440
441
  compactionRetryDelay,
442
+ describeCompactionCandidateFailures,
441
443
  effectiveFallbackDelay,
442
444
  FallbackChainController,
443
445
  type FallbackChainRuntimeState,
@@ -882,6 +884,10 @@ export interface AgentSessionConfig {
882
884
  skills?: Skill[];
883
885
  /** Skill loading warnings (already captured by SDK) */
884
886
  skillWarnings?: SkillWarning[];
887
+ /** Re-enumerate filesystem and marketplace skills after plugin lifecycle changes. */
888
+ reloadSkills?: (cwd: string) => Promise<{ skills: Skill[]; warnings: SkillWarning[] }>;
889
+ /** Publish a successfully committed skill catalog to SDK/process snapshots. */
890
+ onSkillsReloaded?: (skills: Skill[], warnings: SkillWarning[]) => void;
885
891
  /** Custom commands (TypeScript slash commands) */
886
892
  customCommands?: LoadedCustomCommand[];
887
893
  skillsSettings?: SkillsSettings;
@@ -1044,7 +1050,7 @@ type AutoCompactionTerminalStatus =
1044
1050
  | { kind: "compacted"; continuationScheduled?: boolean }
1045
1051
  | { kind: "aborted"; source: "signal" | "hook" }
1046
1052
  | { kind: "skipped"; continuationScheduled?: boolean }
1047
- | { kind: "failed" };
1053
+ | { kind: "failed"; errorMessage: string };
1048
1054
 
1049
1055
  type ToolOutputPruneResult = {
1050
1056
  prunedCount: number;
@@ -2786,7 +2792,7 @@ export class AgentSession {
2786
2792
  this.#activeAttemptScope = undefined;
2787
2793
  this.#activeLogicalRunId = undefined;
2788
2794
  }
2789
- #acceptSdkAttemptRun(handle: AttemptRunHandle, sdkRunToken?: string): void {
2795
+ #acceptSdkAttemptRun(handle: AttemptRunHandle, sdkRunToken?: string, sdkRunTokens?: string[]): void {
2790
2796
  const predecessorScope = this.#activeAttemptScope;
2791
2797
  const predecessorSdkRunToken =
2792
2798
  predecessorScope === undefined ? undefined : this.#sdkRunTokensByAttemptScope.get(predecessorScope);
@@ -2799,6 +2805,11 @@ export class AgentSession {
2799
2805
  if (sdkRunToken !== undefined) {
2800
2806
  this.#activeSdkRunToken = sdkRunToken;
2801
2807
  this.#sdkRunTokensByAttemptScope.set(handle.scope, sdkRunToken);
2808
+ const inheritedCohort =
2809
+ predecessorScope !== undefined && predecessorSdkRunToken === sdkRunToken
2810
+ ? this.#sdkRunCohortsByAttemptScope.get(predecessorScope)
2811
+ : undefined;
2812
+ this.#sdkRunCohortsByAttemptScope.set(handle.scope, sdkRunTokens ?? inheritedCohort ?? [sdkRunToken]);
2802
2813
  }
2803
2814
  if (carryRecoverySkip) this.#skipPostPromptRecoveryWaitByAttemptScope.add(handle.scope);
2804
2815
  }
@@ -3103,6 +3114,7 @@ export class AgentSession {
3103
3114
  #sdkRunTokensByQueuedMessage = new WeakMap<AgentMessage, string>();
3104
3115
  #skipPostPromptRecoveryWaitByAttemptScope = new WeakSet<AttemptScope>();
3105
3116
  #sdkRunTokensByAttemptScope = new WeakMap<AttemptScope, string>();
3117
+ #sdkRunCohortsByAttemptScope = new WeakMap<AttemptScope, string[]>();
3106
3118
  #activeSdkRunToken: string | undefined;
3107
3119
  #activeAttemptScope: AttemptScope | undefined;
3108
3120
  #attemptAuthority!: AttemptScopeAuthority;
@@ -3124,6 +3136,8 @@ export class AgentSession {
3124
3136
 
3125
3137
  #skills: Skill[];
3126
3138
  #skillWarnings: SkillWarning[];
3139
+ #reloadSkills: AgentSessionConfig["reloadSkills"];
3140
+ #onSkillsReloaded: AgentSessionConfig["onSkillsReloaded"];
3127
3141
 
3128
3142
  // Custom commands (TypeScript slash commands)
3129
3143
  #customCommands: LoadedCustomCommand[] = [];
@@ -4556,6 +4570,8 @@ export class AgentSession {
4556
4570
  this.agent.setMainAttemptScopeObserver(scope => this.#bindAttemptScope(scope));
4557
4571
  this.#skills = config.skills ?? [];
4558
4572
  this.#skillWarnings = config.skillWarnings ?? [];
4573
+ this.#reloadSkills = config.reloadSkills;
4574
+ this.#onSkillsReloaded = config.onSkillsReloaded;
4559
4575
  this.#customCommands = config.customCommands ?? [];
4560
4576
  this.#skillsSettings = config.skillsSettings;
4561
4577
  this.#modelRegistry = config.modelRegistry;
@@ -4569,7 +4585,7 @@ export class AgentSession {
4569
4585
  this.#transformContext = config.transformContext ?? (messages => messages);
4570
4586
  const configuredOnPayload = config.onPayload;
4571
4587
  this.#onPayload = configuredOnPayload
4572
- ? (payload, model, scope) => configuredOnPayload(payload, model, scope)
4588
+ ? (payload, model, scope, signal) => configuredOnPayload(payload, model, scope, signal)
4573
4589
  : undefined;
4574
4590
  this.rawSseDebugBuffer = config.rawSseDebugBuffer ?? new RawSseDebugBuffer();
4575
4591
  // Avoid wrapping in an `async` closure when no user callback is configured: the
@@ -4578,9 +4594,9 @@ export class AgentSession {
4578
4594
  // shows up as ~3.5% self time in streaming profiles.
4579
4595
  const configuredOnResponse = config.onResponse;
4580
4596
  this.#onResponse = configuredOnResponse
4581
- ? async (response, model, scope) => {
4597
+ ? async (response, model, scope, signal) => {
4582
4598
  this.rawSseDebugBuffer.recordResponse(response, model);
4583
- await configuredOnResponse(response, model, scope);
4599
+ await configuredOnResponse(response, model, scope, signal);
4584
4600
  }
4585
4601
  : (response, model, _scope) => {
4586
4602
  this.rawSseDebugBuffer.recordResponse(response, model);
@@ -5638,6 +5654,27 @@ export class AgentSession {
5638
5654
  if (failures.length > 0) throw new AggregateError(failures, "Tool session cleanup failed.");
5639
5655
  }
5640
5656
 
5657
+ #claimToolSessionTransitionCleanups(): Promise<void> {
5658
+ const cleanups = Array.from(this.#toolSessionTransitionCleanups);
5659
+ this.#toolSessionTransitionCleanups.clear();
5660
+ return this.#settleClaimedToolSessionCleanups(cleanups, "Tool session transition cleanup failed.");
5661
+ }
5662
+
5663
+ #claimToolSessionCleanups(): Promise<void> {
5664
+ const cleanups = Array.from(this.#toolSessionCleanups);
5665
+ this.#toolSessionCleanups.clear();
5666
+ return this.#settleClaimedToolSessionCleanups(cleanups, "Tool session cleanup failed.");
5667
+ }
5668
+
5669
+ async #settleClaimedToolSessionCleanups(
5670
+ cleanups: readonly (() => Promise<void> | void)[],
5671
+ message: string,
5672
+ ): Promise<void> {
5673
+ const results = await Promise.allSettled(cleanups.map(async cleanup => await cleanup()));
5674
+ const failures = results.flatMap(result => (result.status === "rejected" ? [result.reason] : []));
5675
+ if (failures.length > 0) throw new AggregateError(failures, message);
5676
+ }
5677
+
5641
5678
  getAsyncJobSnapshot(options?: { recentLimit?: number }): AsyncJobSnapshot | null {
5642
5679
  // The session's OWNED manager first: /jobs, the status header, and the
5643
5680
  // active-background count must reflect THIS session's jobs — with
@@ -5918,7 +5955,9 @@ export class AgentSession {
5918
5955
  const canonicalAdmission = this.#reserveCanonicalMessageAdmission(event);
5919
5956
  const terminalOwner = event.type === "agent_end" ? getAgentTerminalOwnerContext(event) : undefined;
5920
5957
  const maintenanceCheckpoint =
5921
- event.type === "agent_end" && event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted";
5958
+ event.type === "agent_end" &&
5959
+ event.stopReason === "maintenance" &&
5960
+ isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome);
5922
5961
  const terminalClaim =
5923
5962
  maintenanceCheckpoint || !terminalOwner
5924
5963
  ? undefined
@@ -6196,9 +6235,13 @@ export class AgentSession {
6196
6235
  this.#requestWorkerIntegrationAttempt();
6197
6236
  }
6198
6237
  // A maintenance agent_end is an internal checkpoint only while another
6199
- // continuation will follow. An aborted maintenance run is its terminal
6200
- // settlement and must reach public subscribers.
6201
- if (event.type === "agent_end" && event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted")
6238
+ // continuation will follow. Failed or aborted maintenance is terminal and
6239
+ // must reach public subscribers.
6240
+ if (
6241
+ event.type === "agent_end" &&
6242
+ event.stopReason === "maintenance" &&
6243
+ isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome)
6244
+ )
6202
6245
  return;
6203
6246
 
6204
6247
  // Hold agent_end until the prompt's finally and all earlier async event work
@@ -6889,12 +6932,15 @@ export class AgentSession {
6889
6932
  assistantMsg.stopReason !== "aborted" &&
6890
6933
  this.#retryAttempt > 0
6891
6934
  ) {
6935
+ // Clear ownership before notifying observers: agent_end may run while
6936
+ // the success event is awaiting its subscribers.
6937
+ const attempt = this.#retryAttempt;
6938
+ this.#retryAttempt = 0;
6892
6939
  await this.#emitSessionEvent({
6893
6940
  type: "auto_retry_end",
6894
6941
  success: true,
6895
- attempt: this.#retryAttempt,
6942
+ attempt,
6896
6943
  });
6897
- this.#retryAttempt = 0;
6898
6944
  // Settle the retry gate here, colocated with the success event, rather
6899
6945
  // than relying on the generic #resolveRetry() at the end of the
6900
6946
  // agent_end branch. That tail resolver is bypassed by every early
@@ -6983,7 +7029,7 @@ export class AgentSession {
6983
7029
 
6984
7030
  if (
6985
7031
  event.type === "agent_end" &&
6986
- !(event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted")
7032
+ !(event.stopReason === "maintenance" && isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome))
6987
7033
  ) {
6988
7034
  this.#releaseDeferredSdkFollowUps();
6989
7035
  }
@@ -6996,28 +7042,33 @@ export class AgentSession {
6996
7042
  // the same run on the rewritten context (no synthetic prompt), skipping
6997
7043
  // the goal-runtime / skill-state / retry / compaction finalization a
6998
7044
  // normal agent_end runs — none of that applies to an in-progress run.
6999
- // "aborted" settles without resuming; the generation guard drops the
7000
- // continuation if a newer prompt/abort has moved the run on.
7045
+ // Failed and aborted outcomes fall through to terminal handling. The
7046
+ // generation guard drops a successful continuation if a newer prompt/abort
7047
+ // has moved the run on.
7001
7048
  if (event.stopReason === "maintenance") {
7002
- this.#lastAssistantMessage = undefined;
7003
- const outcome = event.maintenanceOutcome;
7004
- if (
7005
- outcome &&
7006
- outcome !== "aborted" &&
7007
- !maintenanceWasDisposed &&
7008
- !this.#isDisposed &&
7009
- maintenanceGeneration !== undefined &&
7010
- this.#promptGeneration === maintenanceGeneration
7011
- ) {
7012
- this.#scheduleAgentContinue({
7013
- generation: maintenanceGeneration,
7014
- skipCompactionCheck: true,
7015
- resourceRunId: activePromptHandle,
7016
- maintenanceContinuation: true,
7017
- sdkRunToken: attemptScope ? this.#sdkRunTokensByAttemptScope.get(attemptScope) : undefined,
7018
- });
7049
+ if (isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome)) {
7050
+ this.#lastAssistantMessage = undefined;
7051
+ if (
7052
+ !maintenanceWasDisposed &&
7053
+ !this.#isDisposed &&
7054
+ maintenanceGeneration !== undefined &&
7055
+ this.#promptGeneration === maintenanceGeneration
7056
+ ) {
7057
+ this.#scheduleAgentContinue({
7058
+ generation: maintenanceGeneration,
7059
+ skipCompactionCheck: true,
7060
+ resourceRunId: activePromptHandle,
7061
+ maintenanceContinuation: true,
7062
+ sdkRunToken: attemptScope ? this.#sdkRunTokensByAttemptScope.get(attemptScope) : undefined,
7063
+ });
7064
+ }
7065
+ return;
7019
7066
  }
7020
- return;
7067
+ // Cancellation already owns teardown and terminal publication. Running
7068
+ // normal post-turn finalization here can deadlock abort/dispose while the
7069
+ // maintenance barrier is unwinding. Failed maintenance alone carries the
7070
+ // synthetic assistant error that must flow through terminal handling.
7071
+ if (event.maintenanceOutcome === "aborted") return;
7021
7072
  }
7022
7073
  const usage = this.getSessionStats().tokens;
7023
7074
  await this.#goalRuntime.onAgentEnd({
@@ -7083,6 +7134,13 @@ export class AgentSession {
7083
7134
  return;
7084
7135
  }
7085
7136
  this.#lastSuccessfulYieldToolCallId = undefined;
7137
+ if (event.stopReason === "maintenance" && event.maintenanceOutcome === "failed") {
7138
+ // The maintenance error already terminalized the run. Retrying this
7139
+ // synthetic assistant failure would resubmit the unchanged context that
7140
+ // maintenance failed to rewrite.
7141
+ this.#resolveRetry();
7142
+ return;
7143
+ }
7086
7144
 
7087
7145
  // Check for retryable errors first (overloaded, rate limit, server errors)
7088
7146
  if (this.#isRetryableError(msg)) {
@@ -7482,15 +7540,19 @@ export class AgentSession {
7482
7540
  const inheritedSdkRunToken = options?.continueQueuedOnly
7483
7541
  ? undefined
7484
7542
  : (options?.sdkRunToken ?? scheduledSdkRunToken);
7485
- const consumedSdkRunToken = acceptance.consumedQueuedMessages
7543
+ const consumedSdkRunTokens = acceptance.consumedQueuedMessages
7486
7544
  .map(message => this.#sdkRunTokensByQueuedMessage.get(message))
7487
- .find((token): token is string => token !== undefined);
7488
- const sdkRunToken = consumedSdkRunToken ?? inheritedSdkRunToken;
7545
+ .filter((token): token is string => token !== undefined);
7546
+ const sdkRunToken = consumedSdkRunTokens[0] ?? inheritedSdkRunToken;
7489
7547
  this.#fireQueuedPromotionHooks(acceptance.consumedQueuedMessages, {
7490
7548
  startsOwnRun: startsOwn,
7491
7549
  });
7492
7550
  if (startsOwn) this.#activeSdkRunToken = sdkRunToken;
7493
- this.#acceptSdkAttemptRun(handle, sdkRunToken);
7551
+ this.#acceptSdkAttemptRun(
7552
+ handle,
7553
+ sdkRunToken,
7554
+ consumedSdkRunTokens.length > 0 ? consumedSdkRunTokens : undefined,
7555
+ );
7494
7556
  options?.onRunAccepted?.(handle);
7495
7557
  // Keep the queued token available through the acceptance callback;
7496
7558
  // SDK follow-up owners bind it to the new attempt scope there.
@@ -8580,7 +8642,8 @@ export class AgentSession {
8580
8642
  ? this.#sdkRunTokensByAttemptScope.get(deliveryScope as AttemptScope)
8581
8643
  : undefined;
8582
8644
  const isTerminalAgentEnd =
8583
- event.type === "agent_end" && !(event.stopReason === "maintenance" && event.maintenanceOutcome !== "aborted");
8645
+ event.type === "agent_end" &&
8646
+ !(event.stopReason === "maintenance" && isContinuingMidRunMaintenanceOutcome(event.maintenanceOutcome));
8584
8647
  const finishAttempt = () => {
8585
8648
  if (!isTerminalAgentEnd) return;
8586
8649
  // A merged wake batch whose flush fired while still streaming cleared its
@@ -8625,6 +8688,9 @@ export class AgentSession {
8625
8688
  {
8626
8689
  type: "agent_start",
8627
8690
  ...(sdkRunToken ? { sdkRunToken } : {}),
8691
+ ...(deliveryScope
8692
+ ? { sdkRunTokens: this.#sdkRunCohortsByAttemptScope.get(deliveryScope as AttemptScope) }
8693
+ : {}),
8628
8694
  },
8629
8695
  undefined,
8630
8696
  deliveryScope,
@@ -9371,10 +9437,10 @@ export class AgentSession {
9371
9437
  // one here left signal exit orphaning the subprocess. Each runner clears
9372
9438
  // its set, so a graceful dispose running first makes this a no-op rather
9373
9439
  // than a double free.
9374
- this.#runToolSessionCleanups().catch((error: unknown) =>
9440
+ this.#claimToolSessionCleanups().catch((error: unknown) =>
9375
9441
  logger.warn("signal teardown: tool session cleanups failed", { error }),
9376
9442
  ),
9377
- this.#runToolSessionTransitionCleanups().catch((error: unknown) =>
9443
+ this.#claimToolSessionTransitionCleanups().catch((error: unknown) =>
9378
9444
  logger.warn("signal teardown: tool session transition cleanups failed", { error }),
9379
9445
  ),
9380
9446
  ]);
@@ -9500,6 +9566,11 @@ export class AgentSession {
9500
9566
  });
9501
9567
  }
9502
9568
 
9569
+ /** Test seam: await all currently admitted coordinator sidecar writes. */
9570
+ async awaitCoordinatorRuntimeStatePersistenceForTests(): Promise<void> {
9571
+ await this.#coordinatorPersistQueue;
9572
+ await this.#drainUnbarrieredCoordinatorPersists();
9573
+ }
9503
9574
  queueCoordinatorRuntimeStatePersistForTests(event: AgentSessionEvent, gate: Promise<void>): Promise<void> {
9504
9575
  this.#agentEventAdmission.set(event, {
9505
9576
  persistGeneration: this.#coordinatorPersistGeneration,
@@ -10226,11 +10297,12 @@ export class AgentSession {
10226
10297
  let wrappersByVersion = this.#guardedToolWrapperCache.get(tool);
10227
10298
  const cached = wrappersByVersion?.get(cacheKey);
10228
10299
  if (cached) return cached as T;
10229
- const wrapped = this.#wrapToolForCwdTransitionFence(
10300
+ const innerTool = tool instanceof ExtensionToolWrapper ? tool.getInnerTool() : tool;
10301
+ const guarded = this.#wrapToolForCwdTransitionFence(
10230
10302
  this.#wrapToolForWorkflowMutationGuard(
10231
10303
  this.#wrapToolForAcpPermission(
10232
10304
  guardToolForUltragoalAsk(
10233
- tool,
10305
+ innerTool,
10234
10306
  () => this.sessionManager.getCwd(),
10235
10307
  () => ({
10236
10308
  activeSkillState: this.getActiveSkillState(),
@@ -10241,6 +10313,7 @@ export class AgentSession {
10241
10313
  ),
10242
10314
  ),
10243
10315
  );
10316
+ const wrapped = (this.#extensionRunner ? new ExtensionToolWrapper(guarded, this.#extensionRunner) : guarded) as T;
10244
10317
  // The object published into `agent.state.tools` — and therefore the object the
10245
10318
  // agent loop actually dispatches to — is this guard wrapper, not the registry
10246
10319
  // entry. A wrapper built from a proven built-in inherits that proof; one built
@@ -10921,10 +10994,10 @@ export class AgentSession {
10921
10994
  preparedOptions.onPayload = sessionOnPayload;
10922
10995
  } else {
10923
10996
  const requestOnPayload = options.onPayload;
10924
- preparedOptions.onPayload = async (payload, model, callbackScope) => {
10925
- const sessionPayload = await sessionOnPayload(payload, model, callbackScope);
10997
+ preparedOptions.onPayload = async (payload, model, callbackScope, signal) => {
10998
+ const sessionPayload = await sessionOnPayload(payload, model, callbackScope, signal);
10926
10999
  const sessionResolvedPayload = sessionPayload ?? payload;
10927
- const requestPayload = await requestOnPayload(sessionResolvedPayload, model, callbackScope);
11000
+ const requestPayload = await requestOnPayload(sessionResolvedPayload, model, callbackScope, signal);
10928
11001
  return requestPayload ?? sessionResolvedPayload;
10929
11002
  };
10930
11003
  }
@@ -10935,9 +11008,9 @@ export class AgentSession {
10935
11008
  preparedOptions.onResponse = sessionOnResponse;
10936
11009
  } else {
10937
11010
  const requestOnResponse = options.onResponse;
10938
- preparedOptions.onResponse = async (response, model, callbackScope) => {
10939
- await sessionOnResponse(response, model, callbackScope);
10940
- await requestOnResponse(response, model, callbackScope);
11011
+ preparedOptions.onResponse = async (response, model, callbackScope, signal) => {
11012
+ await sessionOnResponse(response, model, callbackScope, signal);
11013
+ await requestOnResponse(response, model, callbackScope, signal);
10941
11014
  };
10942
11015
  }
10943
11016
  }
@@ -11818,6 +11891,11 @@ export class AgentSession {
11818
11891
  const releaseStartupPromptWaiter = this.#reserveStartupPromptWaiter();
11819
11892
  try {
11820
11893
  await this.#promptInternal(text, options, releaseStartupPromptWaiter);
11894
+ // Agent-core converts listener failures into an aborted response. Keep a
11895
+ // rejected persistence fence typed at the public prompt boundary instead
11896
+ // of reporting the misleading provider-level "Request was aborted".
11897
+ const appendRejection = this.sessionManager.getAppendIdentityMismatch();
11898
+ if (appendRejection) throw appendRejection;
11821
11899
  } finally {
11822
11900
  releaseStartupPromptWaiter();
11823
11901
  }
@@ -12409,6 +12487,14 @@ export class AgentSession {
12409
12487
  hasPendingNextTurnMessages = pendingNextTurnMessageCount > 0;
12410
12488
  const promptAttribution: "user" | "agent" | undefined =
12411
12489
  "attribution" in message ? message.attribution : undefined;
12490
+ let effectivePrompt = expandedText;
12491
+ const messageImages =
12492
+ "content" in message && Array.isArray(message.content)
12493
+ ? message.content.filter((content): content is ImageContent => content.type === "image")
12494
+ : undefined;
12495
+ let effectiveImages =
12496
+ options?.images ?? (messageImages && messageImages.length > 0 ? messageImages : undefined);
12497
+ let promptMessage = message;
12412
12498
  let phaseACompleted = false;
12413
12499
  let fileMentionMessages: AgentMessage[] = [];
12414
12500
  let beforeAgentStartResultMessages: BeforeAgentStartInternalMessage[] = [];
@@ -12429,7 +12515,49 @@ export class AgentSession {
12429
12515
  if (!phaseACompleted) {
12430
12516
  phaseACompleted = true;
12431
12517
  // Phase A (one-time side-effectful products; runs once).
12432
- const fileMentions = extractFileMentions(expandedText);
12518
+ const beforeAgentStartSystemPrompt = await this.#buildSystemPromptForAgentStart(effectivePrompt);
12519
+ hindsightRecall = this.getHindsightSessionState()?.getRecallSnippetForInjection();
12520
+ planReferenceMessage = await this.#buildPlanReferenceMessage();
12521
+ // Emit before_agent_start extension event. Race hook completion with
12522
+ // prompt cancellation so a wedged hook cannot retain SDK prompt authority.
12523
+ if (this.#extensionRunner?.hasHandlers("before_agent_start")) this.#markRetryReplayUnsafe();
12524
+ if (this.#extensionRunner) {
12525
+ const result = await this.#awaitPromptPreflight(
12526
+ generation,
12527
+ preflightSignal,
12528
+ this.#extensionRunner.emitBeforeAgentStart(
12529
+ effectivePrompt,
12530
+ effectiveImages,
12531
+ beforeAgentStartSystemPrompt,
12532
+ preflightSignal,
12533
+ ),
12534
+ );
12535
+ if (result?.messages) beforeAgentStartResultMessages = [...result.messages];
12536
+ if (result?.prompt !== undefined) effectivePrompt = result.prompt;
12537
+ if (result && Object.hasOwn(result, "images")) effectiveImages = result.images;
12538
+ if (result?.prompt !== undefined || (result && Object.hasOwn(result, "images"))) {
12539
+ if (
12540
+ promptMessage.role === "user" ||
12541
+ promptMessage.role === "developer" ||
12542
+ promptMessage.role === "custom"
12543
+ ) {
12544
+ promptMessage = {
12545
+ ...promptMessage,
12546
+ content: [{ type: "text", text: effectivePrompt }, ...(effectiveImages ?? [])],
12547
+ };
12548
+ }
12549
+ }
12550
+ if (result?.systemPrompt !== undefined) {
12551
+ this.agent.setSystemPrompt(result.systemPrompt);
12552
+ } else if (result?.prompt !== undefined) {
12553
+ this.agent.setSystemPrompt(await this.#buildSystemPromptForAgentStart(effectivePrompt));
12554
+ } else {
12555
+ this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
12556
+ }
12557
+ } else {
12558
+ this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
12559
+ }
12560
+ const fileMentions = extractFileMentions(effectivePrompt);
12433
12561
  if (fileMentions.length > 0) {
12434
12562
  const cwd = this.sessionManager.getCwd();
12435
12563
  // Collect resolved paths already shown (read or mentioned) in the
@@ -12457,31 +12585,6 @@ export class AgentSession {
12457
12585
  recentlyShownPaths,
12458
12586
  });
12459
12587
  }
12460
- const beforeAgentStartSystemPrompt = await this.#buildSystemPromptForAgentStart(expandedText);
12461
- hindsightRecall = this.getHindsightSessionState()?.getRecallSnippetForInjection();
12462
- planReferenceMessage = await this.#buildPlanReferenceMessage();
12463
- // Emit before_agent_start extension event. Race hook completion with
12464
- // prompt cancellation so a wedged hook cannot retain SDK prompt authority.
12465
- if (this.#extensionRunner?.hasHandlers("before_agent_start")) this.#markRetryReplayUnsafe();
12466
- if (this.#extensionRunner) {
12467
- const result = await this.#awaitPromptPreflight(
12468
- generation,
12469
- preflightSignal,
12470
- this.#extensionRunner.emitBeforeAgentStart(
12471
- expandedText,
12472
- options?.images,
12473
- beforeAgentStartSystemPrompt,
12474
- ),
12475
- );
12476
- if (result?.messages) beforeAgentStartResultMessages = [...result.messages];
12477
- if (result?.systemPrompt !== undefined) {
12478
- this.agent.setSystemPrompt(result.systemPrompt);
12479
- } else {
12480
- this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
12481
- }
12482
- } else {
12483
- this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
12484
- }
12485
12588
  // Invoke first-party internal before-agent-start contributors. These
12486
12589
  // run alongside the extension runner (not via user-loaded hooks) and
12487
12590
  // append through the same custom-message attribution path. Errors are nonfatal.
@@ -12492,8 +12595,8 @@ export class AgentSession {
12492
12595
  generation,
12493
12596
  preflightSignal,
12494
12597
  contributor({
12495
- prompt: expandedText,
12496
- images: options?.images,
12598
+ prompt: effectivePrompt,
12599
+ images: effectiveImages,
12497
12600
  sessionId: this.sessionId,
12498
12601
  }),
12499
12602
  );
@@ -12551,7 +12654,7 @@ export class AgentSession {
12551
12654
  }
12552
12655
 
12553
12656
  const promptIndex = messages.length;
12554
- messages.push(message);
12657
+ messages.push(promptMessage);
12555
12658
 
12556
12659
  // Re-present captured pending next-turn messages (never drained here).
12557
12660
  // Reclassify deferred envelopes before injecting them into the new
@@ -14200,6 +14303,25 @@ export class AgentSession {
14200
14303
  await this.refreshBaseSystemPrompt();
14201
14304
  }
14202
14305
 
14306
+ /** Refresh the live registered-skill catalog after install/remove/reload. */
14307
+ async reloadSkills(cwd = this.sessionManager.getCwd()): Promise<void> {
14308
+ if (!this.#reloadSkills) return;
14309
+ const reloaded = await this.#reloadSkills(cwd);
14310
+ const previousSkills = this.#skills;
14311
+ const previousWarnings = this.#skillWarnings;
14312
+ this.#skills = reloaded.skills;
14313
+ this.#skillWarnings = reloaded.warnings;
14314
+ try {
14315
+ await this.refreshBaseSystemPrompt();
14316
+ } catch (error) {
14317
+ this.#skills = previousSkills;
14318
+ this.#skillWarnings = previousWarnings;
14319
+ await this.refreshBaseSystemPrompt().catch(() => {});
14320
+ throw error;
14321
+ }
14322
+ this.#onSkillsReloaded?.(reloaded.skills, reloaded.warnings);
14323
+ }
14324
+
14203
14325
  /**
14204
14326
  * Retire the cached workspace tree after a rescope so the next turn re-scans
14205
14327
  * at the new cwd instead of re-presenting the abandoned launcher root.
@@ -18407,18 +18529,23 @@ export class AgentSession {
18407
18529
  * prune → promote → compact machinery and returns an explicit outcome.
18408
18530
  *
18409
18531
  * A non-"not-needed" outcome ends the current run losslessly
18410
- * (`agent_end.stopReason === "maintenance"`); the `agent_end` handler is the
18411
- * single continuation owner that resumes the run with no synthetic prompt.
18412
- * This method never self-continues, runs goal-runtime hooks, clears skill
18413
- * state, or emits a user-facing pause.
18532
+ * (`agent_end.stopReason === "maintenance"`). The `agent_end` handler resumes
18533
+ * committed maintenance with no synthetic prompt and terminalizes failed or
18534
+ * aborted maintenance. This method never self-continues, runs goal-runtime
18535
+ * hooks, clears skill state, or emits a user-facing pause.
18414
18536
  */
18415
18537
  async #runMidRunMaintenance(
18416
18538
  context: AgentContext,
18417
18539
  lifecycle: MidRunMaintenanceLifecycle,
18418
18540
  ): Promise<ContextMaintenanceResult> {
18419
- const result = (outcome: MidRunMaintenanceOutcome, releaseCurrentContext = false): ContextMaintenanceResult => ({
18541
+ const result = (
18542
+ outcome: MidRunMaintenanceOutcome,
18543
+ releaseCurrentContext = false,
18544
+ errorMessage?: string,
18545
+ ): ContextMaintenanceResult => ({
18420
18546
  outcome,
18421
18547
  ...(releaseCurrentContext ? { releaseCurrentContext: true } : {}),
18548
+ ...(errorMessage ? { errorMessage } : {}),
18422
18549
  });
18423
18550
  if (this.#isDisposed) return { outcome: "aborted" };
18424
18551
  const invocationController = new AbortController();
@@ -18529,7 +18656,13 @@ export class AgentSession {
18529
18656
  if (compactionStatus.kind === "aborted") {
18530
18657
  return compactionStatus.source === "hook" ? result("not-needed") : result("aborted");
18531
18658
  }
18532
- return result("failed");
18659
+ return result(
18660
+ "failed",
18661
+ false,
18662
+ compactionStatus.kind === "failed"
18663
+ ? compactionStatus.errorMessage
18664
+ : "Context maintenance could not commit a compaction before the next model request.",
18665
+ );
18533
18666
  } finally {
18534
18667
  this.#activeMidRunBarrierControllers.delete(invocationController);
18535
18668
  }
@@ -19356,10 +19489,10 @@ export class AgentSession {
19356
19489
  isError: message.isError,
19357
19490
  content: this.#normalizeProviderReplayValue(message.content),
19358
19491
  };
19359
- case "bashExecution":
19492
+ case "pythonExecution":
19360
19493
  return {
19361
19494
  role: message.role,
19362
- command: message.command,
19495
+ code: message.code,
19363
19496
  output: message.output,
19364
19497
  exitCode: message.exitCode,
19365
19498
  cancelled: message.cancelled,
@@ -19377,12 +19510,13 @@ export class AgentSession {
19377
19510
  : undefined,
19378
19511
  excludeFromContext: message.excludeFromContext,
19379
19512
  };
19380
- case "pythonExecution":
19513
+ case "bashExecution":
19381
19514
  return {
19382
19515
  role: message.role,
19383
- code: message.code,
19516
+ command: message.command,
19384
19517
  output: message.output,
19385
19518
  exitCode: message.exitCode,
19519
+ signal: message.signal,
19386
19520
  cancelled: message.cancelled,
19387
19521
  meta: message.meta
19388
19522
  ? {
@@ -19643,12 +19777,15 @@ export class AgentSession {
19643
19777
  */
19644
19778
  #maintenanceProviderTransport(): {
19645
19779
  sessionId: string | undefined;
19780
+ providerSessionId: string | undefined;
19646
19781
  providerSessionState: Map<string, ProviderSessionState>;
19647
19782
  preferWebsockets: boolean | undefined;
19648
19783
  remoteCompactionFallbackHealth: RemoteCompactionFallbackHealthHooks;
19649
19784
  } {
19785
+ const providerSessionId = this.agent.providerSessionId ?? this.agent.sessionId;
19650
19786
  return {
19651
- sessionId: this.agent.providerSessionId ?? this.agent.sessionId,
19787
+ sessionId: providerSessionId,
19788
+ providerSessionId,
19652
19789
  providerSessionState: this.#providerSessionState,
19653
19790
  preferWebsockets: this.agent.preferWebsockets,
19654
19791
  remoteCompactionFallbackHealth: this.#remoteCompactionFallbackHealth,
@@ -20078,6 +20215,10 @@ export class AgentSession {
20078
20215
  const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
20079
20216
  let compactResult: CompactionResult | undefined;
20080
20217
  let lastError: unknown;
20218
+ // Every candidate that was tried and failed, in order. Only the last failure
20219
+ // used to reach the user, which named whichever same-provider fallback the
20220
+ // chain ended on and hid that the session model itself had already failed.
20221
+ const candidateFailures: Array<{ model: string; message: string }> = [];
20081
20222
 
20082
20223
  for (const candidate of candidates) {
20083
20224
  const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
@@ -20111,6 +20252,7 @@ export class AgentSession {
20111
20252
  const message = error instanceof Error ? error.message : String(error);
20112
20253
  if (this.#isCompactionAuthFailure(error)) {
20113
20254
  lastError = this.#buildCompactionAuthError();
20255
+ candidateFailures.push({ model: `${candidate.provider}/${candidate.id}`, message });
20114
20256
  break;
20115
20257
  }
20116
20258
  const retryAfterMs = this.#parseRetryAfterMsFromError(message);
@@ -20123,6 +20265,7 @@ export class AgentSession {
20123
20265
  isUsageLimitError(message));
20124
20266
  if (!shouldRetry) {
20125
20267
  lastError = error;
20268
+ candidateFailures.push({ model: `${candidate.provider}/${candidate.id}`, message });
20126
20269
  break;
20127
20270
  }
20128
20271
 
@@ -20147,6 +20290,7 @@ export class AgentSession {
20147
20290
  model: `${candidate.provider}/${candidate.id}`,
20148
20291
  });
20149
20292
  lastError = error;
20293
+ candidateFailures.push({ model: `${candidate.provider}/${candidate.id}`, message });
20150
20294
  break; // Exit retry loop, continue to next candidate
20151
20295
  }
20152
20296
  // No more candidates - we have to wait
@@ -20172,7 +20316,9 @@ export class AgentSession {
20172
20316
 
20173
20317
  if (!compactResult) {
20174
20318
  if (lastError) {
20175
- throw lastError;
20319
+ throw candidateFailures.length > 1
20320
+ ? describeCompactionCandidateFailures(candidateFailures, lastError)
20321
+ : lastError;
20176
20322
  }
20177
20323
  throw new Error("Compaction failed: no available model");
20178
20324
  }
@@ -20286,7 +20432,7 @@ export class AgentSession {
20286
20432
  ? `Context overflow recovery failed: ${errorMessage}`
20287
20433
  : `Auto-compaction failed: ${errorMessage}`,
20288
20434
  });
20289
- return { kind: "failed" };
20435
+ return { kind: "failed", errorMessage };
20290
20436
  } finally {
20291
20437
  if (this.#autoCompactionAbortController === autoCompactionAbortController) {
20292
20438
  this.#autoCompactionAbortController = undefined;
@@ -22247,6 +22393,7 @@ export class AgentSession {
22247
22393
  command,
22248
22394
  output: result.output,
22249
22395
  exitCode: result.exitCode,
22396
+ signal: result.signal,
22250
22397
  cancelled: result.cancelled,
22251
22398
  truncated: result.truncated,
22252
22399
  meta,
@@ -22350,7 +22497,10 @@ export class AgentSession {
22350
22497
  onChunk,
22351
22498
  signal: abortController.signal,
22352
22499
  });
22353
- this.recordPythonResult(code, result, options);
22500
+ // A retained kernel can finish after strict session close has fenced
22501
+ // persistence. The caller still receives its completed result, but it
22502
+ // must not reopen a closing session to append history.
22503
+ if (!this.#evalExecutionDisposing) this.recordPythonResult(code, result, options);
22354
22504
  return result;
22355
22505
  })();
22356
22506
  return await this.trackEvalExecution(execution, abortController);