@gajae-code/coding-agent 0.15.6 → 0.16.1

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 (270) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/args.d.ts +2 -0
  5. package/dist/types/cli/root-flags.d.ts +14 -0
  6. package/dist/types/cli/update-cli.d.ts +14 -0
  7. package/dist/types/cli.d.ts +7 -0
  8. package/dist/types/commands/launch.d.ts +10 -0
  9. package/dist/types/commands/sdk.d.ts +2 -0
  10. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  11. package/dist/types/config/billing-path.d.ts +25 -0
  12. package/dist/types/config/config-schema-version.d.ts +13 -1
  13. package/dist/types/config/file-lock.d.ts +5 -0
  14. package/dist/types/config/model-registry.d.ts +22 -0
  15. package/dist/types/config/settings-schema.d.ts +27 -8
  16. package/dist/types/config/settings.d.ts +2 -0
  17. package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
  18. package/dist/types/crash/record-loader.d.ts +15 -0
  19. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  20. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  21. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  22. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  23. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  24. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
  25. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  26. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  27. package/dist/types/main.d.ts +1 -0
  28. package/dist/types/master-mode/context.d.ts +21 -0
  29. package/dist/types/master-mode/first-request.d.ts +34 -0
  30. package/dist/types/master-mode/snapshot.d.ts +32 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  32. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  33. package/dist/types/modes/components/model-selector.d.ts +4 -0
  34. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  35. package/dist/types/modes/components/session-selector.d.ts +1 -1
  36. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  37. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  38. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  39. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  40. package/dist/types/modes/interactive-mode.d.ts +16 -2
  41. package/dist/types/modes/print-mode.d.ts +17 -0
  42. package/dist/types/modes/theme/theme.d.ts +2 -1
  43. package/dist/types/modes/types.d.ts +2 -0
  44. package/dist/types/modes/ui-language.d.ts +7 -1
  45. package/dist/types/sdk/broker/broker.d.ts +79 -2
  46. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  47. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  48. package/dist/types/sdk/broker/index.d.ts +3 -0
  49. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  50. package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
  51. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  52. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  53. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  54. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  55. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  56. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  57. package/dist/types/sdk/broker/state-version.d.ts +21 -1
  58. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  59. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  60. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  61. package/dist/types/sdk/bus/index.d.ts +9 -1
  62. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  63. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  64. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  65. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  66. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  67. package/dist/types/sdk/cli/index.d.ts +1 -0
  68. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  69. package/dist/types/sdk/cli/rows.d.ts +12 -5
  70. package/dist/types/sdk/cli/session-cli.d.ts +20 -3
  71. package/dist/types/sdk/client/client.d.ts +3 -2
  72. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  73. package/dist/types/sdk/host/control/index.d.ts +1 -1
  74. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  75. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  76. package/dist/types/sdk/host/events.d.ts +1 -0
  77. package/dist/types/sdk/host/host.d.ts +10 -0
  78. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  79. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  80. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +70 -17
  82. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  83. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  84. package/dist/types/sdk/lifecycle-session.d.ts +3 -1
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  86. package/dist/types/sdk/receipt-state.d.ts +2 -0
  87. package/dist/types/sdk/router/session-router.d.ts +6 -0
  88. package/dist/types/sdk/session.d.ts +16 -0
  89. package/dist/types/sdk/startup-capability.d.ts +2 -1
  90. package/dist/types/sdk/turn-result.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +52 -7
  92. package/dist/types/session/credential-store-errors.d.ts +1 -0
  93. package/dist/types/session/fold-coordinator.d.ts +29 -2
  94. package/dist/types/session/session-manager.d.ts +79 -2
  95. package/dist/types/session/yield-queue.d.ts +5 -4
  96. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  97. package/dist/types/tools/ask.d.ts +1 -0
  98. package/dist/types/tools/bash.d.ts +16 -0
  99. package/dist/types/tools/browser/launch.d.ts +10 -0
  100. package/dist/types/tools/browser/registry.d.ts +8 -0
  101. package/dist/types/tools/gh.d.ts +8 -0
  102. package/dist/types/tools/index.d.ts +10 -10
  103. package/dist/types/tools/job.d.ts +3 -1
  104. package/dist/types/utils/external-editor.d.ts +7 -0
  105. package/examples/extensions/README.md +1 -1
  106. package/examples/extensions/pirate.ts +7 -4
  107. package/package.json +7 -7
  108. package/scripts/generate-sdk-operation-inventory.ts +13 -1
  109. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  110. package/src/async/job-manager.ts +107 -9
  111. package/src/browser-backend.ts +29 -0
  112. package/src/cli/accounts-cli.ts +9 -5
  113. package/src/cli/args.ts +17 -0
  114. package/src/cli/fast-help.ts +1 -0
  115. package/src/cli/plugin-cli.ts +29 -0
  116. package/src/cli/root-flags.ts +5 -0
  117. package/src/cli/update-cli.ts +140 -23
  118. package/src/commands/launch.ts +86 -18
  119. package/src/commands/sdk.ts +135 -28
  120. package/src/config/autorouting-tier-map.ts +12 -0
  121. package/src/config/billing-path.ts +42 -0
  122. package/src/config/config-schema-version.ts +18 -1
  123. package/src/config/file-lock.ts +158 -83
  124. package/src/config/model-preset-registry.ts +178 -23
  125. package/src/config/model-profiles.ts +5 -5
  126. package/src/config/model-registry.ts +291 -42
  127. package/src/config/model-resolver.ts +5 -0
  128. package/src/config/settings-schema.ts +27 -8
  129. package/src/config/settings.ts +51 -15
  130. package/src/coordinator-mcp/projection-scan.ts +27 -6
  131. package/src/coordinator-mcp/question-state.ts +9 -11
  132. package/src/coordinator-mcp/server.ts +115 -21
  133. package/src/crash/index-store.ts +2 -0
  134. package/src/crash/record-loader.ts +10 -0
  135. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  136. package/src/extensibility/extensions/runner.ts +27 -0
  137. package/src/extensibility/extensions/types.ts +8 -3
  138. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  139. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  140. package/src/extensibility/skills.ts +22 -12
  141. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  142. package/src/gjc-runtime/launch-worktree.ts +285 -4
  143. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  144. package/src/gjc-runtime/session-state-lock.ts +213 -14
  145. package/src/gjc-runtime/session-state-sidecar.ts +938 -39
  146. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  147. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
  148. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  149. package/src/hooks/skill-state.ts +6 -0
  150. package/src/internal-urls/docs-index.generated.ts +15 -15
  151. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  152. package/src/main.ts +61 -3
  153. package/src/master-mode/context.ts +50 -0
  154. package/src/master-mode/first-request.ts +65 -0
  155. package/src/master-mode/snapshot.ts +205 -0
  156. package/src/modes/acp/acp-agent.ts +610 -161
  157. package/src/modes/components/footer.ts +4 -2
  158. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  159. package/src/modes/components/model-selector.ts +76 -8
  160. package/src/modes/components/oauth-selector.ts +6 -1
  161. package/src/modes/components/session-selector.ts +23 -15
  162. package/src/modes/components/sessions-dashboard.ts +8 -5
  163. package/src/modes/components/settings-selector.ts +1 -0
  164. package/src/modes/components/status-line/model-name.ts +53 -0
  165. package/src/modes/components/status-line/priority-row.ts +107 -0
  166. package/src/modes/components/status-line/segments.ts +23 -9
  167. package/src/modes/components/tool-status-header.ts +151 -15
  168. package/src/modes/controllers/command-controller.ts +1 -1
  169. package/src/modes/controllers/event-controller.ts +30 -4
  170. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  171. package/src/modes/controllers/input-controller.ts +30 -2
  172. package/src/modes/controllers/selector-controller.ts +200 -98
  173. package/src/modes/interactive-mode.ts +36 -12
  174. package/src/modes/print-mode.ts +64 -0
  175. package/src/modes/prompt-action-autocomplete.ts +30 -4
  176. package/src/modes/runtime-init.ts +12 -9
  177. package/src/modes/theme/theme.ts +5 -0
  178. package/src/modes/types.ts +2 -0
  179. package/src/modes/ui-language.ts +49 -2
  180. package/src/modes/utils/ui-helpers.ts +22 -3
  181. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  182. package/src/prompts/system/master-mode.md +28 -0
  183. package/src/prompts/tools/ask.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -0
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/sdk/acp/adapter.ts +5 -0
  187. package/src/sdk/broker/broker.ts +1646 -27
  188. package/src/sdk/broker/endpoint-authority.ts +86 -0
  189. package/src/sdk/broker/ensure.ts +81 -3
  190. package/src/sdk/broker/index.ts +3 -0
  191. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  192. package/src/sdk/broker/lifecycle.ts +440 -75
  193. package/src/sdk/broker/master-capability.ts +232 -0
  194. package/src/sdk/broker/session-index.ts +121 -17
  195. package/src/sdk/broker/session-scope.ts +272 -0
  196. package/src/sdk/broker/spawn-authority.ts +1031 -0
  197. package/src/sdk/broker/spawn-substrate.ts +591 -0
  198. package/src/sdk/broker/startup-budget.ts +144 -2
  199. package/src/sdk/broker/state-version.ts +43 -14
  200. package/src/sdk/broker/transport.ts +1 -0
  201. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  202. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  203. package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
  204. package/src/sdk/bus/discord-daemon.ts +50 -10
  205. package/src/sdk/bus/index.ts +84 -7
  206. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  207. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  208. package/src/sdk/bus/notification-service.ts +22 -5
  209. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  210. package/src/sdk/bus/reconciliation-store.ts +55 -31
  211. package/src/sdk/bus/slack-conversation.ts +1 -1
  212. package/src/sdk/bus/slack-daemon.ts +72 -38
  213. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  214. package/src/sdk/cli/index.ts +1 -0
  215. package/src/sdk/cli/master-cli.ts +259 -0
  216. package/src/sdk/cli/rows.ts +41 -15
  217. package/src/sdk/cli/session-cli.ts +297 -26
  218. package/src/sdk/client/client.ts +14 -8
  219. package/src/sdk/host/control/dispatch.ts +101 -40
  220. package/src/sdk/host/control/index.ts +2 -0
  221. package/src/sdk/host/control/operations.ts +2 -0
  222. package/src/sdk/host/control/runtime-gate.ts +203 -11
  223. package/src/sdk/host/events.ts +1 -0
  224. package/src/sdk/host/host.ts +29 -2
  225. package/src/sdk/host/query/cursor.ts +52 -7
  226. package/src/sdk/host/query/handlers.ts +33 -12
  227. package/src/sdk/host/query/revision-store.ts +12 -0
  228. package/src/sdk/host/session-runtime.test.ts +673 -14
  229. package/src/sdk/host/session-runtime.ts +674 -103
  230. package/src/sdk/host/surface-policy.ts +3 -3
  231. package/src/sdk/lifecycle/broker-client.ts +28 -0
  232. package/src/sdk/lifecycle/service.ts +279 -20
  233. package/src/sdk/lifecycle-session.ts +17 -4
  234. package/src/sdk/prompt-deadline-manager.ts +2 -1
  235. package/src/sdk/protocol/operation-inventory.generated.json +105 -3
  236. package/src/sdk/protocol/operation-registry.ts +14 -4
  237. package/src/sdk/receipt-state.ts +10 -0
  238. package/src/sdk/router/session-router.ts +219 -118
  239. package/src/sdk/session.ts +416 -195
  240. package/src/sdk/startup-capability.ts +1 -0
  241. package/src/sdk/turn-result.ts +22 -0
  242. package/src/session/agent-session.ts +1628 -318
  243. package/src/session/blob-store.ts +15 -5
  244. package/src/session/credential-store-errors.ts +2 -0
  245. package/src/session/fold-coordinator.ts +40 -3
  246. package/src/session/internal/managed-session-storage.ts +153 -78
  247. package/src/session/session-manager.ts +850 -97
  248. package/src/session/yield-queue.ts +24 -10
  249. package/src/setup/frictionless-onboarding.ts +2 -0
  250. package/src/slash-commands/builtin-registry.ts +41 -0
  251. package/src/task/executor.ts +9 -4
  252. package/src/telemetry/events.ts +1138 -12
  253. package/src/tools/ask-contract.test.ts +64 -0
  254. package/src/tools/ask-contract.ts +36 -1
  255. package/src/tools/ask.ts +10 -8
  256. package/src/tools/bash-interactive.ts +11 -2
  257. package/src/tools/bash.ts +412 -75
  258. package/src/tools/browser/launch.ts +37 -0
  259. package/src/tools/browser/registry.ts +99 -9
  260. package/src/tools/cron.ts +6 -4
  261. package/src/tools/descriptors.test.ts +15 -0
  262. package/src/tools/descriptors.ts +5 -1
  263. package/src/tools/gh.ts +104 -48
  264. package/src/tools/github-cache.ts +2 -2
  265. package/src/tools/index.ts +10 -6
  266. package/src/tools/job.ts +4 -1
  267. package/src/tools/monitor.ts +12 -9
  268. package/src/tools/resolve.ts +30 -10
  269. package/src/tools/tool-catalog.generated.ts +2 -2
  270. package/src/utils/external-editor.ts +72 -1
