@triflux/remote 10.50.1 → 10.51.0

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 (120) hide show
  1. package/cto/brief.mjs +0 -12
  2. package/cto/collect.mjs +16 -247
  3. package/cto/current.schema.json +4 -20
  4. package/cto/events.mjs +13 -294
  5. package/cto/hygiene.mjs +26 -346
  6. package/cto/lake-root.mjs +114 -16
  7. package/cto/status.mjs +33 -128
  8. package/hub/index.mjs +0 -8
  9. package/hub/store-adapter.mjs +1 -1
  10. package/hub/team/agent-map.json +0 -1
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/cli/commands/attach.mjs +1 -17
  14. package/hub/team/cli/commands/control.mjs +12 -38
  15. package/hub/team/cli/commands/debug.mjs +1 -37
  16. package/hub/team/cli/commands/focus.mjs +5 -31
  17. package/hub/team/cli/commands/interrupt.mjs +6 -25
  18. package/hub/team/cli/commands/kill.mjs +1 -30
  19. package/hub/team/cli/commands/list.mjs +0 -16
  20. package/hub/team/cli/commands/send.mjs +6 -26
  21. package/hub/team/cli/commands/start/index.mjs +41 -133
  22. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  23. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  24. package/hub/team/cli/commands/start/start-mux.mjs +0 -3
  25. package/hub/team/cli/commands/status.mjs +2 -57
  26. package/hub/team/cli/commands/stop.mjs +14 -19
  27. package/hub/team/cli/help.mjs +4 -5
  28. package/hub/team/cli/manifest.mjs +5 -1
  29. package/hub/team/cli/render.mjs +0 -12
  30. package/hub/team/cli/services/hub-client.mjs +2 -88
  31. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  32. package/hub/team/handoff.mjs +3 -34
  33. package/hub/team/headless.mjs +14 -755
  34. package/hub/team/orchestrator.mjs +4 -30
  35. package/hub/team/pane.mjs +6 -28
  36. package/hub/team/psmux.mjs +16 -403
  37. package/hub/team/remote-session.mjs +1 -2
  38. package/hub/team/session.mjs +8 -119
  39. package/hub/team/swarm-intent.mjs +0 -198
  40. package/hub/team/terminal-opener.mjs +11 -35
  41. package/hub/team/tui-core.mjs +1 -1
  42. package/hub/team/tui-viewer.mjs +2 -2
  43. package/hub/team/tui.mjs +8 -269
  44. package/hub/team/wt-manager.mjs +2 -2
  45. package/hub/workers/factory.mjs +4 -37
  46. package/hub/workers/lib/jsonrpc-ws-uds.mjs +0 -2
  47. package/package.json +1 -1
  48. package/scripts/lib/agent-route-policy.mjs +162 -14
  49. package/scripts/lib/claudemd-scanner.mjs +2 -199
  50. package/scripts/lib/cli-claude.mjs +0 -2
  51. package/scripts/lib/context.mjs +3 -19
  52. package/scripts/lib/env-probe.mjs +5 -64
  53. package/scripts/lib/gemini-profiles.mjs +2 -69
  54. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  55. package/scripts/lib/machine-profile.mjs +3 -17
  56. package/scripts/lib/mcp-filter.mjs +109 -53
  57. package/scripts/lib/mcp-health.mjs +2 -2
  58. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  59. package/scripts/lib/psmux-info.mjs +25 -104
  60. package/scripts/lib/skill-template.mjs +0 -271
  61. package/cto/hygiene-actions.mjs +0 -355
  62. package/cto/hygiene-notify.mjs +0 -170
  63. package/hub/team/build-worker-prompt.mjs +0 -92
  64. package/hub/team/check-mcp-hub.mjs +0 -49
  65. package/hub/team/claude-native-bridge.mjs +0 -987
  66. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  67. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  68. package/hub/team/cli/services/native-control.mjs +0 -145
  69. package/hub/team/codex-review.mjs +0 -520
  70. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  71. package/hub/team/conductor.mjs +0 -1529
  72. package/hub/team/consensus-meta.mjs +0 -176
  73. package/hub/team/cto-pull.mjs +0 -31
  74. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  75. package/hub/team/dashboard.mjs +0 -322
  76. package/hub/team/event-log.mjs +0 -84
  77. package/hub/team/execution-mode.mjs +0 -324
  78. package/hub/team/extract-completion-payload.mjs +0 -110
  79. package/hub/team/headless-bridge-session.mjs +0 -17
  80. package/hub/team/health-probe.mjs +0 -435
  81. package/hub/team/interactive-native-launcher.mjs +0 -119
  82. package/hub/team/interactive-native-worker.mjs +0 -80
  83. package/hub/team/interactive-tui-transport.mjs +0 -479
  84. package/hub/team/launcher-template.mjs +0 -144
  85. package/hub/team/mcp-selector.mjs +0 -145
  86. package/hub/team/native-supervisor.mjs +0 -460
  87. package/hub/team/native.mjs +0 -727
  88. package/hub/team/recovery-store.mjs +0 -149
  89. package/hub/team/remote-probe.mjs +0 -292
  90. package/hub/team/remote-watcher.mjs +0 -506
  91. package/hub/team/runtime-strategy.mjs +0 -200
  92. package/hub/team/schemas/worker-completion.json +0 -39
  93. package/hub/team/sentinel-capture.mjs +0 -127
  94. package/hub/team/swarm-cli.mjs +0 -390
  95. package/hub/team/swarm-hypervisor.mjs +0 -2206
  96. package/hub/team/swarm-locks.mjs +0 -335
  97. package/hub/team/swarm-planner.mjs +0 -371
  98. package/hub/team/swarm-preflight.mjs +0 -366
  99. package/hub/team/swarm-reconciler.mjs +0 -242
  100. package/hub/team/tui-remote-adapter.mjs +0 -377
  101. package/hub/team/worker-completion-validator.mjs +0 -79
  102. package/hub/team/worktree-lifecycle.mjs +0 -639
  103. package/hub/team/wt-templates.json +0 -43
  104. package/hub/workers/antigravity-route-worker.mjs +0 -179
  105. package/hub/workers/gemini-worker.mjs +0 -217
  106. package/scripts/lib/agent-json.mjs +0 -27
  107. package/scripts/lib/async.mjs +0 -174
  108. package/scripts/lib/cli-agy.mjs +0 -85
  109. package/scripts/lib/cli-codex.mjs +0 -62
  110. package/scripts/lib/cli-gemini.mjs +0 -37
  111. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  112. package/scripts/lib/env.mjs +0 -14
  113. package/scripts/lib/hub.mjs +0 -48
  114. package/scripts/lib/pid.mjs +0 -63
  115. package/scripts/lib/quota.mjs +0 -47
  116. package/scripts/lib/skill-state.mjs +0 -220
  117. package/scripts/lib/team.mjs +0 -75
  118. package/scripts/lib/timeout.mjs +0 -66
  119. package/scripts/lib/tmp.mjs +0 -26
  120. package/scripts/lib/toml.mjs +0 -132
