@gajae-code/coding-agent 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +54 -0
  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-runtime.d.ts +18 -0
  16. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  17. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  18. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  19. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  20. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  21. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  22. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  23. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  24. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  25. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  26. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  27. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  28. package/dist/types/reminders/star-reminder.d.ts +3 -13
  29. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  30. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  31. package/dist/types/sdk/broker/transport.d.ts +2 -0
  32. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  33. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  34. package/dist/types/sdk/bus/config.d.ts +6 -2
  35. package/dist/types/sdk/bus/index.d.ts +29 -0
  36. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  37. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  38. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  39. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  40. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  41. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  42. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  43. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  44. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  45. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  46. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  47. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  48. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  49. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  50. package/dist/types/sdk/prompt-status.d.ts +23 -3
  51. package/dist/types/sdk/session.d.ts +6 -0
  52. package/dist/types/session/agent-session.d.ts +38 -2
  53. package/dist/types/session/artifacts.d.ts +8 -0
  54. package/dist/types/session/blob-store.d.ts +57 -4
  55. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  56. package/dist/types/session/session-manager.d.ts +23 -6
  57. package/dist/types/session/session-storage.d.ts +4 -0
  58. package/dist/types/session/streaming-output.d.ts +36 -7
  59. package/dist/types/setup/credential-import.d.ts +12 -0
  60. package/dist/types/skill-state/active-state.d.ts +12 -0
  61. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  62. package/dist/types/task/executor.d.ts +3 -2
  63. package/dist/types/task/index.d.ts +3 -1
  64. package/dist/types/task/provider-retry-status.d.ts +8 -0
  65. package/dist/types/task/receipt.d.ts +7 -1
  66. package/dist/types/task/types.d.ts +23 -0
  67. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  68. package/dist/types/tools/bash-interactive.d.ts +4 -1
  69. package/dist/types/tools/bash.d.ts +3 -1
  70. package/dist/types/tools/image-gen.d.ts +4 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/output-meta.d.ts +23 -2
  73. package/dist/types/tools/path-utils.d.ts +1 -0
  74. package/dist/types/tools/read.d.ts +0 -6
  75. package/dist/types/tools/review.d.ts +5 -7
  76. package/dist/types/tools/subagent.d.ts +2 -0
  77. package/dist/types/utils/gh.d.ts +13 -0
  78. package/dist/types/utils/image-resize.d.ts +1 -0
  79. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  80. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  81. package/dist/types/web/search/providers/xai.d.ts +2 -0
  82. package/package.json +10 -8
  83. package/scripts/acp-conformance-agent.ts +218 -0
  84. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  85. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  86. package/scripts/resident-memory-bench.ts +1341 -0
  87. package/scripts/run-acp-conformance.ts +285 -0
  88. package/src/async/job-manager.ts +78 -20
  89. package/src/cli/fast-help.ts +1 -1
  90. package/src/cli/plugin-cli.ts +176 -18
  91. package/src/commands/team.ts +19 -1
  92. package/src/config/file-lock.ts +11 -2
  93. package/src/config/settings-schema.ts +51 -0
  94. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  95. package/src/coordinator-mcp/server.ts +5 -12
  96. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  97. package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
  98. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  99. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
  100. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  101. package/src/defaults/gjc-defaults.ts +4 -4
  102. package/src/exa/mcp-client.ts +12 -3
  103. package/src/exec/bash-executor.ts +144 -27
  104. package/src/extensibility/extensions/runner.ts +15 -0
  105. package/src/extensibility/extensions/types.ts +18 -1
  106. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  107. package/src/extensibility/gjc-plugins/index.ts +19 -2
  108. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  109. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  110. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  111. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  112. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  113. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  114. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  115. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  116. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  117. package/src/extensibility/gjc-plugins/types.ts +141 -0
  118. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  119. package/src/gjc-runtime/launch-tmux.ts +25 -6
  120. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  121. package/src/gjc-runtime/ralplan-runtime.ts +411 -17
  122. package/src/gjc-runtime/team-launch.ts +8 -0
  123. package/src/gjc-runtime/team-runtime.ts +140 -32
  124. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  126. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  127. package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
  128. package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
  129. package/src/gjc-runtime/workflow-manifest.ts +4 -68
  130. package/src/internal-urls/docs-index.generated.ts +15 -15
  131. package/src/modes/DESIGN.md +51 -0
  132. package/src/modes/acp/acp-agent.ts +298 -186
  133. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  134. package/src/modes/components/notifications-settings-editor.ts +12 -1
  135. package/src/modes/components/oauth-selector.ts +68 -30
  136. package/src/modes/components/session-observer-overlay.ts +305 -86
  137. package/src/modes/components/settings-selector.ts +56 -5
  138. package/src/modes/components/status-line/gh.ts +16 -0
  139. package/src/modes/components/tool-status-header.ts +3 -14
  140. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  141. package/src/modes/controllers/command-controller.ts +4 -0
  142. package/src/modes/controllers/extension-ui-controller.ts +7 -0
  143. package/src/modes/controllers/selector-controller.ts +45 -0
  144. package/src/modes/runtime-init.ts +3 -0
  145. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  146. package/src/prompts/system/eager-todo.md +8 -4
  147. package/src/prompts/system/system-prompt.md +11 -4
  148. package/src/prompts/tools/bash.md +5 -5
  149. package/src/prompts/tools/search-tool-bm25.md +5 -0
  150. package/src/prompts/tools/task.md +1 -1
  151. package/src/reminders/star-reminder.ts +4 -42
  152. package/src/runtime-mcp/manager.ts +19 -4
  153. package/src/sdk/acp/adapter.ts +22 -6
  154. package/src/sdk/broker/transport.ts +35 -3
  155. package/src/sdk/bus/attachment-registry.ts +5 -1
  156. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  157. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  158. package/src/sdk/bus/config.ts +18 -2
  159. package/src/sdk/bus/index.ts +661 -126
  160. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  161. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  162. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  163. package/src/sdk/bus/reconciliation-store.ts +95 -16
  164. package/src/sdk/bus/rich-draft.ts +1 -1
  165. package/src/sdk/bus/rich-render.ts +2 -2
  166. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  167. package/src/sdk/bus/telegram-cli.ts +33 -8
  168. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  169. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  170. package/src/sdk/bus/telegram-daemon.ts +916 -23
  171. package/src/sdk/bus/telegram-reference.ts +40 -3
  172. package/src/sdk/bus/threaded-render.ts +48 -3
  173. package/src/sdk/bus/topic-registry.ts +20 -0
  174. package/src/sdk/host/query/revision-store.ts +36 -2
  175. package/src/sdk/host/reverse-leases.ts +44 -7
  176. package/src/sdk/prompt-status.ts +16 -3
  177. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  178. package/src/sdk/session.ts +88 -9
  179. package/src/session/agent-session.ts +822 -172
  180. package/src/session/artifacts.ts +22 -0
  181. package/src/session/auth-broker-config.ts +10 -3
  182. package/src/session/blob-store.ts +915 -24
  183. package/src/session/internal/managed-session-scope.ts +44 -6
  184. package/src/session/internal/managed-session-storage.ts +48 -6
  185. package/src/session/session-manager.ts +1614 -435
  186. package/src/session/session-storage.ts +9 -1
  187. package/src/session/streaming-output.ts +238 -68
  188. package/src/setup/credential-import.ts +75 -13
  189. package/src/skill-state/active-state.ts +32 -0
  190. package/src/skill-state/workflow-hud.ts +17 -0
  191. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  192. package/src/task/executor.ts +122 -39
  193. package/src/task/index.ts +23 -14
  194. package/src/task/provider-retry-status.ts +16 -0
  195. package/src/task/receipt.ts +23 -7
  196. package/src/task/render.ts +6 -2
  197. package/src/task/types.ts +108 -0
  198. package/src/tools/ask-answer-registry.ts +12 -6
  199. package/src/tools/ask.ts +86 -55
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash-interactive.ts +8 -3
  203. package/src/tools/bash.ts +503 -60
  204. package/src/tools/find.ts +1 -0
  205. package/src/tools/image-gen.ts +29 -5
  206. package/src/tools/index.ts +4 -0
  207. package/src/tools/output-meta.ts +108 -14
  208. package/src/tools/path-utils.ts +3 -2
  209. package/src/tools/read.ts +18 -0
  210. package/src/tools/review.ts +5 -7
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/subagent-render.ts +6 -2
  213. package/src/tools/subagent.ts +6 -0
  214. package/src/tools/telegram-send.ts +6 -3
  215. package/src/utils/gh.ts +39 -0
  216. package/src/utils/image-resize.ts +3 -0
  217. package/src/web/search/providers/anthropic.ts +4 -4
  218. package/src/web/search/providers/kimi.ts +15 -5
  219. package/src/web/search/providers/searxng.ts +47 -4
  220. package/src/web/search/providers/xai.ts +7 -2
  221. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  222. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  223. 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,
