@gajae-code/coding-agent 0.12.0 → 0.12.2

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 (247) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +56 -2
  4. package/dist/types/exa/mcp-client.d.ts +10 -1
  5. package/dist/types/exec/bash-executor.d.ts +45 -12
  6. package/dist/types/extensibility/extensions/types.d.ts +16 -2
  7. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  8. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  9. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  10. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  11. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  12. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  13. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  14. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  15. package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
  16. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +23 -3
  17. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  18. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  19. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  20. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  21. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +37 -80
  22. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  23. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  24. package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
  25. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  26. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  27. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  28. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  29. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  30. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  31. package/dist/types/modes/controllers/event-controller.d.ts +1 -1
  32. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  33. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  34. package/dist/types/modes/interactive-mode.d.ts +9 -0
  35. package/dist/types/modes/theme/theme.d.ts +3 -1
  36. package/dist/types/modes/types.d.ts +32 -0
  37. package/dist/types/reminders/star-reminder.d.ts +3 -13
  38. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  39. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  40. package/dist/types/sdk/broker/transport.d.ts +2 -0
  41. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -1
  43. package/dist/types/sdk/bus/config.d.ts +6 -2
  44. package/dist/types/sdk/bus/index.d.ts +29 -0
  45. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  46. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  47. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  48. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  49. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  50. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  51. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  52. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -4
  53. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  54. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  55. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  56. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  57. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  58. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  59. package/dist/types/sdk/prompt-status.d.ts +23 -3
  60. package/dist/types/sdk/session.d.ts +6 -0
  61. package/dist/types/session/agent-session.d.ts +40 -2
  62. package/dist/types/session/artifacts.d.ts +8 -0
  63. package/dist/types/session/blob-store.d.ts +57 -4
  64. package/dist/types/session/internal/managed-session-storage.d.ts +30 -4
  65. package/dist/types/session/session-manager.d.ts +23 -6
  66. package/dist/types/session/session-storage.d.ts +4 -0
  67. package/dist/types/session/streaming-output.d.ts +36 -7
  68. package/dist/types/setup/credential-import.d.ts +12 -0
  69. package/dist/types/skill-state/active-state.d.ts +12 -0
  70. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  71. package/dist/types/task/executor.d.ts +3 -2
  72. package/dist/types/task/index.d.ts +7 -1
  73. package/dist/types/task/output-manager.d.ts +1 -0
  74. package/dist/types/task/provider-retry-status.d.ts +8 -0
  75. package/dist/types/task/receipt.d.ts +9 -1
  76. package/dist/types/task/types.d.ts +23 -0
  77. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  78. package/dist/types/tools/bash-interactive.d.ts +4 -1
  79. package/dist/types/tools/bash.d.ts +3 -1
  80. package/dist/types/tools/image-gen.d.ts +4 -0
  81. package/dist/types/tools/index.d.ts +6 -0
  82. package/dist/types/tools/output-meta.d.ts +23 -2
  83. package/dist/types/tools/path-utils.d.ts +1 -0
  84. package/dist/types/tools/read.d.ts +0 -6
  85. package/dist/types/tools/review.d.ts +5 -7
  86. package/dist/types/tools/subagent.d.ts +2 -0
  87. package/dist/types/utils/gh.d.ts +13 -0
  88. package/dist/types/utils/image-resize.d.ts +1 -0
  89. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  90. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  91. package/dist/types/web/search/providers/xai.d.ts +2 -0
  92. package/package.json +10 -8
  93. package/scripts/acp-conformance-agent.ts +218 -0
  94. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  95. package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
  96. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  97. package/scripts/resident-memory-bench.ts +1341 -0
  98. package/scripts/run-acp-conformance.ts +290 -0
  99. package/src/async/job-manager.ts +99 -39
  100. package/src/cli/fast-help.ts +1 -1
  101. package/src/cli/plugin-cli.ts +176 -18
  102. package/src/commands/team.ts +19 -1
  103. package/src/config/file-lock.ts +11 -2
  104. package/src/config/settings-schema.ts +53 -2
  105. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  106. package/src/coordinator-mcp/server.ts +5 -12
  107. package/src/debug/index.ts +42 -17
  108. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  109. package/src/defaults/gjc/skills/ralplan/SKILL.md +25 -20
  110. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  111. package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -26
  112. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  113. package/src/defaults/gjc-defaults.ts +4 -4
  114. package/src/exa/mcp-client.ts +12 -3
  115. package/src/exec/bash-executor.ts +144 -27
  116. package/src/extensibility/extensions/runner.ts +15 -0
  117. package/src/extensibility/extensions/types.ts +18 -1
  118. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  119. package/src/extensibility/gjc-plugins/index.ts +19 -2
  120. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  121. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  122. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  123. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  124. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  125. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  126. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  127. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  128. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  129. package/src/extensibility/gjc-plugins/types.ts +141 -0
  130. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  131. package/src/gjc-runtime/launch-tmux.ts +25 -6
  132. package/src/gjc-runtime/launch-worktree.ts +103 -1
  133. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  134. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  135. package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
  136. package/src/gjc-runtime/ralplan-runtime.ts +491 -27
  137. package/src/gjc-runtime/team-launch.ts +8 -0
  138. package/src/gjc-runtime/team-runtime.ts +140 -32
  139. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  140. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  141. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  142. package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
  143. package/src/gjc-runtime/ultragoal-runtime.ts +635 -612
  144. package/src/gjc-runtime/workflow-manifest.generated.json +37 -99
  145. package/src/gjc-runtime/workflow-manifest.ts +21 -70
  146. package/src/internal-urls/docs-index.generated.ts +16 -16
  147. package/src/modes/DESIGN.md +104 -0
  148. package/src/modes/acp/acp-agent.ts +298 -186
  149. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  150. package/src/modes/components/irc-sidebar.ts +30 -1
  151. package/src/modes/components/notifications-settings-editor.ts +12 -1
  152. package/src/modes/components/oauth-selector.ts +68 -30
  153. package/src/modes/components/session-observer-overlay.ts +305 -86
  154. package/src/modes/components/settings-selector.ts +56 -5
  155. package/src/modes/components/status-line/gh.ts +16 -0
  156. package/src/modes/components/tool-status-header.ts +3 -15
  157. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  158. package/src/modes/controllers/command-controller.ts +42 -34
  159. package/src/modes/controllers/event-controller.ts +27 -16
  160. package/src/modes/controllers/extension-ui-controller.ts +193 -34
  161. package/src/modes/controllers/selector-controller.ts +160 -22
  162. package/src/modes/interactive-mode.ts +189 -62
  163. package/src/modes/jobs-observer.ts +1 -1
  164. package/src/modes/runtime-init.ts +3 -0
  165. package/src/modes/theme/theme.ts +11 -9
  166. package/src/modes/types.ts +88 -0
  167. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  168. package/src/prompts/system/eager-todo.md +8 -4
  169. package/src/prompts/system/system-prompt.md +11 -4
  170. package/src/prompts/tools/bash.md +5 -5
  171. package/src/prompts/tools/search-tool-bm25.md +5 -0
  172. package/src/prompts/tools/task.md +1 -1
  173. package/src/reminders/star-reminder.ts +4 -42
  174. package/src/runtime-mcp/manager.ts +19 -4
  175. package/src/sdk/acp/adapter.ts +22 -6
  176. package/src/sdk/broker/lifecycle.ts +18 -13
  177. package/src/sdk/broker/transport.ts +35 -3
  178. package/src/sdk/bus/attachment-registry.ts +5 -1
  179. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  180. package/src/sdk/bus/chat-daemon-control.ts +7 -3
  181. package/src/sdk/bus/config.ts +18 -2
  182. package/src/sdk/bus/index.ts +661 -126
  183. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  184. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  185. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  186. package/src/sdk/bus/reconciliation-store.ts +95 -16
  187. package/src/sdk/bus/rich-draft.ts +1 -1
  188. package/src/sdk/bus/rich-render.ts +2 -2
  189. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  190. package/src/sdk/bus/telegram-cli.ts +33 -8
  191. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  192. package/src/sdk/bus/telegram-daemon-contract.ts +12 -4
  193. package/src/sdk/bus/telegram-daemon.ts +916 -23
  194. package/src/sdk/bus/telegram-reference.ts +40 -3
  195. package/src/sdk/bus/threaded-render.ts +48 -3
  196. package/src/sdk/bus/topic-registry.ts +20 -0
  197. package/src/sdk/host/query/revision-store.ts +36 -2
  198. package/src/sdk/host/reverse-leases.ts +44 -7
  199. package/src/sdk/prompt-status.ts +16 -3
  200. package/src/sdk/protocol/operation-inventory.generated.json +33 -0
  201. package/src/sdk/session.ts +89 -9
  202. package/src/session/agent-session.ts +844 -173
  203. package/src/session/artifacts.ts +22 -0
  204. package/src/session/auth-broker-config.ts +10 -3
  205. package/src/session/blob-store.ts +915 -24
  206. package/src/session/internal/managed-session-scope.ts +200 -111
  207. package/src/session/internal/managed-session-storage.ts +492 -62
  208. package/src/session/session-manager.ts +1614 -435
  209. package/src/session/session-storage.ts +38 -45
  210. package/src/session/streaming-output.ts +238 -68
  211. package/src/setup/credential-import.ts +75 -13
  212. package/src/skill-state/active-state.ts +32 -0
  213. package/src/skill-state/workflow-hud.ts +17 -0
  214. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  215. package/src/task/executor.ts +122 -39
  216. package/src/task/index.ts +280 -45
  217. package/src/task/output-manager.ts +24 -14
  218. package/src/task/provider-retry-status.ts +16 -0
  219. package/src/task/receipt.ts +34 -8
  220. package/src/task/render.ts +6 -2
  221. package/src/task/types.ts +108 -0
  222. package/src/tools/ask-answer-registry.ts +12 -6
  223. package/src/tools/ask.ts +86 -55
  224. package/src/tools/ast-edit.ts +1 -0
  225. package/src/tools/ast-grep.ts +1 -0
  226. package/src/tools/bash-interactive.ts +8 -3
  227. package/src/tools/bash.ts +503 -60
  228. package/src/tools/find.ts +1 -0
  229. package/src/tools/image-gen.ts +29 -5
  230. package/src/tools/index.ts +6 -0
  231. package/src/tools/output-meta.ts +108 -14
  232. package/src/tools/path-utils.ts +3 -2
  233. package/src/tools/read.ts +18 -0
  234. package/src/tools/review.ts +5 -7
  235. package/src/tools/search.ts +1 -0
  236. package/src/tools/subagent-render.ts +6 -2
  237. package/src/tools/subagent.ts +6 -0
  238. package/src/tools/telegram-send.ts +6 -3
  239. package/src/utils/gh.ts +39 -0
  240. package/src/utils/image-resize.ts +3 -0
  241. package/src/web/search/providers/anthropic.ts +4 -4
  242. package/src/web/search/providers/kimi.ts +15 -5
  243. package/src/web/search/providers/searxng.ts +47 -4
  244. package/src/web/search/providers/xai.ts +7 -2
  245. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  246. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  247. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -7,7 +7,19 @@