@@ -69,6 +69,21 @@ interface BeforeAgentStartCombinedResult {
69
69
  systemPrompt?: string[];
70
70
  }
71
71
 
72
+ const BEFORE_AGENT_START_RESULT_KEYS = new Set(["message", "systemPrompt"]);
73
+ const MAX_UNSUPPORTED_RESULT_FIELDS = 8;
74
+ const MAX_RESULT_FIELD_NAME_LENGTH = 80;
75
+
76
+ function unsupportedBeforeAgentStartResultFields(result: unknown): string[] {
77
+ if (result === null || typeof result !== "object" || Array.isArray(result)) return [];
78
+ const fields = Object.keys(result).filter(key => !BEFORE_AGENT_START_RESULT_KEYS.has(key));
79
+ const bounded = fields
80
+ .slice(0, MAX_UNSUPPORTED_RESULT_FIELDS)
81
+ .map(key => key.replace(/[^\x20-\x7e]/gu, "?").slice(0, MAX_RESULT_FIELD_NAME_LENGTH));
82
+ if (fields.length > MAX_UNSUPPORTED_RESULT_FIELDS)
83
+ bounded.push(`…and ${fields.length - MAX_UNSUPPORTED_RESULT_FIELDS} more`);
84
+ return bounded;
85
+ }
86
+
72
87
  export type ExtensionErrorListener = (error: ExtensionError) => void;