@@ -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 => {
@@ -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
 
@@ -19,9 +19,10 @@ Ralplan is the consensus planning workflow. It triggers iterative planning with
19
19
 
20
20
  ## Flags
21
21
 
22
- - `--interactive`: Adds draft-review prompts and one-at-a-time reconciliation; final approval always uses an `ask` workflow gate and never auto-executes.
22
+ - `--interactive`: Adds draft-review prompts and one-at-a-time reconciliation. When the final receipt resolves `auto_handoff.effectiveTarget` to `off` without `degradationReason: "planning_stuck"`, final approval uses an `ask` workflow gate; a configured automatic admission is handled by step 8.
23
23
  - `--deliberate`: Forces high-risk deliberation: pre-mortem plus expanded test planning. It may also auto-enable for explicit auth/security, migration, destructive, incident, compliance/PII, or public-API-breakage risk.
24
24
  - `--architect openai-code` / `--critic openai-code`: Use OpenAI code for that review pass when available; otherwise note the fallback and use default GJC review.
25
+ - `gjc.ralplan.autoHandoff`: Selects final-plan admission: `off` (default), `ultragoal`, or `team`. A `team` target degrades to `off` when tmux is unavailable or no current tmux session is usable; the final receipt reports the `team_unavailable:<reason>` degradation. `PLANNING-STUCK` also resolves every target to `off`. Invalid settings reject the final write before any final artifact is persisted. The final receipt's ledger-backed runtime-owned `auto_handoff.effectiveTarget` is authoritative across state loss and run switching.
25
26
  - `--write --stage <type> --stage_n <N> --artifact <markdown file path or markdown string>`: Native writer for Planner/Architect/Critic/revision/ADR/final pending-approval markdown under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/`; do not edit `.gjc/` directly.
26
27
 
27
28
  ## Corrupt current-session state recovery
@@ -32,24 +33,26 @@ For corrupt, tampered, unreadable, or stale current-session ralplan state, run `
32
33
 
33
34
  ## Planning/Execution Boundary
34
35
 
35
- Ralplan is planning only. It may inspect context and draft plan/spec/proposal artifacts, but those remain `pending approval` until explicit current-turn or structured-UI execution approval. Before that approval, do not mutate product source, run mutation-oriented shell, commit, push, open PRs, invoke execution skills, or delegate implementation.
36
+ Ralplan is planning only. It may inspect context and draft plan/spec/proposal artifacts, but those remain `pending approval` until explicit current-turn or structured-UI execution approval, or a valid non-off final receipt's runtime-owned `auto_handoff.effectiveTarget` admits the existing handoff chain. Before either admission, do not mutate product source, run mutation-oriented shell, commit, push, open PRs, invoke execution skills, or delegate implementation.
36
37
 
37
- Explicitly naming `ultragoal` or `team` (including `/skill:` and `gjc` forms) counts as opting into execution for that skill — do not re-ask for the same consent.
38
+ Except for a terminal `planning_stuck` final receipt, explicitly naming `ultragoal` or `team` (including `/skill:` and `gjc` forms) counts as opting into execution for that skill — do not re-ask for the same consent.
38
39
 
39
40
  Persist planning artifacts and handoffs through the ralplan CLI writer, never direct `.gjc/` edits:
40
41
  Direct `write`, `edit`, or `ast_edit` calls against `.gjc/_session-{sessionid}/specs`, `.gjc/_session-{sessionid}/plans`, `.gjc/_session-{sessionid}/state`, or any other `.gjc/` path are forbidden unless an explicit force override is active.
41
42
 
42
43
  ```bash
43
- gjc ralplan --write --stage <type> --stage_n <N> --artifact "markdown file path or markdown string"
44
+ gjc ralplan --write --session-id <owner-session-id> --run-id <run-id> --stage <type> --stage_n <N> --artifact "markdown file path or markdown string"
44
45
  # restricted role agents use:
45
- gjc ralplan --write --stage <type> --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT
46
+ gjc ralplan --write --session-id <owner-session-id> --run-id <run-id> --stage <type> --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT
46
47
  ```
47
48
 
48
- Use stages `planner`, `architect`, `critic`, `revision`, `post-interview`, `adr`, or `final`; increment `--stage_n` each consensus pass. The writer accepts inline markdown, an artifact path prepared outside `.gjc/`, or `--artifact-env GJC_RALPLAN_ARTIFACT`, persists `stage-<NN>-<stage>.md` plus `index.jsonl` under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/`, and copies `final` to `pending-approval.md`. Ralplan mutation blocking is enforced in code; use temp directories (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) only for oversized scratch artifacts, never the repo or `.gjc/`.
49
+ Use stages `planner`, `architect`, `critic`, `revision`, `post-interview`, `adr`, or `final`; increment `--stage_n` each consensus pass. The writer accepts inline markdown, an artifact path prepared outside `.gjc/`, or `--artifact-env GJC_RALPLAN_ARTIFACT`, persists `stage-<NN>-<stage>.md` plus `index.jsonl` under `.gjc/_session-{sessionid}/plans/ralplan/<run-id>/`, and copies `final` to `pending-approval.md`. Ralplan mutation blocking is enforced in code; use temp directories (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) only for oversized scratch artifacts, never the repo or `.gjc/`. Staging via the `write` tool or a quoted-delimiter bash heredoc (`cat > /tmp/plan.md <<'EOF' … EOF`) into those temp roots is tolerated by the planning-phase guard.
49
50
 
50
51
  Restricted read-only role agents (`planner`, `architect`, `critic`) must pass markdown through `GJC_RALPLAN_ARTIFACT` with `--artifact-env GJC_RALPLAN_ARTIFACT`; their restricted bash environment disables artifact file-path ingestion.
51
52
 
52
- RECEIPT-ONLY guideline: role agents (`planner`, `architect`, and `critic`) persist durable outputs via `gjc ralplan --write` and return ONLY the receipt fields (`run_id`, `path`, `sha256`) plus verdict/status routing fields; include `stage` and `stage_n` when available, and never return the full persisted body.
53
+ RECEIPT-ONLY guideline: role agents (`planner`, `architect`, and `critic`) persist durable outputs via `gjc ralplan --write` and return ONLY the receipt fields (`session_id`, `run_id`, `path`, `sha256`) plus verdict/status routing fields; include `stage` and `stage_n` when available, and never return the full persisted body.
54
+
55
+ The ralplan seed/write receipt's `session_id` is the immutable workflow owner session and `run_id` is the run identity. Include both in every Planner/Architect/Critic assignment and every parent-side revision/post-interview/ADR/final write. A role subagent's own session id is transcript/resume identity only and MUST NOT own ralplan state or artifacts.
53
56
 
54
57
  This skill runs GJC planning in consensus mode for the provided arguments.
55
58
 
@@ -85,25 +88,25 @@ The consensus workflow:
85
88
  - Persist each Planner revision with `gjc ralplan --write --stage revision --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
86
89
  d. Re-join Architect and Critic verdicts for the same revised Planner artifact/pass
87
90
  e. Repeat this loop until Critic returns `OKAY` **and** Architect is `CLEAR`/`APPROVE` for the same Planner artifact/pass, or 5 iterations are reached
88
- f. If 5 iterations are reached without Critic `OKAY` plus Architect `CLEAR`/`APPROVE`, **stop opening further planner/revision passes**. Present the best version to the user (interactive) or surface `PLANNING-STUCK` (headless). Do **not** auto-start implementation.
89
- g. **Runtime budget (#3165):** native `gjc ralplan --write` refuses a new `planner`/`revision` that would open consensus iteration **> max** (default **5**, overridable via `gjc.ralplan.maxIterations` in project/user `.gjc/settings.json`, integer 1..20). Cap uses the same iteration definition as the HUD (`planner`/`revision` openers in `index.jsonl`). Overflow exits **3**, prints operator-visible **`PLANNING-STUCK`** on stdout (and stderr detail; JSON includes `planning_stuck: true`), and still allows `architect`/`critic` within an already-opened pass plus `post-interview`/`adr`/`final` so the best plan can be escalated to `pending approval` without auto-execution. A new `--run-id` starts a fresh budget.
91
+ f. If 5 iterations are reached without Critic `OKAY` plus Architect `CLEAR`/`APPROVE`, **stop opening further planner/revision passes**. Preserve the best version as a terminal `PLANNING-STUCK` result; do not route it to automatic or explicit execution.
92
+ g. **Runtime budget (#3165):** native `gjc ralplan --write` refuses a new `planner`/`revision` that would open consensus iteration **> max** (default **5**, overridable via `gjc.ralplan.maxIterations` in project/user `.gjc/settings.json`, integer 1..20). Cap uses the same iteration definition as the HUD (`planner`/`revision` openers in `index.jsonl`). Overflow exits **3**, prints operator-visible **`PLANNING-STUCK`** on stdout (and stderr detail; JSON includes `planning_stuck: true`), and still allows `architect`/`critic` within an already-opened pass plus `post-interview`/`adr`/`final` so the best plan can be escalated to `pending approval` without dispatch. A new `--run-id` starts a fresh budget.
90
93
  6. **Post-ralplan interview** (intent reconciliation gate): After the review join gate has both Critic `OKAY` and Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass, and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
91
94
  a. **Collect open items** from the run: every assumption the Planner/Architect/Critic resolved by assumption rather than by stated fact, every ambiguity flagged during review, and every decision the loop made without explicit user input. Source these from the persisted `planner`/`architect`/`critic`/`revision` stage artifacts, not from memory.
92
95
  b. **Cross-check prior context for conflicts**: glob `.gjc/_session-{sessionid}/specs/deep-interview-*.md` and other prior specs/plans/context relevant by topic. For each, list points where the consensus plan contradicts, weakens, or expands beyond a previously crystallized decision, constraint, or non-goal. Cite the conflicting artifact and line/section.
93
96
  c. **Reconcile with the user via the `ask` tool (always, regardless of `--interactive`)**: Never stop idle with plain-text prose after the consensus loop. Every reconciliation question MUST go through the `ask` tool with contextual options plus free-text.
94
97
  - If open items exist, confirm the open assumptions and conflicts **one at a time** with the `ask` tool, weakest/highest-impact first, polishing intent. If any confirmation reveals that the plan diverges from user intent, route the consolidated correction back into the re-review loop (step 5b Planner revision) and re-run Architect + Critic before returning here. Cap at the same 5-iteration ceiling.
95
- - If the plan is crystal clear (no open assumptions or prior-context conflicts), skip straight to the step 8 final-options `ask` instead of inventing filler questions.
98
+ - If the plan is crystal clear (no open assumptions or prior-context conflicts), continue to final persistence in step 7; do not choose an approval or handoff path before its final receipt exists.
96
99
  - For every confirmed open item, embed the resolved outcome into the final plan under an **## Intent Reconciliation** section so the `pending approval` artifact records each decision; record any item the user explicitly defers as an open confirmation under that same section.
97
100
  d. Persist the reconciliation with `gjc ralplan --write --stage post-interview --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`, then return the receipt/path plus a compact status (reconciled-clean / reconciled-with-revision / open-confirmations-pending) instead of pasting the full body.
98
- 7. On reconciliation completion, re-check the review join gate (Critic `OKAY` plus Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass), mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT`, and do not directly edit `.gjc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
99
- 8. **Final approval gate (with explicit-execution exception):** If the user already explicitly named an execution skill in the current turn or via the structured approval UI (`ultragoal`, `/skill:ultragoal`, `gjc ultragoal`, `team`, `/skill:team`, `gjc team`, or "Approve execution via ultragoal/team"), that is execution approval — skip the re-ask and proceed to step 9 with that skill. Otherwise, **always** present the finalized plan via the `ask` tool (regardless of `--interactive`) with `workflowGate: { stage: "ralplan", kind: "approval" }` on the final question so RPC/headless clients receive a `ralplan`/`approval` workflow gate, not a deep-interview question gate. Use these options:
101
+ 7. On reconciliation completion, re-check the review join gate (Critic `OKAY` plus Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass), mark the plan `pending approval` unless execution is already authorized by the resolved handoff admission, then persist the ADR/final plan via `gjc ralplan --write --stage final --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json`. Read the successful receipt's `auto_handoff` object; its ledger-backed `effectiveTarget` is runtime-owned and is the only automatic-routing decision; do not directly edit `.gjc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
102
+ 8. **Final admission and approval gate:** Reconciliation must first reach the successful final receipt from step 7. If that receipt has `auto_handoff.degradationReason: "planning_stuck"`, it is terminal: retain the `pending approval` artifact and **never dispatch**, including for an explicitly named execution skill; do not issue an approval `ask`. Otherwise, if its runtime-owned `auto_handoff.effectiveTarget` is `ultragoal` or `team`, that valid non-off receipt is explicit operator admission for same-turn execution through that target; proceed to step 9 without an `ask`. If it is `off`, including ordinary `off` or a runtime degradation such as `team_unavailable:<reason>`, preserve the ordinary approval flow: if the user already explicitly named an execution skill in the current turn or via the structured approval UI (`ultragoal`, `/skill:ultragoal`, `gjc ultragoal`, `team`, `/skill:team`, `gjc team`, or "Approve execution via ultragoal/team"), that is execution approval — skip the re-ask and proceed to step 9 with that skill. Otherwise, present the finalized plan via the `ask` tool (regardless of `--interactive`) with `workflowGate: { stage: "ralplan", kind: "approval" }` on the final question so RPC/headless clients receive a `ralplan`/`approval` workflow gate, not a deep-interview question gate. Use these options:
100
103
  - **Refine further** — re-run the consensus loop / request changes, then return here
101
104
  - **Approve execution via ultragoal (Recommended)** — goal-tracked autonomous execution
102
105
  - **Approve execution via team** — only when tmux-based interactive worker parallelization is required
103
106
  - **Stop here** — keep the plan as `pending approval` and make no further changes
104
107
 
105
- Always include a free-text option. Do not stop with plain text and no `ask`; the post-interview gate's terminal action is this `ask`.
106
- 9. On approval: invoke `/skill:ultragoal` for execution by default; invoke `/skill:team` only when the user explicitly needs tmux-based interactive worker parallelization. On **Refine further**, return to the step 5 re-review loop. On **Stop here**, leave the `pending approval` artifact and stop. Never implement directly.
108
+ Always include a free-text option for the ordinary `off`/degraded approval flow. Do not stop with plain text and no `ask` in that flow; its terminal action is this `ask`.
109
+ 9. On valid automatic admission or explicit approval, invoke the admitted/approved `/skill:ultragoal` target by default; invoke `/skill:team` only when the admitted/approved target is `team`. On **Refine further**, return to the step 5 re-review loop. On **Stop here**, leave the `pending approval` artifact and stop. A `planning_stuck` final receipt never reaches this step. Never implement directly.
107
110
 
108
111
  Before invoking `/skill:team` or `/skill:ultragoal`, mark ralplan ready for handoff so the skill tool's chain guard permits the transition:
109
112
 
@@ -118,9 +121,9 @@ The consensus workflow:
118
121
  ## Consensus iteration cap (operator contract)
119
122
 
120
123
  - Default max consensus iterations: **5** (`gjc.ralplan.maxIterations`).
121
- - On cap: exit code **3**, marker **`PLANNING-STUCK`** (stdout), no silent re-loop, no automatic ultragoal/team handoff. Opener budget is `max(index.jsonl openers, on-disk stage-*-{planner,revision}.md count)` so a missing/empty/malformed ledger cannot fail open after prior openers.
124
+ - On cap: exit code **3**, marker **`PLANNING-STUCK`** (stdout), no silent re-loop, no automatic or explicit ultragoal/team dispatch. Opener budget is `max(index.jsonl openers, on-disk stage-*-{planner,revision}.md count)` so a missing/empty/malformed ledger cannot fail open after prior openers.
122
125
  - Headless/CI: treat `PLANNING-STUCK` / exit 3 as terminal planning failure for orchestration/watchdogs.
123
- - Interactive: present best existing plan via the final approval gate; residual critic findings stay as caveats.
126
+ - Interactive: retain the best existing plan as a terminal planning result; residual critic findings stay as caveats.
124
127
  - Override example (project `.gjc/settings.json`):
125
128
 
126
129
  ```json
@@ -201,6 +201,8 @@ sleep 30 && gjc team monitor <team-name>
201
201
  ```
202
202
  The mutating monitor path also performs bounded liveness recovery: expired task claims, stale heartbeat claims, and missing recorded worker panes are requeued instead of leaving work permanently `in_progress`.
203
203
 
204
+ A GJC worker session publishes its own heartbeat while an agent turn or owned background job is active, at a third of the stale window, so a long build or test run is no longer reported stale. A worker that publishes nothing — for example one wedged before it could report — is still recovered on the normal window.
205
+
204
206
  ### Opt-in stalled-worker continuation
205
207
 
206
208
  `GJC_TEAM_AUTO_CONTINUE_STALLED_WORKERS=1` enables a separate, default-off monitor-only nudge for a stalled live worker. It is considered only when the team is running (not dry-run), the worker heartbeat is stale (using `GJC_TEAM_HEARTBEAT_STALE_MS`, default `120000` ms), and all of these checks pass: