@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
@@ -22,10 +22,12 @@ import type {
22
22
  StatusLineSeparatorStyle,
23
23
  } from "../../config/settings-schema";
24
24
  import { SETTING_TABS, TAB_METADATA } from "../../config/settings-schema";
25
+ import type { GjcRuntimeSnapshotProvider } from "../../extensibility/gjc-plugins/runtime-quarantine";
25
26
  import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
26
27
  import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
27
28
  import { getTabBarTheme } from "../shared";
28
29
  import { DynamicBorder } from "./dynamic-border";
30
+ import { GjcBundleSettingsComponent } from "./gjc-bundle-settings";
29
31
  import {
30
32
  type NotificationsEditorOperations,
31
33
  NotificationsSettingsEditorComponent,
@@ -650,6 +652,7 @@ function getSettingsTabs(): Tab[] {
650
652
  return { id, label: `${icon} ${meta.label}` };
651
653
  }),
652
654
  { id: "plugins", label: `${theme.icon.package} Plugins` },
655
+ { id: "gjc-bundles", label: `${theme.icon.package} GJC Bundles` },
653
656
  ];
654
657
  }
655
658
 
@@ -672,6 +675,14 @@ export interface SettingsRuntimeContext {
672
675
  petAvailable?: boolean;
673
676
  /** Terminal environment used to select unavailable-pet guidance. Omitted in production to use Bun.env. */
674
677
  terminalEnv?: NodeJS.ProcessEnv;
678
+ /**
679
+ * Runtime evidence published by the session for the current activation
680
+ * generation. Omitted when no session published one, in which case the GJC
681
+ * Bundles tab honestly reports runtime status as unavailable.
682
+ */
683
+ gjcRuntimeSnapshot?: GjcRuntimeSnapshotProvider;
684
+ /** Activation generation the published snapshot must match to be merged. */
685
+ gjcActivationGeneration?: number;
675
686
  }
676
687
 
677
688
  /** Status line settings subset for preview */
