@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
@@ -32,8 +32,10 @@ import {
32
32
  $flag,
33
33
  getAgentDbPath,
34
34
  getAgentDir,
35
+ getAgentProfileAuthority,
35
36
  getProjectDir,
36
37
  logger,
38
+ normalizePathForComparison,
37
39
  postmortem,
38
40
  prompt,
39
41
  Snowflake,
@@ -91,10 +93,14 @@ import { CustomToolAdapter } from "../extensibility/custom-tools/wrapper";
91
93
  import {
92
94
  createCustomToolSettings,
93
95
  type ExtensionContext,
96
+ type ExtensionEvent,
94
97
  type ExtensionFactory,
95
98
  ExtensionRunner,
96
99
  ExtensionToolWrapper,
97
100
  type ExtensionUIContext,
101
+ type FunctionHook,
102
+ type FunctionHookEventType,
103
+ type FunctionHookResult,
98
104
  type LoadExtensionsResult,
99
105
  loadExtensionFromFactory,
100
106
  type ToolDefinition,
@@ -168,6 +174,7 @@ import {
168
174
  unregisterOwnedRegistration,
169
175
  } from "../session/terminal-abort";
170
176
  import { formatNoModelsAvailableFallback } from "../setup/model-onboarding-guidance";
177
+ import { getSkillFilesystemIdentity } from "../skill-state/canonical-skills";
171
178
  import {
172
179
  type BuildSystemPromptResult,
173
180
  buildSystemPrompt as buildSystemPromptInternal,
@@ -197,6 +204,11 @@ import { EventBus } from "../utils/event-bus";
197
204
  import { buildNamedToolChoice, buildNamedToolChoiceResult } from "../utils/tool-choice";
198
205
  import type { WorkspaceTree } from "../workspace-tree";
199
206
  import { createSessionLifecycleService } from "./lifecycle/client";
207
+ import {
208
+ isStableSessionApiKeyCredentialType,
209
+ lookupSessionApiKey,
210
+ resolveLiveSessionApiKeyModel,
211
+ } from "./session-api-key";
200
212
  import {
201
213
  attachLifecycleStartupCapability,
202
214
  lifecycleMcpStartupTimeoutOption,
@@ -433,6 +445,8 @@ export interface CreateAgentSessionOptions {
433
445
  cwd?: string;
434
446
  /** Global config directory. Default: ~/.gjc/agent */
435
447
  agentDir?: string;
448
+ /** Resolver-owned classification for `agentDir`; required when authority must survive HOME/config refreshes. */
449
+ profileAuthority?: "default" | "custom";
436
450
  /** Spawns to allow. Default: "*" */
437
451
  spawns?: string;
438
452
 
@@ -776,10 +790,11 @@ export async function discoverSkills(
776
790
  */
777
791
  export async function discoverContextFiles(
778
792
  cwd?: string,
779
- _agentDir?: string,
793
+ agentDir?: string,
780
794
  ): Promise<Array<{ path: string; content: string; depth?: number }>> {
781
795
  return await loadContextFilesInternal({
782
796
  cwd: cwd ?? getProjectDir(),
797
+ agentDir,
783
798
  });
784
799
  }
785
800
 
@@ -816,6 +831,8 @@ export interface BuildSystemPromptOptions {
816
831
  skills?: Skill[];
817
832
  contextFiles?: Array<{ path: string; content: string }>;
818
833
  cwd?: string;
834
+ agentDir?: string;
835
+ profileAuthority?: "default" | "custom";
819
836
  appendPrompt?: string;
820
837
  repeatToolDescriptions?: boolean;
821
838
  }
@@ -829,6 +846,8 @@ export interface BuildSystemPromptOptions {
829
846
  export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
830
847
  return await buildSystemPromptInternal({
831
848
  cwd: options.cwd,
849
+ agentDir: options.agentDir,
850
+ profileAuthority: options.profileAuthority,
832
851
  skills: options.skills,
833
852
  contextFiles: options.contextFiles,
834
853
  appendSystemPrompt: options.appendPrompt,
@@ -1010,14 +1029,89 @@ export function createPluginHooksExtension(hooks: ConstrainedPluginHook[]): Exte
1010
1029
  if (!normalized) throw new Error("Hook normalization invariant violated");
1011
1030
  const registrationEvent = normalized.runtimeEvent;
1012
1031
  const target = normalized.toolName === "*" ? undefined : normalized.toolName;
1013
- const handler = target
1014
- ? (event: { toolName?: string; tool?: { name?: string }; name?: string }, ...rest: unknown[]) => {
1015
- const toolName = event?.toolName ?? event?.tool?.name ?? event?.name;
1016
- if (toolName !== target) return undefined;
1017
- return (hook.handler as (...a: unknown[]) => unknown)(event, ...rest);
1032
+ if (!hook.functionHook) {
1033
+ const legacyHandler: (...args: unknown[]) => unknown = target
1034
+ ? (event: unknown, ...rest: unknown[]) => {
1035
+ const record = event as { toolName?: string; tool?: { name?: string }; name?: string } | undefined;
1036
+ const toolName = record?.toolName ?? record?.tool?.name ?? record?.name;
1037
+ if (toolName !== target) return undefined;
1038
+ return (hook.handler as (...args: unknown[]) => unknown)(event, ...rest);
1039
+ }
1040
+ : (hook.handler as (...args: unknown[]) => unknown);
1041
+ (api.on as (event: string, handler: (...args: unknown[]) => void) => void)(
1042
+ registrationEvent,
1043
+ legacyHandler,
1044
+ );
1045
+ continue;
1046
+ }
1047
+ const handler: FunctionHook = async (invocation, _capabilities, next) => {
1048
+ const legacyResult = await (hook.handler as (...args: unknown[]) => unknown)(invocation.payload, undefined);
1049
+ if (legacyResult === undefined) return await next();
1050
+ if (invocation.eventType === "tool_call") {
1051
+ const result = legacyResult as { block?: unknown; reason?: unknown; input?: unknown };
1052
+ if (result.block === true) {
1053
+ return {
1054
+ action: "deny",
1055
+ reason:
1056
+ typeof result.reason === "string" ? result.reason : "Tool execution blocked by plugin hook",
1057
+ };
1058
+ }
1059
+ if (result.input !== undefined) {
1060
+ return {
1061
+ action: "continue",
1062
+ event: { ...invocation.payload, input: result.input as Record<string, unknown> } as ExtensionEvent,
1063
+ } as FunctionHookResult;
1064
+ }
1065
+ }
1066
+ if (invocation.eventType === "tool_result") {
1067
+ const result = legacyResult as { content?: unknown; details?: unknown; isError?: unknown };
1068
+ if (result.content !== undefined || result.details !== undefined || result.isError !== undefined) {
1069
+ return {
1070
+ action: "continue",
1071
+ event: {
1072
+ ...invocation.payload,
1073
+ ...(result.content === undefined ? {} : { content: result.content }),
1074
+ ...(result.details === undefined ? {} : { details: result.details }),
1075
+ ...(result.isError === undefined ? {} : { isError: result.isError }),
1076
+ },
1077
+ } as FunctionHookResult;
1078
+ }
1079
+ }
1080
+ if (invocation.eventType === "context" && (legacyResult as { messages?: unknown }).messages !== undefined) {
1081
+ return {
1082
+ action: "continue",
1083
+ event: {
1084
+ ...invocation.payload,
1085
+ messages: (legacyResult as { messages: unknown }).messages,
1086
+ } as ExtensionEvent,
1087
+ } as FunctionHookResult;
1088
+ }
1089
+ if (
1090
+ invocation.eventType.startsWith("session_before_") &&
1091
+ (legacyResult as { cancel?: unknown }).cancel === true
1092
+ ) {
1093
+ return { action: "deny", reason: "Session change blocked by plugin hook" };
1094
+ }
1095
+ if (invocation.eventType === "before_agent_start") {
1096
+ const result = legacyResult as { message?: unknown; systemPrompt?: unknown };
1097
+ if (result.message !== undefined || result.systemPrompt !== undefined) {
1098
+ return {
1099
+ action: "return",
1100
+ value: {
1101
+ ...(result.message === undefined ? {} : { messages: [result.message] }),
1102
+ ...(result.systemPrompt === undefined ? {} : { systemPrompt: result.systemPrompt }),
1103
+ },
1104
+ } as FunctionHookResult;
1018
1105
  }
1019
- : hook.handler;
1020
- (api.on as (event: string, handler: (...args: unknown[]) => unknown) => void)(registrationEvent, handler);
1106
+ }
1107
+ return await next();
1108
+ };
1109
+ const options = {
1110
+ ...(target === undefined ? {} : { target }),
1111
+ registrationId: hook.extensionId,
1112
+ ...(hook.grant ?? {}),
1113
+ };
1114
+ api.registerFunctionHook(registrationEvent as FunctionHookEventType, handler, options);
1021
1115
  }
1022
1116
  };
1023
1117
  }
@@ -1107,14 +1201,14 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
1107
1201
  function withEmbeddedDefaultGjcSkills(skills: Skill[]): Skill[] {
1108
1202
  const byName = new Map<string, Skill>();
1109
1203
  for (const skill of skills) {
1110
- const key = skill.name.toLowerCase();
1204
+ const key = getSkillFilesystemIdentity(skill.name);
1111
1205
  if (!byName.has(key)) byName.set(key, skill);
1112
1206
  }
1113
1207
  // The four public GJC workflow skills are a product invariant: even if a
1114
1208
  // caller-supplied or filesystem skill shares a name, the bundled definition
1115
1209
  // wins so workflow routing can never be silently hijacked.
1116
1210
  for (const defaultSkill of getEmbeddedDefaultGjcSkills()) {
1117
- byName.set(defaultSkill.name.toLowerCase(), defaultSkill);
1211
+ byName.set(getSkillFilesystemIdentity(defaultSkill.name), defaultSkill);
1118
1212
  }
1119
1213
  return [...byName.values()];
1120
1214
  }
@@ -1388,7 +1482,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1388
1482
  }
1389
1483
  const cwd = options.cwd ?? getProjectDir();
1390
1484
  const explicitMcpConfigPath = !isCanonicalSubSession && !options.mcpManager ? options.mcpConfigPath : undefined;
1391
- const agentDir = options.agentDir ?? getDefaultAgentDir();
1485
+ const agentDir = options.agentDir ?? options.settings?.getAgentDir() ?? getDefaultAgentDir();
1486
+ const profileAuthority =
1487
+ options.profileAuthority ??
1488
+ (options.agentDir !== undefined || options.settings !== undefined
1489
+ ? normalizePathForComparison(agentDir) !== normalizePathForComparison(getAgentDir())
1490
+ ? "custom"
1491
+ : getAgentProfileAuthority()
1492
+ : getAgentProfileAuthority());
1392
1493
  const eventBus = options.eventBus ?? new EventBus();
1393
1494
  const hasInjectedAuth = options.authStorage !== undefined || options.modelRegistry !== undefined;
1394
1495
 
@@ -1576,7 +1677,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1576
1677
  // session-context build, tool creation, MCP discovery, and extension discovery.
1577
1678
  const contextFilesResultPromise = options.contextFiles
1578
1679
  ? Promise.resolve({ contextFiles: options.contextFiles, warnings: [] })
1579
- : logger.time("discoverContextFiles", loadContextFilesResultInternal, { cwd });
1680
+ : logger.time("discoverContextFiles", loadContextFilesResultInternal, { cwd, agentDir, profileAuthority });
1580
1681
  contextFilesResultPromise.catch(() => {});
1581
1682
  const promptTemplatesPromise = options.promptTemplates
1582
1683
  ? Promise.resolve(options.promptTemplates)
@@ -1977,9 +2078,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1977
2078
  skills = withEmbeddedDefaultGjcSkills(options.skills);
1978
2079
  skillWarnings = [];
1979
2080
  } else if (settings.get("skills.enabled")) {
1980
- const skillsResult = await logger.time("loadSkills", loadSkills, {
2081
+ const skillsResult = await logger.time("discoverSkills", loadSkills, {
1981
2082
  ...settings.getGroup("skills"),
1982
2083
  agentDir,
2084
+ profileAuthority,
1983
2085
  cwd,
1984
2086
  disabledExtensions: settings.get("disabledExtensions"),
1985
2087
  });
@@ -2001,7 +2103,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2001
2103
  const rulesResult =
2002
2104
  options.rules !== undefined
2003
2105
  ? { items: options.rules, warnings: undefined }
2004
- : await loadCapability<Rule>(ruleCapability.id, { cwd, agentDir, settings });
2106
+ : await loadCapability<Rule>(ruleCapability.id, { cwd, agentDir, profileAuthority, settings });
2005
2107
  const rulebookRules: Rule[] = [];
2006
2108
  const alwaysApplyRules: Rule[] = [];
2007
2109
  for (const rule of rulesResult.items) {
@@ -2329,7 +2431,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2329
2431
  */
2330
2432
  const applyRescopedReadState = async (to: string): Promise<void> => {
2331
2433
  try {
2332
- const rediscovered = await loadContextFilesResultInternal({ cwd: to });
2434
+ const rediscovered = await loadContextFilesResultInternal({ cwd: to, agentDir, profileAuthority });
2333
2435
  contextFiles = rediscovered.contextFiles;
2334
2436
  } catch (error) {
2335
2437
  logger.warn("Failed to re-discover context files after session rescope", {
@@ -2338,15 +2440,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2338
2440
  }
2339
2441
  if (options.skills === undefined && settings.get("skills.enabled")) {
2340
2442
  try {
2341
- const reloaded = await loadSkills({
2342
- ...settings.getGroup("skills"),
2343
- agentDir,
2344
- cwd: to,
2345
- disabledExtensions: settings.get("disabledExtensions"),
2346
- });
2347
- skills = withEmbeddedDefaultGjcSkills(reloaded.skills);
2348
- if (!options.parentTaskPrefix) setActiveSkills(skills);
2349
- await session?.replaceSkills(skills);
2443
+ await session?.reloadSkills(to);
2350
2444
  } catch (error) {
2351
2445
  logger.warn("Failed to reload skills after session rescope", { error: safeErrorForLog(error) });
2352
2446
  }
@@ -2378,6 +2472,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2378
2472
  return sessionManager.getCwd();
2379
2473
  },
2380
2474
  hasUI: options.hasUI ?? false,
2475
+ profileAuthority,
2381
2476
  workflowGateEligible: true,
2382
2477
  enableLsp,
2383
2478
  get hasEditTool() {
@@ -2696,6 +2791,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2696
2791
  }
2697
2792
  : {}),
2698
2793
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
2794
+ getSessionAgentDir: () => session?.getSessionAgentDir() ?? options.agentDir ?? settings.getAgentDir(),
2699
2795
  getEvalKernelOwnerId: () => evalKernelOwnerId,
2700
2796
  assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
2701
2797
  trackEvalExecution: (execution, abortController) =>
@@ -2708,13 +2804,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2708
2804
  else signal.addEventListener("abort", () => resolve(), { once: true });
2709
2805
  return promise;
2710
2806
  },
2807
+ getSessionId: () => sessionManager.getSessionId(),
2711
2808
  // Subagents inherit the parent's manager; its registered endpoint is
2712
2809
  // authoritative for owned-registration keying and endpoint-first
2713
2810
  // manager lookup (the child's own id is never registered), so tools
2714
2811
  // pass the same endpoint the manager's completion callback resolves
2715
2812
  // (review thread P1). For a top-level session this equals the
2716
2813
  // session id.
2717
- getSessionId: () => AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
2814
+ getAsyncEndpointId: () => AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
2718
2815
  registerForegroundFoldParticipant: adapter =>
2719
2816
  session?.registerForegroundFoldParticipant(adapter) ?? (() => {}),
2720
2817
  hasForegroundBashBackgroundRequestHandler: () => session?.hasForegroundBashBackgroundRequestHandler() ?? false,
@@ -2976,7 +3073,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2976
3073
  cwdCapturingToolNames.push(...pluginToolResult.tools.map(tool => tool.name));
2977
3074
  }
2978
3075
  for (const q of pluginToolResult.quarantine) {
2979
- gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
3076
+ gjcFindings.add({
3077
+ identity: q.identity,
3078
+ surfaceId: q.surfaceId,
3079
+ code: q.code,
3080
+ message: q.message,
3081
+ decision: "quarantined",
3082
+ provenance: { source: "plugin-bundle", plugin: q.plugin },
3083
+ });
2980
3084
  logger.warn("Quarantined GJC plugin surface", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
2981
3085
  }
2982
3086
  } catch (error) {
@@ -3066,7 +3170,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3066
3170
  try {
3067
3171
  const { configs, quarantine } = await buildPluginMcpConfigs({ cwd });
3068
3172
  for (const q of quarantine) {
3069
- gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
3173
+ gjcFindings.add({
3174
+ identity: q.identity,
3175
+ surfaceId: q.surfaceId,
3176
+ code: q.code,
3177
+ message: q.message,
3178
+ decision: "quarantined",
3179
+ provenance: { source: "plugin-bundle", plugin: q.plugin },
3180
+ });
3070
3181
  logger.warn("Quarantined GJC plugin MCP", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
3071
3182
  }
3072
3183
  const pluginNames = new Set(Object.keys(configs));
@@ -3211,12 +3322,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3211
3322
  // Always-on constrained plugin hooks (validated registry surfaces). Additive
3212
3323
  // and a no-op without installed plugins; the loader denies all dangerous APIs.
3213
3324
  try {
3214
- const pluginHookResult = await loadConstrainedPluginHooks({ cwd });
3325
+ const pluginHookResult = await loadConstrainedPluginHooks({
3326
+ cwd,
3327
+ activationGeneration: gjcActivationGeneration,
3328
+ });
3215
3329
  if (pluginHookResult.hooks.length > 0) {
3216
3330
  inlineExtensions.push(createPluginHooksExtension(pluginHookResult.hooks));
3217
3331
  }
3218
3332
  for (const q of pluginHookResult.quarantine) {
3219
- gjcFindings.add({ identity: q.identity, surfaceId: q.surfaceId, code: q.code, message: q.message });
3333
+ gjcFindings.add({
3334
+ identity: q.identity,
3335
+ surfaceId: q.surfaceId,
3336
+ code: q.code,
3337
+ message: q.message,
3338
+ decision: "quarantined",
3339
+ provenance: { source: "plugin-bundle", plugin: q.plugin },
3340
+ });
3220
3341
  logger.warn("Quarantined GJC plugin hook", { plugin: q.plugin, surface: q.surfaceId, code: q.code });
3221
3342
  }
3222
3343
  } catch (error) {
@@ -3776,7 +3897,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3776
3897
  } else if (!toolRegistry.has("resolve")) {
3777
3898
  const resolveTool = await logger.time("createTools:resolve:session", HIDDEN_TOOLS.resolve, toolSession);
3778
3899
  if (resolveTool) {
3779
- const wrappedResolveTool = wrapToolWithMetaNotice(resolveTool);
3900
+ const resolveWithNotice = wrapToolWithMetaNotice(resolveTool);
3901
+ const wrappedResolveTool = extensionRunner
3902
+ ? (new ExtensionToolWrapper(resolveWithNotice, extensionRunner) as AgentTool)
3903
+ : resolveWithNotice;
3780
3904
  builtinCandidateTools.push(wrappedResolveTool);
3781
3905
  toolRegistry.set(wrappedResolveTool.name, wrappedResolveTool);
3782
3906
  builtinToolIdentities.add(wrappedResolveTool);
@@ -3914,6 +4038,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3914
4038
  // Live cwd: the prompt is rebuilt after a rescope, and describing the
3915
4039
  // retired launcher root there is what makes the model pick wrong paths.
3916
4040
  cwd: getLiveCwd(),
4041
+ agentDir,
4042
+ profileAuthority,
3917
4043
  skills,
3918
4044
  contextFiles,
3919
4045
  tools: promptTools,
@@ -3924,6 +4050,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
3924
4050
  appendSystemPrompt: appendPrompt,
3925
4051
  pluginAppendices: pluginSystemAppendices,
3926
4052
  repeatToolDescriptions,
4053
+ reasoningLanguage: settings.get("reasoningLanguage"),
3927
4054
  intentField,
3928
4055
  toolDiscoveryActive: effectiveDiscoveryMode === "all" || mcpDiscoveryEnabled,
3929
4056
  eagerTasks: resolveEagerTasks(),
@@ -4185,21 +4312,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4185
4312
  if (!obfuscator?.hasSecrets() || !obfuscateMessagesFn) return converted;
4186
4313
  return obfuscateMessagesFn(obfuscator, converted);
4187
4314
  };
4188
- const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal, scope?: AttemptScopeRef) => {
4315
+ const transformContext = async (messages: AgentMessage[], signal?: AbortSignal, scope?: AttemptScopeRef) => {
4189
4316
  // External Agent events dispatch listeners without awaiting them. The
4190
4317
  // session-owned barrier makes any pre-admission artifact transformation
4191
4318
  // visible before this provider context is normalized.
4192
4319
  await session?.awaitPendingContextTransformations();
4193
- return extensionRunner ? await extensionRunner.emitContext(messages, scope) : messages;
4320
+ return extensionRunner ? await extensionRunner.emitContext(messages, scope, signal) : messages;
4194
4321
  };
4195
4322
  const onPayload = extensionRunner
4196
- ? async (payload: unknown, _model?: Model, scope?: AttemptScopeRef) => {
4197
- return await extensionRunner.emitBeforeProviderRequest(payload, scope);
4323
+ ? async (payload: unknown, _model?: Model, scope?: AttemptScopeRef, signal?: AbortSignal) => {
4324
+ return await extensionRunner.emitBeforeProviderRequest(payload, scope, signal);
4198
4325
  }
4199
4326
  : undefined;
4200
4327
  const onResponse: SimpleStreamOptions["onResponse"] | undefined = extensionRunner
4201
- ? async (response, model, scope) => {
4202
- await extensionRunner.emitAfterProviderResponse(response, model, scope);
4328
+ ? async (response, model, scope, signal) => {
4329
+ await extensionRunner.emitAfterProviderResponse(response, model, scope, signal);
4203
4330
  }
4204
4331
  : undefined;
4205
4332
 
@@ -4257,6 +4384,32 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4257
4384
  ).seedNormalizedMessages(options.forkContextSeed.messages);
4258
4385
  }
4259
4386
 
4387
+ const resolvedCredentialScopes = new Map<string, string | undefined>();
4388
+ const resolveAgentApiKey = async (provider: string, signal?: AbortSignal): Promise<string> => {
4389
+ const liveModel = resolveLiveSessionApiKeyModel(sessionAgent?.state.model, model, provider);
4390
+ const hasExplicitScopePolicy =
4391
+ modelRegistry.authStorage.hasSessionCredentialSelector(provider, credentialSessionId) ||
4392
+ modelRegistry.authStorage.hasSessionCredentialAuto(provider, credentialSessionId);
4393
+ const resolution = await lookupSessionApiKey(
4394
+ modelRegistry,
4395
+ provider,
4396
+ credentialSessionId,
4397
+ liveModel,
4398
+ !hasExplicitScopePolicy,
4399
+ signal,
4400
+ );
4401
+ resolvedCredentialScopes.set(provider, resolution.credentialSessionId);
4402
+ return resolution.apiKey;
4403
+ };
4404
+ const resolvedCredentialType = (provider: string): "api_key" | "oauth" | undefined => {
4405
+ const scope = resolvedCredentialScopes.has(provider)
4406
+ ? resolvedCredentialScopes.get(provider)
4407
+ : credentialSessionId;
4408
+ return modelRegistry.authStorage.getEffectiveCredentialType(provider, scope, {
4409
+ owner: modelRegistry.getAuthStorageOwner(),
4410
+ });
4411
+ };
4412
+
4260
4413
  agent = new Agent({
4261
4414
  initialState: {
4262
4415
  systemPrompt,
@@ -4295,44 +4448,42 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4295
4448
  shouldPause: options.shouldPause,
4296
4449
  preferWebsockets: preferOpenAICodexWebsockets,
4297
4450
  getToolContext: tc => toolContextStore.getContext(tc),
4298
- getApiKey: async provider => {
4451
+ getApiKey: provider => {
4299
4452
  // AgentLoop asks by provider, but the active model carries the
4300
4453
  // model-scoped credential selector. Read it at call time so model
4301
4454
  // changes are honored after /new, fork, resume, or branch switches.
4302
- const liveModel = sessionAgent?.state.model ?? model;
4303
- const key =
4304
- liveModel?.provider === provider
4305
- ? await modelRegistry.getApiKey(liveModel, credentialSessionId)
4306
- : undefined;
4307
- const providerKey = key ?? (await modelRegistry.getApiKeyForProvider(provider, credentialSessionId));
4308
- if (!providerKey) {
4309
- throw new Error(`No API key found for provider "${provider}"`);
4310
- }
4311
- return providerKey;
4455
+ return resolveAgentApiKey(provider);
4312
4456
  },
4313
- getAuthCredentialType: provider => modelRegistry.getSessionCredentialType(provider, credentialSessionId),
4457
+ getAuthCredentialType: resolvedCredentialType,
4314
4458
  streamFn: async (streamModel, context, streamOptions) => {
4315
4459
  const requestStartedAt = performance.now();
4316
4460
  let stream: Awaited<ReturnType<typeof streamSimple>>;
4317
4461
  try {
4318
- const effectiveApiKey = await modelRegistry.getApiKey(streamModel, credentialSessionId);
4319
4462
  stream = await streamSimple(streamModel, context, {
4320
4463
  ...streamOptions,
4321
- apiKey: effectiveApiKey,
4322
- authCredentialType: modelRegistry.getSessionCredentialType(streamModel.provider, credentialSessionId),
4323
4464
  onAuthError: async (provider, oldKey, error) => {
4465
+ const resolvedScope = resolvedCredentialScopes.has(provider)
4466
+ ? resolvedCredentialScopes.get(provider)
4467
+ : credentialSessionId;
4324
4468
  await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
4325
4469
  signal: streamOptions?.signal,
4326
- sessionId: credentialSessionId,
4470
+ sessionId: resolvedScope,
4327
4471
  owner: modelRegistry.getAuthStorageOwner(),
4328
4472
  });
4329
4473
  logger.debug("Retrying provider request after credential invalidation", {
4330
4474
  provider,
4331
4475
  error: error instanceof Error ? error.message : String(error),
4332
4476
  });
4333
- return modelRegistry.getApiKey(streamModel, credentialSessionId, {
4334
- signal: streamOptions?.signal,
4335
- });
4477
+ const replacementApiKey = await resolveAgentApiKey(provider, streamOptions?.signal);
4478
+ if (
4479
+ !isStableSessionApiKeyCredentialType(
4480
+ streamOptions?.authCredentialType,
4481
+ resolvedCredentialType(provider),
4482
+ )
4483
+ ) {
4484
+ return undefined;
4485
+ }
4486
+ return replacementApiKey;
4336
4487
  },
4337
4488
  });
4338
4489
  } catch (error) {
@@ -4459,6 +4610,28 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4459
4610
  customCommands: customCommandsResult.commands,
4460
4611
  skills,
4461
4612
  skillWarnings,
4613
+ reloadSkills:
4614
+ options.skills === undefined
4615
+ ? async reloadCwd => {
4616
+ if (!settings.get("skills.enabled")) {
4617
+ return { skills: getEmbeddedDefaultGjcSkills(), warnings: [] };
4618
+ } else {
4619
+ const reloaded = await loadSkills({
4620
+ ...settings.getGroup("skills"),
4621
+ agentDir,
4622
+ profileAuthority,
4623
+ cwd: reloadCwd,
4624
+ disabledExtensions: settings.get("disabledExtensions"),
4625
+ });
4626
+ return { skills: withEmbeddedDefaultGjcSkills(reloaded.skills), warnings: reloaded.warnings };
4627
+ }
4628
+ }
4629
+ : undefined,
4630
+ onSkillsReloaded: (reloadedSkills, reloadedWarnings) => {
4631
+ skills = reloadedSkills;
4632
+ skillWarnings = reloadedWarnings;
4633
+ if (!options.parentTaskPrefix) setActiveSkills(reloadedSkills);
4634
+ },
4462
4635
  skillsSettings: settings.getGroup("skills"),
4463
4636
  modelRegistry,
4464
4637
  taskDepth,
@@ -56,18 +56,25 @@ function upstreamUrl(url: string, token: string): string {
56
56
  return endpoint.toString();
57
57
  }
58
58
 
59
- function waitForDrain(stream: Writable): Promise<void> {
59
+ function waitForDrain(stream: Writable): { promise: Promise<void>; cancel: () => void } {
60
60
  const { promise, reject, resolve } = Promise.withResolvers<void>();
61
+ let settled = false;
61
62
  const onDrain = (): void => done(resolve);
62
63
  const onError = (error: Error): void => done(() => reject(error));
64
+ const onClose = (): void => done(() => reject(new Error("downstream_closed")));
63
65
  const done = (callback: () => void): void => {
66
+ if (settled) return;
67
+ settled = true;
64
68
  stream.removeListener("drain", onDrain);
65
69
  stream.removeListener("error", onError);
70
+ stream.removeListener("close", onClose);
66
71
  callback();
67
72
  };
68
73
  stream.once("drain", onDrain);
69
74
  stream.once("error", onError);
70
- return promise;
75
+ stream.once("close", onClose);
76
+ if (stream.destroyed) onClose();
77
+ return { promise, cancel: onDrain };
71
78
  }
72
79
 
73
80
  const WEBSOCKET_CONNECTING = 0;
@@ -96,7 +103,9 @@ export async function startRelayPair(options: RelayOptions): Promise<RelayPair>
96
103
  const finished = Promise.withResolvers<void>();
97
104
  let closed = false;
98
105
  let completed = false;
99
- let downstreamBuffer = Buffer.alloc(0);
106
+ let downstreamChunks: Buffer[] = [];
107
+ let downstreamBytes = 0;
108
+ let downstreamDrain: { promise: Promise<void>; cancel: () => void } | undefined;
100
109
  const toWs: QueuedFrame[] = [];
101
110
  const toDownstream: QueuedFrame[] = [];
102
111
  let pendingToWs = 0;
@@ -123,6 +132,7 @@ export async function startRelayPair(options: RelayOptions): Promise<RelayPair>
123
132
  const close = async (error?: Error): Promise<void> => {
124
133
  if (closed) return finished.promise.catch(() => undefined);
125
134
  closed = true;
135
+ downstreamDrain?.cancel();
126
136
  detach();
127
137
  options.downstream.pause();
128
138
  if (ws.readyState === WEBSOCKET_OPEN || ws.readyState === WEBSOCKET_CONNECTING) ws.close();
@@ -175,33 +185,45 @@ export async function startRelayPair(options: RelayOptions): Promise<RelayPair>
175
185
  writingDownstream = true;
176
186
  if (frame.accounted) pendingToDownstream -= frame.bytes.length;
177
187
  try {
178
- if (!options.downstreamSink.write(frame.bytes)) await waitForDrain(options.downstreamSink);
188
+ if (!options.downstreamSink.write(frame.bytes) && !closed) {
189
+ downstreamDrain = waitForDrain(options.downstreamSink);
190
+ await downstreamDrain.promise;
191
+ }
179
192
  } catch (error) {
180
193
  await close(error instanceof Error ? error : new Error(String(error)));
181
194
  return;
182
195
  } finally {
196
+ downstreamDrain = undefined;
183
197
  writingDownstream = false;
184
198
  }
185
199
  void pumpDownstream();
186
200
  };
187
201
  const consumeLines = (chunk: Buffer): void => {
188
- downstreamBuffer = Buffer.concat([downstreamBuffer, chunk]);
189
- let newline = downstreamBuffer.indexOf(0x0a);
202
+ let start = 0;
203
+ let newline = chunk.indexOf(0x0a);
190
204
  while (newline >= 0) {
191
- const line = downstreamBuffer.subarray(0, newline);
192
- downstreamBuffer = downstreamBuffer.subarray(newline + 1);
193
- if (line.length === 0)
205
+ const length = downstreamBytes + newline - start;
206
+ if (length === 0)
194
207
  return fail({ type: "transport_error", code: "protocol_error", direction: "downstream->ws" });
195
- if (line.length > REQUEST_FRAME_BYTES)
208
+ if (length > REQUEST_FRAME_BYTES)
196
209
  return fail({ type: "transport_error", code: "frame_oversize", direction: "downstream->ws" });
210
+ downstreamChunks.push(chunk.subarray(start, newline));
211
+ const line = Buffer.concat(downstreamChunks, length);
212
+ downstreamChunks = [];
213
+ downstreamBytes = 0;
197
214
  if (options.validateDownstreamFrame && !options.validateDownstreamFrame(line.toString("utf8")))
198
215
  return fail({ type: "transport_error", code: "protocol_error", direction: "downstream->ws" });
199
216
  enqueue(toWs, "downstream->ws", line);
200
217
  if (closed) return;
201
- newline = downstreamBuffer.indexOf(0x0a);
218
+ start = newline + 1;
219
+ newline = chunk.indexOf(0x0a, start);
220
+ }
221
+ if (start < chunk.length) {
222
+ downstreamBytes += chunk.length - start;
223
+ if (downstreamBytes > REQUEST_FRAME_BYTES)
224
+ return fail({ type: "transport_error", code: "frame_oversize", direction: "downstream->ws" });
225
+ downstreamChunks.push(chunk.subarray(start));
202
226
  }
203
- if (downstreamBuffer.length > REQUEST_FRAME_BYTES)
204
- fail({ type: "transport_error", code: "frame_oversize", direction: "downstream->ws" });
205
227
  };
206
228
  const onData = (chunk: Buffer | string): void => consumeLines(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
207
229
  const onEnd = (): void => void close();
@@ -92,10 +92,10 @@ function brokerSessionRows(sessions: readonly unknown[]): BrokerSessionRow[] {
92
92
  }
93
93
 
94
94
  /** Exhausts strict broker `session.list` pages into one full session snapshot. */
95
- export async function listBrokerSessions(broker: SdkClient): Promise<BrokerSessionRow[]> {
95
+ export async function listBrokerSessions(broker: SdkClient, explicitSessionId?: string): Promise<BrokerSessionRow[]> {
96
96
  try {
97
97
  const pages = await traverseSessionList(
98
- {},
98
+ { ...(explicitSessionId ? { resolveSessionId: explicitSessionId } : {}) },
99
99
  async input => await broker.global("session.list", input),
100
100
  response => {
101
101
  brokerResult(response);
@@ -147,7 +147,7 @@ export async function runSdkServe(argv: string[]): Promise<void> {
147
147
  throw new Error("broker_unavailable: SDK broker is not reachable");
148
148
  }
149
149
  try {
150
- const sessionId = selectBrokerSession(await listBrokerSessions(broker), parsed.sessionId);
150
+ const sessionId = selectBrokerSession(await listBrokerSessions(broker, parsed.sessionId), parsed.sessionId);
151
151
  const endpoint = brokerResult(await broker.global("session.get_endpoint", { sessionId }));
152
152
  const url = typeof endpoint.url === "string" && endpoint.url ? endpoint.url : undefined;
153
153
  const token = typeof endpoint.token === "string" ? endpoint.token : "";