73
88
 
74
89
  /** Bounded timeout for session_shutdown handlers — generous but never infinite. */
@@ -247,6 +262,7 @@ export class ExtensionRunner {
247
262
  #getArtifactRangeFn: ExtensionContextActions["getArtifactRange"] = undefined;
248
263
 
249
264
  #getJobsFn: ExtensionContextActions["getJobs"] = undefined;
265
+ #onJobFoldFn: ExtensionContextActions["onJobFold"] = undefined;
250
266
  #sdkControlFn: ExtensionContextActions["sdkControl"] = undefined;
251
267
  #setSdkPermissionProviderFn: ExtensionContextActions["setSdkPermissionProvider"] = undefined;
252
268
  #setSdkClientBridgeFn: ExtensionContextActions["setSdkClientBridge"] = undefined;
@@ -378,6 +394,7 @@ export class ExtensionRunner {
378
394
  this.#getArtifactRangeFn = contextActions.getArtifactRange;
379
395
 
380
396
  this.#getJobsFn = contextActions.getJobs;
397
+ this.#onJobFoldFn = contextActions.onJobFold;
381
398
  this.#sdkControlFn = contextActions.sdkControl;
382
399
  this.#setSdkPermissionProviderFn = contextActions.setSdkPermissionProvider;
383
400
  this.#setSdkClientBridgeFn = contextActions.setSdkClientBridge;
@@ -662,6 +679,7 @@ export class ExtensionRunner {
662
679
  getArtifactRange: (id, offset, length) => this.#getArtifactRangeFn?.(id, offset, length),
663
680
 
664
681
  getJobs: () => this.#getJobsFn?.(),
682
+ onJobFold: listener => this.#onJobFoldFn?.(listener) ?? (() => {}),
665
683
  sdkControl: (operation, input) => this.#sdkControlFn?.(operation, input),
666
684
  setSdkPermissionProvider: provider => this.#setSdkPermissionProviderFn?.(provider),
667
685
  setSdkClientBridge: bridge => this.#setSdkClientBridgeFn?.(bridge),
@@ -679,6 +697,7 @@ export class ExtensionRunner {
679
697
  ...(this.#getExtensionsFn ? ["getExtensions"] : []),
680
698
  ...(this.#getArtifactRangeFn ? ["getArtifactRange"] : []),
681
699
  ...(this.#getJobsFn ? ["getJobs"] : []),
700
+ ...(this.#onJobFoldFn ? ["onJobFold"] : []),
682
701
  ...(this.#sdkControlFn ? ["sdkControl"] : []),
683
702
  ...(this.#invokeSkillFn ? ["invokeSkill"] : []),
684
703
  ...(this.#setPlanModeFn ? ["setPlanMode"] : []),
@@ -1108,6 +1127,14 @@ export class ExtensionRunner {
1108
1127
  const handlerResult = await this.#runHandlerWithTimeout(handler, event, ctx, ext, extensionHandlerTimeoutMs);
1109
1128
 
1110
1129
  if (handlerResult) {
1130
+ const unsupportedFields = unsupportedBeforeAgentStartResultFields(handlerResult);
1131
+ if (unsupportedFields.length > 0) {
1132
+ this.emitError({
1133
+ extensionPath: ext.path,
1134
+ event: "before_agent_start",
1135
+ error: `Unsupported before_agent_start result field(s): ${unsupportedFields.join(", ")}. Supported fields: message, systemPrompt.`,
1136
+ });
1137
+ }
1111
1138
  const result = handlerResult as BeforeAgentStartEventResult;
1112
1139
  if (result.message) {
1113
1140
  messages.push(result.message);
@@ -36,6 +36,7 @@ import type {
36
36
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@gajae-code/ai/utils/oauth/types";
37
37
  import type * as piCodingAgent from "@gajae-code/coding-agent";
38
38
  import type { AutocompleteItem, Component, EditorTheme, KeyId, TUI } from "@gajae-code/tui";
39
+ import type { JobFoldEvent } from "../../async/job-manager";
39
40
  import type { KeybindingsManager } from "../../config/keybindings";
40
41
  import type { ModelRegistry } from "../../config/model-registry";
41
42
  import type { ModelSelectorValue } from "../../config/model-selector-value";
@@ -511,7 +512,7 @@ export interface ExtensionContext {
511
512
  /** Run a control-surface mutation inside the session admission boundary. */
512
513
  withSdkControlMutation?<T>(body: () => Promise<T>): Promise<T>;
513
514
  cycleThinkingLevel(): ThinkingLevel | undefined;
514
- setQueueMode(kind: "steering" | "follow_up" | "interrupt", mode: unknown): boolean;
515
+ setQueueMode(kind: "steering" | "follow_up" | "tool_interrupt", mode: unknown): boolean;
515
516
  getSkillState(): unknown;
516
517
  getConfigItems(): unknown;
517
518
  getBranchCandidates(): unknown;
@@ -527,6 +528,8 @@ export interface ExtensionContext {
527
528
  | Promise<{ bytes: Uint8Array; totalBytes: number } | undefined>;
528
529
 
529
530
  getJobs(): unknown;
531
+ /** Subscribe to foreground-wait folds (`bash_folded`). Returns an unsubscribe function. */
532
+ onJobFold?(listener: (event: JobFoldEvent) => void): () => void;
530
533
  /** Typed skill and mode controls exposed to the SDK host. */
531
534
  invokeSkill?(
532
535
  name: string,
@@ -1103,7 +1106,7 @@ export type { ToolResultEventResult } from "../shared-events";
1103
1106
 
1104
1107
  export interface BeforeAgentStartEventResult {
1105
1108
  message?: Pick<CustomMessage, "customType" | "content" | "display" | "details" | "attribution">;
1106
- /** Replace the system prompt for this turn. If multiple extensions return this, they are chained. */
1109
+ /** Replace the system prompt for this turn. To append, spread event.systemPrompt into the returned array. */
1107
1110
  systemPrompt?: string[];
1108
1111
  }
1109
1112
 
@@ -1672,7 +1675,7 @@ export interface ExtensionContextActions {
1672
1675
  getActiveModelProfile?: () => string | undefined;
1673
1676
  withSdkControlMutation?: <T>(body: () => Promise<T>) => Promise<T>;
1674
1677
  cycleThinkingLevel?: () => ThinkingLevel | undefined;
1675
- setQueueMode?: (kind: "steering" | "follow_up" | "interrupt", mode: unknown) => boolean;
1678
+ setQueueMode?: (kind: "steering" | "follow_up" | "tool_interrupt", mode: unknown) => boolean;
1676
1679
  getSkillState?: () => unknown;
1677
1680
  getConfigItems?: () => unknown;
1678
1681
  getBranchCandidates?: () => unknown;
@@ -1687,6 +1690,8 @@ export interface ExtensionContextActions {
1687
1690
  | undefined
1688
1691
  | Promise<{ bytes: Uint8Array; totalBytes: number } | undefined>;
1689
1692
  getJobs?: () => unknown;
1693
+ /** Subscribe to foreground-wait folds so the SDK host can publish `bash_folded`. Returns an unsubscribe. */
1694
+ onJobFold?: (listener: (event: JobFoldEvent) => void) => () => void;
1690
1695
  setSdkPermissionProvider?: (
1691
1696
  provider:
1692
1697
  | ((
@@ -54,6 +54,11 @@ export type GjcBundleTransactionResult =
54
54
  const TAR_MAX_FILES = 8192;
55
55
  const TAR_MAX_FILE_BYTES = 16 * 1024 * 1024;
56
56
  const TAR_MAX_TOTAL_BYTES = 128 * 1024 * 1024;
57
+ // Preserve the existing aggregate file budget plus the maximum per-entry
58
+ // header/padding overhead and the two terminating TAR blocks.
59
+ const TAR_MAX_ARCHIVE_BYTES = TAR_MAX_TOTAL_BYTES + TAR_MAX_FILES * 1024 + 1024;
60
+ // A gzip stream can be slightly larger than its incompressible TAR payload.
61
+ const TAR_MAX_COMPRESSED_BYTES = TAR_MAX_ARCHIVE_BYTES + 1024 * 1024;
57
62
 
58
63
  function scopeRoot(scope: GjcPluginScope, cwd: string): string {
59
64
  return scope === "user" ? gjcPluginUserRoot() : gjcPluginProjectRoot(cwd);
@@ -95,6 +100,45 @@ export class GjcPluginSourceUnavailableError extends Error {
95
100
  }
96
101
  }
97
102
 
103
+ async function readTarballInput(tarPath: string, maxBytes: number): Promise<Buffer> {
104
+ let file: fs.FileHandle;
105
+ try {
106
+ file = await fs.open(tarPath, "r");
107
+ } catch {
108
+ throw new GjcPluginSourceUnavailableError();
109
+ }
110
+ try {
111
+ const stat = await file.stat();
112
+ if (!stat.isFile()) throw new GjcPluginSourceUnavailableError();
113
+ if (stat.size > maxBytes) {
114
+ throw new GjcPluginLoadError("security_policy", "GJC plugin tarball exceeds the archive input limit");
115
+ }
116
+ const raw = Buffer.allocUnsafe(stat.size);
117
+ let offset = 0;
118
+ while (offset < raw.byteLength) {
119
+ const { bytesRead } = await file.read(raw, offset, raw.byteLength - offset, offset);
120
+ if (bytesRead === 0) {
121
+ throw new GjcPluginLoadError("security_policy", "GJC plugin tarball changed while being read");
122
+ }
123
+ offset += bytesRead;
124
+ }
125
+ const probe = Buffer.allocUnsafe(1);
126
+ if ((await file.read(probe, 0, 1, offset)).bytesRead !== 0) {
127
+ throw new GjcPluginLoadError("security_policy", "GJC plugin tarball changed while being read");
128
+ }
129
+ return raw;
130
+ } catch (error) {
131
+ if (error instanceof GjcPluginLoadError || error instanceof GjcPluginSourceUnavailableError) throw error;
132
+ throw new GjcPluginSourceUnavailableError();
133
+ } finally {
134
+ await file.close().catch(() => {});
135
+ }
136
+ }
137
+
138
+ function isGunzipOutputLimitError(error: unknown): boolean {
139
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ERR_BUFFER_TOO_LARGE";
140
+ }
141
+
98
142
  interface ResolvedSource {
99
143
  dir: string;
100
144
  source: GjcPluginRegistrySource;
@@ -136,19 +180,15 @@ function tarHeaderChecksumOk(header: Uint8Array): boolean {
136
180
 
137
181
  /** Minimal, traversal/symlink-safe, resource-bounded extraction of a tar(.gz). */
138
182
  async function extractTarball(tarPath: string, destRoot: string): Promise<void> {
139
- // A missing or corrupt archive surfaces as a native fs/zlib error. Translate
140
- // it here so callers see the same typed source failure they get for every
141
- // other unreachable source, instead of a raw errno escaping the lifecycle.
142
- let raw: Buffer;
143
- try {
144
- raw = await fs.readFile(tarPath);
145
- } catch {
146
- throw new GjcPluginSourceUnavailableError();
147
- }
183
+ const isGzip = /\.(tgz|tar\.gz)$/i.test(tarPath);
184
+ const raw = await readTarballInput(tarPath, isGzip ? TAR_MAX_COMPRESSED_BYTES : TAR_MAX_ARCHIVE_BYTES);
148
185
  let buf: Buffer;
149
186
  try {
150
- buf = /\.(tgz|tar\.gz)$/i.test(tarPath) ? gunzipSync(raw) : raw;
151
- } catch {
187
+ buf = isGzip ? gunzipSync(raw, { maxOutputLength: TAR_MAX_ARCHIVE_BYTES }) : raw;
188
+ } catch (error) {
189
+ if (isGunzipOutputLimitError(error)) {
190
+ throw new GjcPluginLoadError("security_policy", "GJC plugin tarball exceeds the expansion limit");
191
+ }
152
192
  throw new GjcPluginLoadError("invalid_manifest", "GJC plugin tarball could not be decompressed");
153
193
  }
154
194
  const resolvedRoot = path.resolve(destRoot);
@@ -1,13 +1,13 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
- import { getTrustedHomeDir } from "@gajae-code/utils";
4
- import { findRepoRoot } from "../capability/fs";
3
+ import { getPluginsDir, getTrustedHomeDir } from "@gajae-code/utils";
4
+ import { findRepoRoot, invalidate as invalidateFsCache } from "../capability/fs";
5
5
  import type { Skill as CapabilitySkill } from "../capability/skill";
6
6
  import type { SkillsSettings } from "../config/settings-schema";
7
7
  import { resolveSkillScopeTrust } from "../config/skill-settings-defaults";
8
8
  import { scanClaudeProjectSkills, scanClaudeUserSkills } from "../discovery/claude";
9
9
  import { scanCodexProjectSkills, scanCodexUserSkills } from "../discovery/codex";
10
- import { compareSkillOrder, SOURCE_PATHS, scanSkillsFromDir } from "../discovery/helpers";
10
+ import { compareSkillOrder, listClaudePluginRoots, SOURCE_PATHS, scanSkillsFromDir } from "../discovery/helpers";
11
11
  import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/canonical-skills";
12
12
  import { expandTilde } from "../tools/path-utils";
13
13
  import type { Skill } from "./skills";
@@ -234,6 +234,53 @@ function pushDiagnostic(diagnostics: string[], message: string): void {
234
234
  if (diagnostics.length < MAX_DIAGNOSTICS) diagnostics.push(message);
235
235
  }
236
236
 
237
+ async function collectPluginSkillDirs(
238
+ home: string,
239
+ cwd: string,
240
+ ): Promise<Array<{ dir: string; pluginName: string; level: "user" | "project" }>> {
241
+ // Ensure the underlying installed_plugins.json fs cache is current — install/upgrade
242
+ // writers already invalidate via clearPluginRootsAndCaches; this is a defensive
243
+ // fallback so a stale in-memory cache cannot mask a disabled/removed plugin.
244
+ invalidateFsCache(`${getPluginsDir(home)}/installed_plugins.json`);
245
+ try {
246
+ const { roots } = await listClaudePluginRoots(home, cwd);
247
+ return roots.map(root => ({
248
+ dir: `${root.path}/skills`,
249
+ pluginName: root.plugin,
250
+ level: root.scope,
251
+ }));
252
+ } catch {
253
+ return [];
254
+ }
255
+ }
256
+
257
+ async function diagnoseCustomDir(expandedDir: string, diagnostics: string[]): Promise<boolean> {
258
+ try {
259
+ const stat = await fs.lstat(expandedDir);
260
+ if (stat.isSymbolicLink()) {
261
+ try {
262
+ await fs.stat(expandedDir);
263
+ } catch {
264
+ pushDiagnostic(
265
+ diagnostics,
266
+ `skills.customDirectories entry "${expandedDir}" is a dangling symlink (target missing, likely a purged plugin cache dir); fix or remove it`,
267
+ );
268
+ return true;
269
+ }
270
+ }
271
+ return false;
272
+ } catch (error) {
273
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
274
+ pushDiagnostic(
275
+ diagnostics,
276
+ `skills.customDirectories entry "${expandedDir}" does not exist (missing directory, likely a purged plugin cache dir); fix or remove it`,
277
+ );
278
+ return true;
279
+ }
280
+ return false;
281
+ }
282
+ }
283
+
237
284
  async function scanProjectOrUserDir(
238
285
  ctx: { cwd: string; home: string; repoRoot: string | null },
239
286
  dir: string,
@@ -376,6 +423,44 @@ export async function discoverRuntimeSkills(
376
423
  }
377
424
  }
378
425
 
426
+ // Marketplace plugin skills: version-stable, enabled-only.
427
+ // Uses the same trust gate as native user/project scopes and the same
428
+ // discovery root (<installPath>/skills) the capability provider uses,
429
+ // namespaced as `plugin:skillName`.
430
+ if (policy?.enabled === true) {
431
+ const pluginDirs = await collectPluginSkillDirs(home, options.cwd);
432
+ for (const entry of pluginDirs) {
433
+ if (entry.level === "project" && !(source === "all" || source === "project")) continue;
434
+ if (entry.level === "user" && !(source === "all" || source === "user")) continue;
435
+ if (!sourceEnabled(entry.level, policy)) continue;
436
+ const label = `plugin ${entry.pluginName} skills`;
437
+ scanJobs.push(
438
+ (async () => {
439
+ const pluginCtx = { cwd: options.cwd, home, repoRoot: home };
440
+ const result = await scanSkillsFromDir(pluginCtx, {
441
+ dir: entry.dir,
442
+ providerId: "plugin",
443
+ level: entry.level,
444
+ requireDescription: true,
445
+ });
446
+ for (const skill of result.items) {
447
+ skill.name = `${entry.pluginName}:${skill.name}`;
448
+ }
449
+ return {
450
+ items: result.items.map(skill => ({ skill, source: entry.level as RuntimeSkillDiscoverySource })),
451
+ warnings: result.warnings ?? [],
452
+ label,
453
+ };
454
+ })(),
455
+ );
456
+ }
457
+ }
458
+ if (policy?.enabled === true && policy.customDirectories && policy.customDirectories.length > 0) {
459
+ for (const dir of getCustomSkillDirs(policy, home)) {
460
+ await diagnoseCustomDir(dir, diagnostics);
461
+ }
462
+ }
463
+
379
464
  const settled = await Promise.all(scanJobs.map(job => job.catch(error => ({ error: String(error), label: "" }))));
380
465
 
381
466
  const seenNames = new Set<string>();
@@ -470,6 +555,28 @@ export async function findRuntimeSkillByName(
470
555
  );
471
556
  }
472
557
  }
558
+ if (policy?.enabled === true) {
559
+ try {
560
+ const pluginDirs = await collectPluginSkillDirs(home, cwd);
561
+ for (const entry of pluginDirs) {
562
+ if (!sourceEnabled(entry.level, policy)) continue;
563
+ scanJobs.push(
564
+ scanSkillsFromDir(
565
+ { cwd, home, repoRoot: home },
566
+ {
567
+ dir: entry.dir,
568
+ providerId: "plugin",
569
+ level: entry.level,
570
+ requireDescription: true,
571
+ },
572
+ ).then(result => {
573
+ for (const skill of result.items) skill.name = `${entry.pluginName}:${skill.name}`;
574
+ return result.items.map(skill => ({ skill, source: entry.level as RuntimeSkillDiscoverySource }));
575
+ }),
576
+ );
577
+ }
578
+ } catch {}
579
+ }
473
580
  for (const entry of (await Promise.all(scanJobs)).flat()) {
474
581
  if (entry.skill.name === normalized && isAllowedByPolicy(entry.skill, policy, [])) {
475
582
  return toRuntimeSkill(entry.skill, entry.source);
@@ -122,7 +122,7 @@ export interface LoadSkillsOptions extends SkillsSettings {
122
122
  */
123
123
  const LOADABLE_SKILL_PROVIDERS = new Set(["native"]);
124
124
 
125
- const BUILT_IN_SKILL_NAMES = new Set<string>(CANONICAL_GJC_WORKFLOW_SKILLS);
125
+ const BUILT_IN_SKILL_NAMES = new Set<string>(CANONICAL_GJC_WORKFLOW_SKILLS.map(name => name.toLowerCase()));
126
126
 
127
127
  /**
128
128
  * Load skills from all configured locations.
@@ -235,21 +235,22 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
235
235
  // workflow-routing hijack: warn, and let the session merge keep the bundled
236
236
  // definition authoritative (sdk/session.ts). User-scope copies are not
237
237
  // warned about: installed defaults legitimately mirror bundled skills.
238
- if (capSkill.level === "project" && BUILT_IN_SKILL_NAMES.has(capSkill.name)) {
238
+ if (capSkill.level === "project" && BUILT_IN_SKILL_NAMES.has(capSkill.name.toLowerCase())) {
239
239
  collisionWarnings.push({
240
240
  skillPath: capSkill.path,
241
241
  message: `name collision: "${capSkill.name}" is a bundled GJC workflow skill; the bundled definition takes precedence in sessions and this project copy is never used`,
242
242
  });
243
243
  }
244
244
 
245
- const existing = skillMap.get(capSkill.name);
245
+ const skillKey = capSkill.name.toLowerCase();
246
+ const existing = skillMap.get(skillKey);
246
247
  if (existing) {
247
248
  collisionWarnings.push({
248
249
  skillPath: capSkill.path,
249
250
  message: `name collision: "${capSkill.name}" already loaded from ${existing.filePath}, skipping this one`,
250
251
  });
251
252
  } else {
252
- skillMap.set(capSkill.name, {
253
+ skillMap.set(skillKey, {
253
254
  name: capSkill.name,
254
255
  description: typeof capSkill.frontmatter?.description === "string" ? capSkill.frontmatter.description : "",
255
256
  filePath: capSkill.path,
@@ -318,14 +319,15 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
318
319
  const resolvedPath = customRealPaths[i];
319
320
  if (realPathSet.has(resolvedPath)) continue;
320
321
 
321
- const existing = skillMap.get(skill.name);
322
+ const skillKey = skill.name.toLowerCase();
323
+ const existing = skillMap.get(skillKey);
322
324
  if (existing) {
323
325
  collisionWarnings.push({
324
326
  skillPath: skill.filePath,
325
327
  message: `name collision: "${skill.name}" already loaded from ${existing.filePath}, skipping this one`,
326
328
  });
327
329
  } else {
328
- skillMap.set(skill.name, skill);
330
+ skillMap.set(skillKey, skill);
329
331
  realPathSet.add(resolvedPath);
330
332
  }
331
333
  }
@@ -358,7 +360,7 @@ export function getSkillSlashCommandName(skill: Pick<Skill, "name">): string {
358
360
  }
359
361
 
360
362
  export function isNamespacedSkillSlashCommandName(commandName: string): boolean {
361
- return commandName.startsWith("skill:");
363
+ return commandName.toLowerCase().startsWith("skill:");
362
364
  }
363
365
 
364
366
  export function getSkillSlashCommandNames(skill: Pick<Skill, "name">): string[] {
@@ -366,7 +368,7 @@ export function getSkillSlashCommandNames(skill: Pick<Skill, "name">): string[]
366
368
  }
367
369
 
368
370
  export function isSkillSlashCommandName(commandName: string, skill: Pick<Skill, "name">): boolean {
369
- return commandName === getSkillSlashCommandName(skill);
371
+ return commandName.toLowerCase() === getSkillSlashCommandName(skill).toLowerCase();
370
372
  }
371
373
 
372
374
  export interface ResolvedSkillSlashCommand {
@@ -410,12 +412,19 @@ export function parseSkillInvocations(
410
412
  ): ParsedSkillInvocation[] {
411
413
  const trimmedText = text.trim();
412
414
  if (!trimmedText) return [];
413
- const canonicalSkillCommandPattern = /(^|\s)\/(skill:[^\s]+)/g;
415
+ const normalizedSkillsByCommandName = new Map<string, Skill>();
416
+ for (const [commandName, skill] of skillsByCommandName) {
417
+ const normalizedCommandName = commandName.toLowerCase();
418
+ if (!normalizedSkillsByCommandName.has(normalizedCommandName)) {
419
+ normalizedSkillsByCommandName.set(normalizedCommandName, skill);
420
+ }
421
+ }
422
+ const canonicalSkillCommandPattern = /(^|\s)\/(skill:[^\s]+)/gi;
414
423
  const matches: SkillTokenMatch[] = [];
415
424
  for (const match of trimmedText.matchAll(canonicalSkillCommandPattern)) {
416
425
  const commandName = match[2];
417
426
  if (!commandName) continue;
418
- const skill = skillsByCommandName.get(commandName);
427
+ const skill = normalizedSkillsByCommandName.get(commandName.toLowerCase());
419
428
  if (!skill) continue;
420
429
  const leading = match[1] ?? "";
421
430
  const index = (match.index ?? 0) + leading.length;
@@ -458,10 +467,11 @@ export function resolveSkillSlashCommands(
458
467
  for (const skill of skills) {
459
468
  if (skill.hide === true) continue;
460
469
  for (const name of getSkillSlashCommandNames(skill)) {
461
- if (claimedNames.has(name)) {
470
+ const normalizedName = name.toLowerCase();
471
+ if (claimedNames.has(normalizedName)) {
462
472
  continue;
463
473
  }
464
- claimedNames.add(name);
474
+ claimedNames.add(normalizedName);
465
475
  commands.push({ name, description: skill.description, skill });
466
476
  }
467
477
  }