7
7
  import { APP_NAME, getProjectDir } from "@gajae-code/utils";
8
8
  import chalk from "chalk";
9
9
  import { resolveOrDefaultProjectRegistryPath } from "../discovery/helpers";
10
- import { installGjcPluginBundle, isGjcPluginBundleSource, readRegistry } from "../extensibility/gjc-plugins";
10
+ import {
11
+ applyGjcBundleUpdate,
12
+ bundleIdentity,
13
+ type GjcBundleIdentity,
14
+ type GjcBundleSummary,
15
+ GjcPluginLoadError,
16
+ getGjcBundle,
17
+ installGjcBundle,
18
+ isGjcPluginBundleSource,
19
+ isGjcPluginSourceShape,
20
+ listGjcBundles,
21
+ previewGjcBundleUpdate,
22
+ } from "../extensibility/gjc-plugins";
11
23
  import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
12
24
  import {
13
25
  getInstalledPluginsRegistryPath,
@@ -312,9 +324,118 @@ async function handleDiscover(args: string[], _flags: PluginCommandArgs["flags"]
312
324
  }
313
325
  }
314
326
 
327
+ /**
328
+ * Scope-qualified GJC bundle upgrade: re-resolve the stored source, review the
329
+ * candidate, then apply it as a compare-and-swap. `--dry-run` stops after the
330
+ * preview. Requires exactly one of `--user` / `--project` because (scope, name)
331
+ * is the canonical target.
332
+ */
333
+ async function handleGjcUpgrade(name: string, flags: PluginCommandArgs["flags"]): Promise<void> {
334
+ if (flags.user === flags.project) {
335
+ console.error(chalk.red(`GJC bundle upgrade requires exactly one of --user or --project for "${name}".`));
336
+ process.exit(1);
337
+ }
338
+ const scope: "user" | "project" = flags.user ? "user" : "project";
339
+ const ctx = { cwd: getProjectDir() };
340
+ const identity = bundleIdentity(scope, name);
341
+
342
+ const emitError = (error: { code: string; message: string; recovery?: string }): never => {
343
+ if (flags.json) console.log(JSON.stringify({ error }, null, 2));
344
+ else {
345
+ console.error(chalk.red(`${theme.status.error} ${error.message}`));
346
+ if (error.recovery) console.error(chalk.dim(` Try: ${error.recovery}`));
347
+ }
348
+ process.exit(3);
349
+ };
350
+
351
+ // Source re-resolution can throw with a cause carrying the raw locator, so
352
+ // the whole flow reports a stable code instead of the underlying error.
353
+ try {
354
+ await runGjcUpgrade(ctx, identity, name, scope, flags, emitError);
355
+ } catch (err) {
356
+ const reason = err instanceof GjcPluginLoadError ? err.code : "upgrade_failed";
357
+ console.error(chalk.red(`${theme.status.error} Failed to upgrade GJC bundle ${name} (${reason})`));
358
+ process.exit(1);
359
+ }
360
+ }
361
+
362
+ async function runGjcUpgrade(
363
+ ctx: { cwd: string },
364
+ identity: GjcBundleIdentity,
365
+ name: string,
366
+ scope: "user" | "project",
367
+ flags: PluginCommandArgs["flags"],
368
+ emitError: (error: { code: string; message: string; recovery?: string }) => never,
369
+ ): Promise<void> {
370
+ const preview = await previewGjcBundleUpdate(ctx, identity);
371
+ if (!preview.ok) emitError(preview.error);
372
+ else if (flags.dryRun || !preview.value.changed) {
373
+ const { changed, candidateVersion, addedSurfaceIds, removedSurfaceIds } = preview.value;
374
+ if (flags.json) {
375
+ console.log(
376
+ JSON.stringify(
377
+ {
378
+ status: changed ? "update-available" : "up-to-date",
379
+ identity,
380
+ currentVersion: preview.value.current.version,
381
+ candidateVersion,
382
+ addedSurfaceIds,
383
+ removedSurfaceIds,
384
+ },
385
+ null,
386
+ 2,
387
+ ),
388
+ );
389
+ } else if (!changed) {
390
+ console.log(chalk.dim(`GJC bundle ${name} (${scope}) is up to date at ${preview.value.current.version}`));
391
+ } else {
392
+ console.log(
393
+ chalk.cyan(`[dry-run] ${name} (${scope}): ${preview.value.current.version} -> ${candidateVersion}`),
394
+ );
395
+ if (addedSurfaceIds.length > 0) console.log(chalk.dim(` + ${addedSurfaceIds.join(", ")}`));
396
+ if (removedSurfaceIds.length > 0) console.log(chalk.dim(` - ${removedSurfaceIds.join(", ")}`));
397
+ }
398
+ } else {
399
+ const applied = await applyGjcBundleUpdate(ctx, preview.value.token);
400
+ if (!applied.ok) emitError(applied.error);
401
+ else if (flags.json) {
402
+ console.log(
403
+ JSON.stringify(
404
+ {
405
+ status: applied.value.status,
406
+ bundle: applied.value.summary,
407
+ remnantCount: applied.value.remnantCount,
408
+ },
409
+ null,
410
+ 2,
411
+ ),
412
+ );
413
+ } else {
414
+ console.log(
415
+ chalk.green(
416
+ `${theme.status.success} ${applied.value.status} GJC bundle ${name}@${applied.value.summary.version} (${scope})`,
417
+ ),
418
+ );
419
+ if (applied.value.remnantCount > 0) {
420
+ console.error(chalk.yellow(` ${applied.value.remnantCount} leftover directory could not be removed`));
421
+ }
422
+ }
423
+ }
424
+ }
425
+
315
426
  async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]): Promise<void> {
316
- const manager = await makeMarketplaceManager();
317
427
  const pluginId = args[0];
428
+ // Scope-qualified GJC bundles upgrade through the lifecycle service, never
429
+ // through the marketplace manager.
430
+ if (pluginId && (flags.user || flags.project)) {
431
+ const scope: "user" | "project" = flags.user ? "user" : "project";
432
+ const existing = await getGjcBundle({ cwd: getProjectDir() }, bundleIdentity(scope, pluginId));
433
+ if (existing.ok) {
434
+ await handleGjcUpgrade(pluginId, flags);
435
+ return;
436
+ }
437
+ }
438
+ const manager = await makeMarketplaceManager();
318
439
  try {
319
440
  if (pluginId) {
320
441
  if (flags.scope) {
@@ -349,6 +470,15 @@ async function handleUpgrade(args: string[], flags: PluginCommandArgs["flags"]):
349
470
  }
350
471
  }
351
472
 
473
+ /**
474
+ * Stable, non-identifying reason for an install failure. The raw spec and the
475
+ * underlying cause can both carry credentials, a query string, or an absolute
476
+ * home path, so neither is ever printed.
477
+ */
478
+ function describeInstallFailure(error: unknown): string {
479
+ return error instanceof GjcPluginLoadError ? error.code : "install_failed";
480
+ }
481
+
352
482
  async function handleInstall(
353
483
  manager: PluginManager,
354
484
  packages: string[],
@@ -374,27 +504,52 @@ async function handleInstall(
374
504
  const knownMarketplaces = new Set((await mktMgr.listMarketplaces()).map(m => m.name));
375
505
 
376
506
  for (const spec of packages) {
377
- // GJC plugin bundle classifier: a source containing gajae-plugin.json (or a
378
- // git/tarball source) routes to the bundle installer BEFORE marketplace/npm.
379
- if (await isGjcPluginBundleSource(spec)) {
507
+ // A GJC bundle is identified by the SHAPE of its source: a filesystem path,
508
+ // a git locator, or a tarball. npm and marketplace specs are never any of
509
+ // those, so shape alone separates the two worlds without resolving.
510
+ //
511
+ // Shape is checked BEFORE `isGjcPluginBundleSource`, which resolves the
512
+ // source: a deleted or unreachable GJC source fails that probe and would
513
+ // otherwise fall through to npm, losing the create-only refusal the
514
+ // lifecycle owes for an already-installed target.
515
+ if (isGjcPluginSourceShape(spec) || (await isGjcPluginBundleSource(spec))) {
380
516
  if (flags.user === flags.project) {
381
517
  console.error(
382
- chalk.red(`GJC plugin bundle install requires exactly one of --user or --project for "${spec}".`),
518
+ // The spec can carry credentials or an absolute home path, so name
519
+ // the missing flag instead of echoing it back.
520
+ chalk.red("GJC plugin bundle install requires exactly one of --user or --project."),
383
521
  );
384
522
  process.exit(1);
385
523
  }
386
524
  const scope: "user" | "project" = flags.user ? "user" : "project";
387
525
  try {
388
- const res = await installGjcPluginBundle(spec, { scope, cwd: process.cwd(), force: flags.force });
526
+ const res = await installGjcBundle({ cwd: getProjectDir() }, scope, spec);
527
+ if (!res.ok) {
528
+ const doc = {
529
+ error: { code: res.error.code, message: res.error.message, recovery: res.error.recovery },
530
+ };
531
+ if (flags.json) console.log(JSON.stringify(doc, null, 2));
532
+ else {
533
+ console.error(chalk.red(`${theme.status.error} ${res.error.message}`));
534
+ if (res.error.recovery) console.error(chalk.dim(` Try: ${res.error.recovery}`));
535
+ }
536
+ process.exit(3);
537
+ }
538
+ const { summary } = res.value;
389
539
  if (flags.json) {
390
- console.log(JSON.stringify({ name: res.entry.name, status: res.status, scope }, null, 2));
540
+ console.log(JSON.stringify({ status: res.value.status, bundle: summary }, null, 2));
391
541
  } else {
392
542
  console.log(
393
- chalk.green(`${theme.status.success} ${res.status} GJC plugin ${res.entry.name} (${scope})`),
543
+ chalk.green(
544
+ `${theme.status.success} installed GJC plugin ${summary.identity.name}@${summary.version} (${scope})`,
545
+ ),
394
546
  );
395
547
  }
396
548
  } catch (err) {
397
- console.error(chalk.red(`${theme.status.error} Failed to install GJC plugin ${spec}: ${err}`));
549
+ // Never echo the raw spec or the underlying cause: either can carry
550
+ // credentials, a query string, or an absolute home path.
551
+ const reason = err instanceof GjcPluginLoadError ? err.code : "install_failed";
552
+ console.error(chalk.red(`${theme.status.error} Failed to install GJC bundle (${reason})`));
398
553
  process.exit(1);
399
554
  }
400
555
  continue;
@@ -414,7 +569,9 @@ async function handleInstall(
414
569
  ),
415
570
  );
416
571
  } catch (err) {
417
- console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
572
+ // The spec can carry credentials, a query string, or an absolute home
573
+ // path, so report the failure without echoing it or the raw cause.
574
+ console.error(chalk.red(`${theme.status.error} Failed to install plugin (${describeInstallFailure(err)})`));
418
575
  process.exit(1);
419
576
  }
420
577
  continue;
@@ -449,7 +606,9 @@ async function handleInstall(
449
606
  }
450
607
  }
451
608
  } catch (err) {
452
- console.error(chalk.red(`${theme.status.error} Failed to install ${spec}: ${err}`));
609
+ // The spec can carry credentials, a query string, or an absolute home
610
+ // path, so report the failure without echoing it or the raw cause.
611
+ console.error(chalk.red(`${theme.status.error} Failed to install plugin (${describeInstallFailure(err)})`));
453
612
  process.exit(1);
454
613
  }
455
614
  }
@@ -503,8 +662,7 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
503
662
  const mktMgr = await makeMarketplaceManager();
504
663
  const mktPlugins = await mktMgr.listInstalledPlugins();
505
664
  const cwd = getProjectDir();
506
- const [gjcUser, gjcProject] = await Promise.all([readRegistry("user", cwd), readRegistry("project", cwd)]);
507
- const gjcBundles = [...gjcUser.plugins, ...gjcProject.plugins];
665
+ const gjcBundles: GjcBundleSummary[] = await listGjcBundles({ cwd });
508
666
 
509
667
  if (flags.json) {
510
668
  console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins, gjc: gjcBundles }, null, 2));
@@ -559,9 +717,9 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
559
717
  console.log(chalk.bold("GJC Plugin Bundles:\n"));
560
718
  for (const plugin of gjcBundles) {
561
719
  const status = plugin.enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
562
- const scopeLabel = chalk.dim(` (${plugin.scope})`);
563
- const disabledCount = plugin.disabledSurfaceIds.length;
564
- const quarantineCount = plugin.quarantine?.length ?? 0;
720
+ const scopeLabel = chalk.dim(` (${plugin.identity.scope})`);
721
+ const disabledCount = plugin.surfaces.filter(s => !s.enabled).length;
722
+ const quarantineCount = plugin.surfaces.filter(s => s.quarantined).length;
565
723
  const detail = [
566
724
  disabledCount > 0 ? `${disabledCount} disabled` : null,
567
725
  quarantineCount > 0 ? `${quarantineCount} quarantined` : null,
@@ -569,7 +727,7 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
569
727
  .filter((v): v is string => Boolean(v))
570
728
  .join(", ");
571
729
  console.log(
572
- `${status} ${plugin.name}@${plugin.version}${scopeLabel}${detail ? chalk.dim(` — ${detail}`) : ""}`,
730
+ `${status} ${plugin.identity.name}@${plugin.version}${scopeLabel}${detail ? chalk.dim(` — ${detail}`) : ""}`,
573
731
  );
574
732
  }
575
733
  }
@@ -13,6 +13,7 @@ import {
13
13
  readGjcTeamSnapshot,
14
14
  shutdownGjcTeam,
15
15
  startGjcTeam,
16
+ UnknownGjcTeamApiOperationError,
16
17
  } from "../gjc-runtime/team-runtime";
17
18
  import { syncSkillActiveState } from "../skill-state/active-state";
18
19
 
@@ -183,7 +184,24 @@ export default class Team extends Command {
183
184
  return;
184
185
  }
185
186
  const input = parseInputFlag(rest);
186
- const result = await executeGjcTeamApiOperation(operation, input);
187
+ let result: unknown;
188
+ try {
189
+ result = await executeGjcTeamApiOperation(operation, input);
190
+ } catch (error) {
191
+ if (!(error instanceof UnknownGjcTeamApiOperationError)) throw error;
192
+ process.exitCode = 1;
193
+ if (json) {
194
+ writeReceipt({
195
+ ok: false,
196
+ error: error.code,
197
+ operation: error.operation,
198
+ suggestions: error.suggestions,
199
+ });
200
+ } else {
201
+ process.stderr.write(`${error.message}\n`);
202
+ }
203
+ return;
204
+ }
187
205
  const teamName = String(input.team_name ?? input.teamName ?? "").trim();