@@ -1,520 +0,0 @@
1
- // Codex headless review helper. Runs a cross-model review of a git diff
2
- // through node `spawn` rather than the Bash tool, because `tfx` is the
3
- // sanctioned entry point for Codex.
4
- //
5
- // Contract:
6
- // runCodexReview({ ref = "HEAD", base?: string, timeoutMs = 180_000 })
7
- // → { ok, code?, verdict, stdout, stderr, diffBytes, error? }
8
- // runCodexReviewSharded({ ref, base, timeoutMs })
9
- // → { ok, verdict, range, files, perFile: [{ file, verdict, ... }] }
10
- // Verdict parsed from Codex output: APPROVED / REQUEST_CHANGES / COMMENT
11
- // / UNKNOWN when none is stated.
12
- //
13
- // Motivation: session 12 post-mortem (BUG-J fix merged without cross-review).
14
- // Without an ergonomic `tfx review` path, every swarm bugfix skips the
15
- // Codex independent opinion — the exact check that caught BUG-I's path-only
16
- // filter bypass in round 1 (session 11 PR #134). Session 13 adds --shard
17
- // per-file for diffs that exceed the 32KB single-spawn ceiling.
18
-
19
- import { execFileSync, spawn } from "node:child_process";
20
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
21
- import { tmpdir } from "node:os";
22
- import { join } from "node:path";
23
-
24
- // OS arg-list ceiling. The whole chain (bash → codex.cmd → node) fails
25
- // with ENAMETOOLONG / "Argument list too long" well below the documented
26
- // 128KB on Windows. Observed breakage at 62KB diff (session 12 self-dogfood).
27
- // 32KB is a safe ceiling that covers most single-commit swarm fixes.
28
- // Larger ranges should be reviewed per-file with --shard per-file or
29
- // narrowed with --base.
30
- export const MAX_PROMPT_BYTES = 32_000;
31
-
32
- /**
33
- * Expand a {ref, base} pair into a git range string.
34
- * - `base` set → `base..ref`
35
- * - `ref` already contains `..` → pass through
36
- * - otherwise → `ref~1..ref` (single-commit review)
37
- *
38
- * Shared by single-mode `runCodexReview` and shard-mode
39
- * `runCodexReviewSharded` so both interpret identical inputs identically.
40
- *
41
- * @param {{ ref?: string, base?: string }} opts
42
- * @returns {string}
43
- */
44
- export function expandRange({ ref = "HEAD", base } = {}) {
45
- if (base) return `${base}..${ref}`;
46
- if (ref.includes("..")) return ref;
47
- return `${ref}~1..${ref}`;
48
- }
49
-
50
- /**
51
- * Resolve the diff payload for a given ref.
52
- * - `a..b` ranges pass through unchanged
53
- * - `<commit>` expands to `<commit>~1..<commit>` (single-commit review)
54
- * - `HEAD` with no prior commit returns an empty string
55
- * - optional `file` scopes the diff to a single path via `-- <file>`
56
- *
57
- * @param {{ ref?: string, base?: string, file?: string, runner?: (cmd: string, args: string[]) => string }} opts
58
- * @returns {{ diff: string, range: string, file?: string }}
59
- */
60
- export function resolveReviewDiff({
61
- ref = "HEAD",
62
- base,
63
- file,
64
- runner = _defaultGitRunner,
65
- } = {}) {
66
- const range = expandRange({ ref, base });
67
-
68
- const args = ["log", "-p", "--stat", "--no-color", range];
69
- if (file) {
70
- args.push("--", file);
71
- }
72
- const diff = runner("git", args);
73
- return { diff, range, file };
74
- }
75
-
76
- /**
77
- * Parse the raw `git log --name-only --pretty=format:` output into a
78
- * de-duplicated, non-blank file path array. Pure function — extracted
79
- * so shard enumeration can be tested against canned log output without
80
- * requiring a temp git repo fixture.
81
- *
82
- * @param {string|null|undefined} rawLogOutput
83
- * @returns {string[]}
84
- */
85
- export function parseChangedFilesFromLog(rawLogOutput) {
86
- const seen = new Set();
87
- for (const line of (rawLogOutput || "").split(/\r?\n/)) {
88
- const trimmed = line.trim();
89
- if (trimmed) seen.add(trimmed);
90
- }
91
- return [...seen];
92
- }
93
-
94
- /**
95
- * List files changed in a range via `git log --name-only`, matching
96
- * the commit-log semantics used by `resolveReviewDiff`. Using
97
- * `git diff --name-only` would report only the net tree diff and
98
- * silently drop files changed then reverted within a multi-commit
99
- * range — those files would then be invisible to shard-mode review
100
- * even though `git log -p` still contains their edits.
101
- *
102
- * Merge commits are included so enumeration stays aligned with
103
- * `resolveReviewDiff`'s plain `git log -p`. Merges rarely introduce
104
- * novel content but also rarely add noise since the dedupe Set
105
- * collapses paths that already appear in parent commits.
106
- *
107
- * @param {string} range — e.g. "HEAD~1..HEAD" or "main..feature"
108
- * @param {(cmd: string, args: string[]) => string} [runner] — injectable
109
- * process runner. Default shells out to git; tests can pass a stub to
110
- * verify argv contract (e.g. absence of `--no-merges`) without spawning.
111
- * @returns {string[]}
112
- */
113
- export function listChangedFiles(range, runner = _defaultGitRunner) {
114
- const out = runner("git", ["log", "--name-only", "--pretty=format:", range]);
115
- return parseChangedFilesFromLog(out);
116
- }
117
-
118
- function _defaultGitRunner(cmd, args) {
119
- return execFileSync(cmd, args, {
120
- encoding: "utf8",
121
- windowsHide: true,
122
- maxBuffer: 50 * 1024 * 1024,
123
- });
124
- }
125
-
126
- /**
127
- * Build the Codex review prompt.
128
- * @param {string} diff
129
- * @param {{ range: string }} ctx
130
- */
131
- export function buildReviewPrompt(diff, { range }) {
132
- return [
133
- `You are acting as an independent cross-model reviewer.`,
134
- `Review the following git log range: ${range}.`,
135
- ``,
136
- `Scope: correctness, regression risk, security, test coverage, code style.`,
137
- ``,
138
- `Classify each finding as:`,
139
- `- HIGH: blocking — logic bug, security hole, regression`,
140
- `- MEDIUM: should-fix — missing test, readability, maintainability`,
141
- `- LOW: nit — style, micro-optimization`,
142
- ``,
143
- `Format each finding as: [SEVERITY] file:line — description. Suggested fix: ...`,
144
- `If a category has no findings, write 'none'.`,
145
- ``,
146
- `End with exactly one line stating the verdict:`,
147
- `VERDICT: APPROVED (no HIGH findings)`,
148
- `VERDICT: REQUEST_CHANGES (any HIGH finding)`,
149
- `VERDICT: COMMENT (observations only, no blockers)`,
150
- ``,
151
- "```diff",
152
- diff,
153
- "```",
154
- ].join("\n");
155
- }
156
-
157
- /**
158
- * Parse the verdict marker from Codex stdout.
159
- * @param {string} stdout
160
- */
161
- export function parseVerdict(stdout) {
162
- const match = stdout.match(
163
- /VERDICT:\s*(APPROVED|REQUEST_CHANGES|COMMENT)\b/i,
164
- );
165
- if (match) return match[1].toUpperCase();
166
- if (/REQUEST_CHANGES/i.test(stdout)) return "REQUEST_CHANGES";
167
- if (/\bAPPROVED\b/i.test(stdout)) return "APPROVED";
168
- return "UNKNOWN";
169
- }
170
-
171
- /**
172
- * Aggregate per-file verdicts into an overall verdict.
173
- * - Any REQUEST_CHANGES → REQUEST_CHANGES
174
- * - Any COMMENT (no REQUEST_CHANGES) → COMMENT
175
- * - All APPROVED (skipped excluded) → APPROVED
176
- * - Mixed with UNKNOWN → UNKNOWN (conservative)
177
- *
178
- * @param {Array<{verdict: string|null, skipped?: boolean}>} fileResults
179
- * @returns {string}
180
- */
181
- export function aggregateVerdicts(fileResults) {
182
- const active = fileResults.filter((r) => !r.skipped);
183
- if (active.length === 0) return "UNKNOWN";
184
- const verdicts = active.map((r) => r.verdict);
185
- if (verdicts.some((v) => v === "REQUEST_CHANGES")) return "REQUEST_CHANGES";
186
- if (verdicts.some((v) => v === "COMMENT")) return "COMMENT";
187
- if (verdicts.every((v) => v === "APPROVED")) return "APPROVED";
188
- return "UNKNOWN";
189
- }
190
-
191
- /**
192
- * Internal: run Codex on a pre-built prompt and parse the response.
193
- * Shared by single-mode runCodexReview and shard-mode runCodexReviewSharded.
194
- */
195
- async function _runCodexOnPrompt({
196
- prompt,
197
- range,
198
- diffBytes,
199
- timeoutMs,
200
- sandbox,
201
- env,
202
- }) {
203
- // Prompt is a full git diff — often >10KB, regularly >60KB on swarm
204
- // bugfixes. Windows cmd.exe caps command lines at ~8KB and node's
205
- // `spawn(..., [promptArg])` route cannot exceed the OS arg limit
206
- // (ENAMETOOLONG on Windows). Persist the prompt to a tmp file and let
207
- // bash read it back — same pattern session 11 used manually
208
- // (`codex exec "$(cat prompt.md)" ...`).
209
- const tmpDir = mkdtempSync(join(tmpdir(), "tfx-review-"));
210
- const promptFile = join(tmpDir, "prompt.md").replace(/\\/g, "/");
211
- writeFileSync(promptFile, prompt);
212
-
213
- const shellCmd = [
214
- "codex",
215
- "exec",
216
- "-s",
217
- sandbox,
218
- "--dangerously-bypass-approvals-and-sandbox",
219
- `"$(cat '${promptFile.replace(/'/g, "'\\''")}')"`,
220
- ].join(" ");
221
-
222
- return new Promise((resolve) => {
223
- const child = spawn("bash", ["-c", shellCmd], {
224
- stdio: ["ignore", "pipe", "pipe"],
225
- windowsHide: true,
226
- env,
227
- });
228
-
229
- let stdout = "";
230
- let stderr = "";
231
- let timedOut = false;
232
-
233
- const timer = setTimeout(() => {
234
- timedOut = true;
235
- child.kill("SIGTERM");
236
- }, timeoutMs);
237
-
238
- child.stdout.on("data", (d) => {
239
- stdout += d.toString();
240
- });
241
- child.stderr.on("data", (d) => {
242
- stderr += d.toString();
243
- });
244
- const cleanupTmp = () => {
245
- try {
246
- rmSync(tmpDir, { recursive: true, force: true });
247
- } catch {
248
- /* best-effort */
249
- }
250
- };
251
-
252
- child.on("error", (err) => {
253
- clearTimeout(timer);
254
- cleanupTmp();
255
- resolve({
256
- ok: false,
257
- error: err.message,
258
- verdict: null,
259
- stdout,
260
- stderr,
261
- diffBytes,
262
- range,
263
- });
264
- });
265
- child.on("close", (code) => {
266
- clearTimeout(timer);
267
- cleanupTmp();
268
- if (timedOut) {
269
- resolve({
270
- ok: false,
271
- code,
272
- error: `codex review timed out after ${timeoutMs}ms`,
273
- verdict: null,
274
- stdout,
275
- stderr,
276
- diffBytes,
277
- range,
278
- });
279
- return;
280
- }
281
- resolve({
282
- ok: code === 0,
283
- code,
284
- verdict: parseVerdict(stdout),
285
- stdout,
286
- stderr,
287
- diffBytes,
288
- range,
289
- });
290
- });
291
- });
292
- }
293
-
294
- /**
295
- * Run Codex review of a git range (single-spawn mode).
296
- * @param {object} opts
297
- * @param {string} [opts.ref="HEAD"]
298
- * @param {string} [opts.base]
299
- * @param {number} [opts.timeoutMs=180000]
300
- * @param {string} [opts.sandbox="read-only"]
301
- * @param {object} [opts.env]
302
- */
303
- export async function runCodexReview({
304
- ref = "HEAD",
305
- base,
306
- timeoutMs = 180_000,
307
- sandbox = "read-only",
308
- env = process.env,
309
- _deps = {},
310
- } = {}) {
311
- let range;
312
- let diff;
313
- try {
314
- ({ diff, range } = (_deps.resolveReviewDiff || resolveReviewDiff)({
315
- ref,
316
- base,
317
- }));
318
- } catch (err) {
319
- return {
320
- ok: false,
321
- error: `git log failed: ${err.message}`,
322
- verdict: null,
323
- stdout: "",
324
- stderr: "",
325
- diffBytes: 0,
326
- };
327
- }
328
-
329
- const diffBytes = Buffer.byteLength(diff, "utf8");
330
- if (diffBytes === 0) {
331
- return {
332
- ok: false,
333
- error: `empty diff for range ${range}`,
334
- verdict: null,
335
- stdout: "",
336
- stderr: "",
337
- diffBytes: 0,
338
- range,
339
- };
340
- }
341
-
342
- const prompt = buildReviewPrompt(diff, { range });
343
- const promptBytes = Buffer.byteLength(prompt, "utf8");
344
-
345
- if (promptBytes > MAX_PROMPT_BYTES) {
346
- return {
347
- ok: false,
348
- error:
349
- `prompt too large (${promptBytes} bytes > ${MAX_PROMPT_BYTES}). ` +
350
- `Retry with --shard per-file or narrow with --base <sha>.`,
351
- verdict: null,
352
- stdout: "",
353
- stderr: "",
354
- diffBytes,
355
- promptBytes,
356
- range,
357
- };
358
- }
359
-
360
- return (_deps.runCodexOnPrompt || _runCodexOnPrompt)({
361
- prompt,
362
- range,
363
- diffBytes,
364
- timeoutMs,
365
- sandbox,
366
- env,
367
- });
368
- }
369
-
370
- /**
371
- * Run Codex review sharded per-file. Each changed file in the range gets
372
- * its own Codex spawn with its own 32KB budget. Results are aggregated.
373
- *
374
- * Sequential, not parallel — Codex headless startup is ~30-90s per call
375
- * and parallel spawns risk auth contention. Wall-clock scales linearly
376
- * with file count, budget accordingly (default timeout applies per-file).
377
- *
378
- * @param {object} opts — same shape as runCodexReview
379
- * @returns {Promise<{ok: boolean, verdict: string, range: string, files: string[], perFile: object[], error?: string}>}
380
- */
381
- export async function runCodexReviewSharded({
382
- ref = "HEAD",
383
- base,
384
- timeoutMs = 180_000,
385
- sandbox = "read-only",
386
- env = process.env,
387
- onFileStart,
388
- onFileDone,
389
- } = {}) {
390
- const range = expandRange({ ref, base });
391
-
392
- let files;
393
- try {
394
- files = listChangedFiles(range);
395
- } catch (err) {
396
- return {
397
- ok: false,
398
- error: `git log --name-only failed: ${err.message}`,
399
- verdict: "UNKNOWN",
400
- range,
401
- files: [],
402
- perFile: [],
403
- };
404
- }
405
-
406
- if (files.length === 0) {
407
- return {
408
- ok: false,
409
- error: `no changed files in range ${range}`,
410
- verdict: "UNKNOWN",
411
- range,
412
- files: [],
413
- perFile: [],
414
- };
415
- }
416
-
417
- const perFile = [];
418
- for (const file of files) {
419
- if (typeof onFileStart === "function") {
420
- try {
421
- onFileStart({ file, index: perFile.length, total: files.length });
422
- } catch {
423
- /* caller callback errors should not block review */
424
- }
425
- }
426
-
427
- let fileDiff;
428
- try {
429
- ({ diff: fileDiff } = resolveReviewDiff({ ref, base, file }));
430
- } catch (err) {
431
- const entry = {
432
- file,
433
- ok: false,
434
- error: `git log scope failed: ${err.message}`,
435
- verdict: "UNKNOWN",
436
- diffBytes: 0,
437
- range: `${range} -- ${file}`,
438
- };
439
- perFile.push(entry);
440
- if (typeof onFileDone === "function") {
441
- try {
442
- onFileDone(entry);
443
- } catch {
444
- /* ignore */
445
- }
446
- }
447
- continue;
448
- }
449
-
450
- const diffBytes = Buffer.byteLength(fileDiff, "utf8");
451
- if (diffBytes === 0) {
452
- const entry = {
453
- file,
454
- ok: true,
455
- skipped: true,
456
- error: `empty diff for ${file} (likely rename/mode-only)`,
457
- verdict: "APPROVED",
458
- diffBytes: 0,
459
- range: `${range} -- ${file}`,
460
- };
461
- perFile.push(entry);
462
- if (typeof onFileDone === "function") {
463
- try {
464
- onFileDone(entry);
465
- } catch {
466
- /* ignore */
467
- }
468
- }
469
- continue;
470
- }
471
-
472
- const scopedRange = `${range} -- ${file}`;
473
- const prompt = buildReviewPrompt(fileDiff, { range: scopedRange });
474
- const promptBytes = Buffer.byteLength(prompt, "utf8");
475
- if (promptBytes > MAX_PROMPT_BYTES) {
476
- const entry = {
477
- file,
478
- ok: false,
479
- error:
480
- `prompt too large (${promptBytes} bytes > ${MAX_PROMPT_BYTES}) ` +
481
- `for single file. Review this file manually or split the commit.`,
482
- verdict: "UNKNOWN",
483
- diffBytes,
484
- promptBytes,
485
- range: scopedRange,
486
- };
487
- perFile.push(entry);
488
- if (typeof onFileDone === "function") {
489
- try {
490
- onFileDone(entry);
491
- } catch {
492
- /* ignore */
493
- }
494
- }
495
- continue;
496
- }
497
-
498
- const res = await _runCodexOnPrompt({
499
- prompt,
500
- range: scopedRange,
501
- diffBytes,
502
- timeoutMs,
503
- sandbox,
504
- env,
505
- });
506
- const entry = { file, ...res };
507
- perFile.push(entry);
508
- if (typeof onFileDone === "function") {
509
- try {
510
- onFileDone(entry);
511
- } catch {
512
- /* ignore */
513
- }
514
- }
515
- }
516
-
517
- const verdict = aggregateVerdicts(perFile);
518
- const ok = perFile.every((r) => r.ok);
519
- return { ok, verdict, range, files, perFile };
520
- }
@@ -1,122 +0,0 @@
1
- import { createMessage, MSG_TYPES } from "@triflux/core/mesh/mesh-protocol.mjs";
2
-
3
- const BRIDGE_AGENT_ID = "conductor";
4
-
5
- /**
6
- * Creates a bridge that converts Conductor EventEmitter events
7
- * into Mesh protocol messages and keeps the registry in sync.
8
- *
9
- * @param {object} conductor - A conductor instance (with on/off)
10
- * @param {object} registry - A mesh-registry instance
11
- * @param {object} [opts]
12
- * @param {string} [opts.bridgeAgentId="conductor"] - Agent ID for the bridge
13
- * @param {function} [opts.onMessage] - Called with each generated mesh message
14
- * @returns {object} Bridge API
15
- */
16
- export function createConductorMeshBridge(conductor, registry, opts = {}) {
17
- const { bridgeAgentId = BRIDGE_AGENT_ID, onMessage } = opts;
18
-
19
- const sessionAgentMap = new Map();
20
- let attached = false;
21
-
22
- function emit(message) {
23
- if (typeof onMessage === "function") {
24
- onMessage(message);
25
- }
26
- }
27
-
28
- function agentIdForSession(sessionId) {
29
- return `session:${sessionId}`;
30
- }
31
-
32
- /**
33
- * Handle conductor stateChange events.
34
- * - starting → register agent in registry
35
- * - terminal states → unregister agent
36
- * - all transitions → emit mesh EVENT message
37
- */
38
- function handleStateChange(event) {
39
- const { sessionId, from, to, reason } = event;
40
- const agentId = agentIdForSession(sessionId);
41
-
42
- // Register on first starting transition
43
- if (to === "starting" && !sessionAgentMap.has(sessionId)) {
44
- registry.register(agentId, ["session"]);
45
- sessionAgentMap.set(sessionId, agentId);
46
- }
47
-
48
- // Emit mesh event
49
- const msg = createMessage(MSG_TYPES.EVENT, bridgeAgentId, "*", {
50
- event: "stateChange",
51
- sessionId,
52
- from,
53
- to,
54
- reason,
55
- });
56
- emit(msg);
57
-
58
- // Unregister on terminal states
59
- if (to === "dead" || to === "completed") {
60
- registry.unregister(agentId);
61
- sessionAgentMap.delete(sessionId);
62
- }
63
- }
64
-
65
- /**
66
- * Handle conductor completed events.
67
- */
68
- function handleCompleted(event) {
69
- const msg = createMessage(MSG_TYPES.EVENT, bridgeAgentId, "*", {
70
- event: "completed",
71
- sessionId: event.sessionId,
72
- });
73
- emit(msg);
74
- }
75
-
76
- /**
77
- * Handle conductor dead events.
78
- */
79
- function handleDead(event) {
80
- const msg = createMessage(MSG_TYPES.EVENT, bridgeAgentId, "*", {
81
- event: "dead",
82
- sessionId: event.sessionId,
83
- reason: event.reason,
84
- });
85
- emit(msg);
86
- }
87
-
88
- /**
89
- * Attaches event listeners to the conductor.
90
- */
91
- function attach() {
92
- if (attached) return;
93
- conductor.on("stateChange", handleStateChange);
94
- conductor.on("completed", handleCompleted);
95
- conductor.on("dead", handleDead);
96
- attached = true;
97
- }
98
-
99
- /**
100
- * Detaches event listeners and cleans up registry entries.
101
- */
102
- function detach() {
103
- if (!attached) return;
104
- conductor.off("stateChange", handleStateChange);
105
- conductor.off("completed", handleCompleted);
106
- conductor.off("dead", handleDead);
107
-
108
- for (const [, agentId] of sessionAgentMap) {
109
- registry.unregister(agentId);
110
- }
111
- sessionAgentMap.clear();
112
- attached = false;
113
- }
114
-
115
- return {
116
- attach,
117
- detach,
118
- get isAttached() {
119
- return attached;
120
- },
121
- };
122
- }