@@ -740,10 +751,11 @@ export class SettingsSelectorComponent extends Container {
740
751
  #tabBar: TabBar;
741
752
  #currentList: SettingsList | null = null;
742
753
  #pluginComponent: PluginSettingsComponent | null = null;
754
+ #gjcBundleComponent: GjcBundleSettingsComponent | null = null;
743
755
  #notificationsEditor: NotificationsSettingsEditorComponent | null = null;
744
756
  #statusPreviewContainer: Container | null = null;
745
757
  #statusPreviewText: Text | null = null;
746
- #currentTabId: SettingTab | "plugins" = "appearance";
758
+ #currentTabId: SettingTab | "plugins" | "gjc-bundles" = "appearance";
747
759
  #textInputActive = false;
748
760
 
749
761
  constructor(
@@ -759,7 +771,7 @@ export class SettingsSelectorComponent extends Container {
759
771
  // Tab bar
760
772
  this.#tabBar = new TabBar("Settings", getSettingsTabs(), getTabBarTheme());
761
773
  this.#tabBar.onTabChange = () => {
762
- this.#switchToTab(this.#tabBar.getActiveTab().id as SettingTab | "plugins");
774
+ this.#switchToTab(this.#tabBar.getActiveTab().id as SettingTab | "plugins" | "gjc-bundles");
763
775
  };
764
776
 
765
777
  this.addChild(this.#tabBar);
@@ -774,7 +786,7 @@ export class SettingsSelectorComponent extends Container {
774
786
  this.addChild(new DynamicBorder());
775
787
  }
776
788
 
777
- #switchToTab(tabId: SettingTab | "plugins"): void {
789
+ #switchToTab(tabId: SettingTab | "plugins" | "gjc-bundles"): void {
778
790
  if (this.#currentTabId === "notifications" && tabId !== "notifications" && !this.#disposeNotificationsEditor()) {
779
791
  return;
780
792
  }
@@ -789,6 +801,11 @@ export class SettingsSelectorComponent extends Container {
789
801
  this.removeChild(this.#pluginComponent);
790
802
  this.#pluginComponent = null;
791
803
  }
804
+ if (this.#gjcBundleComponent) {
805
+ this.removeChild(this.#gjcBundleComponent);
806
+ this.#gjcBundleComponent.dispose();
807
+ this.#gjcBundleComponent = null;
808
+ }
792
809
  if (this.#statusPreviewContainer) {
793
810
  this.removeChild(this.#statusPreviewContainer);
794
811
  this.#statusPreviewContainer = null;
@@ -801,6 +818,8 @@ export class SettingsSelectorComponent extends Container {
801
818
 
802
819
  if (tabId === "plugins") {
803
820
  this.#showPluginsTab();
821
+ } else if (tabId === "gjc-bundles") {
822
+ this.#showGjcBundlesTab();
804
823
  } else if (tabId === "notifications") {
805
824
  this.#showNotificationsTab();
806
825
  } else {
@@ -1224,7 +1243,7 @@ export class SettingsSelectorComponent extends Container {
1224
1243
 
1225
1244
  /** Re-evaluate condition gates against the current settings and refresh the active list. */
1226
1245
  #refreshCurrentTabItems(defs: SettingDef[]): void {
1227
- if (this.#currentTabId === "plugins" || !this.#currentList) return;
1246
+ if (this.#currentTabId === "plugins" || this.#currentTabId === "gjc-bundles" || !this.#currentList) return;
1228
1247
  this.#currentList.setItems(this.#buildItemsForTab(defs, this.#currentTabId));
1229
1248
  }
1230
1249
 
@@ -1271,15 +1290,31 @@ export class SettingsSelectorComponent extends Container {
1271
1290
  });
1272
1291
  this.addChild(this.#pluginComponent);
1273
1292
  }
1293
+ #showGjcBundlesTab(): void {
1294
+ this.#gjcBundleComponent = new GjcBundleSettingsComponent(
1295
+ this.context.cwd,
1296
+ {
1297
+ onClose: () => this.callbacks.onCancel(),
1298
+ onBundlesChanged: () => this.callbacks.onPluginsChanged?.(),
1299
+ },
1300
+ {
1301
+ runtimeSnapshotProvider: this.context.gjcRuntimeSnapshot,
1302
+ activationGeneration: this.context.gjcActivationGeneration,
1303
+ },
1304
+ );
1305
+ this.addChild(this.#gjcBundleComponent);
1306
+ }
1274
1307
 
1275
1308
  getFocusComponent(): Component {
1276
- return (this.#currentList || this.#pluginComponent || this.#notificationsEditor)!;
1309
+ return (this.#currentList || this.#pluginComponent || this.#gjcBundleComponent || this.#notificationsEditor)!;
1277
1310
  }
1278
1311
 
1279
1312
  override dispose(): void {
1280
1313
  if (this.#notificationsEditor?.navigationLocked) return;
1281
1314
  this.#notificationsEditor?.dispose();
1282
1315
  this.#notificationsEditor = null;
1316
+ this.#gjcBundleComponent?.dispose();
1317
+ this.#gjcBundleComponent = null;
1283
1318
  super.dispose();
1284
1319
  }
1285
1320
 
@@ -1301,6 +1336,18 @@ export class SettingsSelectorComponent extends Container {
1301
1336
  this.#notificationsEditor.handleInput(data);
1302
1337
  return;
1303
1338
  }
1339
+ if (this.#gjcBundleComponent && this.#currentTabId === "gjc-bundles") {
1340
+ if (tabNavigation) {
1341
+ if (this.#gjcBundleComponent.navigationLocked) {
1342
+ this.#gjcBundleComponent.handleInput(data);
1343
+ return;
1344
+ }
1345
+ this.#tabBar.handleInput(data);
1346
+ return;
1347
+ }
1348
+ this.#gjcBundleComponent.handleInput(data);
1349
+ return;
1350
+ }
1304
1351
 
1305
1352
  // Handle tab switching — but NOT when a text input is active, since
1306
1353
  // arrow keys must reach the cursor and Tab must not switch tabs.
@@ -1319,6 +1366,10 @@ export class SettingsSelectorComponent extends Container {
1319
1366
  this.#pluginComponent.handleInput(data);
1320
1367
  return;
1321
1368
  }
1369
+ if (this.#gjcBundleComponent) {
1370
+ this.#gjcBundleComponent.handleInput(data);
1371
+ return;
1372
+ }
1322
1373
 
1323
1374
  // Fallback for future top-level content that does not own cancellation.
1324
1375
  if (matchesAppInterrupt(data)) {
@@ -0,0 +1,16 @@
1
+ import { type RunGh, runGhDefault } from "../../../utils/gh";
2
+
3
+ const STATUS_LINE_GH_TIMEOUT_MS = 5_000;
4
+
5
+ export async function lookupCurrentPr(runGh: RunGh = runGhDefault): Promise<{ number: number; url: string } | null> {
6
+ try {
7
+ const result = await runGh(["pr", "view", "--json", "number,url"], { timeoutMs: STATUS_LINE_GH_TIMEOUT_MS });
8
+ if (result.exitCode !== 0 || result.timedOut) return null;
9
+
10
+ const pr = JSON.parse(result.stdout) as { number?: unknown; url?: unknown };
11
+ if (typeof pr.number !== "number" || typeof pr.url !== "string") return null;
12
+ return { number: pr.number, url: pr.url };
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
@@ -2,7 +2,6 @@ import * as fs from "node:fs";
2
2
 
3
3
  import { type Component, truncateToWidth, visibleWidth } from "@gajae-code/tui";
4
4
  import { formatCount, getProjectDir } from "@gajae-code/utils";
5
- import { $ } from "bun";
6
5
  import {
7
6
  type AppKeybinding,
8
7
  KEYBINDINGS,
@@ -20,6 +19,7 @@ import type { ActionRegistry, FocusDomain } from "../action-registry";
20
19
  import { EMPTY_JOBS_SNAPSHOT, type JobsSnapshot } from "../jobs-observer";
21
20
  import { sanitizeStatusText } from "../shared";
22
21
  import { renderSkillHudBar } from "./skill-hud/render";
22
+ import { lookupCurrentPr } from "./status-line/gh";
23
23
  import {
24
24
  canReuseCachedPr,
25
25
  createPrCacheContext,
@@ -416,19 +416,8 @@ export class StatusLineComponent implements Component {
416
416
  };
417
417
  try {
418
418
  // Requires `gh repo set-default` to be configured; fails gracefully if not
419
- const result = await $`gh pr view --json number,url`.quiet().nothrow();
420
- if (result.exitCode !== 0) {
421
- setCachedPr(null);
422
- return;
423
- }
424
- const pr = JSON.parse(result.stdout.toString()) as { number: number; url: string };
425
- if (typeof pr.number === "number") {
426
- setCachedPr({ number: pr.number, url: pr.url });
427
- } else {
428
- setCachedPr(null);
429
- }
430
- } catch {
431
- setCachedPr(null);
419
+ const pr = await lookupCurrentPr();
420
+ setCachedPr(pr);
432
421
  } finally {
433
422
  this.#prLookupInFlight = false;
434
423
  if (this.#onBranchChange) {
@@ -54,6 +54,34 @@ export type TranscriptViewerOverlayOptions = {
54
54
 
55
55
  type RenderedEntry = { lineStart: number; lineCount: number };
56
56
 
57
+ export const __transcriptViewerPerfCounters = {
58
+ enabled: false,
59
+ layoutCacheHits: 0,
60
+ layoutCacheMisses: 0,
61
+ refreshRuns: 0,
62
+ rebuildRuns: 0,
63
+ snapshot() {
64
+ return {
65
+ layoutCacheHits: this.layoutCacheHits,
66
+ layoutCacheMisses: this.layoutCacheMisses,
67
+ refreshRuns: this.refreshRuns,
68
+ rebuildRuns: this.rebuildRuns,
69
+ };
70
+ },
71
+ enable(): void {
72
+ this.enabled = true;
73
+ },
74
+ disable(): void {
75
+ this.enabled = false;
76
+ },
77
+ reset(): void {
78
+ this.layoutCacheHits = 0;
79
+ this.layoutCacheMisses = 0;
80
+ this.refreshRuns = 0;
81
+ this.rebuildRuns = 0;
82
+ },
83
+ };
84
+
57
85
  /** Message-agnostic, modal transcript browser over resolved projection entries. */
58
86
  export class TranscriptViewerOverlay extends Container {
59
87
  #options: TranscriptViewerOverlayOptions;
@@ -73,6 +101,7 @@ export class TranscriptViewerOverlay extends Container {
73
101
  #followTailPending = false;
74
102
  #skipFollowTailOnce = false;
75
103
  #followTailActive = false;
104
+ #layoutCache = new Map<string, Map<string, string[]>>();
76
105
 
77
106
  constructor(options: TranscriptViewerOverlayOptions) {
78
107
  super();
@@ -87,7 +116,11 @@ export class TranscriptViewerOverlay extends Container {
87
116
  get isFullscreen(): boolean {
88
117
  return this.#fullscreen;
89
118
  }
90
- refresh(identityMap?: ReadonlyMap<string, string>): void {
119
+ refresh(identityMap?: ReadonlyMap<string, string>, preserveLayoutCache = false): void {
120
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.refreshRuns++;
121
+ // Incremental sources explicitly invalidate same-id payload patches before requesting a
122
+ // preserving refresh. Other source changes conservatively drop all layout variants.
123
+ if (!preserveLayoutCache) this.#layoutCache.clear();
91
124
  const previous = this.selectedEntryId;
92
125
  const previousPosition = this.#selected;
93
126
  const reconciledPrevious = previous ? (identityMap?.get(previous) ?? previous) : undefined;
@@ -109,9 +142,15 @@ export class TranscriptViewerOverlay extends Container {
109
142
  this.#initialized = true;
110
143
  this.#rebuild();
111
144
  }
145
+ invalidateLayoutEntries(entryIds: readonly string[]): void {
146
+ for (const id of entryIds) this.#layoutCache.delete(id);
147
+ }
112
148
  override render(width: number): string[] {
113
- this.#width = Math.max(1, width);
114
- this.refresh();
149
+ const nextWidth = Math.max(1, width);
150
+ const widthChanged = nextWidth !== this.#width;
151
+ this.#width = nextWidth;
152
+ if (widthChanged) this.#layoutCache.clear();
153
+ if (widthChanged || getMarkdownTheme() !== this.#mdTheme) this.#rebuild();
115
154
  const defaultHeader = this.#fullscreen
116
155
  ? [theme.fg("accent", "Transcript block")]
117
156
  : [theme.fg("accent", sanitizeText(this.#options.title ?? "Transcript"))];
@@ -179,6 +218,7 @@ export class TranscriptViewerOverlay extends Container {
179
218
  if (this.#fullscreen) {
180
219
  this.#fullscreen = false;
181
220
  this.#scrollOffset = 0;
221
+ this.#rebuild();
182
222
  this.#requestRender();
183
223
  } else this.#options.onClose();
184
224
  return;
@@ -208,6 +248,7 @@ export class TranscriptViewerOverlay extends Container {
208
248
  this.#followTailActive = false;
209
249
  this.#followTailPending = false;
210
250
  this.#selected = 0;
251
+ this.#rebuild();
211
252
  this.#scrollOffset = 0;
212
253
  this.#requestRender();
213
254
  return;
@@ -216,6 +257,7 @@ export class TranscriptViewerOverlay extends Container {
216
257
  this.#followTailActive = true;
217
258
  this.#followTailPending = true;
218
259
  this.#selected = Math.max(0, count - 1);
260
+ this.#rebuild();
219
261
  this.#scrollOffset = this.#lines.length;
220
262
  this.#requestRender();
221
263
  return;
@@ -228,6 +270,7 @@ export class TranscriptViewerOverlay extends Container {
228
270
  this.#followTailActive = false;
229
271
  this.#fullscreen = true;
230
272
  this.#scrollOffset = 0;
273
+ this.#rebuild();
231
274
  this.#requestRender();
232
275
  return;
233
276
  }
@@ -243,6 +286,7 @@ export class TranscriptViewerOverlay extends Container {
243
286
  const entry = this.#entries[this.#selected];
244
287
  if (entry?.rawViewable !== false) {
245
288
  this.#raw.has(entry.id) ? this.#raw.delete(entry.id) : this.#raw.add(entry.id);
289
+ this.#rebuild();
246
290
  this.#requestRender();
247
291
  }
248
292
  }
@@ -284,6 +328,7 @@ export class TranscriptViewerOverlay extends Container {
284
328
  this.#followTailActive = false;
285
329
  this.#followTailPending = false;
286
330
  this.#selected = Math.max(0, Math.min(this.#selected + direction * 5, this.#entries.length - 1));
331
+ this.#rebuild();
287
332
  this.#scrollOffset += direction * PAGE_SIZE;
288
333
  this.#requestRender();
289
334
  }
@@ -327,32 +372,58 @@ export class TranscriptViewerOverlay extends Container {
327
372
  this.#options.requestRender?.();
328
373
  }
329
374
  #rebuild(): void {
375
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.rebuildRuns++;
376
+ // Theme is tracked by reference outside the variant key; a reference change clears
377
+ // the whole cache so plain and markdown layouts pick up new markdown colors.
378
+ const currentMdTheme = getMarkdownTheme();
379
+ if (currentMdTheme !== this.#mdTheme) {
380
+ this.#mdTheme = currentMdTheme;
381
+ this.#layoutCache.clear();
382
+ }
330
383
  const lines: string[] = [];
331
384
  this.#renderedEntries = [];
332
385
  const display = this.#fullscreen ? this.#entries.slice(this.#selected, this.#selected + 1) : this.#entries;
333
386
  const contentWidth = Math.max(1, this.#width - INDENT.length - 1);
334
387
  for (const entry of display) {
335
388
  const start = lines.length;
336
- const selected = entry.id === this.selectedEntryId;
337
- const expanded = this.#fullscreen || this.#expanded.has(entry.id);
338
- const raw = this.#raw.has(entry.id);
339
- lines.push("");
340
- lines.push(
341
- `${selected ? theme.fg("accent", "▶") : " "} ${theme.fg("muted", `[${sanitizeText(entry.label ?? entry.kind)}]`)}`,
342
- );
343
- if (
344
- !raw &&
345
- expanded &&
346
- selected &&
347
- entry.kind === "tool" &&
348
- entry.richRenderEligible === true &&
349
- entry.renderDescriptor
350
- ) {
351
- for (const line of renderToolDisplayLines(entry.renderDescriptor, contentWidth, theme))
352
- lines.push(`${INDENT}${line}`);
353
- this.#renderedEntries.push({ lineStart: start, lineCount: lines.length - start });
354
- continue;
389
+ const entryLines = this.#renderEntryLines(entry, contentWidth);
390
+ lines.push(...entryLines);
391
+ // Cumulative positions are recomputed from cached line lengths on every rebuild
392
+ // so PR2c can later separate virtual coordinates from materialized lines.
393
+ this.#renderedEntries.push({ lineStart: start, lineCount: entryLines.length });
394
+ }
395
+ this.#lines = lines.length ? lines : [theme.fg("dim", "No transcript entries yet.")];
396
+ }
397
+ #renderEntryLines(entry: TranscriptViewerEntry, contentWidth: number): string[] {
398
+ const selected = entry.id === this.selectedEntryId;
399
+ const expanded = this.#fullscreen || this.#expanded.has(entry.id);
400
+ const raw = this.#raw.has(entry.id);
401
+ const variantKey = `${expanded ? 1 : 0}|${raw ? 1 : 0}|${selected ? 1 : 0}|${contentWidth}`;
402
+ let variants = this.#layoutCache.get(entry.id);
403
+ if (variants) {
404
+ const cached = variants.get(variantKey);
405
+ if (cached) {
406
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.layoutCacheHits++;
407
+ return cached;
355
408
  }
409
+ }
410
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.layoutCacheMisses++;
411
+ const lines: string[] = [];
412
+ lines.push("");
413
+ lines.push(
414
+ `${selected ? theme.fg("accent", "▶") : " "} ${theme.fg("muted", `[${sanitizeText(entry.label ?? entry.kind)}]`)}`,
415
+ );
416
+ if (
417
+ !raw &&
418
+ expanded &&
419
+ selected &&
420
+ entry.kind === "tool" &&
421
+ entry.richRenderEligible === true &&
422
+ entry.renderDescriptor
423
+ ) {
424
+ for (const line of renderToolDisplayLines(entry.renderDescriptor, contentWidth, theme))
425
+ lines.push(`${INDENT}${line}`);
426
+ } else {
356
427
  const text = sanitizeText(
357
428
  (raw
358
429
  ? entry.payload.text
@@ -375,9 +446,13 @@ export class TranscriptViewerOverlay extends Container {
375
446
  for (const line of preview) lines.push(`${INDENT}${truncateToWidth(line, contentWidth)}`);
376
447
  if (text.split("\n").length > PREVIEW_LINES) lines.push(`${INDENT}${theme.fg("dim", "... more (Space)")}`);
377
448
  }
378
- this.#renderedEntries.push({ lineStart: start, lineCount: lines.length - start });
379
449
  }
380
- this.#lines = lines.length ? lines : [theme.fg("dim", "No transcript entries yet.")];
450
+ if (!variants) {
451
+ variants = new Map();
452
+ this.#layoutCache.set(entry.id, variants);
453
+ }
454
+ variants.set(variantKey, lines);
455
+ return lines;
381
456
  }
382
457
  #markdown(text: string, width: number): string[] {
383
458
  return new Markdown(text, 0, 0, this.#mdTheme).render(width).map(line => `${INDENT}${line.trimEnd()}`);
@@ -1175,6 +1175,10 @@ export class CommandController {
1175
1175
  }
1176
1176
  this.ctx.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1177
1177
  }
1178
+ const bashComponent = this.ctx.bashComponent;
1179
+ if (isDeferred && bashComponent && this.ctx.pendingBashComponents.includes(bashComponent)) {
1180
+ this.ctx.pendingMessagesContainer.detachChild(bashComponent);
1181
+ }
1178
1182
 
1179
1183
  this.ctx.bashComponent = undefined;
1180
1184
  this.ctx.ui.requestRender();
@@ -416,7 +416,9 @@ export class ExtensionUiController {
416
416
  const contextActions: ExtensionContextActions = {
417
417
  getModel: () => this.ctx.session.model,
418
418
  isIdle: () => !this.ctx.session.isStreaming,
419
+ getActivePromptHandle: () => this.ctx.session.activePromptHandle,
419
420
  abort: () => this.ctx.session.abort(),
421
+ abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
420
422
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
421
423
  getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
422
424
  getTranscript: () => this.ctx.session.getTranscript(),
@@ -468,6 +470,7 @@ export class ExtensionUiController {
468
470
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
469
471
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
470
472
  setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
473
+ setSdkClientBridge: bridge => this.ctx.session.setClientBridge(bridge),
471
474
  sdkControl: this.#sdkControl,
472
475
  };
473
476
  const commandActions: ExtensionCommandContextActions = {
@@ -715,7 +718,9 @@ export class ExtensionUiController {
715
718
  const contextActions: ExtensionContextActions = {
716
719
  getModel: () => this.ctx.session.model,
717
720
  isIdle: () => !this.ctx.session.isStreaming,
721
+ getActivePromptHandle: () => this.ctx.session.activePromptHandle,
718
722
  abort: () => this.ctx.session.abort(),
723
+ abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
719
724
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
720
725
  getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
721
726
  getTranscript: () => this.ctx.session.getTranscript(),
@@ -764,6 +769,7 @@ export class ExtensionUiController {
764
769
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
765
770
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
766
771
  setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
772
+ setSdkClientBridge: bridge => this.ctx.session.setClientBridge(bridge),
767
773
  sdkControl: this.#sdkControl,
768
774
  };
769
775
  const commandActions: ExtensionCommandContextActions = {
@@ -935,6 +941,7 @@ export class ExtensionUiController {
935
941
  sessionManager: createReadonlySessionManager(this.ctx.session.sessionManager),
936
942
  modelRegistry: this.ctx.session.modelRegistry,
937
943
  model: this.ctx.session.model,
944
+ getActivePromptHandle: () => this.ctx.session.activePromptHandle,
938
945
  isIdle: () => !this.ctx.session.isStreaming,
939
946
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
940
947
  getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
@@ -130,6 +130,7 @@ import { CustomProviderWizardComponent, type CustomProviderWizardSubmit } from "
130
130
  import { ExtensionDashboard } from "../components/extensions";
131
131
  import type { PetMode } from "../components/gajae-pet-widget";
132
132
  import { HistorySearchComponent } from "../components/history-search";
133
+ import { HookSelectorComponent } from "../components/hook-selector";
133
134
  import { JobsOverlayComponent } from "../components/jobs-overlay";
134
135
  import { ModelSelectorComponent } from "../components/model-selector";
135
136
  import type {
@@ -351,6 +352,7 @@ export function createNotificationsEditorOperations(
351
352
  richDraftEnabled: config.richDraft.enabled,
352
353
  toolActivityEnabled: config.toolActivity.enabled,
353
354
  streamingEnabled: config.streaming.enabled,
355
+ sound: config.sound,
354
356
  },
355
357
  };
356
358
  },
@@ -724,6 +726,7 @@ export function createNotificationsEditorOperations(
724
726
  { path: "notifications.redact", op: "set", value: preferences.redact },
725
727
  { path: "notifications.verbosity", op: "set", value: preferences.verbosity },
726
728
  { path: "notifications.sessionScope", op: "set", value: preferences.sessionScope },
729
+ { path: "notifications.telegram.sound", op: "set", value: preferences.sound },
727
730
  { path: "notifications.telegram.rich.enabled", op: "set", value: preferences.richEnabled },
728
731
  { path: "notifications.telegram.richDraft.enabled", op: "set", value: preferences.richDraftEnabled },
729
732
  { path: "notifications.telegram.streaming.enabled", op: "set", value: preferences.streamingEnabled },
@@ -1280,6 +1283,8 @@ export class SelectorController {
1280
1283
  availableThemes,
1281
1284
  availableModelProfiles: [...this.ctx.session.modelRegistry.getModelProfiles().keys()],
1282
1285
  cwd: getProjectDir(),
1286
+ gjcRuntimeSnapshot: this.ctx.session.gjcRuntimeSnapshot,
1287
+ gjcActivationGeneration: this.ctx.session.gjcActivationGeneration,
1283
1288
  },
1284
1289
  {
1285
1290
  onChange: (id, value) => this.handleSettingChange(id, value),
@@ -2327,11 +2332,51 @@ export class SelectorController {
2327
2332
  );
2328
2333
  await this.ctx.reloadTodos();
2329
2334
  this.ctx.showStatus("Resumed session");
2335
+ this.#maybePromptResumeModelChoice();
2330
2336
  } finally {
2331
2337
  progressLease.clear();
2332
2338
  }
2333
2339
  }
2334
2340
 
2341
+ /**
2342
+ * When `session.resumeModelBehavior` is "ask", offer a one-shot choice after
2343
+ * resuming: keep the model the session was last using, or switch to whatever
2344
+ * `modelRoles.default` currently resolves to. No-op if the setting is unset,
2345
+ * the two models already match, or either model can't be resolved.
2346
+ */
2347
+ #maybePromptResumeModelChoice(): void {
2348
+ if (this.ctx.settings?.get("session.resumeModelBehavior") !== "ask") return;
2349
+ const sessionModel = this.ctx.session.model;
2350
+ const currentDefault = this.ctx.session.resolveConfiguredDefaultModel?.();
2351
+ if (!sessionModel || !currentDefault) return;
2352
+ if (sessionModel.provider === currentDefault.provider && sessionModel.id === currentDefault.id) return;
2353
+
2354
+ this.showSelector(done => {
2355
+ const selector = new HookSelectorComponent(
2356
+ `This session last used ${sessionModel.provider}/${sessionModel.id}.\n` +
2357
+ `Current default model is ${currentDefault.provider}/${currentDefault.id}.`,
2358
+ [`Keep ${sessionModel.id}`, `Use ${currentDefault.id}`],
2359
+ async (option: string) => {
2360
+ done();
2361
+ if (option === `Use ${currentDefault.id}`) {
2362
+ try {
2363
+ await this.ctx.session.setModel(currentDefault);
2364
+ this.ctx.showStatus(`Switched to ${currentDefault.provider}/${currentDefault.id}`);
2365
+ } catch (err) {
2366
+ this.ctx.showError(err instanceof Error ? err.message : String(err));
2367
+ }
2368
+ }
2369
+ this.ctx.ui.requestRender();
2370
+ },
2371
+ () => {
2372
+ done();
2373
+ this.ctx.ui.requestRender();
2374
+ },
2375
+ );
2376
+ return { component: selector, focus: selector };
2377
+ });
2378
+ }
2379
+
2335
2380
  async handleSessionDeleteCommand(): Promise<void> {
2336
2381
  const sessionFile = this.ctx.sessionManager.getSessionFile();
2337
2382
  if (!sessionFile) {
@@ -94,7 +94,9 @@ export async function initializeExtensions(session: AgentSession, options: Initi
94
94
  {
95
95
  getModel: () => session.model,
96
96
  isIdle: () => !session.isStreaming,
97
+ getActivePromptHandle: () => session.activePromptHandle,
97
98
  abort: () => session.abort(),
99
+ abortPromptAndWait: (handle, abortOptions) => session.abortPromptAndWait(handle, abortOptions),
98
100
  hasPendingMessages: () => session.queuedMessageCount > 0,
99
101
  getPendingMessageCounts: () => session.pendingMessageCounts,
100
102
  getTranscript: () => session.getTranscript(),
@@ -168,6 +170,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
168
170
  },
169
171
  getJobs: () => session.getAsyncJobSnapshot(),
170
172
  setSdkPermissionProvider: provider => session.setSdkPermissionProvider(provider),
173
+ setSdkClientBridge: bridge => session.setClientBridge(bridge),
171
174
  sdkControl: async (operation, input) => {
172
175
  switch (operation) {
173
176
  case "model.set": {
@@ -1,6 +1,7 @@
1
1
  Persistence (ralplan runs only):
2
- - Only when the assignment references a ralplan stage or `stage_n`, persist the full artifact through:
2
+ - Only when the assignment references a ralplan stage or `stage_n`, it must also provide the ralplan owner `session_id` and `run_id`. If either identifier is missing, do not persist; return a compact error asking the caller to supply both.
3
+ - Persist the full artifact through:
3
4
 
4
- gjc ralplan --write --stage {{stage}} --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
5
+ gjc ralplan --write --session-id <owner-session-id> --run-id <run-id> --stage {{stage}} --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
5
6
 
6
- Use the assignment-provided `stage_n`; on a duplicate-write error retry with the incremented N. Return the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) and the role's compact verdict only. Otherwise, do not call `gjc ralplan --write`; return the full result in `yield.result.data`.
7
+ Use the assignment-provided owner `session_id`, `run_id`, and `stage_n`; never substitute the role subagent's own session id. On a duplicate-write error retry with the incremented N. Return the write receipt (`session_id`, `run_id`, `path`, `sha256`, `stage`, `stage_n`) and the role's compact verdict only. Otherwise, do not call `gjc ralplan --write`; return the full result in `yield.result.data`.
@@ -2,11 +2,15 @@
2
2
  Before substantive work, create a phased todo.
3
3
 
4
4
  You MUST call `todo_write` first in this turn.
5
- You MUST initialize the todo list with a single `init` op.
5
+ Your call MUST use the top-level `ops` array with exactly one `init` operation.
6
+ The `init` operation MUST contain `list`, whose entries each have a unique `phase` and an `items` array of task strings.
7
+ Use this canonical shape:
8
+ ```json
9
+ {"ops":[{"op":"init","list":[{"phase":"Investigation","items":["Locate relevant source and existing tests","Reproduce failure with focused contract test"]},{"phase":"Implementation","items":["Apply root cause fix across callsites"]},{"phase":"Verification","items":["Run focused tests and static checks"]}]}]}
10
+ ```
6
11
  You MUST cover the entire request from investigation through implementation and verification — not just the next immediate step.
7
- Task descriptions MUST be specific. A future turn MUST execute them without re-planning.
8
- You MUST keep task `content` to a short label (5-10 words). Put file paths, implementation steps, and specifics in `details`.
9
- You MUST keep exactly one task `in_progress` and all later tasks `pending`.
12
+ Task strings MUST be specific, unique labels of 5-10 words. A future turn MUST execute them without re-planning.
13
+ The tool automatically promotes the first task after initialization. Add extra context later with a separate `note` operation only when needed.
10
14
 
11
15
  After `todo_write` succeeds, continue the request in the same turn.
12
16
  Do not call `todo_write` again unless task state materially changed.
@@ -22,12 +22,13 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
22
22
  - Clear, low-risk implementation requests use direct tools and focused verification; do not invoke workflows or role agents for ceremony. Small verification needs do not turn a clear request into a planning workflow.
23
23
  - Ambiguous implementation asks with a missing target, scope, acceptance criteria, or safety boundary require clarification or the appropriate planning workflow before mutation.
24
24
  - Informational questions are answer-only/read-only unless the user explicitly requests a change, command, or execution.
25
- - Vague requirements use `/skill:deep-interview`: a requirements-only workflow that must not mutate product code. Its spec hands off as deep-interview → ralplan consensus → pending approval → separately approved execution.
26
- - Clear work with non-trivial architecture or sequencing risk uses `/skill:ralplan --deliberate` and stops pending approval.
25
+ - Vague requirements use `/skill:deep-interview`: a requirements-only workflow that must not mutate product code. Its spec hands off as deep-interview → ralplan consensus → final receipt → approval or valid non-off runtime admission → separately authorized execution.
26
+ - Clear work with non-trivial architecture or sequencing risk uses `/skill:ralplan --deliberate`; reconciliation must persist its final receipt before choosing approval or an admitted handoff. A valid non-off final runtime receipt enters the existing handoff chain; otherwise it stops pending approval.
27
27
  - Use `/skill:ultragoal` for durable goal ledgers and `/skill:team` for approved coordinated persistent work.
28
28
  - Delegate large implementation slices to `executor`; use `planner`, `architect`, or `critic` for bounded planning and review.
29
+ - An explicit user request to use a worktree (for example, "use worktree") overrides direct editing: delegate implementation through `task` with `isolated: true`. This is the in-session counterpart of launching `gjc --worktree`; if task isolation is unavailable, report that conflict instead of editing in the parent session.
29
30
  - Active skills are authoritative: never ignore an invoked skill; read the full skill text and follow it exactly.
30
- - Before explicit execution approval, planning and interview workflows NEVER edit product source, run mutating shell commands, commit, push, open PRs, or delegate implementation.
31
+ - Before explicit execution approval or a valid non-off ralplan final runtime receipt, planning and interview workflows NEVER edit product source, run mutating shell commands, commit, push, open PRs, or delegate implementation.
31
32
  </routing>
32
33
  </gjc-runtime>
33
34
  {{/unless}}
@@ -82,8 +83,14 @@ Use tools whenever they materially improve correctness, completeness, or groundi
82
83
 
83
84
  {{#if toolDiscoveryActive}}
84
85
  <tool-discovery>
85
- Use `{{toolRefs.search_tool_bm25}}` to activate hidden tools when a purpose-built capability would improve the task; then call the activated tool. Essential tools stay loaded up front.
86
+ Use `{{toolRefs.search_tool_bm25}}` to activate hidden tools when a purpose-built capability would improve the task; then call the activated tool. Activation makes a tool available, but does not perform its action. Essential tools stay loaded up front.
86
87
  Discoverable capabilities include browser automation, scheduling, debugging, and external integrations.
88
+ {{#has tools "task"}}
89
+ For requested subagents, delegation, or independent parallel lanes, call `{{toolRefs.task}}` before saying agents are running.
90
+ {{else}}
91
+ For requested subagents, delegation, or independent parallel lanes, search for `subagent delegation` before saying agents are running.
92
+ {{/has}}
93
+ Do not claim a web search, browser action, integration, or subagent ran without that tool's result.
87
94
  </tool-discovery>
88
95
  {{/if}}
89
96