188
206
  if (teamName) {
189
207
  try {
@@ -1,7 +1,7 @@
1
1
  import type { Stats } from "node:fs";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
- import { isEnoent } from "@gajae-code/utils/fs-error";
4
+ import { hasFsCode, isEnoent } from "@gajae-code/utils/fs-error";
5
5
 
6
6
  export interface FileLockOptions {
7
7
  staleMs?: number;
@@ -185,7 +185,16 @@ async function staleLockSnapshot(
185
185
  staleMs: number,
186
186
  startTimeCache?: Map<string, string | null>,
187
187
  ): Promise<LockStaleSnapshot> {
188
- const info = await readLockInfo(lockPath);
188
+ let info: LockInfo | null;
189
+ try {
190
+ info = await readLockInfo(lockPath);
191
+ } catch (error) {
192
+ // Windows can transiently deny reads of a just-created lock metadata file
193
+ // while another contender is publishing it. Treat that as active
194
+ // contention and retry rather than failing the caller or reaping by path.
195
+ if (hasFsCode(error, "EPERM")) return { stale: false };
196
+ throw error;
197
+ }
189
198
  if (!info) {
190
199
  try {
191
200
  const stats = await fs.stat(lockPath);
@@ -275,6 +275,13 @@ export const SETTINGS_SCHEMA = {
275
275
  values: ["copy-retain", "disabled"] as const,
276
276
  default: "copy-retain",
277
277
  },
278
+ // SDK-owned prompt deadline. Hidden from the UI; ACP has no separate timeout.
279
+ "sdk.promptDeadlineMs": {
280
+ type: "number",
281
+ default: 1_800_000,
282
+ description: "SDK-owned prompt deadline; ACP has no separate timeout.",
283
+ validate: (value: number) => Number.isSafeInteger(value) && value >= 60_000 && value <= 86_400_000,
284
+ },
278
285
 
279
286
  // Notifications (shared daemon with Telegram/Discord/Slack presentation adapters)
280
287
  "notifications.enabled": { type: "boolean", default: false },
@@ -287,6 +294,17 @@ export const SETTINGS_SCHEMA = {
287
294
  "notifications.telegram.activation": { type: "record", default: {} as Record<string, unknown> },
288
295
  "notifications.telegram.btw.enabled": { type: "boolean", default: true },
289
296
  "notifications.telegram.streaming.enabled": { type: "boolean", default: true },
297
+ "notifications.telegram.sound": {
298
+ type: "enum",
299
+ values: ["all", "important", "none"] as const,
300
+ default: "all",
301
+ ui: {
302
+ tab: "notifications",
303
+ label: "Telegram Notification Sounds",
304
+ description: "Choose which Telegram notifications play a sound.",
305
+ editing: "notification-atomic",
306
+ },
307
+ },
290
308
  "notifications.telegram.rich.enabled": {
291
309
  type: "boolean",
292
310
  default: true,
@@ -489,6 +507,22 @@ export const SETTINGS_SCHEMA = {
489
507
  options: "runtime",
490
508
  },
491
509
  },
510
+ "session.resumeModelBehavior": {
511
+ type: "enum",
512
+ values: ["keepSessionModel", "useCurrentDefault", "ask"] as const,
513
+ default: "keepSessionModel",
514
+ ui: {
515
+ tab: "model",
516
+ label: "Resume Model Behavior",
517
+ description:
518
+ "When resuming a session: keep the model that session last used, switch to the currently configured default model, or ask (TUI only; falls back to keeping the session's model in headless/CLI resume).",
519
+ options: [
520
+ { value: "keepSessionModel", label: "Keep session's saved model" },
521
+ { value: "useCurrentDefault", label: "Use current default model" },
522
+ { value: "ask", label: "Ask on resume (TUI only)" },
523
+ ],
524
+ },
525
+ },
492
526
 
493
527
  modelTags: { type: "record", default: EMPTY_MODEL_TAGS_RECORD },
494
528
 
@@ -501,6 +535,11 @@ export const SETTINGS_SCHEMA = {
501
535
  default: 0.05,
502
536
  validate: (value: number) => Number.isFinite(value) && value > 0 && value <= 1,
503
537
  },
538
+ "gjc.ralplan.autoHandoff": {
539
+ type: "enum",
540
+ values: ["off", "ultragoal", "team"],
541
+ default: "off",
542
+ },
504
543
  "gjc.ralplan.maxIterations": {
505
544
  type: "number",
506
545
  default: 5,
@@ -834,8 +873,8 @@ export const SETTINGS_SCHEMA = {
834
873
  default: true,
835
874
  ui: {
836
875
  tab: "appearance",
837
- label: "Status Line Action Hints",
838
- description: "Show contextual keyboard shortcuts in the status line",
876
+ label: "Composer Shortcut Hints",
877
+ description: "Show contextual keyboard shortcuts in the composer placeholder",
839
878
  },
840
879
  },
841
880
 
@@ -1224,6 +1263,16 @@ export const SETTINGS_SCHEMA = {
1224
1263
  "Maximum provider stream replay retries for replay-safe transient stream failures. Counts retries, not the first attempt. Set to 0 to disable provider stream retries.",
1225
1264
  },
1226
1265
  },
1266
+ "retry.streamFirstEventTimeoutMs": {
1267
+ type: "number",
1268
+ default: 100_000,
1269
+ validate: (value: number) => Number.isFinite(value) && value >= 0,
1270
+ ui: {
1271
+ tab: "model",
1272
+ label: "First Event Timeout",
1273
+ description: "Maximum wait for the first provider stream event, in ms. Set to 0 to disable the watchdog.",
1274
+ },
1275
+ },
1227
1276
  "retry.fallbackChains": { type: "record", default: {} as Record<string, string[]> },
1228
1277
  "retry.fallbackRevertPolicy": {
1229
1278
  type: "enum",
@@ -3810,6 +3859,7 @@ export interface RetrySettings {
3810
3859
  maxDelayMs: number;
3811
3860
  requestMaxRetries: number;
3812
3861
  streamMaxRetries: number;
3862
+ streamFirstEventTimeoutMs: number;
3813
3863
  }
3814
3864
 
3815
3865
  export interface MemoriesSettings {
@@ -3929,6 +3979,7 @@ export interface NotificationsSettings {
3929
3979
  telegram: {
3930
3980
  botToken: string | undefined;
3931
3981
  chatId: string | undefined;
3982
+ sound: "all" | "important" | "none";
3932
3983
  btw: {
3933
3984
  enabled: boolean;
3934
3985
  };
@@ -147,7 +147,7 @@ function askSchema(labels: string[], multi: boolean, allowEmpty: boolean): JsonS
147
147
  }
148
148
  export function decodeAskGateV1(gate: WorkflowGate): PrivateAskGateCodecV1 | null {
149
149
  if (
150
- gate.stage !== "deep-interview" ||
150
+ (gate.stage !== "deep-interview" && gate.stage !== "ralplan" && gate.stage !== "ultragoal") ||
151
151
  gate.kind !== "question" ||
152
152
  !isRecord(gate.context) ||
153
153
  !boundedText(gate.context.prompt, MAX_TEXT) ||
@@ -859,7 +859,7 @@ interface RuntimePromptAcknowledgement {
859
859
  turn_id: string;
860
860
  }
861
861
 
862
- function acknowledgementPayload(result: unknown): Record<string, unknown> | null {
862
+ function sdkResultPayload(result: unknown): Record<string, unknown> | null {
863
863
  const response = asRecord(result);
864
864
  if (!response) return null;
865
865
  const envelope = ["ok", "result", "error"].some(key => Object.hasOwn(response, key));
@@ -887,7 +887,7 @@ function runtimeAcknowledgementIdentity(
887
887
  }
888
888
 
889
889
  function normalizeRuntimePromptAcknowledgement(result: unknown): RuntimePromptAcknowledgement {
890
- const acknowledgement = acknowledgementPayload(result);
890
+ const acknowledgement = sdkResultPayload(result);
891
891
  if (acknowledgement?.accepted !== true)
892
892
  throw new SdkClientError("unavailable", "SDK did not acknowledge prompt delivery.");
893
893
  return {
@@ -2458,15 +2458,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
2458
2458
  items.push(...pageItems);
2459
2459
  if (complete) {
2460
2460
  const valid = items.every(item => {
2461
- if (!item || typeof item !== "object" || Buffer.byteLength(JSON.stringify(item)) > 16 * 1024)
2462
- return false;
2463
- const gate = item as WorkflowGateQueryRecord & WorkflowGate;
2464
- return (
2465
- gate.tag === "pending" &&
2466
- typeof gate.gate_id === "string" &&
2467
- gate.gate_id.length > 0 &&
2468
- !!decodeAskGateV1(gate)
2469
- );
2461
+ const encoded = JSON.stringify(item);
2462
+ return typeof encoded === "string" && Buffer.byteLength(encoded) <= 16 * 1024;
2470
2463
  });
2471
2464
  return valid
2472
2465
  ? { items, revision, complete: true, reason: null }
@@ -4492,7 +4485,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
4492
4485
  { id: gateId, response: translated, expectedSessionId: sessionId },
4493
4486
  (claimed as { requestId: string }).requestId,
4494
4487
  );
4495
- const resolution = asRecord(result);
4488
+ const resolution = sdkResultPayload(result);
4496
4489
  const status = resolution?.status;
4497
4490
  if (status === "rejected") {
4498
4491
  await withAdmittedSessionTransaction(questionPaths, sessionId, async transaction => {
@@ -11,6 +11,7 @@ import { getSessionsDir } from "@gajae-code/utils";
11
11
  import { DynamicBorder } from "../modes/components/dynamic-border";
12
12
  import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme";
13
13
  import type { InteractiveModeContext } from "../modes/types";
14
+ import { suspendInteractiveActivityIndicator } from "../modes/types";
14
15
  import { formatBytes } from "../tools/render-utils";
15
16
  import { openPath } from "../utils/open";
16
17
  import { DebugLogViewerComponent } from "./log-viewer";
@@ -77,10 +78,12 @@ export class DebugSelectorComponent extends Container {
77
78
  }
78
79
 
79
80
  handleInput(keyData: string): void {
81
+ if (this.ctx.isStopped?.()) return;
80
82
  this.#selectList.handleInput(keyData);
81
83
  }
82
84
 
83
85
  async #handleSelection(value: string): Promise<void> {
86
+ if (this.ctx.isStopped?.()) return;
84
87
  switch (value) {
85
88
  case "open-artifacts":
86
89
  await this.#handleOpenArtifacts();
@@ -115,12 +118,23 @@ export class DebugSelectorComponent extends Container {
115
118
  }
116
119
  }
117
120
 
121
+ #finishStatusLoader(loader: Loader, releaseActivityIndicator: () => void): void {
122
+ loader.stop();
123
+ if (!this.ctx.isStopped?.()) this.ctx.statusContainer.clear();
124
+ releaseActivityIndicator();
125
+ }
126
+
118
127
  async #handlePerformanceReport(): Promise<void> {
119
128
  // Start profiling
120
129
  let session: ProfilerSession;
121
130
  try {
122
131
  session = await startCpuProfile();
132
+ if (this.ctx.isStopped?.()) {
133
+ await session.stop().catch(() => {});
134
+ return;
135
+ }
123
136
  } catch (err) {
137
+ if (this.ctx.isStopped?.()) return;
124
138
  this.ctx.showError(`Failed to start profiler: ${err instanceof Error ? err.message : String(err)}`);
125
139
  return;
126
140
  }
@@ -151,9 +165,17 @@ export class DebugSelectorComponent extends Container {
151
165
  resolve();
152
166
  };
153
167
 
154
- await promise;
168
+ const stopped = Promise.withResolvers<void>();
169
+ const unsubscribeStop = this.ctx.onStop?.(() => stopped.resolve());
170
+ const stopWon = await Promise.race([promise.then(() => false), stopped.promise.then(() => true)]);
171
+ unsubscribeStop?.();
172
+ if (stopWon || this.ctx.isStopped?.()) {
173
+ await session.stop().catch(() => {});
174
+ return;
175
+ }
155
176
 
156
177
  // Stop profiling and create report
178
+ const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
157
179
  const loader = new Loader(
158
180
  this.ctx.ui,
159
181
  spinner => theme.fg("accent", spinner),
@@ -174,8 +196,8 @@ export class DebugSelectorComponent extends Container {
174
196
  workProfile,
175
197
  });
176
198
 
177
- loader.stop();
178
- this.ctx.statusContainer.clear();
199
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
200
+ if (this.ctx.isStopped?.()) return;
179
201
 
180
202
  this.ctx.chatContainer.addChild(new Spacer(1));
181
203
  this.ctx.chatContainer.addChild(
@@ -184,8 +206,8 @@ export class DebugSelectorComponent extends Container {
184
206
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
185
207
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
186
208
  } catch (err) {
187
- loader.stop();
188
- this.ctx.statusContainer.clear();
209
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
210
+ if (this.ctx.isStopped?.()) return;
189
211
  this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
190
212
  }
191
213
 
@@ -219,6 +241,7 @@ export class DebugSelectorComponent extends Container {
219
241
  }
220
242
 
221
243
  async #handleDumpReport(): Promise<void> {
244
+ const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
222
245
  const loader = new Loader(
223
246
  this.ctx.ui,
224
247
  spinner => theme.fg("accent", spinner),
@@ -235,8 +258,8 @@ export class DebugSelectorComponent extends Container {
235
258
  settings: this.#getResolvedSettings(),
236
259
  });
237
260
 
238
- loader.stop();
239
- this.ctx.statusContainer.clear();
261
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
262
+ if (this.ctx.isStopped?.()) return;
240
263
 
241
264
  this.ctx.chatContainer.addChild(new Spacer(1));
242
265
  this.ctx.chatContainer.addChild(
@@ -245,8 +268,8 @@ export class DebugSelectorComponent extends Container {
245
268
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
246
269
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
247
270
  } catch (err) {
248
- loader.stop();
249
- this.ctx.statusContainer.clear();
271
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
272
+ if (this.ctx.isStopped?.()) return;
250
273
  this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
251
274
  }
252
275
 
@@ -254,6 +277,7 @@ export class DebugSelectorComponent extends Container {
254
277
  }
255
278
 
256
279
  async #handleMemoryReport(): Promise<void> {
280
+ const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
257
281
  const loader = new Loader(
258
282
  this.ctx.ui,
259
283
  spinner => theme.fg("accent", spinner),
@@ -274,8 +298,8 @@ export class DebugSelectorComponent extends Container {
274
298
  heapSnapshot,
275
299
  });
276
300
 
277
- loader.stop();
278
- this.ctx.statusContainer.clear();
301
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
302
+ if (this.ctx.isStopped?.()) return;
279
303
 
280
304
  this.ctx.chatContainer.addChild(new Spacer(1));
281
305
  this.ctx.chatContainer.addChild(
@@ -284,8 +308,8 @@ export class DebugSelectorComponent extends Container {
284
308
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
285
309
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
286
310
  } catch (err) {
287
- loader.stop();
288
- this.ctx.statusContainer.clear();
311
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
312
+ if (this.ctx.isStopped?.()) return;
289
313
  this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
290
314
  }
291
315
 
@@ -405,6 +429,7 @@ export class DebugSelectorComponent extends Container {
405
429
  }
406
430
 
407
431
  // Clear cache
432
+ const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
408
433
  const loader = new Loader(
409
434
  this.ctx.ui,
410
435
  spinner => theme.fg("accent", spinner),
@@ -418,8 +443,8 @@ export class DebugSelectorComponent extends Container {
418
443
  try {
419
444
  const result = await clearArtifactCache(sessionsDir, 30);
420
445
 
421
- loader.stop();
422
- this.ctx.statusContainer.clear();
446
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
447
+ if (this.ctx.isStopped?.()) return;
423
448
 
424
449
  this.ctx.chatContainer.addChild(new Spacer(1));
425
450
  this.ctx.chatContainer.addChild(
@@ -430,8 +455,8 @@ export class DebugSelectorComponent extends Container {
430
455
  ),
431
456
  );
432
457
  } catch (err) {
433
- loader.stop();
434
- this.ctx.statusContainer.clear();
458
+ this.#finishStatusLoader(loader, releaseActivityIndicator);
459
+ if (this.ctx.isStopped?.()) return;
435
460
  this.ctx.showError(`Failed to clear cache: ${err instanceof Error ? err.message : String(err)}`);
436
461
  }
437
462
 
@@ -180,6 +180,7 @@ Run this phase only when the active deep-interview state or invocation indicates
180
180
  - Direct `.gjc/` file edits are forbidden unless an explicit force override is active; do not use `write`, `edit`, or `ast_edit` against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or other `.gjc/` paths during normal workflow operation.
181
181
  - Preferred: pass the spec markdown **inline** to the native deep-interview write command (`--write … --spec "<markdown>"`) — no scratch file is needed. The CLI is the only sanctioned writer for `.gjc/_session-{sessionid}/specs`.
182
182
  - Only if a spec is too large to pass inline, stage it with the `write` tool to a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree, then pass that path to `--spec`. The planning phase-boundary block tolerates these neutral temp writes; never stage interview artifacts inside the repo or under `.gjc/`, and do not improvise repo-relative scratch files.
183
+ - When staging via bash instead of the `write` tool, a heredoc into a neutral temp path (`cat > /tmp/spec.md <<'EOF' … EOF`) is also tolerated; quote the heredoc delimiter (`<<'EOF'`) so the document body stays inert. Never stage into the repo or `.gjc/`, and prefer the `write` tool over bash for large bodies.
183
184
 
184
185
  4. **Initialize state** via `gjc deep-interview write --input '<json>'`:
185
186