@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
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AgentLoop calls `getApiKey(provider)` before `streamFn`.
3
+ * Resolve/stream already use `getApiKey(model)`. If those two lookups diverge,
4
+ * the child dies locally (~8ms, 0 tokens, sanitized "Agent run failed.")
5
+ * and never reaches the working model-scoped path (#5081).
6
+ */
7
+ export type SessionApiKeyModel = {
8
+ provider: string;
9
+ baseUrl?: string;
10
+ };
11
+ export type SessionApiKeyRegistry = {
12
+ getApiKey(model: SessionApiKeyModel, sessionId?: string, options?: {
13
+ signal?: AbortSignal;
14
+ }): Promise<string | undefined>;
15
+ getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string, options?: {
16
+ signal?: AbortSignal;
17
+ }): Promise<string | undefined>;
18
+ };
19
+ export type SessionApiKeyResolution = {
20
+ apiKey: string;
21
+ credentialSessionId: string | undefined;
22
+ };
23
+ export type SessionApiKeyCredentialType = "api_key" | "oauth" | undefined;
24
+ export declare function isStableSessionApiKeyCredentialType(previous: SessionApiKeyCredentialType, replacement: SessionApiKeyCredentialType): boolean;
25
+ export declare function resolveLiveSessionApiKeyModel(live: SessionApiKeyModel | undefined, captured: SessionApiKeyModel | undefined, provider: string): SessionApiKeyModel | undefined;
26
+ export declare function lookupSessionApiKey(registry: SessionApiKeyRegistry, provider: string, sessionId: string | undefined, model: SessionApiKeyModel | undefined, allowUnscopedRetry?: boolean, signal?: AbortSignal): Promise<SessionApiKeyResolution>;
@@ -62,6 +62,8 @@ export interface CreateAgentSessionOptions {
62
62
  cwd?: string;
63
63
  /** Global config directory. Default: ~/.gjc/agent */
64
64
  agentDir?: string;
65
+ /** Resolver-owned classification for `agentDir`; required when authority must survive HOME/config refreshes. */
66
+ profileAuthority?: "default" | "custom";
65
67
  /** Spawns to allow. Default: "*" */
66
68
  spawns?: string;
67
69
  /** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
@@ -336,7 +338,7 @@ export declare function discoverSkills(_cwd?: string, _agentDir?: string, _setti
336
338
  * Discover context files (AGENTS.md) walking up from cwd.
337
339
  * Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
338
340
  */
339
- export declare function discoverContextFiles(cwd?: string, _agentDir?: string): Promise<Array<{
341
+ export declare function discoverContextFiles(cwd?: string, agentDir?: string): Promise<Array<{
340
342
  path: string;
341
343
  content: string;
342
344
  depth?: number;
@@ -361,6 +363,8 @@ export interface BuildSystemPromptOptions {
361
363
  content: string;
362
364
  }>;
363
365
  cwd?: string;
366
+ agentDir?: string;
367
+ profileAuthority?: "default" | "custom";
364
368
  appendPrompt?: string;
365
369
  repeatToolDescriptions?: boolean;
366
370
  }
@@ -7,7 +7,7 @@ type BrokerSessionRow = {
7
7
  ambiguous: boolean;
8
8
  };
9
9
  /** Exhausts strict broker `session.list` pages into one full session snapshot. */
10
- export declare function listBrokerSessions(broker: SdkClient): Promise<BrokerSessionRow[]>;
10
+ export declare function listBrokerSessions(broker: SdkClient, explicitSessionId?: string): Promise<BrokerSessionRow[]>;
11
11
  /** Selects the session to serve through broker `session.list` truth (C10); exported for tests. */
12
12
  export declare function selectBrokerSession(sessions: BrokerSessionRow[], explicitSessionId: string | undefined): string;
13
13
  /**
@@ -241,6 +241,13 @@ export interface AgentSessionConfig {
241
241
  skills?: Skill[];
242
242
  /** Skill loading warnings (already captured by SDK) */
243
243
  skillWarnings?: SkillWarning[];
244
+ /** Re-enumerate filesystem and marketplace skills after plugin lifecycle changes. */
245
+ reloadSkills?: (cwd: string) => Promise<{
246
+ skills: Skill[];
247
+ warnings: SkillWarning[];
248
+ }>;
249
+ /** Publish a successfully committed skill catalog to SDK/process snapshots. */
250
+ onSkillsReloaded?: (skills: Skill[], warnings: SkillWarning[]) => void;
244
251
  /** Custom commands (TypeScript slash commands) */
245
252
  customCommands?: LoadedCustomCommand[];
246
253
  skillsSettings?: SkillsSettings;
@@ -819,6 +826,8 @@ export declare class AgentSession {
819
826
  awaitSessionSettlement(): Promise<void>;
820
827
  /** Test seam: hold prompt admission while bypassing a pending selection fence. */
821
828
  runWithPromptAdmissionForTests(body: () => Promise<void>): Promise<void>;
829
+ /** Test seam: await all currently admitted coordinator sidecar writes. */
830
+ awaitCoordinatorRuntimeStatePersistenceForTests(): Promise<void>;
822
831
  queueCoordinatorRuntimeStatePersistForTests(event: AgentSessionEvent, gate: Promise<void>): Promise<void>;
823
832
  parkAgentEndForCoordinatorPersistForTests(event: Extract<AgentSessionEvent, {
824
833
  type: "agent_end";
@@ -1176,6 +1185,8 @@ export declare class AgentSession {
1176
1185
  * they must not survive a move out of it.
1177
1186
  */
1178
1187
  replaceSkills(skills: Skill[]): Promise<void>;
1188
+ /** Refresh the live registered-skill catalog after install/remove/reload. */
1189
+ reloadSkills(cwd?: string): Promise<void>;
1179
1190
  /**
1180
1191
  * Retire the cached workspace tree after a rescope so the next turn re-scans
1181
1192
  * at the new cwd instead of re-presenting the abandoned launcher root.
@@ -95,5 +95,20 @@ export declare function cappedExponentialWithFullJitter(baseDelayMs: number, max
95
95
  * A missing, NaN, or infinite hint collapses to "no usable hint".
96
96
  */
97
97
  export declare function compactionRetryDelay(baseDelayMs: number, maxDelayMs: number, attempt: number, retryAfterMs: number | undefined): number;
98
+ export interface CompactionCandidateFailure {
99
+ readonly model: string;
100
+ readonly message: string;
101
+ }
102
+ /**
103
+ * Error for an auto-compaction run whose every candidate failed.
104
+ *
105
+ * The candidate chain starts at the session model and ends on a same-provider
106
+ * largest-context fallback, so surfacing only the final error named a model
107
+ * the user never chose and hid that their own model had already failed the
108
+ * same way. The message leads with the first failure (the one the user can act
109
+ * on), then lists every candidate that was tried with its own error. `cause`
110
+ * is the final error so callers that classify by type keep the same object.
111
+ */
112
+ export declare function describeCompactionCandidateFailures(failures: readonly CompactionCandidateFailure[], lastError: unknown): Error;
98
113
  /** Retry-After is intentionally uncapped. */
99
114
  export declare function effectiveFallbackDelay(baseDelayMs: number, maxDelayMs: number, attemptK: number, retryAfterMs: number | undefined, random?: () => number): number;
@@ -6,6 +6,13 @@ export declare const MANAGED_ARTIFACT_MAX_FILES = 50000;
6
6
  export declare const MANAGED_ARTIFACT_MAX_FILE_BYTES: number;
7
7
  export declare const MANAGED_ARTIFACT_MAX_TOTAL_BYTES: number;
8
8
  export declare const MANAGED_ARTIFACT_COPY_BATCH_SIZE = 256;
9
+ /** A pre-mutation append fence rejection: no bytes from this append were written. */
10
+ export declare class ManagedAppendIdentityMismatchError extends Error {
11
+ readonly code = "managed_append_identity_mismatch";
12
+ readonly relativePath: string;
13
+ readonly operatorMessage = "Session was resumed by another process; this resume did not run. Retry, or resume a different session.";
14
+ constructor(relativePath: string);
15
+ }
9
16
  export declare class ManagedPublishError extends Error {
10
17
  readonly classification: "destination_conflict" | "atomic_unavailable" | "invalid_request" | "durability_not_provable" | "identity_mismatch" | "io_error" | "durability_failed";
11
18
  readonly diagnostic: string;
@@ -82,6 +82,7 @@ export interface BashExecutionMessage {
82
82
  command: string;
83
83
  output: string;
84
84
  exitCode: number | undefined;
85
+ signal?: string;
85
86
  cancelled: boolean;
86
87
  truncated: boolean;
87
88
  meta?: OutputMeta;
@@ -9,7 +9,7 @@ import type { ManagedLegacyLocalMigrationSource } from "../internal-urls/local-p
9
9
  import { ArtifactManager } from "./artifacts";
10
10
  import { type BlobPutResult, BlobStore } from "./blob-store";
11
11
  import { type ManagedMigrationPolicy } from "./internal/managed-session-scope";
12
- import { type ManagedBoundedAppendExpectation, type ManagedDirectoryRoot, type ManagedFileIdentity, ManagedSessionDescendantStore } from "./internal/managed-session-storage";
12
+ import { ManagedAppendIdentityMismatchError, type ManagedBoundedAppendExpectation, type ManagedDirectoryRoot, type ManagedFileIdentity, ManagedSessionDescendantStore } from "./internal/managed-session-storage";
13
13
  import { type BaseAnchor, BoundedLabelsPinsStore, type CommittedTail, type DescriptorSnapshot, type DictionaryPartitionCommit, FixedCacheAccount, type MetadataDeltaValue, type ParentBucketCommit, type ReducerState, type ReopenClassification, SessionMemoryAccountant } from "./internal/session-memory-sidecar";
14
14
  import { type MemoryGuardCreateCheckpointInput, type MemoryGuardParticipantIngressLease, type MemoryGuardRestoreInput, type MemoryGuardRestoreResult, type MemoryGuardSessionManagerCheckpointV1 } from "./memory-guard-checkpoint-participant";
15
15
  import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages";
@@ -1291,6 +1291,8 @@ export declare class SessionManager {
1291
1291
  }) => Promise<fs.BigIntStats>;
1292
1292
  };
1293
1293
  }): Promise<void>;
1294
+ /** Ownership loss remains observable even when an agent event listener aborts the turn. */
1295
+ getAppendIdentityMismatch(): ManagedAppendIdentityMismatchError | undefined;
1294
1296
  isPersisted(): boolean;
1295
1297
  stageDefaultModelSelection(model: string, thinkingLevel: string | undefined, options?: {
1296
1298
  readonly appendThinkingLevel: boolean;
@@ -0,0 +1,112 @@
1
+ export type PaseoDaemonTarget = {
2
+ readonly kind: "ipc";
3
+ readonly socketPath: string;
4
+ } | {
5
+ readonly kind: "tcp";
6
+ readonly host: string;
7
+ readonly port: number;
8
+ };
9
+ /** Why an announcement did nothing. Each reason is a distinct, actionable state. */
10
+ export type PaseoAnnounceSkip = "no-paseo-config" | "no-provider" | "cli-missing" | "daemon-unreachable" | "unsupported-daemon-target" | "daemon-auth-required" | "session-not-live";
11
+ export type PaseoAnnounceOutcome = {
12
+ readonly kind: "imported";
13
+ readonly providerKey: string;
14
+ } | {
15
+ readonly kind: "already-imported";
16
+ readonly providerKey: string;
17
+ } | {
18
+ readonly kind: "skipped";
19
+ readonly reason: PaseoAnnounceSkip;
20
+ } | {
21
+ readonly kind: "failed";
22
+ readonly detail: string;
23
+ };
24
+ export interface PaseoAnnounceDependencies {
25
+ /** `~/.paseo/config.json` (or the `$PASEO_HOME` equivalent). */
26
+ readonly configJson: string;
27
+ /** `~/.paseo` (or `$PASEO_HOME`). */
28
+ readonly paseoHome: string;
29
+ readonly env: NodeJS.ProcessEnv;
30
+ /** Parsed JSON, or `undefined` when the file is absent or unreadable. */
31
+ readJson(file: string): Promise<unknown>;
32
+ /** Absolute path of the `paseo` CLI, or `undefined` when it is not installed. */
33
+ resolveCli(): string | undefined;
34
+ probeDaemon(target: PaseoDaemonTarget): Promise<boolean>;
35
+ /** True only when the broker index reports this session id as live in `cwd`. */
36
+ isSessionLive(sessionId: string, cwd: string): Promise<boolean>;
37
+ runImport(input: {
38
+ readonly cli: string;
39
+ readonly providerKey: string;
40
+ readonly cwd: string;
41
+ readonly sessionId: string;
42
+ readonly daemonTarget: PaseoDaemonTarget;
43
+ readonly daemonHost: string;
44
+ readonly signal?: AbortSignal;
45
+ }): Promise<PaseoAnnounceOutcome>;
46
+ }
47
+ /**
48
+ * Pick the Paseo provider entry to import under.
49
+ *
50
+ * `gjc setup paseo` writes `gjc`, and `--mpreset <name>` writes `gjc-<name>`, so
51
+ * the base key is preferred and a preset provider is only used when it is the
52
+ * only one installed. A disabled entry is never selected: Paseo would refuse to
53
+ * launch it.
54
+ */
55
+ export declare function selectProviderKey(config: unknown): string | undefined;
56
+ /**
57
+ * Parse one Paseo listener spelling into a connectable target.
58
+ *
59
+ * Mirrors the spellings Paseo's own CLI accepts: `unix://`/`pipe://` prefixes, a
60
+ * bare absolute path, a bare port, `tcp://host:port`, and plain `host:port`.
61
+ */
62
+ export declare function parseDaemonListen(raw: string): PaseoDaemonTarget | undefined;
63
+ /**
64
+ * Resolve where the daemon listens, in the same precedence Paseo's CLI uses:
65
+ * environment override, then the running daemon's own pid file, then the
66
+ * configured listener, then Paseo's default.
67
+ */
68
+ export declare function resolveDaemonTarget(config: unknown, deps: Pick<PaseoAnnounceDependencies, "env" | "paseoHome" | "readJson">): Promise<PaseoDaemonTarget | undefined>;
69
+ /**
70
+ * Turn a non-zero `paseo import` into an outcome.
71
+ *
72
+ * Two of Paseo's refusals are not failures:
73
+ *
74
+ * - an already-known session is exactly the state we wanted to reach;
75
+ * - a password-protected daemon (`Cannot connect to daemon ...: Password
76
+ * required`) is a configuration the user chose. The socket probe cannot see
77
+ * it -- the TCP connect succeeds -- so it can only be recognized here, and
78
+ * reporting it would put an error on screen at every single launch for
79
+ * something GJC must not fix on its own. Exporting `PASEO_PASSWORD` makes it
80
+ * work, since the CLI inherits this process's environment.
81
+ */
82
+ export declare function classifyImportFailure(providerKey: string, detail: string): PaseoAnnounceOutcome;
83
+ /**
84
+ * Run the full announcement once.
85
+ *
86
+ * The order is load-bearing: every cheap filesystem check runs before the socket
87
+ * probe, and the socket probe runs before anything spawns the Paseo CLI.
88
+ */
89
+ export declare function announceSessionToPaseo(input: {
90
+ readonly sessionId: string;
91
+ readonly cwd: string;
92
+ }, deps: PaseoAnnounceDependencies, signal?: AbortSignal): Promise<PaseoAnnounceOutcome>;
93
+ /** Only a daemon that is not up yet, or a registration that has not landed yet, is worth retrying. */
94
+ export declare function isRetryableOutcome(outcome: PaseoAnnounceOutcome): boolean;
95
+ export interface PaseoAnnouncementHandle {
96
+ cancel(): void;
97
+ }
98
+ /**
99
+ * Announce the session, retrying only while Paseo is not ready to hear it.
100
+ *
101
+ * The user routinely opens Paseo after their terminal, so a single attempt at
102
+ * launch would silently never land. Retries are socket probes, not CLI spawns,
103
+ * and every timer is unref'd so the loop can never hold the process open.
104
+ */
105
+ export declare function startPaseoAnnouncement(input: {
106
+ readonly sessionId: string;
107
+ readonly cwd: string;
108
+ isEnabled?: () => boolean;
109
+ onOutcome?: (outcome: PaseoAnnounceOutcome) => void;
110
+ }, deps: PaseoAnnounceDependencies): PaseoAnnouncementHandle;
111
+ export declare function sessionListContainsLiveSession(sessions: readonly unknown[], sessionId: string, cwd: string): boolean;
112
+ export declare function createDefaultPaseoAnnounceDependencies(agentDir: string, env?: NodeJS.ProcessEnv): PaseoAnnounceDependencies;
@@ -61,7 +61,8 @@ export interface PaseoSetupDependencies {
61
61
  runProviderLs(timeoutMs: number): Promise<PaseoLsOutcome>;
62
62
  now(): Date;
63
63
  }
64
- export declare function createDefaultPaseoPaths(agentDir?: string, home?: string): PaseoPaths;
64
+ export declare function resolvePaseoHome(env?: NodeJS.ProcessEnv, home?: string): string;
65
+ export declare function createDefaultPaseoPaths(agentDir?: string, home?: string, env?: NodeJS.ProcessEnv): PaseoPaths;
65
66
  /**
66
67
  * Parse `paseo provider ls --json`.
67
68
  *
@@ -1,3 +1,7 @@
1
1
  /** Native-free canonical GJC workflow skill identifiers. */
2
2
  export declare const CANONICAL_GJC_WORKFLOW_SKILLS: readonly ["deep-interview", "ralplan", "ultragoal", "autoresearch"];
3
3
  export type CanonicalGjcWorkflowSkill = (typeof CANONICAL_GJC_WORKFLOW_SKILLS)[number];
4
+ /** Case-insensitive, Win32-path-equivalent key used for skill collision checks. */
5
+ export declare function getSkillFilesystemIdentity(name: string): string;
6
+ /** Whether a name resolves to a bundled workflow under portable filesystem semantics. */
7
+ export declare function isCanonicalGjcWorkflowSkillFilesystemAlias(name: string): boolean;
@@ -15,6 +15,10 @@ export declare function resolvePromptInput(input: string | undefined, descriptio
15
15
  export interface LoadContextFilesOptions {
16
16
  /** Working directory to start walking up from. Default: getProjectDir() */
17
17
  cwd?: string;
18
+ /** Agent directory backing native user-scope context and system prompt files. */
19
+ agentDir?: string;
20
+ /** Resolver-owned classification for `agentDir`; preserve it across HOME/config refreshes. */
21
+ profileAuthority?: "default" | "custom";
18
22
  }
19
23
  export interface ProjectContextFilesResult {
20
24
  contextFiles: Array<{
@@ -64,10 +68,16 @@ export interface BuildSystemPromptOptions {
64
68
  pluginAppendices?: string;
65
69
  /** Repeat full tool descriptions in system prompt. Default: false */
66
70
  repeatToolDescriptions?: boolean;
71
+ /** Language guidance for internal technical reasoning. Default: off. */
72
+ reasoningLanguage?: "off" | "english";
67
73
  /** Skills settings for discovery. */
68
74
  skillsSettings?: SkillsSettings;
69
75
  /** Working directory. Default: getProjectDir() */
70
76
  cwd?: string;
77
+ /** Agent directory backing native user-scope prompt discovery. */
78
+ agentDir?: string;
79
+ /** Resolver-owned classification for `agentDir`; preserve it across HOME/config refreshes. */
80
+ profileAuthority?: "default" | "custom";
71
81
  /** Pre-loaded context files (skips discovery if provided). */
72
82
  contextFiles?: Array<{
73
83
  path: string;
@@ -95,6 +95,9 @@ export interface ExecutorOptions {
95
95
  authStorage?: AuthStorage;
96
96
  modelRegistry?: ModelRegistry;
97
97
  settings?: Settings;
98
+ /** Parent session's selected agent directory and immutable resolver-owned authority. */
99
+ agentDir?: string;
100
+ profileAuthority?: "default" | "custom";
98
101
  /** Parent session's registry; shared by child sessions for IRC routing and roster visibility. */
99
102
  agentRegistry?: AgentRegistry;
100
103
  /**
@@ -114,7 +117,8 @@ export interface ExecutorOptions {
114
117
  managedPersistence?: ManagedTaskPersistence;
115
118
  /**
116
119
  * The parent session's ENDPOINT-owned AsyncJobManager (resolved by the
117
- * TaskTool via forEndpoint(sessionId) ?? instance()). Model metadata and
120
+ * TaskTool via its opaque async endpoint accessor, then the process-global
121
+ * fallback). Model metadata and
118
122
  * live-handle state for THIS subagent are recorded in the SAME manager the
119
123
  * task job runs in — with concurrent top-level sessions the process-global
120
124
  * instance belongs to a different session and would surface this subagent
@@ -19,6 +19,7 @@ export declare const STEER_FOLD_GRACE_MS = 2000;
19
19
  /** Model-facing line appended to a steer-folded background-start result. */
20
20
  export declare function steerFoldReasonLine(jobId: string): string;
21
21
  export type BashOriginalArtifactSaveResult = BashArtifactSaveResult;
22
+ export declare function suffixPrefixOverlap(source: string, target: string): number;
22
23
  export declare function saveBashOriginalArtifactForTests(session: ToolSession, originalText: string, onResult?: (result: BashOriginalArtifactSaveResult) => void): Promise<string | undefined>;
23
24
  declare const bashSchemaBase: z.ZodObject<{
24
25
  command: z.ZodString;
@@ -58,6 +58,13 @@ interface ReadUrlCacheEntry {
58
58
  /** Offset of the preamble after `wrapUntrustedContent(output)` is persisted. */
59
59
  wrappedPreambleChars?: number;
60
60
  }
61
+ export declare const READ_URL_CACHE_MAX_KEYS = 64;
62
+ /** Test-only seam for deterministic module-global cache assertions. */
63
+ export declare const readUrlCacheTestHooks: {
64
+ readonly size: number;
65
+ readonly retainedOutputChars: number;
66
+ reset(): void;
67
+ };
61
68
  export declare function loadReadUrlCacheEntry(session: ToolSession, params: {
62
69
  path: string;
63
70
  raw?: boolean;
@@ -78,9 +78,10 @@ export interface CacheLookupOptions<T> {
78
78
  * must bypass persistent cache reads/writes.
79
79
  */
80
80
  authKey?: string | null;
81
- fetchFresh: () => Promise<FreshResult<T>>;
81
+ fetchFresh: (signal?: AbortSignal) => Promise<FreshResult<T>>;
82
82
  settings?: Settings | undefined;
83
83
  now?: number;
84
+ signal?: AbortSignal;
84
85
  }
85
86
  export type CacheStatus = "miss" | "fresh" | "stale" | "disabled";
86
87
  export interface CacheLookupResult<T> {
@@ -152,6 +152,8 @@ export interface ToolSession {
152
152
  * Runtime discovery only; never threaded into capability loading.
153
153
  */
154
154
  home?: string;
155
+ /** Resolver-owned classification for the session's selected agent directory. */
156
+ profileAuthority?: "default" | "custom";
155
157
  skills?: Skill[];
156
158
  /** Currently executing skill prompt, when this tool session is inside one. */
157
159
  getActiveSkillState?: () => Pick<SkillActiveEntry, "skill" | "session_id"> | undefined;
@@ -197,6 +199,14 @@ export interface ToolSession {
197
199
  waitForUserSteering?: (signal: AbortSignal) => Promise<void>;
198
200
  /** Get session ID */
199
201
  getSessionId?: () => string | null;
202
+ /**
203
+ * Opaque async-job ownership/lookup endpoint key. This is not a workflow or
204
+ * path identity: workflow/session consumers must use getSessionId(). Only
205
+ * AsyncJobManager ownership and endpoint-first lookup may use this accessor
206
+ * (subagent-inherited managers are registered under the parent's endpoint;
207
+ * review thread P1).
208
+ */
209
+ getAsyncEndpointId?: () => string | null;
200
210
  /** Get credential-selection session identity. */
201
211
  getCredentialSessionId?: () => string | null;
202
212
  /** Scope-held MCP facade for mcp:// resolution. */
@@ -122,6 +122,8 @@ export interface ToolScopeOptions {
122
122
  surfaceExactFilePaths?: boolean;
123
123
  /** Extra hint appended to "Path not found" when stat fails and the user supplied multiple paths. */
124
124
  multipathStatHint?: string;
125
+ /** Cancels internal-URL resolution and prevents further scope work. */
126
+ signal?: AbortSignal;
125
127
  }
126
128
  export interface ToolScopeResolution {
127
129
  searchPath: string;
@@ -1,4 +1,5 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
+ import type { grep as grepFn } from "@gajae-code/natives";
2
3
  import type { Component } from "@gajae-code/tui";
3
4
  import * as z from "zod/v4";
4
5
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
@@ -12,6 +13,7 @@ declare const searchSchema: z.ZodObject<{
12
13
  i: z.ZodOptional<z.ZodBoolean>;
13
14
  gitignore: z.ZodOptional<z.ZodBoolean>;
14
15
  skip: z.ZodOptional<z.ZodNumber>;
16
+ timeout: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
15
17
  }, z.core.$strict>;
16
18
  export type SearchToolInput = z.infer<typeof searchSchema>;
17
19
  /** Maximum number of distinct files surfaced in a single response. The
@@ -56,6 +58,8 @@ export interface SearchToolOptions {
56
58
  context?: number;
57
59
  /** Per-call total match cap used by Cursor's native Pi grep frame. */
58
60
  totalMatchLimit?: number;
61
+ /** Test or remote override for the native grep implementation. */
62
+ grep?: typeof grepFn;
59
63
  }
60
64
  type SearchParams = z.infer<typeof searchSchema>;
61
65
  export declare class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
@@ -72,6 +76,7 @@ export declare class SearchTool implements AgentTool<typeof searchSchema, Search
72
76
  i: z.ZodOptional<z.ZodBoolean>;
73
77
  gitignore: z.ZodOptional<z.ZodBoolean>;
74
78
  skip: z.ZodOptional<z.ZodNumber>;
79
+ timeout: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
75
80
  }, z.core.$strict>;
76
81
  readonly strict = true;
77
82
  constructor(session: ToolSession, options?: SearchToolOptions);
@@ -83,6 +88,7 @@ interface SearchRenderArgs {
83
88
  i?: boolean;
84
89
  gitignore?: boolean;
85
90
  skip?: number;
91
+ timeout?: number;
86
92
  }
87
93
  export declare const searchToolRenderer: {
88
94
  inline: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.16.3",
4
+ "version": "0.16.6",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -56,12 +56,12 @@
56
56
  "@agentclientprotocol/sdk": "1.3.0",
57
57
  "@babel/parser": "^7.29.3",
58
58
  "@mozilla/readability": "^0.6.0",
59
- "@gajae-code/stats": "0.16.3",
60
- "@gajae-code/agent-core": "0.16.3",
61
- "@gajae-code/ai": "0.16.3",
62
- "@gajae-code/natives": "0.16.3",
63
- "@gajae-code/tui": "0.16.3",
64
- "@gajae-code/utils": "0.16.3",
59
+ "@gajae-code/stats": "0.16.6",
60
+ "@gajae-code/agent-core": "0.16.6",
61
+ "@gajae-code/ai": "0.16.6",
62
+ "@gajae-code/natives": "0.16.6",
63
+ "@gajae-code/tui": "0.16.6",
64
+ "@gajae-code/utils": "0.16.6",
65
65
  "@puppeteer/browsers": "^2.13.0",
66
66
  "@types/turndown": "5.0.6",
67
67
  "@xterm/headless": "^6.0.0",
@@ -130,6 +130,8 @@
130
130
  "./session/session-manager-internal.js": null,
131
131
  "./session/sdk-run-capability": null,
132
132
  "./session/sdk-run-capability.js": null,
133
+ "./extensibility/extensions/function-hooks-internal": null,
134
+ "./extensibility/extensions/function-hooks-internal.js": null,
133
135
  "./sdk": {
134
136
  "types": "./dist/types/sdk/index.d.ts",
135
137
  "import": "./src/sdk/index.ts"
@@ -0,0 +1,151 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import {
4
+ renderStatusLineCustomEditorShowcase,
5
+ STATUS_LINE_CUSTOM_EDITOR_SHOWCASE_ENTRIES,
6
+ type StatusLineCustomEditorShowcaseEntry,
7
+ } from "../test/fixtures/tui/status-line-custom-editor-showcase";
8
+ import { ansiToHtml } from "./capture-sticky-viewport-showcase";
9
+
10
+ const CANONICAL_COMMAND =
11
+ "bun packages/coding-agent/scripts/capture-status-line-custom-editor-showcase.ts --out .gjc/qa/status-line-custom-editor-<run>";
12
+ const CAPTURE_TOOL_VERSION = "status-line-custom-editor-showcase-v1";
13
+ const SOURCE_PATHS = [
14
+ "packages/coding-agent/src/modes/components/settings-selector.ts",
15
+ "packages/coding-agent/src/modes/components/tool-status-header.ts",
16
+ "packages/coding-agent/src/modes/controllers/selector-controller.ts",
17
+ "packages/tui/src/components/settings-list.ts",
18
+ "packages/coding-agent/test/fixtures/tui/status-line-custom-editor-showcase.ts",
19
+ "packages/coding-agent/scripts/verify-status-line-custom-editor-showcase.ts",
20
+ ] as const;
21
+
22
+ interface ArtifactFile {
23
+ path: string;
24
+ sha256: string;
25
+ byte_length: number;
26
+ }
27
+
28
+ interface ManifestEntry {
29
+ key: string;
30
+ stateId: string;
31
+ viewport: { columns: number; rows: number };
32
+ renderMode: string;
33
+ simulatedStatusbarRows: number;
34
+ files: ArtifactFile[];
35
+ }
36
+
37
+ function usage(): never {
38
+ throw new Error(`Usage: ${CANONICAL_COMMAND}`);
39
+ }
40
+
41
+ function outputPath(args: string[]): string {
42
+ if (args.length !== 2 || args[0] !== "--out" || !args[1]) usage();
43
+ return args[1];
44
+ }
45
+
46
+ function json(value: unknown): string {
47
+ return `${JSON.stringify(value, null, 2)}\n`;
48
+ }
49
+
50
+ function sha256(value: string): string {
51
+ return new Bun.CryptoHasher("sha256").update(value).digest("hex");
52
+ }
53
+
54
+ function commandOutput(command: string[]): string | undefined {
55
+ const result = Bun.spawnSync({ cmd: command, stdout: "pipe", stderr: "pipe" });
56
+ if (result.exitCode !== 0) return undefined;
57
+ const output = result.stdout.toString().trim();
58
+ return output || undefined;
59
+ }
60
+
61
+ async function sourceHash(): Promise<string> {
62
+ const entries = await Promise.all(
63
+ SOURCE_PATHS.map(async sourcePath => `${sourcePath}:${sha256(await Bun.file(sourcePath).text())}`),
64
+ );
65
+ return `sha256:${sha256(entries.join("\n"))}`;
66
+ }
67
+
68
+ function entryKey(entry: StatusLineCustomEditorShowcaseEntry): string {
69
+ return `${entry.stateId}/${entry.columns}x${entry.rows}/${entry.renderMode}`;
70
+ }
71
+
72
+ function simulatedStatusbarRows(text: string): number {
73
+ const lines = Bun.stripANSI(text).split("\n");
74
+ const heading = lines.findIndex(line => line.includes("Simulated statusbar"));
75
+ if (heading < 0) return 0;
76
+ let end = heading + 1;
77
+ while (end < lines.length && lines[end]?.trim() && !lines[end]?.includes("Warning:")) end++;
78
+ return Math.max(0, end - heading - 1);
79
+ }
80
+
81
+ async function writeArtifact(root: string, relativePath: string, content: string): Promise<ArtifactFile> {
82
+ const fullPath = path.join(root, relativePath);
83
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
84
+ await Bun.write(fullPath, content);
85
+ return { path: relativePath, sha256: sha256(content), byte_length: Buffer.byteLength(content) };
86
+ }
87
+
88
+ async function main(): Promise<void> {
89
+ const root = outputPath(process.argv.slice(2));
90
+ const sourceHead = commandOutput(["git", "rev-parse", "HEAD"]);
91
+ if (!sourceHead) throw new Error("Could not resolve source HEAD");
92
+ const captureActor = commandOutput(["gh", "api", "user", "--jq", ".login"]) ?? "unauthenticated";
93
+ const digest = await sourceHash();
94
+ const independentReviewPath = path.join(root, "independent-review.json");
95
+ const existingIndependentReview = (await Bun.file(independentReviewPath).exists())
96
+ ? await Bun.file(independentReviewPath).text()
97
+ : undefined;
98
+ await fs.rm(root, { recursive: true, force: true });
99
+ await fs.mkdir(root, { recursive: true });
100
+ if (existingIndependentReview !== undefined) {
101
+ await Bun.write(independentReviewPath, existingIndependentReview);
102
+ }
103
+ const entries: ManifestEntry[] = [];
104
+ for (const entry of STATUS_LINE_CUSTOM_EDITOR_SHOWCASE_ENTRIES) {
105
+ const key = entryKey(entry);
106
+ const dir = key.replace(/[^a-zA-Z0-9._/-]/g, "_");
107
+ const terminalAnsi = await renderStatusLineCustomEditorShowcase(entry);
108
+ const renderedRows = terminalAnsi.split("\n").length;
109
+ if (renderedRows > entry.rows) {
110
+ throw new Error(`${key}: rendered ${renderedRows} rows for a ${entry.rows}-row viewport`);
111
+ }
112
+ const statusbarRows = simulatedStatusbarRows(terminalAnsi);
113
+ const terminalPlain = Bun.stripANSI(terminalAnsi);
114
+ const html = ansiToHtml(terminalAnsi);
115
+ const metadata = json({
116
+ tool: CAPTURE_TOOL_VERSION,
117
+ command: CANONICAL_COMMAND,
118
+ stateId: entry.stateId,
119
+ viewport: { columns: entry.columns, rows: entry.rows },
120
+ renderMode: entry.renderMode,
121
+ fixedClock: "2026-01-01T00:00:00.000Z",
122
+ simulatedStatusbarRows: statusbarRows,
123
+ wrappingPolicy: statusbarRows > 2 ? "two-row" : "single-row",
124
+ });
125
+ const files = [
126
+ await writeArtifact(root, `${dir}/terminal.txt`, terminalPlain),
127
+ await writeArtifact(root, `${dir}/terminal-ansi.txt`, terminalAnsi),
128
+ await writeArtifact(root, `${dir}/terminal.html`, html),
129
+ await writeArtifact(root, `${dir}/metadata.json`, metadata),
130
+ ];
131
+ entries.push({
132
+ key,
133
+ stateId: entry.stateId,
134
+ viewport: { columns: entry.columns, rows: entry.rows },
135
+ renderMode: entry.renderMode,
136
+ simulatedStatusbarRows: statusbarRows,
137
+ files,
138
+ });
139
+ }
140
+ await writeArtifact(
141
+ root,
142
+ "manifest.json",
143
+ json({ tool: CAPTURE_TOOL_VERSION, sourceHead, sourceHash: digest, captureActor, entries }),
144
+ );
145
+ process.stdout.write(`Captured ${entries.length} status-line custom editor showcase entries at ${root}\n`);
146
+ }
147
+
148
+ void main().catch(error => {
149
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
150
+ process.exit(1);
151
+ });