@gajae-code/coding-agent 0.12.5 → 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +4 -1
  3. package/dist/types/eval/backend.d.ts +2 -2
  4. package/dist/types/exec/bash-executor.d.ts +10 -8
  5. package/dist/types/extensibility/extensions/runner.d.ts +9 -7
  6. package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
  7. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  8. package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
  9. package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
  10. package/dist/types/gjc-runtime/team-store.d.ts +0 -4
  11. package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
  12. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  13. package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
  14. package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
  15. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
  16. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
  17. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
  18. package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
  19. package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
  20. package/dist/types/modes/components/plugin-settings.d.ts +2 -0
  21. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  22. package/dist/types/sdk/broker/broker.d.ts +18 -0
  23. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
  25. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -3
  26. package/dist/types/sdk/bus/index.d.ts +4 -0
  27. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
  28. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
  29. package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
  30. package/dist/types/sdk/host/host.d.ts +23 -2
  31. package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
  32. package/dist/types/session/agent-session.d.ts +5 -4
  33. package/dist/types/session/artifacts.d.ts +8 -5
  34. package/dist/types/session/attempt-record-store.d.ts +19 -0
  35. package/dist/types/session/client-bridge.d.ts +14 -0
  36. package/dist/types/session/internal/managed-session-storage.d.ts +5 -2
  37. package/dist/types/session/messages.d.ts +4 -1
  38. package/dist/types/session/session-manager.d.ts +6 -4
  39. package/dist/types/session/session-storage.d.ts +8 -0
  40. package/dist/types/session/streaming-output.d.ts +32 -0
  41. package/dist/types/task/receipt.d.ts +2 -0
  42. package/dist/types/task/types.d.ts +178 -0
  43. package/dist/types/tools/bash.d.ts +1 -1
  44. package/dist/types/tools/fetch.d.ts +3 -0
  45. package/dist/types/tools/output-meta.d.ts +37 -5
  46. package/dist/types/tools/tool-result.d.ts +1 -0
  47. package/dist/types/utils/artifact-id.d.ts +2 -0
  48. package/package.json +8 -8
  49. package/scripts/capture-sticky-viewport-showcase.ts +131 -5
  50. package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
  51. package/scripts/verify-sticky-viewport-showcase.ts +620 -93
  52. package/src/config/model-profiles.ts +8 -0
  53. package/src/config/settings-schema.ts +17 -3
  54. package/src/cursor.ts +20 -13
  55. package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
  56. package/src/eval/backend.ts +2 -2
  57. package/src/exec/bash-executor.ts +425 -151
  58. package/src/extensibility/extensions/runner.ts +80 -6
  59. package/src/extensibility/extensions/wrapper.ts +22 -15
  60. package/src/extensibility/runtime-skill-discovery.ts +4 -6
  61. package/src/gjc-runtime/launch-tmux.ts +309 -293
  62. package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
  63. package/src/gjc-runtime/psmux-detect.ts +52 -6
  64. package/src/gjc-runtime/team-launch.ts +50 -2
  65. package/src/gjc-runtime/team-runtime.ts +716 -251
  66. package/src/gjc-runtime/team-store.ts +25 -7
  67. package/src/gjc-runtime/team-workers.ts +161 -1
  68. package/src/gjc-runtime/tmux-common.ts +16 -4
  69. package/src/gjc-runtime/tmux-gc.ts +37 -5
  70. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
  71. package/src/gjc-runtime/tmux-provider-context.ts +451 -0
  72. package/src/gjc-runtime/tmux-sessions.ts +409 -156
  73. package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
  74. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
  75. package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
  76. package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
  77. package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
  78. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  79. package/src/internal-urls/agent-protocol.ts +98 -38
  80. package/src/internal-urls/artifact-protocol.ts +28 -17
  81. package/src/internal-urls/docs-index.generated.ts +5 -5
  82. package/src/internal-urls/local-protocol.ts +39 -18
  83. package/src/internal-urls/registry-helpers.ts +62 -0
  84. package/src/modes/acp/acp-agent.ts +51 -13
  85. package/src/modes/acp/acp-client-bridge.ts +154 -11
  86. package/src/modes/components/gjc-bundle-settings.ts +2 -0
  87. package/src/modes/components/plugin-settings.ts +5 -0
  88. package/src/modes/components/settings-selector.ts +4 -0
  89. package/src/modes/controllers/selector-controller.ts +1 -0
  90. package/src/prompts/tools/bash.md +2 -2
  91. package/src/prompts/tools/monitor.md +4 -4
  92. package/src/sdk/acp/adapter.ts +200 -9
  93. package/src/sdk/broker/broker.ts +93 -10
  94. package/src/sdk/broker/lifecycle-ledger.ts +227 -11
  95. package/src/sdk/broker/lifecycle.ts +691 -92
  96. package/src/sdk/bus/chat-daemon-control.ts +32 -6
  97. package/src/sdk/bus/index.ts +347 -14
  98. package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
  99. package/src/sdk/bus/telegram-daemon.ts +283 -44
  100. package/src/sdk/bus/topic-registry.ts +133 -10
  101. package/src/sdk/host/host.ts +187 -9
  102. package/src/sdk/host/reverse-leases.ts +249 -42
  103. package/src/sdk/session.ts +7 -6
  104. package/src/session/agent-session.ts +930 -354
  105. package/src/session/artifacts.ts +211 -74
  106. package/src/session/attempt-record-store.ts +106 -0
  107. package/src/session/client-bridge.ts +15 -0
  108. package/src/session/internal/managed-session-scope.ts +252 -67
  109. package/src/session/internal/managed-session-storage.ts +105 -5
  110. package/src/session/messages.ts +32 -7
  111. package/src/session/session-manager.ts +19 -21
  112. package/src/session/session-storage.ts +146 -24
  113. package/src/session/streaming-output.ts +198 -40
  114. package/src/task/index.ts +45 -0
  115. package/src/task/provider-retry-status.ts +4 -1
  116. package/src/task/receipt.ts +4 -0
  117. package/src/task/render.ts +30 -0
  118. package/src/task/types.ts +34 -0
  119. package/src/tools/bash.ts +818 -239
  120. package/src/tools/eval.ts +99 -4
  121. package/src/tools/fetch.ts +77 -15
  122. package/src/tools/find.ts +7 -3
  123. package/src/tools/monitor.ts +7 -1
  124. package/src/tools/output-meta.ts +706 -116
  125. package/src/tools/search.ts +9 -4
  126. package/src/tools/tool-result.ts +1 -0
  127. package/src/utils/artifact-id.ts +6 -0
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+
3
4
  import {
4
5
  renderStickyViewportShowcase,
5
6
  STICKY_VIEWPORT_SHOWCASE_ENTRIES,
@@ -7,6 +8,10 @@ import {
7
8
  type StickyViewportShowcaseEntry,
8
9
  } from "../test/fixtures/tui/sticky-viewport-showcase";
9
10
 
11
+ export const REPOSITORY_ROOT = path.resolve(import.meta.dir, "../../..");
12
+ export const resolveRepositoryPath = (repositoryRelativePath: string): string =>
13
+ path.join(REPOSITORY_ROOT, repositoryRelativePath);
14
+
10
15
  const COMMAND =
11
16
  "bun packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts --out .gjc/qa/sticky-viewport-<run>";
12
17
  const REVISION = "sticky-viewport-showcase-v2";
@@ -168,17 +173,126 @@ export function ansiToHtml(value: string): string {
168
173
  return `<!doctype html><html lang="en"><meta charset="utf-8"><title>Sticky viewport showcase</title><style>body{margin:0;background:#110b0b;color:#ffe7dc}pre{white-space:pre;font-family:ui-monospace,monospace}@keyframes blink{50%{visibility:hidden}}</style><pre>${body}</pre></html>\n`;
169
174
  }
170
175
  const json = (value: unknown) => `${JSON.stringify(value, null, 2)}\n`;
171
- const hash = (value: string) => new Bun.CryptoHasher("sha256").update(value).digest("hex");
176
+ const hash = (value: string | Uint8Array) => new Bun.CryptoHasher("sha256").update(value).digest("hex");
177
+ const PROVENANCE_SOURCES = [
178
+ "packages/coding-agent/test/fixtures/tui/sticky-viewport-showcase.ts",
179
+ "packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts",
180
+ "packages/coding-agent/scripts/verify-sticky-viewport-showcase.ts",
181
+ "packages/coding-agent/src/modes/interactive-mode.ts",
182
+ "packages/coding-agent/src/modes/components/irc-sidebar.ts",
183
+ "packages/tui/src/tui.ts",
184
+ ] as const;
185
+ // Working-tree scope for `git_diff_binary_sha256`, persisted alongside the digest
186
+ // as `git_diff_scope` so a reviewer reads the covered surface off the bundle
187
+ // instead of inferring it.
188
+ //
189
+ // This digest used to hash `git diff --binary HEAD --` over the ENTIRE worktree.
190
+ // The verifier recomputes it live at verify time, so that coupled bundle validity
191
+ // to every tracked file in the repo: an unrelated edit anywhere — a doc typo,
192
+ // another package's test — retroactively made every already-captured bundle
193
+ // "stale". Those are false positives, and they are nondeterministic, because any
194
+ // write landing in the capture→verify window flips the digest mid-run and masks
195
+ // whichever guard was actually under test.
196
+ //
197
+ // The scope below is the transitive render-dependency closure of the fixture:
198
+ // every workspace package the capture reaches (coding-agent → agent, ai,
199
+ // bridge-client, natives, stats, tui, utils), the fixture plus the virtual
200
+ // terminal it paints into, both showcase scripts, and the lockfile pinning the
201
+ // installed dependency versions. Uncommitted edits inside this closure still
202
+ // invalidate a bundle — that is the property the staleness guard exists to
203
+ // enforce. Edits outside it no longer can, because they cannot change the paint.
204
+ export const PROVENANCE_DIFF_SCOPE = [
205
+ "Cargo.lock",
206
+ "Cargo.toml",
207
+ "bun.lock",
208
+ "crates",
209
+
210
+ "packages/agent/src",
211
+ "packages/ai/src",
212
+ "packages/bridge-client/src",
213
+ "packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts",
214
+ "packages/coding-agent/scripts/verify-sticky-viewport-showcase.ts",
215
+ "packages/coding-agent/src",
216
+ "packages/coding-agent/test/fixtures/tui/sticky-viewport-showcase.ts",
217
+ "packages/natives/native",
218
+ "packages/stats/src",
219
+ "packages/tui/src",
220
+ "packages/tui/test/virtual-terminal.ts",
221
+ "packages/utils/src",
222
+ ] as const;
223
+ async function git(args: string[]): Promise<Uint8Array> {
224
+ const result = Bun.spawn(["git", ...args], { cwd: REPOSITORY_ROOT, stdout: "pipe", stderr: "pipe" });
225
+ if ((await result.exited) !== 0)
226
+ throw new Error(`git ${args.join(" ")} failed: ${await new Response(result.stderr).text()}`);
227
+ return new Uint8Array(await new Response(result.stdout).arrayBuffer());
228
+ }
229
+
230
+ // Digest of a path's COMMITTED blob at a given commit, read straight out of the
231
+ // object database. This is the only provenance input a bundle author cannot
232
+ // restamp: `captureProvenance()` hashes the worktree, so mutating an oracle file
233
+ // and re-running it yields a self-consistent stamp. The committed blob is fixed
234
+ // by the commit id, so changing it requires a new commit -- which changes
235
+ // `git_head` and is therefore visible to the reviewer.
236
+ /** sha256 of every distinct blob this path has ever had in any ref-reachable commit. */
237
+ export async function gitObjectType(commitish: string): Promise<string | null> {
238
+ try {
239
+ const out = new TextDecoder().decode(await git(["cat-file", "-t", commitish])).trim();
240
+ return out || null;
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
245
+ export async function committedBlobSha256(commit: string, filePath: string): Promise<string | null> {
246
+ const result = Bun.spawn(["git", "cat-file", "blob", `${commit}:${filePath}`], {
247
+ cwd: REPOSITORY_ROOT,
248
+ stdout: "pipe",
249
+ stderr: "pipe",
250
+ });
251
+ const bytes = new Uint8Array(await new Response(result.stdout).arrayBuffer());
252
+ if ((await result.exited) !== 0) return null;
253
+ return hash(bytes);
254
+ }
255
+ export type CaptureProvenance = {
256
+ git_head: string;
257
+ oracle_commit: string;
258
+ git_diff_scope: readonly string[];
259
+ git_diff_binary_sha256: string;
260
+ source_sha256: Record<string, string>;
261
+ };
262
+
263
+ // Single source of truth: the verifier imports this so capture and verify can
264
+ // never drift into computing the field two different ways.
265
+ export async function captureProvenance(): Promise<CaptureProvenance> {
266
+ const gitHead = new TextDecoder().decode(await git(["rev-parse", "HEAD"])).trim();
267
+ const sourceSha256 = Object.fromEntries(
268
+ await Promise.all(
269
+ PROVENANCE_SOURCES.map(async source => [
270
+ source,
271
+ hash(new Uint8Array(await Bun.file(resolveRepositoryPath(source)).arrayBuffer())),
272
+ ]),
273
+ ),
274
+ );
275
+ return {
276
+ git_head: gitHead,
277
+ oracle_commit: process.env.GJC_STICKY_VIEWPORT_ORACLE_COMMIT?.trim() ?? gitHead,
278
+ git_diff_scope: PROVENANCE_DIFF_SCOPE,
279
+ git_diff_binary_sha256: hash(await git(["diff", "--binary", "HEAD", "--", ...PROVENANCE_DIFF_SCOPE])),
280
+ source_sha256: sourceSha256,
281
+ };
282
+ }
172
283
  function out(args: string[]): string {
173
284
  if (args.length !== 2 || args[0] !== "--out" || !args[1]) throw new Error(`Usage: ${COMMAND}`);
174
285
  return args[1];
175
286
  }
176
- async function capture(entry: StickyViewportShowcaseEntry, root: string) {
287
+ async function capture(entry: StickyViewportShowcaseEntry, root: string, sourceProvenance: CaptureProvenance) {
177
288
  const rendered = await renderStickyViewportShowcase(entry);
178
289
  if (!rendered.state.composer_visible)
179
290
  throw new Error(`${entry.key}: focused composer was not visible in production frame`);
180
- if (entry.stateId === "manual-new-output" && rendered.state.notice !== true)
181
- throw new Error(`${entry.key}: manual output notice precondition failed`);
291
+ if (
292
+ (entry.stateId === "manual-new-output" && rendered.state.notice !== true) ||
293
+ (entry.stateId !== "manual-new-output" && rendered.state.notice !== false)
294
+ )
295
+ throw new Error(`${entry.key}: renderer-owned output notice precondition failed`);
182
296
  if (
183
297
  JSON.stringify(rendered.cjkPhraseBoundaries) !==
184
298
  JSON.stringify(entry.stateId === "narrow-cjk" ? CJK_PHRASE_BOUNDARIES : [])
@@ -210,6 +324,7 @@ async function capture(entry: StickyViewportShowcaseEntry, root: string) {
210
324
  fixed_clock: true,
211
325
  author_identity: "capture-sticky-viewport-showcase",
212
326
  executor_identity: "capture-sticky-viewport-showcase",
327
+ ...sourceProvenance,
213
328
  },
214
329
  cjk_phrase_boundaries: rendered.cjkPhraseBoundaries,
215
330
  });
@@ -241,8 +356,9 @@ async function capture(entry: StickyViewportShowcaseEntry, root: string) {
241
356
  async function main() {
242
357
  const root = path.resolve(out(process.argv.slice(2)));
243
358
  await fs.mkdir(root, { recursive: true });
359
+ const sourceProvenance = await captureProvenance();
244
360
  const entries = [];
245
- for (const entry of STICKY_VIEWPORT_SHOWCASE_ENTRIES) entries.push(await capture(entry, root));
361
+ for (const entry of STICKY_VIEWPORT_SHOWCASE_ENTRIES) entries.push(await capture(entry, root, sourceProvenance));
246
362
  const manifest = json({
247
363
  schema_version: 2,
248
364
  fixture_revision: REVISION,
@@ -258,6 +374,7 @@ async function main() {
258
374
  fixed_clock: true,
259
375
  author_identity: "capture-sticky-viewport-showcase",
260
376
  executor_identity: "capture-sticky-viewport-showcase",
377
+ ...sourceProvenance,
261
378
  },
262
379
  review_input_file: "review-input.json",
263
380
  entries,
@@ -281,6 +398,15 @@ async function main() {
281
398
  acceptance_version: ACCEPTANCE_VERSION,
282
399
  design_version: DESIGN_VERSION,
283
400
  host_matrix: HOST_MATRIX,
401
+ provenance: {
402
+ capture_mode: "production-tui-virtual-terminal",
403
+ live_pty: false,
404
+ network: false,
405
+ fixed_clock: true,
406
+ author_identity: "capture-sticky-viewport-showcase",
407
+ executor_identity: "capture-sticky-viewport-showcase",
408
+ ...sourceProvenance,
409
+ },
284
410
  narrow_cjk: {
285
411
  entry_key: "narrow-cjk/48x10/unicode-color",
286
412
  phrase_boundaries: ["의미 있는 문장 경계", "意味のある文の境界", "保留语义短语边界"],
@@ -1421,13 +1421,6 @@ function braceBlockRange(contents: string, openingBrace: number): ShellRange | u
1421
1421
  return undefined;
1422
1422
  }
1423
1423
 
1424
- function scopeMatches<T extends RegExpMatchArray>(matches: T[], range: ShellRange): T[] {
1425
- return matches.filter(match => {
1426
- const index = match.index ?? -1;
1427
- return index >= range.start && index < range.end;
1428
- });
1429
- }
1430
-
1431
1424
  function findFunctionRange(contents: string, headerPattern: RegExp): ShellRange | undefined {
1432
1425
  const header = headerPattern.exec(contents);
1433
1426
  if (!header) return undefined;
@@ -1504,121 +1497,62 @@ function exactMemoryGuardSendKeysRanges(contents: string): ShellRange[] {
1504
1497
  ];
1505
1498
  }
1506
1499
  function exactTeamRuntimeSendKeysRanges(contents: string): ShellRange[] {
1507
- // Scope the loop-dispatch counts to `startTmuxSession` so the second
1508
- // sanctioned call site (`relaunchWorkerPaneForMemoryGuard`, matched by
1509
- // `exactMemoryGuardSendKeysRanges`) does not read as a duplicate. Fixtures
1510
- // that carry the canonical shape without the enclosing function keep the
1511
- // original whole-file scope, so exact-match strictness is unchanged.
1512
- const mainFnRange = findFunctionRange(
1500
+ const executor =
1501
+ /function\s+executeTeamTmuxMutation\s*\([\s\S]*?\)\s*:\s*Bun\.SyncSubprocess<"pipe",\s*"pipe">\s*\{/.exec(
1502
+ contents,
1503
+ );
1504
+ const continuation = /async\s+function\s+continueStalledGjcTeamWorkers\s*\([^)]*\)\s*:\s*Promise<void>\s*\{/.exec(
1513
1505
  contents,
1514
- /async\s+function\s+startTmuxSession\s*\([\s\S]{0,600}?\)\s*:\s*Promise<GjcTeamWorker\[\]>\s*\{/,
1515
- ) ?? { start: 0, end: contents.length };
1516
- const guardMatches = scopeMatches([...contents.matchAll(/if\s*\(\s*useSendKeysFallback\s*\)\s*\{/g)], mainFnRange);
1517
- const payloadMatches = scopeMatches(
1518
- [
1519
- ...contents.matchAll(
1520
- /Bun\.spawnSync\(\s*\[\s*config\.tmux_command\s*,\s*["']send-keys["']\s*,\s*["']-l["']\s*,\s*["']-t["']\s*,\s*paneId\s*,\s*workerCommand\s*\]\s*,\s*\{[\s\S]{0,200}?stdout\s*:\s*["']ignore["'][\s\S]{0,200}?stderr\s*:\s*["']ignore["'][\s\S]{0,100}?\}\s*\)\s*;/g,
1521
- ),
1522
- ],
1523
- mainFnRange,
1524
- );
1525
- const continuationPromptMatches = [
1526
- ...contents.matchAll(
1527
- /(?:const\s+GJC_TEAM_CONTINUATION_PROMPT\s*=\s*["']Continue only your current claimed GJC team task\. Re-read current GJC team state; do not replay prior output; report status\.["']\s*;|import\s*\{[\s\S]{0,1000}?\bGJC_TEAM_CONTINUATION_PROMPT\b[\s\S]{0,1000}?\}\s*from\s*["']\.\/team-workers["']\s*;)/g,
1528
- ),
1529
- ];
1530
- const continuationMatches = [
1531
- ...contents.matchAll(
1532
- /const\s+args\s*=\s*Object\.freeze\(\s*\[\s*["']send-keys["']\s*,\s*["']-l["']\s*,\s*["']-t["']\s*,\s*worker\.pane_id\s*,\s*GJC_TEAM_CONTINUATION_PROMPT\s*,\s*["'];["']\s*,\s*["']send-keys["']\s*,\s*["']-t["']\s*,\s*worker\.pane_id\s*,\s*["']Enter["']\s*,?\s*\]\s*\)\s*;\s*const\s+dispatch\s*=\s*gjcTeamRuntimeTestSeams\?\.continuationTmuxDispatch\s*\?\s*gjcTeamRuntimeTestSeams\.continuationTmuxDispatch\(config\.tmux_command\s*,\s*args\)\s*:\s*Bun\.spawnSync\(\[config\.tmux_command\s*,\s*\.\.\.args\]\s*,\s*\{\s*stdout\s*:\s*["']ignore["']\s*,\s*stderr\s*:\s*["']ignore["']\s*,\s*timeout\s*:\s*GJC_TEAM_CONTINUATION_DISPATCH_TIMEOUT_MS\s*,?\s*\}\s*\)\s*;/g,
1533
- ),
1534
- ];
1535
- const enterMatches = scopeMatches(
1536
- [
1537
- ...contents.matchAll(
1538
- /const\s+sendKeys\s*=\s*Bun\.spawnSync\(\s*\[\s*config\.tmux_command\s*,\s*["']send-keys["']\s*,\s*["']-t["']\s*,\s*paneId\s*,\s*["']Enter["']\s*\]\s*,\s*\{[\s\S]{0,200}?stdout\s*:\s*["']ignore["'][\s\S]{0,200}?stderr\s*:\s*["']ignore["'][\s\S]{0,100}?\}\s*\)\s*;/g,
1539
- ),
1540
- ],
1541
- mainFnRange,
1542
- );
1543
- const fallbackPredicateMatches = [
1544
- ...contents.matchAll(
1545
- /function\s+shouldDispatchWorkerWithSendKeys\([^)]*\)\s*:\s*boolean\s*\{\s*return\s+platform\s*===\s*["']win32["']\s*\|\|\s*path\.basename\(tmuxCommand\)\.toLowerCase\(\)\s*===\s*["']psmux["']\s*;\s*\}/g,
1546
- ),
1547
- ];
1548
- const useFallbackMatches = scopeMatches(
1549
- [
1550
- ...contents.matchAll(
1551
- /const\s+useSendKeysFallback\s*=\s*shouldDispatchWorkerWithSendKeys\(config\.tmux_command\)\s*;/g,
1552
- ),
1553
- ],
1554
- mainFnRange,
1555
- );
1556
- const splitWorkerCommandMatches = scopeMatches(
1557
- [...contents.matchAll(/\.\.\.\(useSendKeysFallback\s*\?\s*\[\]\s*:\s*\[workerCommand\]\)\s*,/g)],
1558
- mainFnRange,
1559
1506
  );
1507
+ if (!executor || !continuation) return [];
1508
+
1509
+ const executorRange = braceBlockRange(contents, (executor.index ?? 0) + executor[0].lastIndexOf("{"));
1510
+ const continuationRange = braceBlockRange(contents, (continuation.index ?? 0) + continuation[0].lastIndexOf("{"));
1511
+ if (!executorRange || !continuationRange) return [];
1512
+
1513
+ const executorBody = contents.slice(executorRange.start, executorRange.end);
1514
+ const literalSend =
1515
+ /\?\s*\[\s*["']send-keys["']\s*,\s*["']-l["']\s*,\s*["']-t["']\s*,\s*operation\.paneId\s*,\s*operation\.text\s*\]/.exec(
1516
+ executorBody,
1517
+ );
1518
+ const keySend =
1519
+ /operation\.type\s*===\s*["']key-send["']\s*\?\s*\[\s*["']send-keys["']\s*,\s*["']-t["']\s*,\s*operation\.paneId\s*,\s*operation\.key\s*\]/.exec(
1520
+ executorBody,
1521
+ );
1522
+ const authorityChecks = executorBody.match(/assertGjcTmuxMutationAuthoritySync\(authority\)/g) ?? [];
1560
1523
  if (
1561
- guardMatches.length !== 1 ||
1562
- payloadMatches.length !== 1 ||
1563
- enterMatches.length !== 1 ||
1564
- continuationPromptMatches.length !== 1 ||
1565
- continuationMatches.length !== 1 ||
1566
- fallbackPredicateMatches.length !== 1 ||
1567
- useFallbackMatches.length !== 1 ||
1568
- splitWorkerCommandMatches.length !== 1
1524
+ !literalSend ||
1525
+ !keySend ||
1526
+ !executorBody.includes("const authority = teamProviderAuthority(config);") ||
1527
+ !executorBody.includes("assertTeamTmuxMutationPreproof(config, operation);") ||
1528
+ authorityChecks.length < 2 ||
1529
+ !executorBody.includes("Bun.spawnSync(")
1569
1530
  )
1570
1531
  return [];
1571
- const guardStart = guardMatches[0].index ?? 0;
1572
- const payloadStart = payloadMatches[0].index ?? 0;
1573
- const enterStart = enterMatches[0].index ?? 0;
1574
- const voidStart = contents.indexOf("void sendKeys.exitCode;", enterStart);
1532
+
1533
+ const continuationBody = contents.slice(continuationRange.start, continuationRange.end);
1534
+ const continuationArgs =
1535
+ /const\s+args\s*=\s*Object\.freeze\(\s*\[\s*["']send-keys["']\s*,\s*["']-l["']\s*,\s*["']-t["']\s*,\s*worker\.pane_id\s*,\s*continuationPrompt\s*,\s*["'];["']\s*,\s*["']send-keys["']\s*,\s*["']-t["']\s*,\s*worker\.pane_id\s*,\s*["']Enter["']\s*,?\s*\]\s*\)\s*;/.exec(
1536
+ continuationBody,
1537
+ );
1575
1538
  if (
1576
- (useFallbackMatches[0].index ?? 0) >= (splitWorkerCommandMatches[0].index ?? 0) ||
1577
- (splitWorkerCommandMatches[0].index ?? 0) >= guardStart ||
1578
- payloadStart >= enterStart ||
1579
- voidStart < enterStart
1580
- )
1581
- return [];
1582
- const guard = guardMatches[0];
1583
- const openingBrace = (guard.index ?? 0) + guard[0].lastIndexOf("{");
1584
- const range = braceBlockRange(contents, openingBrace);
1585
- const continuationFunction =
1586
- /async\s+function\s+continueStalledGjcTeamWorkers\s*\([^)]*\)\s*:\s*Promise<void>\s*\{/.exec(contents);
1587
- const monitorFence = /await\s+withGjcTeamTaskMutation\([\s\S]{0,500}?async\s+capability\s*=>\s*\{/.exec(contents);
1588
- if (!range || !continuationFunction || !monitorFence) return [];
1589
- const continuationOpeningBrace = (continuationFunction.index ?? 0) + continuationFunction[0].lastIndexOf("{");
1590
- const continuationRange = braceBlockRange(contents, continuationOpeningBrace);
1591
- const monitorOpeningBrace = (monitorFence.index ?? 0) + monitorFence[0].lastIndexOf("{");
1592
- const monitorRange = braceBlockRange(contents, monitorOpeningBrace);
1593
- if (!continuationRange || !monitorRange) return [];
1594
- const monitorBody = contents.slice(monitorRange.start, monitorRange.end);
1595
- const continuationCall = /await\s+continueStalledGjcTeamWorkers\s*\(/.exec(monitorBody);
1596
- const reconciliationCall = /await\s+reconcileGjcTeamStaleClaimsUnlocked\s*\(/.exec(monitorBody);
1597
- const continuationStart = continuationMatches[0].index ?? 0;
1598
- const finalValidationStart = contents.lastIndexOf("const revalidationReason =", continuationStart);
1599
- const skippedBranch = finalValidationStart === -1 ? "" : contents.slice(finalValidationStart, continuationStart);
1600
- if (
1601
- payloadStart < range.start ||
1602
- payloadStart >= range.end ||
1603
- enterStart < range.start ||
1604
- enterStart >= range.end ||
1605
- contents.indexOf("void sendKeys.exitCode;", enterStart) >= range.end ||
1606
- continuationStart < continuationRange.start ||
1607
- continuationStart >= continuationRange.end ||
1608
- finalValidationStart < continuationRange.start ||
1609
- !continuationCall ||
1610
- !reconciliationCall ||
1611
- (continuationCall.index ?? 0) >= (reconciliationCall.index ?? 0) ||
1612
- !/if\s*\(\s*revalidationReason\s*\)[\s\S]*?return\s*;/.test(skippedBranch)
1539
+ !continuationArgs ||
1540
+ !continuationBody.includes("const revalidationReason =") ||
1541
+ !continuationBody.includes("await createJsonNoClobber(") ||
1542
+ !continuationBody.includes('type: "literal-send"') ||
1543
+ !continuationBody.includes('type: "key-send"') ||
1544
+ !continuationBody.includes('deferredProof: "continuation-outcome"') ||
1545
+ /args\s*\.\s*(?:push|unshift|splice)\s*\(/.test(continuationBody)
1613
1546
  )
1614
1547
  return [];
1548
+
1549
+ const literalStart = executorRange.start + (literalSend.index ?? 0);
1550
+ const keyStart = executorRange.start + (keySend.index ?? 0);
1551
+ const argsStart = continuationRange.start + (continuationArgs.index ?? 0);
1615
1552
  return [
1616
- { start: payloadStart, end: payloadStart + payloadMatches[0][0].length },
1617
- { start: enterStart, end: enterStart + enterMatches[0][0].length },
1618
- {
1619
- start: continuationMatches[0].index ?? 0,
1620
- end: (continuationMatches[0].index ?? 0) + continuationMatches[0][0].length,
1621
- },
1553
+ { start: literalStart, end: literalStart + literalSend[0].length },
1554
+ { start: keyStart, end: keyStart + keySend[0].length },
1555
+ { start: argsStart, end: argsStart + continuationArgs[0].length },
1622
1556
  ];
1623
1557
  }
1624
1558
 
@@ -3219,47 +3153,68 @@ fi
3219
3153
  "tmux send-keys content injection is outside sanctioned process lifecycle",
3220
3154
  );
3221
3155
  const canonicalTeamRuntimeSendKeysFixture = `
3222
- async function continueStalledGjcTeamWorkers(): Promise<void> {
3223
- const revalidationReason = null;
3224
- if (revalidationReason) return;
3225
- function shouldDispatchWorkerWithSendKeys(tmuxCommand: string, platform: NodeJS.Platform = process.platform): boolean {
3226
- return platform === "win32" || path.basename(tmuxCommand).toLowerCase() === "psmux";
3227
- }
3228
- const useSendKeysFallback = shouldDispatchWorkerWithSendKeys(config.tmux_command);
3229
- const splitArgs = [...(useSendKeysFallback ? [] : [workerCommand]),];
3230
- if (useSendKeysFallback) {
3231
- Bun.spawnSync([config.tmux_command, "send-keys", "-l", "-t", paneId, workerCommand], {
3232
- stdout: "ignore",
3233
- stderr: "ignore",
3234
- });
3235
- const sendKeys = Bun.spawnSync([config.tmux_command, "send-keys", "-t", paneId, "Enter"], {
3236
- stdout: "ignore",
3237
- stderr: "ignore",
3238
- });
3239
- void sendKeys.exitCode;
3156
+ type TeamTmuxMutation =
3157
+ | { type: "literal-send"; paneId: string; text: string; deferredProof: "continuation-outcome" }
3158
+ | { type: "key-send"; paneId: string; key: string; deferredProof: "continuation-outcome" };
3159
+ function executeTeamTmuxMutation(
3160
+ config: GjcTeamConfig,
3161
+ operation: TeamTmuxMutation,
3162
+ ): Bun.SyncSubprocess<"pipe", "pipe"> {
3163
+ const authority = teamProviderAuthority(config);
3164
+ assertTeamTmuxMutationPreproof(config, operation);
3165
+ const args =
3166
+ operation.type === "literal-send"
3167
+ ? ["send-keys", "-l", "-t", operation.paneId, operation.text]
3168
+ : operation.type === "key-send"
3169
+ ? ["send-keys", "-t", operation.paneId, operation.key]
3170
+ : [];
3171
+ assertGjcTmuxMutationAuthoritySync(authority);
3172
+ const result = Bun.spawnSync(args);
3173
+ assertGjcTmuxMutationAuthoritySync(authority);
3174
+ return result;
3240
3175
  }
3241
- const GJC_TEAM_CONTINUATION_PROMPT = "Continue only your current claimed GJC team task. Re-read current GJC team state; do not replay prior output; report status.";
3242
- const GJC_TEAM_CONTINUATION_DISPATCH_TIMEOUT_MS = 5_000;
3243
- const args = Object.freeze([
3244
- "send-keys",
3245
- "-l",
3246
- "-t",
3247
- worker.pane_id,
3248
- GJC_TEAM_CONTINUATION_PROMPT,
3249
- ";",
3250
- "send-keys",
3251
- "-t",
3252
- worker.pane_id,
3253
- "Enter",
3254
- ]);
3255
-
3256
- const dispatch = gjcTeamRuntimeTestSeams?.continuationTmuxDispatch
3257
- ? gjcTeamRuntimeTestSeams.continuationTmuxDispatch(config.tmux_command, args)
3258
- : Bun.spawnSync([config.tmux_command, ...args], {
3259
- stdout: "ignore",
3260
- stderr: "ignore",
3261
- timeout: GJC_TEAM_CONTINUATION_DISPATCH_TIMEOUT_MS,
3262
- });
3176
+ async function continueStalledGjcTeamWorkers(): Promise<void> {
3177
+ const reservationPath = "reservation";
3178
+ const reservation = {};
3179
+ await createJsonNoClobber(
3180
+ reservationPath,
3181
+ reservation,
3182
+ stateWriterOptions(reservationPath, "state", "continuation-reservation"),
3183
+ );
3184
+ const continuationPrompt = "Continue only your current claimed GJC team task. Re-read current GJC team state; do not replay prior output; report status.";
3185
+ const revalidationReason = await validateGjcContinuationEligibility(dir, config, worker);
3186
+ if (revalidationReason) {
3187
+ return;
3188
+ }
3189
+ const args = Object.freeze([
3190
+ "send-keys",
3191
+ "-l",
3192
+ "-t",
3193
+ worker.pane_id,
3194
+ continuationPrompt,
3195
+ ";",
3196
+ "send-keys",
3197
+ "-t",
3198
+ worker.pane_id,
3199
+ "Enter",
3200
+ ]);
3201
+ const dispatch = gjcTeamRuntimeTestSeams?.continuationTmuxDispatch
3202
+ ? gjcTeamRuntimeTestSeams.continuationTmuxDispatch(config.tmux_command, args)
3203
+ : (() => {
3204
+ executeTeamTmuxMutation(config, {
3205
+ type: "literal-send",
3206
+ paneId: worker.pane_id!,
3207
+ text: continuationPrompt,
3208
+ deferredProof: "continuation-outcome",
3209
+ });
3210
+ return executeTeamTmuxMutation(config, {
3211
+ type: "key-send",
3212
+ paneId: worker.pane_id!,
3213
+ key: "Enter",
3214
+ deferredProof: "continuation-outcome",
3215
+ });
3216
+ })();
3217
+ void dispatch;
3263
3218
  }
3264
3219
  async function monitorGjcTeam(): Promise<void> {
3265
3220
  await withGjcTeamTaskMutation(taskStore(dir), async capability => {
@@ -3275,18 +3230,8 @@ async function monitorGjcTeam(): Promise<void> {
3275
3230
  await runSelfTestFixture(
3276
3231
  {
3277
3232
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3278
- "async function continueStalledGjcTeamWorkers(): Promise<void>",
3279
- "async function relocatedContinuation(): Promise<void>",
3280
- ),
3281
- },
3282
- 1,
3283
- "tmux send-keys content injection is outside sanctioned process lifecycle",
3284
- );
3285
- await runSelfTestFixture(
3286
- {
3287
- "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3288
- "if (revalidationReason) return;",
3289
- "if (revalidationReason) {}",
3233
+ "\tassertTeamTmuxMutationPreproof(config, operation);\n",
3234
+ "",
3290
3235
  ),
3291
3236
  },
3292
3237
  1,
@@ -3295,8 +3240,8 @@ async function monitorGjcTeam(): Promise<void> {
3295
3240
  await runSelfTestFixture(
3296
3241
  {
3297
3242
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3298
- "await continueStalledGjcTeamWorkers();",
3299
- "",
3243
+ "\tassertGjcTmuxMutationAuthoritySync(authority);\n\treturn result;",
3244
+ "\treturn result;",
3300
3245
  ),
3301
3246
  },
3302
3247
  1,
@@ -3305,8 +3250,8 @@ async function monitorGjcTeam(): Promise<void> {
3305
3250
  await runSelfTestFixture(
3306
3251
  {
3307
3252
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3308
- "await continueStalledGjcTeamWorkers();\n\t\tawait reconcileGjcTeamStaleClaimsUnlocked(workerOrchestrationRuntime, teamName, dir, config, env, capability);",
3309
- "await reconcileGjcTeamStaleClaimsUnlocked(workerOrchestrationRuntime, teamName, dir, config, env, capability);\n\t\tawait continueStalledGjcTeamWorkers();",
3253
+ "async function continueStalledGjcTeamWorkers(): Promise<void>",
3254
+ "async function relocatedContinuation(): Promise<void>",
3310
3255
  ),
3311
3256
  },
3312
3257
  1,
@@ -3315,8 +3260,8 @@ async function monitorGjcTeam(): Promise<void> {
3315
3260
  await runSelfTestFixture(
3316
3261
  {
3317
3262
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3318
- "const dispatch =",
3319
- 'args.push("forged");\nconst dispatch =',
3263
+ "if (revalidationReason) {\n\t\treturn;\n\t}",
3264
+ "if (revalidationReason) {}",
3320
3265
  ),
3321
3266
  },
3322
3267
  1,
@@ -3325,8 +3270,8 @@ async function monitorGjcTeam(): Promise<void> {
3325
3270
  await runSelfTestFixture(
3326
3271
  {
3327
3272
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3328
- "report status.",
3329
- "report injected status.",
3273
+ "await continueStalledGjcTeamWorkers();",
3274
+ "",
3330
3275
  ),
3331
3276
  },
3332
3277
  1,
@@ -3335,8 +3280,8 @@ async function monitorGjcTeam(): Promise<void> {
3335
3280
  await runSelfTestFixture(
3336
3281
  {
3337
3282
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3338
- 'return platform === "win32" || path.basename(tmuxCommand).toLowerCase() === "psmux";',
3339
- "return true;",
3283
+ "await continueStalledGjcTeamWorkers();\n\t\tawait reconcileGjcTeamStaleClaimsUnlocked(workerOrchestrationRuntime, teamName, dir, config, env, capability);",
3284
+ "await reconcileGjcTeamStaleClaimsUnlocked(workerOrchestrationRuntime, teamName, dir, config, env, capability);\n\t\tawait continueStalledGjcTeamWorkers();",
3340
3285
  ),
3341
3286
  },
3342
3287
  1,
@@ -3345,8 +3290,8 @@ async function monitorGjcTeam(): Promise<void> {
3345
3290
  await runSelfTestFixture(
3346
3291
  {
3347
3292
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3348
- 'return platform === "win32" || path.basename(tmuxCommand).toLowerCase() === "psmux";',
3349
- 'return platform === "win32" || path.basename(tmuxCommand).toLowerCase() === "psmux" || tmuxCommand === "tmux";',
3293
+ "const dispatch =",
3294
+ 'args.push("forged");\nconst dispatch =',
3350
3295
  ),
3351
3296
  },
3352
3297
  1,
@@ -3372,8 +3317,8 @@ async function monitorGjcTeam(): Promise<void> {
3372
3317
  await runSelfTestFixture(
3373
3318
  {
3374
3319
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3375
- 'GJC_TEAM_CONTINUATION_PROMPT,\n\t";",',
3376
- "`$" + "{GJC_TEAM_CONTINUATION_PROMPT}" + '`,\n\t";",',
3320
+ 'continuationPrompt,\n\t\t";",',
3321
+ "`$" + "{continuationPrompt}" + '`,\n\t\t";",',
3377
3322
  ),
3378
3323
  },
3379
3324
  1,
@@ -3382,8 +3327,8 @@ async function monitorGjcTeam(): Promise<void> {
3382
3327
  await runSelfTestFixture(
3383
3328
  {
3384
3329
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3385
- '"send-keys",\n\t"-l",\n\t"-t",\n\tworker.pane_id,\n\tGJC_TEAM_CONTINUATION_PROMPT,\n\t";",\n\t"send-keys",\n\t"-t",\n\tworker.pane_id,\n\t"Enter",',
3386
- '"send-keys", "-t", worker.pane_id, "Enter", ";", "send-keys", "-l", "-t", worker.pane_id, GJC_TEAM_CONTINUATION_PROMPT,',
3330
+ '"send-keys",\n\t\t"-l",\n\t\t"-t",\n\t\tworker.pane_id,\n\t\tcontinuationPrompt,\n\t\t";",\n\t\t"send-keys",\n\t\t"-t",\n\t\tworker.pane_id,\n\t\t"Enter",',
3331
+ '"send-keys", "-t", worker.pane_id, "Enter", ";", "send-keys", "-l", "-t", worker.pane_id, continuationPrompt,',
3387
3332
  ),
3388
3333
  },
3389
3334
  1,
@@ -3392,8 +3337,8 @@ async function monitorGjcTeam(): Promise<void> {
3392
3337
  await runSelfTestFixture(
3393
3338
  {
3394
3339
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts": canonicalTeamRuntimeSendKeysFixture.replace(
3395
- "Bun.spawnSync([config.tmux_command, ...args]",
3396
- 'Bun.spawnSync([config.tmux_command, "run-shell", ...args]',
3340
+ ": (() => {",
3341
+ ": Bun.spawnSync([config.tmux_command, ...args])",
3397
3342
  ),
3398
3343
  },
3399
3344
  1,