@gajae-code/coding-agent 0.8.2 → 0.9.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 (214) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/types/append-only-mode.d.ts +31 -0
  3. package/dist/types/capability/rule.d.ts +8 -0
  4. package/dist/types/cli/fixture-report.d.ts +20 -0
  5. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  6. package/dist/types/cli/nofile-limit.d.ts +11 -0
  7. package/dist/types/cli/update-cli.d.ts +22 -0
  8. package/dist/types/config/settings-schema.d.ts +119 -1
  9. package/dist/types/discovery/index.d.ts +1 -0
  10. package/dist/types/export/html/template.generated.d.ts +1 -1
  11. package/dist/types/export/ttsr.d.ts +14 -3
  12. package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
  13. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
  14. package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
  15. package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
  16. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  17. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
  19. package/dist/types/goals/runtime.d.ts +1 -0
  20. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  21. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  22. package/dist/types/modes/components/status-line.d.ts +9 -0
  23. package/dist/types/modes/components/thinking-selector.d.ts +2 -2
  24. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  25. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  26. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  27. package/dist/types/modes/interactive-mode.d.ts +4 -0
  28. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
  29. package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
  30. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  31. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  32. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  33. package/dist/types/modes/types.d.ts +3 -0
  34. package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
  35. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  36. package/dist/types/notifications/config-commands.d.ts +9 -0
  37. package/dist/types/notifications/config.d.ts +22 -0
  38. package/dist/types/notifications/index.d.ts +2 -0
  39. package/dist/types/notifications/lifecycle-commands.d.ts +1 -0
  40. package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
  41. package/dist/types/notifications/reply-sent-store.d.ts +53 -0
  42. package/dist/types/notifications/rich-draft.d.ts +68 -0
  43. package/dist/types/notifications/rich-render.d.ts +90 -0
  44. package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
  45. package/dist/types/notifications/telegram-daemon.d.ts +30 -0
  46. package/dist/types/notifications/telegram-reference.d.ts +7 -0
  47. package/dist/types/notifications/threaded-render.d.ts +14 -0
  48. package/dist/types/sdk.d.ts +3 -0
  49. package/dist/types/session/agent-session.d.ts +43 -1
  50. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  51. package/dist/types/session/messages.d.ts +4 -0
  52. package/dist/types/session/session-manager.d.ts +17 -1
  53. package/dist/types/skill-state/active-state.d.ts +18 -1
  54. package/dist/types/system-prompt.d.ts +18 -2
  55. package/dist/types/task/executor.d.ts +1 -0
  56. package/dist/types/task/fork-context-budget.d.ts +7 -0
  57. package/dist/types/task/token-log.d.ts +30 -0
  58. package/dist/types/task/token-log.test.d.ts +1 -0
  59. package/dist/types/tools/bisect.d.ts +81 -0
  60. package/dist/types/tools/index.d.ts +1 -0
  61. package/dist/types/tools/search.d.ts +2 -2
  62. package/dist/types/utils/file-mentions.d.ts +14 -0
  63. package/dist/types/utils/git.d.ts +32 -0
  64. package/dist/types/utils/pasted-image-path.d.ts +30 -0
  65. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  66. package/package.json +8 -11
  67. package/src/append-only-mode.ts +58 -0
  68. package/src/autoresearch/prompt.md +1 -1
  69. package/src/autoresearch/tools/log-experiment.ts +1 -1
  70. package/src/capability/rule.ts +9 -0
  71. package/src/cli/config-cli.ts +27 -3
  72. package/src/cli/fixture-report.test.ts +230 -0
  73. package/src/cli/fixture-report.ts +171 -0
  74. package/src/cli/nofile-limit.ts +48 -0
  75. package/src/cli/notify-cli.ts +11 -9
  76. package/src/cli/update-cli.ts +85 -9
  77. package/src/cli.ts +26 -0
  78. package/src/commands/contribution-prep.ts +1 -1
  79. package/src/config/keybindings.ts +1 -1
  80. package/src/config/settings-schema.ts +76 -1
  81. package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
  82. package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
  83. package/src/defaults/gjc/skills/team/SKILL.md +16 -13
  84. package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
  85. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  86. package/src/defaults/gjc-defaults.ts +9 -0
  87. package/src/discovery/helpers.ts +10 -0
  88. package/src/discovery/index.ts +1 -0
  89. package/src/export/html/template.generated.ts +1 -1
  90. package/src/export/html/template.js +60 -2
  91. package/src/export/ttsr.ts +96 -18
  92. package/src/extensibility/extensions/runner.ts +181 -219
  93. package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
  94. package/src/gjc-runtime/deep-interview-state.ts +31 -2
  95. package/src/gjc-runtime/launch-tmux.ts +6 -0
  96. package/src/gjc-runtime/session-state-sidecar.ts +208 -13
  97. package/src/gjc-runtime/state-writer.ts +15 -0
  98. package/src/gjc-runtime/team-runtime.ts +175 -19
  99. package/src/gjc-runtime/tmux-common.ts +1 -0
  100. package/src/gjc-runtime/ultragoal-guard.ts +55 -66
  101. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
  102. package/src/gjc-runtime/ultragoal-runtime.ts +1602 -143
  103. package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
  104. package/src/gjc-runtime/workflow-manifest.ts +74 -1
  105. package/src/goals/runtime.ts +10 -2
  106. package/src/hindsight/backend.ts +1 -3
  107. package/src/hooks/native-skill-hook.ts +95 -4
  108. package/src/internal-urls/docs-index.generated.ts +18 -9
  109. package/src/lsp/client.ts +1 -0
  110. package/src/main.ts +43 -2
  111. package/src/memories/index.ts +12 -1
  112. package/src/modes/components/assistant-message.ts +241 -82
  113. package/src/modes/components/custom-editor.ts +1 -0
  114. package/src/modes/components/footer.ts +7 -2
  115. package/src/modes/components/status-line.ts +167 -54
  116. package/src/modes/components/thinking-selector.ts +10 -8
  117. package/src/modes/components/tool-execution.ts +44 -19
  118. package/src/modes/controllers/command-controller.ts +39 -2
  119. package/src/modes/controllers/event-controller.ts +77 -49
  120. package/src/modes/controllers/extension-ui-controller.ts +8 -1
  121. package/src/modes/controllers/input-controller.ts +13 -18
  122. package/src/modes/controllers/selector-controller.ts +41 -0
  123. package/src/modes/interactive-mode.ts +14 -1
  124. package/src/modes/rpc/rpc-mode.ts +32 -12
  125. package/src/modes/rpc/rpc-socket-security.ts +13 -1
  126. package/src/modes/rpc/rpc-types.ts +9 -0
  127. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  128. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  129. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  130. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  131. package/src/modes/types.ts +3 -0
  132. package/src/modes/utils/injected-user-submission.ts +94 -0
  133. package/src/modes/utils/ui-helpers.ts +17 -4
  134. package/src/notifications/config-commands.ts +20 -0
  135. package/src/notifications/config.ts +43 -4
  136. package/src/notifications/index.ts +105 -9
  137. package/src/notifications/lifecycle-commands.ts +37 -16
  138. package/src/notifications/lifecycle-control-runtime.ts +8 -3
  139. package/src/notifications/reply-sent-store.ts +134 -0
  140. package/src/notifications/rich-draft.ts +107 -0
  141. package/src/notifications/rich-render.ts +142 -0
  142. package/src/notifications/telegram-daemon-cli.ts +54 -7
  143. package/src/notifications/telegram-daemon-control.ts +2 -2
  144. package/src/notifications/telegram-daemon.ts +390 -56
  145. package/src/notifications/telegram-reference.ts +17 -0
  146. package/src/notifications/threaded-render.ts +45 -2
  147. package/src/prompts/agents/architect.md +19 -10
  148. package/src/prompts/agents/critic.md +16 -18
  149. package/src/prompts/agents/executor.md +14 -11
  150. package/src/prompts/agents/planner.md +18 -13
  151. package/src/prompts/ci-green-request.md +3 -8
  152. package/src/prompts/goals/goal-continuation.md +0 -5
  153. package/src/prompts/goals/goal-mode-active.md +0 -5
  154. package/src/prompts/system/agent-creation-architect.md +2 -2
  155. package/src/prompts/system/plan-mode-active.md +1 -1
  156. package/src/prompts/system/plan-mode-subagent.md +3 -3
  157. package/src/prompts/system/project-prompt.md +0 -13
  158. package/src/prompts/system/subagent-system-prompt.md +3 -2
  159. package/src/prompts/system/system-prompt.md +26 -19
  160. package/src/prompts/system/volatile-project-context.md +10 -0
  161. package/src/prompts/system/web-search.md +4 -7
  162. package/src/prompts/tools/apply-patch.md +6 -0
  163. package/src/prompts/tools/ast-edit.md +1 -0
  164. package/src/prompts/tools/bash.md +1 -1
  165. package/src/prompts/tools/bisect.md +22 -0
  166. package/src/prompts/tools/browser.md +1 -1
  167. package/src/prompts/tools/cron.md +0 -2
  168. package/src/prompts/tools/eval.md +2 -2
  169. package/src/prompts/tools/find.md +1 -1
  170. package/src/prompts/tools/github.md +6 -6
  171. package/src/prompts/tools/goal.md +1 -3
  172. package/src/prompts/tools/image-gen.md +12 -5
  173. package/src/prompts/tools/irc.md +5 -4
  174. package/src/prompts/tools/job.md +2 -2
  175. package/src/prompts/tools/lsp.md +1 -1
  176. package/src/prompts/tools/monitor.md +2 -2
  177. package/src/prompts/tools/patch.md +2 -2
  178. package/src/prompts/tools/replace.md +2 -21
  179. package/src/prompts/tools/rewind.md +1 -1
  180. package/src/prompts/tools/search-tool-bm25.md +6 -11
  181. package/src/prompts/tools/search.md +1 -1
  182. package/src/prompts/tools/skill.md +3 -2
  183. package/src/prompts/tools/ssh.md +2 -1
  184. package/src/prompts/tools/subagent.md +2 -2
  185. package/src/prompts/tools/task.md +4 -5
  186. package/src/prompts/tools/todo-write.md +1 -1
  187. package/src/prompts/tools/vim.md +1 -0
  188. package/src/prompts/tools/web-search.md +2 -0
  189. package/src/prompts/tools/write.md +8 -2
  190. package/src/sdk.ts +68 -30
  191. package/src/session/agent-session.ts +734 -107
  192. package/src/session/file-mention-pruning.ts +64 -0
  193. package/src/session/messages.ts +4 -0
  194. package/src/session/session-manager.ts +59 -2
  195. package/src/skill-state/active-state.ts +141 -5
  196. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  197. package/src/slash-commands/builtin-registry.ts +143 -1
  198. package/src/system-prompt.ts +57 -16
  199. package/src/task/executor.ts +125 -20
  200. package/src/task/fork-context-advisory.ts +2 -7
  201. package/src/task/fork-context-budget.ts +9 -0
  202. package/src/task/index.ts +4 -3
  203. package/src/task/output-manager.ts +4 -2
  204. package/src/task/token-log.test.ts +188 -0
  205. package/src/task/token-log.ts +179 -0
  206. package/src/tools/bisect.ts +367 -0
  207. package/src/tools/index.ts +3 -0
  208. package/src/tools/output-meta.ts +116 -4
  209. package/src/tools/search.ts +8 -2
  210. package/src/tools/skill.ts +15 -0
  211. package/src/utils/file-mentions.ts +39 -7
  212. package/src/utils/git.ts +73 -0
  213. package/src/utils/pasted-image-path.ts +170 -0
  214. package/src/web/scrapers/youtube.ts +24 -12
@@ -304,6 +304,18 @@ function activeStateDir(cwd: string, sessionScope?: string | ActiveSessionScope)
304
304
  return layoutActiveStateDir(cwd, requireSessionId(sessionScope, "activeStateDir"));
305
305
  }
306
306
 
307
+ type ActiveStateCacheInvalidator = (cwd?: string, sessionId?: string) => void;
308
+ var activeStateCacheInvalidator: ActiveStateCacheInvalidator | undefined;
309
+
310
+ export function setActiveStateCacheInvalidator(invalidator: ActiveStateCacheInvalidator): void {
311
+ activeStateCacheInvalidator = invalidator;
312
+ }
313
+
314
+ function invalidateActiveStateCacheForScope(cwd: string, sessionScope?: string | ActiveSessionScope): void {
315
+ const sessionId = typeof sessionScope === "string" ? sessionScope : sessionScope?.sessionId;
316
+ activeStateCacheInvalidator?.(cwd, sessionId);
317
+ }
318
+
307
319
  function activeSnapshotPath(cwd: string, sessionScope?: string | ActiveSessionScope): string {
308
320
  return layoutActiveSnapshotPath(cwd, requireSessionId(sessionScope, "activeSnapshotPath"));
309
321
  }
@@ -979,6 +991,7 @@ export async function writeActiveEntry(
979
991
  persistedSourceRevision(entry) || persistedSourceRevision(await readJsonIfPresent(filePath)) + 1,
980
992
  },
981
993
  );
994
+ invalidateActiveStateCacheForScope(cwd, sessionScope);
982
995
  return filePath;
983
996
  }
984
997
 
@@ -1003,6 +1016,7 @@ export async function removeActiveEntry(
1003
1016
  }
1004
1017
  const deleted = await atomicRemove(filePath);
1005
1018
  if (deleted) await maybeAudit(filePath, options);
1019
+ if (deleted) invalidateActiveStateCacheForScope(cwd, sessionScope);
1006
1020
  return { path: filePath, deleted };
1007
1021
  },
1008
1022
  options?.lock,
@@ -1049,6 +1063,7 @@ export async function rebuildActiveSnapshot(
1049
1063
  ...entries.map(entry => persistedSourceRevision(entry)),
1050
1064
  ),
1051
1065
  });
1066
+ invalidateActiveStateCacheForScope(cwd, sessionScope);
1052
1067
  return snapshotPath;
1053
1068
  }
1054
1069
 
@@ -22,6 +22,7 @@ import {
22
22
  import {
23
23
  buildGjcTmuxExactOptionTarget,
24
24
  buildGjcTmuxUntaggedSessionHint,
25
+ GJC_TMUX_ACTIVE_SESSION_ENV,
25
26
  GJC_TMUX_PROFILE_OPTION,
26
27
  GJC_TMUX_PROFILE_VALUE,
27
28
  resolveGjcTmuxCommand,
@@ -204,6 +205,7 @@ export type GjcTeamNotificationDeliveryState =
204
205
  | "acknowledged";
205
206
 
206
207
  export type GjcTeamPaneAttemptResult = "sent" | "queued" | "deferred" | "failed";
208
+ export type GjcTeamMailboxDeliveryTransportKind = "notifications_sdk" | "pane";
207
209
 
208
210
  export interface GjcTeamNotification {
209
211
  id: string;
@@ -220,6 +222,36 @@ export interface GjcTeamNotification {
220
222
  updated_at: string;
221
223
  replay_count: number;
222
224
  }
225
+ export interface GjcTeamMailboxDeliveryInput {
226
+ team_name: string;
227
+ state_dir: string;
228
+ config: GjcTeamConfig;
229
+ notification: GjcTeamNotification;
230
+ message: GjcTeamMailboxMessage;
231
+ cwd: string;
232
+ env: NodeJS.ProcessEnv;
233
+ }
234
+ export type GjcTeamMailboxDeliveryResult =
235
+ | { transport: "notifications_sdk"; state: GjcTeamNotificationDeliveryState; reason?: string }
236
+ | { transport: "pane"; state: GjcTeamPaneAttemptResult; reason?: string };
237
+ export interface GjcTeamMailboxDeliveryTransport {
238
+ deliverMailboxMessage(input: GjcTeamMailboxDeliveryInput): Promise<GjcTeamMailboxDeliveryResult | null>;
239
+ }
240
+
241
+ let gjcTeamMailboxDeliveryTransport: GjcTeamMailboxDeliveryTransport | undefined;
242
+
243
+ export function setGjcTeamMailboxDeliveryTransport(transport: GjcTeamMailboxDeliveryTransport | undefined): () => void {
244
+ const previous = gjcTeamMailboxDeliveryTransport;
245
+ gjcTeamMailboxDeliveryTransport = transport;
246
+ return () => {
247
+ gjcTeamMailboxDeliveryTransport = previous;
248
+ };
249
+ }
250
+ export function setGjcTeamMailboxDeliveryTransportForTest(
251
+ transport: GjcTeamMailboxDeliveryTransport | undefined,
252
+ ): () => void {
253
+ return setGjcTeamMailboxDeliveryTransport(transport);
254
+ }
223
255
 
224
256
  export interface GjcTeamNotificationSummary {
225
257
  total: number;
@@ -256,6 +288,7 @@ export interface GjcTeamStartOptions {
256
288
  cwd?: string;
257
289
  env?: NodeJS.ProcessEnv;
258
290
  dryRun?: boolean;
291
+ mailboxDeliveryTransport?: GjcTeamMailboxDeliveryTransport;
259
292
  }
260
293
 
261
294
  export interface GjcTeamApiClaimResult {
@@ -1056,6 +1089,22 @@ async function readConfig(dir: string): Promise<GjcTeamConfig> {
1056
1089
  worker_cli_plan: config.worker_cli_plan ?? Array.from({ length: config.worker_count }, () => "gjc"),
1057
1090
  };
1058
1091
  }
1092
+ const WORKER_INTEGRATION_CONFIG_CACHE_TTL_MS = 100;
1093
+ type WorkerIntegrationConfigCacheEntry = { checkedAt: number; mtimeMs: number; config: GjcTeamConfig };
1094
+ const workerIntegrationConfigCache = new Map<string, WorkerIntegrationConfigCacheEntry>();
1095
+
1096
+ async function readConfigForWorkerIntegration(dir: string): Promise<GjcTeamConfig> {
1097
+ const configPath = path.join(dir, "config.json");
1098
+ const nowMs = Date.now();
1099
+ const stat = await fs.stat(configPath);
1100
+ const cached = workerIntegrationConfigCache.get(configPath);
1101
+ if (cached && cached.mtimeMs === stat.mtimeMs && nowMs - cached.checkedAt <= WORKER_INTEGRATION_CONFIG_CACHE_TTL_MS)
1102
+ return cached.config;
1103
+ const config = await readConfig(dir);
1104
+ workerIntegrationConfigCache.set(configPath, { checkedAt: nowMs, mtimeMs: stat.mtimeMs, config });
1105
+
1106
+ return config;
1107
+ }
1059
1108
  async function readPhase(dir: string): Promise<GjcTeamPhase> {
1060
1109
  const phase = await readJsonFile<{ current_phase?: GjcTeamPhase }>(path.join(dir, "phase.json"));
1061
1110
  return phase?.current_phase ?? "running";
@@ -1704,6 +1753,23 @@ function runGitResult(cwd: string, args: string[]): GitResult {
1704
1753
  stderr: result.stderr.toString().trim(),
1705
1754
  };
1706
1755
  }
1756
+ async function runGitResultAsync(cwd: string, args: string[]): Promise<GitResult> {
1757
+ const result = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
1758
+ const [exitCode, stdout, stderr] = await Promise.all([
1759
+ result.exited,
1760
+ new Response(result.stdout).text(),
1761
+ new Response(result.stderr).text(),
1762
+ ]);
1763
+ return {
1764
+ ok: exitCode === 0,
1765
+ stdout: stdout.trim(),
1766
+ stderr: stderr.trim(),
1767
+ };
1768
+ }
1769
+ async function tryRunGitAsync(cwd: string, args: string[]): Promise<string | null> {
1770
+ const result = await runGitResultAsync(cwd, args);
1771
+ return result.ok ? result.stdout : null;
1772
+ }
1707
1773
  function runGit(cwd: string, args: string[]): string {
1708
1774
  const result = runGitResult(cwd, args);
1709
1775
  if (result.ok) return result.stdout;
@@ -1857,16 +1923,22 @@ function tagTmuxSessionAsGjcLeader(tmuxCommand: string, sessionName: string): bo
1857
1923
  function readCurrentTmuxLeaderContext(tmuxCommand: string, env: NodeJS.ProcessEnv): GjcTmuxLeaderContext {
1858
1924
  if (Bun.which(tmuxCommand) === null)
1859
1925
  throw new Error(buildTeamTmuxLeaderRequirementMessage(`tmux_not_installed:${tmuxCommand}`));
1860
- const paneTarget = env.TMUX_PANE?.trim();
1861
- const args = paneTarget
1862
- ? ["display-message", "-p", "-t", paneTarget, "#S:#I #{pane_id}"]
1926
+ // Prefer the explicit GJC-managed session name propagated by `gjc --tmux`
1927
+ // (GJC_TMUX_ACTIVE_SESSION). Under psmux on Windows the inherited TMUX_PANE
1928
+ // can resolve to the wrong/default session, so querying the tagged session
1929
+ // by name is authoritative for GJC-launched leaders. Fall back to TMUX_PANE,
1930
+ // then to the ambient session, to keep native tmux/WSL flows unchanged.
1931
+ const activeSession = env[GJC_TMUX_ACTIVE_SESSION_ENV]?.trim();
1932
+ const displayTarget = activeSession ? buildGjcTmuxExactOptionTarget(activeSession, { env }) : env.TMUX_PANE?.trim();
1933
+ const args = displayTarget
1934
+ ? ["display-message", "-p", "-t", displayTarget, "#S:#I #{pane_id}"]
1863
1935
  : ["display-message", "-p", "#S:#I #{pane_id}"];
1864
1936
  const result = Bun.spawnSync([tmuxCommand, ...args], { stdout: "pipe", stderr: "pipe" });
1865
1937
  if (result.exitCode !== 0) {
1866
1938
  // Distinguish "you are not inside any tmux session" from a genuine tmux
1867
1939
  // query failure so the caller gets actionable guidance instead of raw
1868
1940
  // tmux stderr. `gjc team` needs a tmux leader; outside tmux there is none.
1869
- const insideTmux = Boolean(env.TMUX?.trim() || env.TMUX_PANE?.trim());
1941
+ const insideTmux = Boolean(env.TMUX?.trim() || env.TMUX_PANE?.trim() || activeSession);
1870
1942
  const stderr = result.stderr.toString().trim();
1871
1943
  throw new Error(
1872
1944
  buildTeamTmuxLeaderRequirementMessage(
@@ -2318,6 +2390,9 @@ async function appendCommitHygieneEntries(config: GjcTeamConfig, entries: GjcTea
2318
2390
  function resolveHead(cwd: string): string | null {
2319
2391
  return tryRunGit(cwd, ["rev-parse", "HEAD"]);
2320
2392
  }
2393
+ async function resolveHeadAsync(cwd: string): Promise<string | null> {
2394
+ return tryRunGitAsync(cwd, ["rev-parse", "HEAD"]);
2395
+ }
2321
2396
  function isAncestor(cwd: string, ancestor: string, descendant: string): boolean {
2322
2397
  return runGitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok;
2323
2398
  }
@@ -2337,6 +2412,14 @@ function listConflictFiles(cwd: string): string[] {
2337
2412
  .map(line => line.trim())
2338
2413
  .filter(Boolean);
2339
2414
  }
2415
+ async function listConflictFilesAsync(cwd: string): Promise<string[]> {
2416
+ const result = await runGitResultAsync(cwd, ["diff", "--name-only", "--diff-filter=U"]);
2417
+ if (!result.ok || !result.stdout) return [];
2418
+ return result.stdout
2419
+ .split(/\r?\n/)
2420
+ .map(line => line.trim())
2421
+ .filter(Boolean);
2422
+ }
2340
2423
 
2341
2424
  export type GjcWorkerCheckpointClassification =
2342
2425
  | { kind: "clean"; files: string[] }
@@ -2346,17 +2429,14 @@ export type GjcWorkerCheckpointClassification =
2346
2429
  | { kind: "git_error"; files: string[]; detail: string };
2347
2430
 
2348
2431
  const UNMERGED_GIT_STATUS_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
2349
- const PROTECTED_WORKER_CHECKPOINT_PREFIXES = [
2350
- ".gjc/_session-*/state/",
2351
- ".gjc/_session-*/logs/",
2352
- ".gjc/_session-*/reports/",
2353
- ".gjc/_session-*/runtime/",
2354
- ".gjc/_session-*/ultragoal/",
2355
- ".gjc/_session-*/plans/",
2356
- ".gjc/_session-*/specs/",
2357
- ".gjc/_session-*/rlm/",
2358
- ".gjc/_session-*/audit/",
2359
- ];
2432
+ // Every generated/runtime artifact for a GJC session lives under
2433
+ // `.gjc/_session-{id}/...` (see session-layout.ts), so worker auto-checkpoints
2434
+ // exclude the whole session subtree instead of enumerating its subdirectories.
2435
+ // The enumerated form drifted: subtrees outside the list (for example the
2436
+ // extragoal gate receipts from docs/extragoal-skill-template.md, or the
2437
+ // session-root `.session-activity.json` marker) were auto-committed and merged
2438
+ // into the leader branch on projects that do not gitignore `.gjc/_session-*/`.
2439
+ const PROTECTED_WORKER_CHECKPOINT_PREFIXES = [".gjc/_session-*/"];
2360
2440
 
2361
2441
  function parsePorcelainStatusFiles(stdout: string): string[] {
2362
2442
  return stdout
@@ -2407,6 +2487,26 @@ export function classifyWorkerCheckpointStatus(cwd: string): GjcWorkerCheckpoint
2407
2487
  return { kind: "protected_only", files: classified.protected };
2408
2488
  return { kind: "eligible", files: classified.eligible };
2409
2489
  }
2490
+ export async function classifyWorkerCheckpointStatusAsync(cwd: string): Promise<GjcWorkerCheckpointClassification> {
2491
+ const status = await runGitResultAsync(cwd, ["status", "--porcelain", "-uall"]);
2492
+ if (!status.ok) {
2493
+ return { kind: "git_error", files: [], detail: status.stderr || status.stdout || "git status failed" };
2494
+ }
2495
+ if (!status.stdout.trim()) return { kind: "clean", files: [] };
2496
+ const files = parsePorcelainStatusFiles(status.stdout);
2497
+ const hasUnmergedStatus = status.stdout
2498
+ .split(/\r?\n/)
2499
+ .filter(Boolean)
2500
+ .some(line => UNMERGED_GIT_STATUS_CODES.has(line.slice(0, 2)));
2501
+ const conflictFiles = await listConflictFilesAsync(cwd);
2502
+ if (hasUnmergedStatus || conflictFiles.length > 0) {
2503
+ return { kind: "conflicted", files: conflictFiles.length > 0 ? conflictFiles : files };
2504
+ }
2505
+ const classified = classifyGjcTeamCheckpointFiles(files);
2506
+ if (classified.eligible.length === 0 && classified.protected.length > 0)
2507
+ return { kind: "protected_only", files: classified.protected };
2508
+ return { kind: "eligible", files: classified.eligible };
2509
+ }
2410
2510
  async function appendIntegrationEvent(
2411
2511
  dir: string,
2412
2512
  type: string,
@@ -2884,6 +2984,7 @@ async function initializeStateDirs(dir: string, workers: GjcTeamWorker[]): Promi
2884
2984
  export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTeamSnapshot> {
2885
2985
  const cwd = options.cwd ?? process.cwd();
2886
2986
  const env = options.env ?? process.env;
2987
+ if (options.mailboxDeliveryTransport) setGjcTeamMailboxDeliveryTransport(options.mailboxDeliveryTransport);
2887
2988
  if (!Number.isInteger(options.workerCount) || options.workerCount < 1 || options.workerCount > GJC_TEAM_MAX_WORKERS)
2888
2989
  throw new Error(`invalid_team_worker_count:${options.workerCount}:expected_1_${GJC_TEAM_MAX_WORKERS}`);
2889
2990
  const workerCliPlan = resolveGjcTeamWorkerCliPlan(options.workerCount, env);
@@ -3065,13 +3166,15 @@ export async function requestGjcWorkerIntegrationAttempt(
3065
3166
  const worker = env.GJC_TEAM_WORKER_ID?.trim() || env.GJC_TEAM_INTERNAL_WORKER?.split("/").pop()?.trim();
3066
3167
  if (!teamName || !worker) return { requested: false, reason: "not_worker" };
3067
3168
  const dir = await findTeamDir(teamName, cwd, env);
3068
- const config = await readConfig(dir);
3169
+ const config = await readConfigForWorkerIntegration(dir);
3069
3170
  const configuredWorker = config.workers.find(candidate => candidate.id === worker);
3070
3171
  const worktreePath = env.GJC_TEAM_WORKTREE_PATH?.trim() || configuredWorker?.worktree_path;
3071
3172
  if (!worktreePath || !(await pathExists(worktreePath)))
3072
3173
  return { requested: false, reason: "missing_worktree", worker, team_name: teamName };
3073
- const classification = classifyWorkerCheckpointStatus(worktreePath);
3074
- const head = resolveHead(worktreePath);
3174
+ const [classification, head] = await Promise.all([
3175
+ classifyWorkerCheckpointStatusAsync(worktreePath),
3176
+ resolveHeadAsync(worktreePath),
3177
+ ]);
3075
3178
  if (classification.kind === "git_error") {
3076
3179
  return { requested: false, reason: "git_error", worker, team_name: teamName, head, status: classification.kind };
3077
3180
  }
@@ -3839,12 +3942,59 @@ async function reconcileTeamNotifications(dir: string, config: GjcTeamConfig): P
3839
3942
  }
3840
3943
  return summarizeNotifications(await listNotificationRecords(dir));
3841
3944
  }
3945
+ async function messageForNotification(
3946
+ dir: string,
3947
+ notification: GjcTeamNotification,
3948
+ ): Promise<GjcTeamMailboxMessage | null> {
3949
+ if (notification.kind !== "mailbox_message" || notification.source.type !== "message") return null;
3950
+ const mailbox = await readMailbox(dir, notification.recipient);
3951
+ return mailbox.messages.find(message => message.message_id === notification.source.id) ?? null;
3952
+ }
3953
+
3954
+ async function attemptConfiguredMailboxTransport(
3955
+ dir: string,
3956
+ config: GjcTeamConfig,
3957
+ notification: GjcTeamNotification,
3958
+ cwd: string,
3959
+ env: NodeJS.ProcessEnv,
3960
+ ): Promise<GjcTeamNotification | null> {
3961
+ if (!gjcTeamMailboxDeliveryTransport) return null;
3962
+ const message = await messageForNotification(dir, notification);
3963
+ if (!message) return null;
3964
+ try {
3965
+ const result = await gjcTeamMailboxDeliveryTransport.deliverMailboxMessage({
3966
+ team_name: config.team_name,
3967
+ state_dir: dir,
3968
+ config,
3969
+ notification,
3970
+ message,
3971
+ cwd,
3972
+ env,
3973
+ });
3974
+ if (!result) return null;
3975
+ if (result.transport === "notifications_sdk" && result.state === "failed") return null;
3976
+ return writeNotificationRecord(dir, {
3977
+ ...notification,
3978
+ delivery_state: result.state,
3979
+ pane_attempt_result: result.transport === "pane" ? result.state : undefined,
3980
+ pane_attempt_reason: result.reason ?? result.transport,
3981
+ pane_attempt_at: now(),
3982
+ updated_at: now(),
3983
+ });
3984
+ } catch {
3985
+ return null;
3986
+ }
3987
+ }
3988
+
3842
3989
  async function attemptPaneNotification(
3843
3990
  dir: string,
3844
3991
  config: GjcTeamConfig,
3845
3992
  notification: GjcTeamNotification,
3846
3993
  env: NodeJS.ProcessEnv,
3994
+ cwd = process.cwd(),
3847
3995
  ): Promise<GjcTeamNotification> {
3996
+ const transported = await attemptConfiguredMailboxTransport(dir, config, notification, cwd, env);
3997
+ if (transported) return transported;
3848
3998
  const paneId =
3849
3999
  notification.recipient === "leader-fixed"
3850
4000
  ? config.leader.pane_id
@@ -3891,6 +4041,7 @@ export async function replayGjcTeamNotifications(
3891
4041
  replay_count: (notification.replay_count ?? 0) + 1,
3892
4042
  },
3893
4043
  env,
4044
+ cwd,
3894
4045
  );
3895
4046
  next.push(attempted);
3896
4047
  }
@@ -3919,8 +4070,13 @@ export async function sendGjcTeamMessage(
3919
4070
  ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
3920
4071
  };
3921
4072
  const written = await writeMailboxMessage(dir, toWorker, message);
4073
+ const existingNotification = await readJsonFile<GjcTeamNotification>(
4074
+ notificationPath(dir, messageNotificationId(config.team_name, toWorker, written.message_id)),
4075
+ );
3922
4076
  const notification = await createMessageNotification(dir, config.team_name, written);
3923
- await attemptPaneNotification(dir, config, notification, env);
4077
+ if (!existingNotification) {
4078
+ await attemptPaneNotification(dir, config, notification, env, cwd);
4079
+ }
3924
4080
  await appendEvent(dir, {
3925
4081
  type: "message_sent",
3926
4082
  worker: fromWorker,
@@ -4,6 +4,7 @@ import { resolveGjcTmuxBinary } from "./psmux-detect";
4
4
  export const GJC_DEFAULT_TMUX_SESSION = "gajae_code";
5
5
  export const GJC_TMUX_SESSION_PREFIX = `${GJC_DEFAULT_TMUX_SESSION}_`;
6
6
  export const GJC_TMUX_COMMAND_ENV = "GJC_TMUX_COMMAND";
7
+ export const GJC_TMUX_ACTIVE_SESSION_ENV = "GJC_TMUX_ACTIVE_SESSION";
7
8
  export const GJC_TMUX_PROFILE_ENV = "GJC_TMUX_PROFILE";
8
9
  export const GJC_TMUX_MOUSE_ENV = "GJC_MOUSE";
9
10
  export const GJC_TMUX_PROFILE_OPTION = "@gjc-profile";
@@ -1,17 +1,15 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
3
3
  import { resolveGjcSessionForRead, SessionResolutionError } from "./session-resolution";
4
+ import { validateDeferredMemberReceiptFresh, validateReceiptFreshBase } from "./ultragoal-receipt-freshness";
4
5
  import {
5
- computeUltragoalPlanGeneration,
6
6
  getUltragoalPaths,
7
7
  getUltragoalRunCompletionState,
8
- hashStructuredValue,
9
8
  readUltragoalLedger,
10
9
  readUltragoalPlan,
11
10
  recordUltragoalNudgeIfBudgetRemaining,
12
11
  resolveUltragoalNudgeBudget,
13
12
  selectUltragoalNudgeTarget,
14
- type UltragoalCompletionVerification,
15
13
  type UltragoalGoal,
16
14
  type UltragoalLedgerEvent,
17
15
  type UltragoalNudgeSurface,
@@ -176,26 +174,6 @@ function findReceiptGoal(
176
174
  return storyGoal ? { goal: storyGoal, receiptKind: "per-goal" } : null;
177
175
  }
178
176
 
179
- function findLedgerReceiptEvent(
180
- ledger: readonly UltragoalLedgerEvent[],
181
- receipt: UltragoalCompletionVerification,
182
- ): UltragoalLedgerEvent | null {
183
- return (
184
- ledger.find(event => {
185
- if (event.eventId !== receipt.checkpointLedgerEventId) return false;
186
- if (event.event !== "goal_checkpointed") return false;
187
- if (event.goalId !== receipt.goalId) return false;
188
- const eventReceipt = event.completionVerification as UltragoalCompletionVerification | undefined;
189
- return (
190
- event.status === "complete" &&
191
- eventReceipt?.receiptId === receipt.receiptId &&
192
- eventReceipt.receiptKind === receipt.receiptKind &&
193
- eventReceipt.planGeneration === receipt.planGeneration
194
- );
195
- }) ?? null
196
- );
197
- }
198
-
199
177
  export function validateCompletionReceipt(input: {
200
178
  plan: UltragoalPlan;
201
179
  ledger: readonly UltragoalLedgerEvent[];
@@ -210,55 +188,66 @@ export function validateCompletionReceipt(input: {
210
188
  goalId: input.goal.id,
211
189
  };
212
190
  }
213
- if (
214
- receipt.schemaVersion !== 1 ||
215
- receipt.goalId !== input.goal.id ||
216
- receipt.receiptKind !== input.receiptKind ||
217
- !receipt.planGeneration ||
218
- !receipt.checkpointLedgerEventId
219
- ) {
220
- return {
221
- state: "active_stale_receipt",
222
- message: `Ultragoal ${input.goal.id} receipt is malformed or stale.`,
223
- goalId: input.goal.id,
224
- };
225
- }
226
- const event = findLedgerReceiptEvent(input.ledger, receipt);
227
- if (!event) {
228
- return {
229
- state: "active_stale_receipt",
230
- message: `Ultragoal ${input.goal.id} receipt ledger event is missing.`,
231
- goalId: input.goal.id,
232
- };
191
+ if (receipt.validationBatch?.role === "deferred-member") {
192
+ return validateDeferredMemberReceiptFresh({
193
+ plan: input.plan,
194
+ ledger: input.ledger,
195
+ goal: input.goal,
196
+ receipt,
197
+ receiptKind: input.receiptKind,
198
+ requireClose: true,
199
+ });
233
200
  }
234
- const generation = computeUltragoalPlanGeneration({
201
+ const baseDiagnostic = validateReceiptFreshBase({
235
202
  plan: input.plan,
236
203
  ledger: input.ledger,
237
204
  goal: input.goal,
205
+ receipt,
238
206
  receiptKind: input.receiptKind,
239
- beforeStatus: receipt.goalStatusBeforeCheckpoint,
240
- excludeEventId: receipt.checkpointLedgerEventId,
241
207
  });
242
- if (generation.planGeneration !== receipt.planGeneration) {
243
- return {
244
- state: "active_stale_receipt",
245
- message: `Ultragoal ${input.goal.id} receipt generation is stale.`,
246
- goalId: input.goal.id,
247
- };
248
- }
249
- if (hashStructuredValue(event.qualityGateJson) !== receipt.qualityGateHash) {
250
- return {
251
- state: "active_dirty_quality_gate",
252
- message: `Ultragoal ${input.goal.id} receipt quality-gate hash does not match ledger.`,
253
- goalId: input.goal.id,
254
- };
255
- }
256
- if (input.goal.updatedAt !== receipt.verifiedAt) {
257
- return {
258
- state: "active_stale_receipt",
259
- message: `Ultragoal ${input.goal.id} receipt target changed after verification.`,
260
- goalId: input.goal.id,
261
- };
208
+ if (baseDiagnostic) return baseDiagnostic;
209
+ if (receipt.validationBatch?.role === "batch-close") {
210
+ for (const memberId of receipt.validationBatch.memberIds) {
211
+ const member = input.plan.goals.find(goal => goal.id === memberId);
212
+ if (
213
+ !member?.validationBatch ||
214
+ member.validationBatch.metadataHash !== receipt.validationBatch.memberMetadataHashes[memberId]
215
+ ) {
216
+ return {
217
+ state: "active_stale_receipt",
218
+ message: `Ultragoal ${input.goal.id} batch-close receipt has stale member metadata for ${memberId}.`,
219
+ goalId: input.goal.id,
220
+ };
221
+ }
222
+ if (memberId === receipt.validationBatch.finalGoalId) continue;
223
+ const memberReceipt = member.completionVerification;
224
+ if (memberReceipt?.validationBatch?.role !== "deferred-member") {
225
+ return {
226
+ state: "active_missing_final_receipt",
227
+ message: `Ultragoal ${input.goal.id} batch-close receipt requires deferred member receipt for ${memberId}.`,
228
+ goalId: input.goal.id,
229
+ };
230
+ }
231
+ const memberDiagnostic = validateDeferredMemberReceiptFresh({
232
+ plan: input.plan,
233
+ ledger: input.ledger,
234
+ goal: member,
235
+ receipt: memberReceipt,
236
+ receiptKind: "per-goal",
237
+ requireClose: false,
238
+ });
239
+ if (memberDiagnostic.state !== "active_verified_complete") return memberDiagnostic;
240
+ if (
241
+ receipt.validationBatch.memberReceiptIds[memberId] !== memberReceipt.receiptId ||
242
+ receipt.validationBatch.memberChangeSetHashes[memberId] !== memberReceipt.validationBatch.changeSetHash
243
+ ) {
244
+ return {
245
+ state: "active_stale_receipt",
246
+ message: `Ultragoal ${input.goal.id} batch-close receipt is stale for deferred member ${memberId}.`,
247
+ goalId: input.goal.id,
248
+ };
249
+ }
250
+ }
262
251
  }
263
252
  if (input.receiptKind === "final-aggregate") {
264
253
  const incomplete = requiredGoals(input.plan).filter(goal => goal.status !== "complete");