@synkro-sh/cli 1.7.87 → 1.7.89

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.
package/dist/bootstrap.js CHANGED
@@ -147,7 +147,7 @@ function getIdentity() {
147
147
  if (cached2) return cached2;
148
148
  let cliVersion = "0.0.0";
149
149
  try {
150
- cliVersion = "1.7.87";
150
+ cliVersion = "1.7.89";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -283,6 +283,100 @@ var init_emit = __esm({
283
283
  }
284
284
  });
285
285
 
286
+ // cli/telemetry/httpSql.ts
287
+ import { readFileSync as readFileSync3 } from "fs";
288
+ import { homedir as homedir4 } from "os";
289
+ import { join as join4 } from "path";
290
+ function quoteIdentifier(value) {
291
+ if (!/^[a-z_][a-z0-9_]*$/i.test(value)) throw new Error("invalid SQL identifier");
292
+ return `"${value}"`;
293
+ }
294
+ function shiftPlaceholders(text, offset) {
295
+ return text.replace(/\$(\d+)/g, (_, raw) => `$${Number(raw) + offset}`);
296
+ }
297
+ function bulkFragment(rows, columns) {
298
+ if (!rows.length || !columns.length) throw new Error("empty SQL helper");
299
+ const params = [];
300
+ const tuples = rows.map((row) => {
301
+ const placeholders = columns.map((column) => {
302
+ let value = row[column];
303
+ let cast = "";
304
+ if (column === "context" && typeof value === "object" && value !== null) {
305
+ value = JSON.stringify(value);
306
+ cast = "::jsonb";
307
+ }
308
+ params.push(value ?? null);
309
+ return `$${params.length}${cast}`;
310
+ });
311
+ return `(${placeholders.join(", ")})`;
312
+ });
313
+ return {
314
+ [FRAGMENT]: true,
315
+ text: `(${columns.map(quoteIdentifier).join(", ")}) VALUES ${tuples.join(", ")}`,
316
+ params
317
+ };
318
+ }
319
+ function createTelemetryHttpSql(options = {}) {
320
+ const port = options.port ?? Number.parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
321
+ const endpoint = `http://127.0.0.1:${port}/api/local/telemetry/query`;
322
+ const token = options.token ?? readFileSync3(join4(homedir4(), ".synkro", ".mcp-jwt"), "utf-8").trim();
323
+ const fetchImpl = options.fetchImpl ?? fetch;
324
+ const query = async (text, params = []) => {
325
+ const response = await fetchImpl(endpoint, {
326
+ method: "POST",
327
+ headers: {
328
+ Authorization: `Bearer ${token}`,
329
+ "Content-Type": "application/json"
330
+ },
331
+ body: JSON.stringify({ text, params }),
332
+ signal: AbortSignal.timeout(3e4)
333
+ });
334
+ const payload = await response.json().catch(() => ({}));
335
+ if (!response.ok) throw new Error(payload.error || `local telemetry query failed (${response.status})`);
336
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
337
+ Object.defineProperty(rows, "count", {
338
+ value: Number(payload.count ?? rows.length),
339
+ enumerable: false,
340
+ configurable: true
341
+ });
342
+ return rows;
343
+ };
344
+ const sql = function(first, ...values) {
345
+ if (Array.isArray(first) && "raw" in first) {
346
+ const strings = first;
347
+ let text = strings[0];
348
+ const params = [];
349
+ for (let i = 0; i < values.length; i++) {
350
+ const value = values[i];
351
+ if (value && typeof value === "object" && value[FRAGMENT]) {
352
+ text += shiftPlaceholders(value.text, params.length);
353
+ params.push(...value.params);
354
+ } else {
355
+ params.push(value);
356
+ text += `$${params.length}`;
357
+ }
358
+ text += strings[i + 1];
359
+ }
360
+ return query(text, params);
361
+ }
362
+ if (Array.isArray(first)) {
363
+ return bulkFragment(first, values);
364
+ }
365
+ throw new Error("unsupported SQL invocation");
366
+ };
367
+ sql.unsafe = query;
368
+ sql.end = async () => {
369
+ };
370
+ return sql;
371
+ }
372
+ var FRAGMENT;
373
+ var init_httpSql = __esm({
374
+ "cli/telemetry/httpSql.ts"() {
375
+ "use strict";
376
+ FRAGMENT = /* @__PURE__ */ Symbol("synkro-http-sql-fragment");
377
+ }
378
+ });
379
+
286
380
  // cli/telemetry/db.ts
287
381
  var db_exports = {};
288
382
  __export(db_exports, {
@@ -290,24 +384,11 @@ __export(db_exports, {
290
384
  connectDb: () => connectDb,
291
385
  resetDbForTests: () => resetDbForTests
292
386
  });
293
- import postgres from "postgres";
294
387
  async function connectDb() {
295
388
  if (cached3) return cached3;
296
389
  if (initFailedAt && Date.now() - initFailedAt < INIT_RETRY_MS) return null;
297
390
  try {
298
- const sql = postgres({
299
- host: PGLITE_HOST,
300
- port: PGLITE_PORT,
301
- username: PGLITE_USER,
302
- database: PGLITE_DB,
303
- // max:1 mirrors synkro-server — PGlite is single-threaded; multiple
304
- // sockets just queue at the multiplexer.
305
- max: 1,
306
- connect_timeout: 2,
307
- idle_timeout: 30,
308
- onnotice: () => {
309
- }
310
- });
391
+ const sql = createTelemetryHttpSql();
311
392
  await sql`SELECT 1`;
312
393
  cached3 = sql;
313
394
  initFailedAt = 0;
@@ -317,7 +398,7 @@ async function connectDb() {
317
398
  initFailedAt = Date.now();
318
399
  if (process.env.SYNKRO_TELEMETRY_DEBUG === "1") {
319
400
  const msg = err instanceof Error ? err.message : String(err);
320
- process.stderr.write(`[synkro] telemetry pglite unreachable at ${PGLITE_HOST}:${PGLITE_PORT}: ${msg}
401
+ process.stderr.write(`[synkro] local telemetry API unreachable: ${msg}
321
402
  `);
322
403
  }
323
404
  return null;
@@ -354,17 +435,11 @@ function resetDbForTests() {
354
435
  initFailedAt = 0;
355
436
  migrated = false;
356
437
  }
357
- var PGLITE_HOST, PGLITE_PORT, PGLITE_USER, PGLITE_DB, SCHEMA_VERSION, cached3, migrated, INIT_RETRY_MS, initFailedAt, MIGRATION_V1;
438
+ var SCHEMA_VERSION, cached3, migrated, INIT_RETRY_MS, initFailedAt, MIGRATION_V1;
358
439
  var init_db = __esm({
359
440
  "cli/telemetry/db.ts"() {
360
441
  "use strict";
361
- PGLITE_HOST = process.env.SYNKRO_PGLITE_HOST || "127.0.0.1";
362
- PGLITE_PORT = parseInt(
363
- process.env.SYNKRO_PGLITE_PORT || process.env.SYNKRO_HOST_PGLITE_PORT || "15433",
364
- 10
365
- );
366
- PGLITE_USER = process.env.SYNKRO_PGLITE_USER || "postgres";
367
- PGLITE_DB = process.env.SYNKRO_PGLITE_DB || "postgres";
442
+ init_httpSql();
368
443
  SCHEMA_VERSION = 1;
369
444
  cached3 = null;
370
445
  migrated = false;
@@ -438,9 +513,9 @@ var init_db = __esm({
438
513
  });
439
514
 
440
515
  // cli/telemetry/drain.ts
441
- import { openSync, closeSync, fstatSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync4 } from "fs";
442
- import { homedir as homedir4 } from "os";
443
- import { join as join4 } from "path";
516
+ import { openSync, closeSync, fstatSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2, appendFileSync as appendFileSync2, unlinkSync, mkdirSync as mkdirSync3, renameSync as renameSync2, existsSync as existsSync4 } from "fs";
517
+ import { homedir as homedir5 } from "os";
518
+ import { join as join5 } from "path";
444
519
  function ensureDir3() {
445
520
  if (!existsSync4(SYNKRO_DIR3)) mkdirSync3(SYNKRO_DIR3, { recursive: true, mode: 448 });
446
521
  }
@@ -494,7 +569,7 @@ function parseQueueFile(path) {
494
569
  if (!existsSync4(path)) return { rows: [], malformed: 0 };
495
570
  let raw;
496
571
  try {
497
- raw = readFileSync3(path, "utf-8");
572
+ raw = readFileSync4(path, "utf-8");
498
573
  } catch {
499
574
  return { rows: [], malformed: 0 };
500
575
  }
@@ -534,7 +609,7 @@ function recoverProcessing() {
534
609
  ensureDir3();
535
610
  let body;
536
611
  try {
537
- body = readFileSync3(PROCESSING_PATH, "utf-8");
612
+ body = readFileSync4(PROCESSING_PATH, "utf-8");
538
613
  } catch {
539
614
  return;
540
615
  }
@@ -663,39 +738,41 @@ async function insertEvents(sql, rows, mirroredIds) {
663
738
  return inserted;
664
739
  }
665
740
  async function drainToPglite() {
666
- const { rows } = readQueue();
667
- if (rows.length === 0) return { ok: true, ingested: 0, pending: 0, reason: "no_pending" };
741
+ const queued = readQueue().rows;
742
+ if (queued.length === 0) return { ok: true, ingested: 0, pending: 0, reason: "no_pending" };
743
+ const rows = queued.length > MIRROR_MAX_PER_DRAIN ? queued.slice(-MIRROR_MAX_PER_DRAIN) : queued;
668
744
  const sql = await connectDb();
669
- if (!sql) return { ok: false, ingested: 0, pending: rows.length, reason: "db_unavailable" };
745
+ if (!sql) return { ok: false, ingested: 0, pending: queued.length, reason: "db_unavailable" };
670
746
  try {
671
747
  const ingested = await insertEvents(sql, rows);
672
748
  patchMetaCache({ last_drained_at: (/* @__PURE__ */ new Date()).toISOString() });
673
- return { ok: true, ingested, pending: rows.length };
749
+ return { ok: true, ingested, pending: queued.length };
674
750
  } catch (err) {
675
751
  const msg = err instanceof Error ? err.message : String(err);
676
- return { ok: false, ingested: 0, pending: rows.length, reason: "error", error: msg };
752
+ return { ok: false, ingested: 0, pending: queued.length, reason: "error", error: msg };
677
753
  }
678
754
  }
679
- var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, STALE_LOCK_MS, MAX_QUEUE_ROWS;
755
+ var SYNKRO_DIR3, QUEUE_LOCK, PROCESSING_PATH, CHUNK, MIRROR_MAX_PER_DRAIN, STALE_LOCK_MS, MAX_QUEUE_ROWS;
680
756
  var init_drain = __esm({
681
757
  "cli/telemetry/drain.ts"() {
682
758
  "use strict";
683
759
  init_db();
684
760
  init_emit();
685
761
  init_metaCache();
686
- SYNKRO_DIR3 = join4(homedir4(), ".synkro");
687
- QUEUE_LOCK = join4(SYNKRO_DIR3, ".telemetry-queue.lock");
762
+ SYNKRO_DIR3 = join5(homedir5(), ".synkro");
763
+ QUEUE_LOCK = join5(SYNKRO_DIR3, ".telemetry-queue.lock");
688
764
  PROCESSING_PATH = `${PENDING_PATH}.processing`;
689
765
  CHUNK = 500;
766
+ MIRROR_MAX_PER_DRAIN = 5e3;
690
767
  STALE_LOCK_MS = 6e4;
691
768
  MAX_QUEUE_ROWS = 5e4;
692
769
  }
693
770
  });
694
771
 
695
772
  // cli/telemetry/optout.ts
696
- import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
697
- import { homedir as homedir5 } from "os";
698
- import { join as join5 } from "path";
773
+ import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
774
+ import { homedir as homedir6 } from "os";
775
+ import { join as join6 } from "path";
699
776
  function sanitize(raw, maxLen = 256) {
700
777
  return raw.replace(/[^\x20-\x7E]/g, "").slice(0, maxLen);
701
778
  }
@@ -708,7 +785,7 @@ function writeConfigEnvFlag(key, value) {
708
785
  let content = "";
709
786
  if (existsSync5(CONFIG_PATH2)) {
710
787
  try {
711
- content = readFileSync4(CONFIG_PATH2, "utf-8");
788
+ content = readFileSync5(CONFIG_PATH2, "utf-8");
712
789
  } catch {
713
790
  content = "";
714
791
  }
@@ -778,8 +855,8 @@ var init_optout = __esm({
778
855
  "use strict";
779
856
  init_emit();
780
857
  init_metaCache();
781
- SYNKRO_DIR4 = join5(homedir5(), ".synkro");
782
- CONFIG_PATH2 = join5(SYNKRO_DIR4, "config.env");
858
+ SYNKRO_DIR4 = join6(homedir6(), ".synkro");
859
+ CONFIG_PATH2 = join6(SYNKRO_DIR4, "config.env");
783
860
  KEY_ENABLED = "SYNKRO_TELEMETRY_ENABLED";
784
861
  KEY_REMOTE = "SYNKRO_TELEMETRY_REMOTE_FLUSH";
785
862
  }
@@ -828,15 +905,15 @@ __export(flush_exports, {
828
905
  flushDetached: () => flushDetached
829
906
  });
830
907
  import { spawn } from "child_process";
831
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
832
- import { homedir as homedir6 } from "os";
833
- import { join as join6 } from "path";
908
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
909
+ import { homedir as homedir7 } from "os";
910
+ import { join as join7 } from "path";
834
911
  function readConfigEnv() {
835
912
  if (!existsSync6(CONFIG_PATH3)) return {};
836
913
  const out = {};
837
914
  let raw;
838
915
  try {
839
- raw = readFileSync5(CONFIG_PATH3, "utf-8");
916
+ raw = readFileSync6(CONFIG_PATH3, "utf-8");
840
917
  } catch {
841
918
  return {};
842
919
  }
@@ -877,14 +954,14 @@ function resolveGatewayUrl() {
877
954
  function loadJwt() {
878
955
  if ((process.env.SYNKRO_STORAGE_MODE || "") === "cloud") {
879
956
  try {
880
- const mcp = readFileSync5(join6(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
957
+ const mcp = readFileSync6(join7(SYNKRO_DIR5, ".mcp-jwt"), "utf-8").trim();
881
958
  if (mcp) return mcp;
882
959
  } catch {
883
960
  }
884
961
  }
885
962
  try {
886
963
  if (!existsSync6(CREDS_PATH2)) return null;
887
- const creds = JSON.parse(readFileSync5(CREDS_PATH2, "utf-8"));
964
+ const creds = JSON.parse(readFileSync6(CREDS_PATH2, "utf-8"));
888
965
  return creds.access_token || null;
889
966
  } catch {
890
967
  return null;
@@ -1039,20 +1116,20 @@ var init_flush = __esm({
1039
1116
  init_optout();
1040
1117
  init_metaCache();
1041
1118
  init_wire();
1042
- SYNKRO_DIR5 = join6(homedir6(), ".synkro");
1043
- CONFIG_PATH3 = join6(SYNKRO_DIR5, "config.env");
1044
- CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH || join6(SYNKRO_DIR5, "credentials.json");
1119
+ SYNKRO_DIR5 = join7(homedir7(), ".synkro");
1120
+ CONFIG_PATH3 = join7(SYNKRO_DIR5, "config.env");
1121
+ CREDS_PATH2 = process.env.SYNKRO_CREDENTIALS_PATH || join7(SYNKRO_DIR5, "credentials.json");
1045
1122
  THROTTLE_MS = 6e4;
1046
1123
  DEFAULT_GATEWAY = "https://api.synkro.sh";
1047
1124
  }
1048
1125
  });
1049
1126
 
1050
1127
  // cli/telemetry/stats.ts
1051
- import { existsSync as existsSync7, readFileSync as readFileSync6, statSync, writeFileSync as writeFileSync4 } from "fs";
1128
+ import { existsSync as existsSync7, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync4 } from "fs";
1052
1129
  function countPending() {
1053
1130
  if (!existsSync7(PENDING_PATH)) return 0;
1054
1131
  try {
1055
- const raw = readFileSync6(PENDING_PATH, "utf-8");
1132
+ const raw = readFileSync7(PENDING_PATH, "utf-8");
1056
1133
  if (!raw) return 0;
1057
1134
  let n = 0;
1058
1135
  for (const line of raw.split("\n")) if (line.trim()) n++;
@@ -1174,13 +1251,13 @@ var init_stats = __esm({
1174
1251
  });
1175
1252
 
1176
1253
  // cli/telemetry/purge.ts
1177
- import { existsSync as existsSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
1254
+ import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
1178
1255
  import { randomUUID as randomUUID3 } from "crypto";
1179
1256
  function unlinkPendingCount() {
1180
1257
  if (!existsSync8(PENDING_PATH)) return 0;
1181
1258
  let n = 0;
1182
1259
  try {
1183
- const raw = readFileSync7(PENDING_PATH, "utf-8");
1260
+ const raw = readFileSync8(PENDING_PATH, "utf-8");
1184
1261
  for (const line of raw.split("\n")) if (line.trim()) n++;
1185
1262
  } catch {
1186
1263
  }
@@ -1345,8 +1422,8 @@ var init_telemetry = __esm({
1345
1422
 
1346
1423
  // cli/installer/agentDetect.ts
1347
1424
  import { existsSync as existsSync9 } from "fs";
1348
- import { homedir as homedir7 } from "os";
1349
- import { join as join7 } from "path";
1425
+ import { homedir as homedir8 } from "os";
1426
+ import { join as join8 } from "path";
1350
1427
  import { execFileSync as execFileSync2, execSync } from "child_process";
1351
1428
  function which(cmd3) {
1352
1429
  try {
@@ -1367,21 +1444,21 @@ function getVersion(cmd3) {
1367
1444
  }
1368
1445
  function detectAgents() {
1369
1446
  const agents = [];
1370
- const home = homedir7();
1447
+ const home = homedir8();
1371
1448
  const claudeBinary = which("claude");
1372
- const claudeConfigDir = join7(home, ".claude");
1449
+ const claudeConfigDir = join8(home, ".claude");
1373
1450
  if (claudeBinary) {
1374
1451
  agents.push({
1375
1452
  kind: "claude_code",
1376
1453
  name: "Claude Code",
1377
1454
  binaryPath: claudeBinary,
1378
1455
  configDir: claudeConfigDir,
1379
- settingsPath: join7(claudeConfigDir, "settings.json"),
1456
+ settingsPath: join8(claudeConfigDir, "settings.json"),
1380
1457
  version: getVersion("claude")
1381
1458
  });
1382
1459
  }
1383
1460
  const cursorBinary = which("cursor");
1384
- const cursorConfigDir = join7(home, ".cursor");
1461
+ const cursorConfigDir = join8(home, ".cursor");
1385
1462
  const cursorApp = process.platform === "darwin" && existsSync9("/Applications/Cursor.app");
1386
1463
  if (cursorBinary || cursorApp || existsSync9(cursorConfigDir)) {
1387
1464
  let version;
@@ -1399,19 +1476,19 @@ function detectAgents() {
1399
1476
  name: "Cursor",
1400
1477
  binaryPath: cursorBinary,
1401
1478
  configDir: cursorConfigDir,
1402
- settingsPath: join7(cursorConfigDir, "hooks.json"),
1479
+ settingsPath: join8(cursorConfigDir, "hooks.json"),
1403
1480
  version
1404
1481
  });
1405
1482
  }
1406
1483
  const codexBinary = which("codex");
1407
- const codexConfigDir = process.env.CODEX_HOME || join7(home, ".codex");
1484
+ const codexConfigDir = process.env.CODEX_HOME || join8(home, ".codex");
1408
1485
  if (codexBinary || existsSync9(codexConfigDir)) {
1409
1486
  agents.push({
1410
1487
  kind: "codex",
1411
1488
  name: "Codex",
1412
1489
  binaryPath: codexBinary,
1413
1490
  configDir: codexConfigDir,
1414
- settingsPath: join7(codexConfigDir, "hooks.json"),
1491
+ settingsPath: join8(codexConfigDir, "hooks.json"),
1415
1492
  version: codexBinary ? getVersion("codex") : void 0
1416
1493
  });
1417
1494
  }
@@ -1425,16 +1502,16 @@ var init_agentDetect = __esm({
1425
1502
 
1426
1503
  // cli/installer/platform.ts
1427
1504
  import { existsSync as existsSync10 } from "fs";
1428
- import { homedir as homedir8 } from "os";
1429
- import { join as join8 } from "path";
1505
+ import { homedir as homedir9 } from "os";
1506
+ import { join as join9 } from "path";
1430
1507
  import { spawnSync } from "child_process";
1431
1508
  function resolveBunBin() {
1432
1509
  const finder = IS_WINDOWS ? "where" : "which";
1433
1510
  const r = spawnSync(finder, ["bun"], { encoding: "utf-8", timeout: 5e3 });
1434
1511
  const resolved = (r.stdout || "").split(/\r?\n/).map((s) => s.trim()).find(Boolean);
1435
1512
  if (resolved) return resolved;
1436
- const home = process.env.USERPROFILE || homedir8();
1437
- const candidates = IS_WINDOWS ? [join8(home, ".bun", "bin", "bun.exe"), "C:\\Program Files\\bun\\bin\\bun.exe"] : ["/opt/homebrew/bin/bun", "/usr/local/bin/bun", join8(home, ".bun", "bin", "bun")];
1513
+ const home = process.env.USERPROFILE || homedir9();
1514
+ const candidates = IS_WINDOWS ? [join9(home, ".bun", "bin", "bun.exe"), "C:\\Program Files\\bun\\bin\\bun.exe"] : ["/opt/homebrew/bin/bun", "/usr/local/bin/bun", join9(home, ".bun", "bin", "bun")];
1438
1515
  for (const p of candidates) if (existsSync10(p)) return p;
1439
1516
  return IS_WINDOWS ? "bun.exe" : "bun";
1440
1517
  }
@@ -1458,12 +1535,12 @@ var init_platform = __esm({
1458
1535
  });
1459
1536
 
1460
1537
  // cli/installer/ccHookConfig.ts
1461
- import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync6, renameSync as renameSync3, mkdirSync as mkdirSync4 } from "fs";
1538
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync6, renameSync as renameSync3, mkdirSync as mkdirSync4 } from "fs";
1462
1539
  import { dirname } from "path";
1463
1540
  function readSettings(path) {
1464
1541
  if (!existsSync11(path)) return {};
1465
1542
  try {
1466
- const raw = readFileSync8(path, "utf-8");
1543
+ const raw = readFileSync9(path, "utf-8");
1467
1544
  return JSON.parse(raw);
1468
1545
  } catch (err) {
1469
1546
  throw new Error(`Failed to parse ${path}: ${err.message}`);
@@ -1609,7 +1686,7 @@ function installCCHooks(settingsPath, config) {
1609
1686
  }
1610
1687
  if (config.taskActivateIntentScriptPath) {
1611
1688
  settings.hooks.PreToolUse.push({
1612
- matcher: "mcp__synkro-guardrails__activate_standard",
1689
+ matcher: "mcp__synkro[-_]guardrails__activate_standard",
1613
1690
  hooks: [
1614
1691
  {
1615
1692
  type: "command",
@@ -1723,9 +1800,9 @@ var init_ccHookConfig = __esm({
1723
1800
  });
1724
1801
 
1725
1802
  // cli/installer/cursorHookConfig.ts
1726
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync5 } from "fs";
1803
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, renameSync as renameSync4, mkdirSync as mkdirSync5 } from "fs";
1727
1804
  import { dirname as dirname2, resolve, normalize } from "path";
1728
- import { homedir as homedir9 } from "os";
1805
+ import { homedir as homedir10 } from "os";
1729
1806
  function shellQuote(s) {
1730
1807
  return "'" + s.replace(/'/g, "'\\''") + "'";
1731
1808
  }
@@ -1745,7 +1822,7 @@ function validateHooksPath(path) {
1745
1822
  function readHooksFile(rawPath) {
1746
1823
  const safePath = validateHooksPath(rawPath);
1747
1824
  try {
1748
- const raw = readFileSync9(safePath, "utf-8");
1825
+ const raw = readFileSync10(safePath, "utf-8");
1749
1826
  return JSON.parse(raw);
1750
1827
  } catch (err) {
1751
1828
  if (err?.code === "ENOENT") return { version: 1, hooks: {} };
@@ -1927,8 +2004,8 @@ var init_cursorHookConfig = __esm({
1927
2004
  init_platform();
1928
2005
  SYNKRO_MARKER2 = "__synkro_managed__";
1929
2006
  ALLOWED_PARENT_DIRS = [
1930
- resolve(homedir9(), ".cursor"),
1931
- resolve(homedir9(), ".config", "cursor")
2007
+ resolve(homedir10(), ".cursor"),
2008
+ resolve(homedir10(), ".config", "cursor")
1932
2009
  ];
1933
2010
  ALL_EVENTS = [
1934
2011
  "sessionStart",
@@ -1948,9 +2025,9 @@ var init_cursorHookConfig = __esm({
1948
2025
  });
1949
2026
 
1950
2027
  // cli/installer/codexHookConfig.ts
1951
- import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync6 } from "fs";
2028
+ import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync6 } from "fs";
1952
2029
  import { dirname as dirname3, resolve as resolve2, normalize as normalize2 } from "path";
1953
- import { homedir as homedir10 } from "os";
2030
+ import { homedir as homedir11 } from "os";
1954
2031
  function shellQuote2(s) {
1955
2032
  return "'" + s.replace(/'/g, "'\\''") + "'";
1956
2033
  }
@@ -1967,8 +2044,8 @@ function codexCmd(scriptPath) {
1967
2044
  function validateHooksPath2(path) {
1968
2045
  const resolved = resolve2(normalize2(path));
1969
2046
  const allowedParentDirs = [
1970
- resolve2(homedir10(), ".codex"),
1971
- resolve2(homedir10(), ".config", "codex"),
2047
+ resolve2(homedir11(), ".codex"),
2048
+ resolve2(homedir11(), ".config", "codex"),
1972
2049
  ...process.env.CODEX_HOME ? [resolve2(process.env.CODEX_HOME)] : []
1973
2050
  ];
1974
2051
  if (!allowedParentDirs.some((dir) => resolved.startsWith(dir + "/") || resolved === dir)) {
@@ -1980,7 +2057,7 @@ function readHooksFile2(rawPath) {
1980
2057
  const safePath = validateHooksPath2(rawPath);
1981
2058
  if (!existsSync12(safePath)) return { hooks: {} };
1982
2059
  try {
1983
- return JSON.parse(readFileSync10(safePath, "utf-8"));
2060
+ return JSON.parse(readFileSync11(safePath, "utf-8"));
1984
2061
  } catch (err) {
1985
2062
  throw new Error(`Failed to parse ${safePath}: ${err.message}`);
1986
2063
  }
@@ -2026,13 +2103,19 @@ function installCodexHooks(hooksJsonPath, config) {
2026
2103
  cmd(config.cvePrecheckScriptPath, 15),
2027
2104
  ...config.skillJudgeScriptPath ? [cmd(config.skillJudgeScriptPath, 50)] : []
2028
2105
  ], M_EDIT);
2106
+ push(h, "PostToolUse", [
2107
+ cmd(config.editFollowupScriptPath, 30, "Recording edit")
2108
+ ], M_EDIT);
2029
2109
  push(h, "PreToolUse", [cmd(config.agentJudgeScriptPath, 50)], M_AGENT);
2030
2110
  if (config.mcpGateScriptPath) push(h, "PreToolUse", [cmd(config.mcpGateScriptPath, 50)], M_MCP);
2031
2111
  if (config.taskActivateIntentScriptPath) push(h, "PreToolUse", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
2112
+ if (config.taskActivateIntentScriptPath) push(h, "PermissionRequest", [cmd(config.taskActivateIntentScriptPath, 5)], M_ACTIVATE);
2032
2113
  push(h, "PostToolUse", [cmd(config.bashFollowupScriptPath, 10)], M_BASH);
2033
2114
  push(h, "UserPromptSubmit", [cmd(config.userPromptSubmitScriptPath, 5)]);
2034
2115
  if (config.promptRouteScriptPath) push(h, "UserPromptSubmit", [cmd(config.promptRouteScriptPath, 5)]);
2035
2116
  push(h, "SessionStart", [cmd(config.sessionStartScriptPath, 5)]);
2117
+ push(h, "SubagentStart", [cmd(config.subagentStartScriptPath, 5)]);
2118
+ if (!config.skipTranscriptSync) push(h, "SubagentStop", [cmd(config.subagentStopScriptPath, 5)]);
2036
2119
  push(h, "SessionEnd", [cmd(config.stopSummaryScriptPath, 3)]);
2037
2120
  if (!config.skipTranscriptSync) push(h, "Stop", [cmd(config.transcriptSyncScriptPath, 3)]);
2038
2121
  writeHooksFileAtomic2(hooksJsonPath, file);
@@ -2054,30 +2137,31 @@ function uninstallCodexHooks(hooksJsonPath) {
2054
2137
  writeHooksFileAtomic2(hooksJsonPath, file);
2055
2138
  return true;
2056
2139
  }
2057
- var SYNKRO_MARKER3, M_BASH, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
2140
+ var SYNKRO_MARKER3, M_BASH, CODEX_EDIT_MATCHER, M_EDIT, M_AGENT, M_MCP, M_ACTIVATE, ALL_EVENTS2;
2058
2141
  var init_codexHookConfig = __esm({
2059
2142
  "cli/installer/codexHookConfig.ts"() {
2060
2143
  "use strict";
2061
2144
  init_platform();
2062
2145
  SYNKRO_MARKER3 = "__synkro_managed__";
2063
2146
  M_BASH = "Bash";
2064
- M_EDIT = "apply_patch|Edit|Write";
2147
+ CODEX_EDIT_MATCHER = "^(?:apply_patch|ApplyPatch|Edit|Write|functions[._]apply_patch)$";
2148
+ M_EDIT = CODEX_EDIT_MATCHER;
2065
2149
  M_AGENT = "Agent";
2066
2150
  M_MCP = "mcp__.*";
2067
- M_ACTIVATE = "mcp__synkro-guardrails__activate_standard";
2068
- ALL_EVENTS2 = ["PreToolUse", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "Stop"];
2151
+ M_ACTIVATE = "mcp__synkro[-_]guardrails__activate_standard";
2152
+ ALL_EVENTS2 = ["PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "SessionStart", "SessionEnd", "SubagentStart", "SubagentStop", "Stop"];
2069
2153
  }
2070
2154
  });
2071
2155
 
2072
2156
  // cli/installer/mcpConfig.ts
2073
- import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
2074
- import { homedir as homedir11 } from "os";
2075
- import { dirname as dirname4, join as join9 } from "path";
2157
+ import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync9, renameSync as renameSync6, mkdirSync as mkdirSync7 } from "fs";
2158
+ import { homedir as homedir12 } from "os";
2159
+ import { dirname as dirname4, join as join10 } from "path";
2076
2160
  import { randomBytes } from "crypto";
2077
2161
  function readClaudeJson() {
2078
2162
  if (!existsSync13(CC_CONFIG_PATH)) return {};
2079
2163
  try {
2080
- const raw = readFileSync11(CC_CONFIG_PATH, "utf-8");
2164
+ const raw = readFileSync12(CC_CONFIG_PATH, "utf-8");
2081
2165
  return JSON.parse(raw);
2082
2166
  } catch (err) {
2083
2167
  throw new Error(`Failed to parse ${CC_CONFIG_PATH}: ${err.message}`);
@@ -2096,7 +2180,7 @@ function installMcpConfig(opts) {
2096
2180
  if (entry?.[SYNKRO_MARKER4] === true) delete config.mcpServers[name];
2097
2181
  }
2098
2182
  if (opts.local) {
2099
- const proxyScript = join9(homedir11(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2183
+ const proxyScript = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2100
2184
  config.mcpServers[SYNKRO_SERVER_NAME] = {
2101
2185
  type: "stdio",
2102
2186
  command: "bun",
@@ -2135,7 +2219,7 @@ function uninstallMcpConfig() {
2135
2219
  function readCursorMcpJson() {
2136
2220
  if (!existsSync13(CURSOR_MCP_PATH)) return {};
2137
2221
  try {
2138
- const raw = readFileSync11(CURSOR_MCP_PATH, "utf-8");
2222
+ const raw = readFileSync12(CURSOR_MCP_PATH, "utf-8");
2139
2223
  return JSON.parse(raw);
2140
2224
  } catch (err) {
2141
2225
  throw new Error(`Failed to parse ${CURSOR_MCP_PATH}: ${err.message}`);
@@ -2156,10 +2240,10 @@ function installCursorMcpConfig(opts) {
2156
2240
  if (opts.local) {
2157
2241
  const port = process.env.SYNKRO_MCP_PORT || "18931";
2158
2242
  const url2 = `http://127.0.0.1:${port}/`;
2159
- const jwtPath = join9(homedir11(), ".synkro", ".mcp-jwt");
2243
+ const jwtPath = join10(homedir12(), ".synkro", ".mcp-jwt");
2160
2244
  let jwt2 = "";
2161
2245
  try {
2162
- jwt2 = readFileSync11(jwtPath, "utf-8").trim();
2246
+ jwt2 = readFileSync12(jwtPath, "utf-8").trim();
2163
2247
  } catch {
2164
2248
  }
2165
2249
  config.mcpServers[SYNKRO_SERVER_NAME] = {
@@ -2201,7 +2285,7 @@ function codexConfigPath(path) {
2201
2285
  function readCodexToml(path) {
2202
2286
  if (!existsSync13(path)) return "";
2203
2287
  try {
2204
- return readFileSync11(path, "utf-8");
2288
+ return readFileSync12(path, "utf-8");
2205
2289
  } catch (err) {
2206
2290
  throw new Error(`Failed to read ${path}: ${err.message}`);
2207
2291
  }
@@ -2290,7 +2374,7 @@ function resolveBunBin2() {
2290
2374
  function readDesktopJson() {
2291
2375
  if (!existsSync13(CLAUDE_DESKTOP_CONFIG_PATH)) return {};
2292
2376
  try {
2293
- return JSON.parse(readFileSync11(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
2377
+ return JSON.parse(readFileSync12(CLAUDE_DESKTOP_CONFIG_PATH, "utf-8"));
2294
2378
  } catch (err) {
2295
2379
  throw new Error(`Failed to parse ${CLAUDE_DESKTOP_CONFIG_PATH}: ${err.message}`);
2296
2380
  }
@@ -2347,31 +2431,31 @@ var init_mcpConfig = __esm({
2347
2431
  init_platform();
2348
2432
  SYNKRO_MARKER4 = "__synkro_managed__";
2349
2433
  SYNKRO_SERVER_NAME = "synkro-guardrails";
2350
- CC_CONFIG_PATH = join9(homedir11(), ".claude.json");
2351
- CURSOR_MCP_PATH = join9(homedir11(), ".cursor", "mcp.json");
2352
- CODEX_CONFIG_PATH = join9(process.env.CODEX_HOME || join9(homedir11(), ".codex"), "config.toml");
2434
+ CC_CONFIG_PATH = join10(homedir12(), ".claude.json");
2435
+ CURSOR_MCP_PATH = join10(homedir12(), ".cursor", "mcp.json");
2436
+ CODEX_CONFIG_PATH = join10(process.env.CODEX_HOME || join10(homedir12(), ".codex"), "config.toml");
2353
2437
  CODEX_MCP_BEGIN = "# >>> synkro managed MCP: synkro-guardrails";
2354
2438
  CODEX_MCP_END = "# <<< synkro managed MCP: synkro-guardrails";
2355
2439
  CODEX_MCP_SECTION = 'mcp_servers."synkro-guardrails"';
2356
- CLAUDE_DESKTOP_CONFIG_PATH = join9(
2357
- homedir11(),
2440
+ CLAUDE_DESKTOP_CONFIG_PATH = join10(
2441
+ homedir12(),
2358
2442
  "Library",
2359
2443
  "Application Support",
2360
2444
  "Claude",
2361
2445
  "claude_desktop_config.json"
2362
2446
  );
2363
- MCP_STDIO_PROXY_PATH = join9(homedir11(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2447
+ MCP_STDIO_PROXY_PATH = join10(homedir12(), ".synkro", "hooks", "mcp-stdio-proxy.ts");
2364
2448
  }
2365
2449
  });
2366
2450
 
2367
2451
  // cli/installer/synkroCommand.ts
2368
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
2369
- import { homedir as homedir12 } from "os";
2370
- import { join as join10 } from "path";
2452
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
2453
+ import { homedir as homedir13 } from "os";
2454
+ import { join as join11 } from "path";
2371
2455
  function installSynkroCommand() {
2372
2456
  try {
2373
2457
  if (existsSync14(COMMAND_PATH)) {
2374
- const current = readFileSync12(COMMAND_PATH, "utf-8");
2458
+ const current = readFileSync13(COMMAND_PATH, "utf-8");
2375
2459
  if (!current.includes(MARKER)) return null;
2376
2460
  }
2377
2461
  mkdirSync8(COMMANDS_DIR, { recursive: true });
@@ -2384,7 +2468,7 @@ function installSynkroCommand() {
2384
2468
  function uninstallSynkroCommand() {
2385
2469
  try {
2386
2470
  if (!existsSync14(COMMAND_PATH)) return false;
2387
- const current = readFileSync12(COMMAND_PATH, "utf-8");
2471
+ const current = readFileSync13(COMMAND_PATH, "utf-8");
2388
2472
  if (!current.includes(MARKER)) return false;
2389
2473
  unlinkSync4(COMMAND_PATH);
2390
2474
  return true;
@@ -2397,8 +2481,8 @@ var init_synkroCommand = __esm({
2397
2481
  "cli/installer/synkroCommand.ts"() {
2398
2482
  "use strict";
2399
2483
  MARKER = "__synkro_managed__";
2400
- COMMANDS_DIR = join10(homedir12(), ".claude", "commands");
2401
- COMMAND_PATH = join10(COMMANDS_DIR, "synkro.md");
2484
+ COMMANDS_DIR = join11(homedir13(), ".claude", "commands");
2485
+ COMMAND_PATH = join11(COMMANDS_DIR, "synkro.md");
2402
2486
  COMMAND_BODY = `<!-- ${MARKER} -->
2403
2487
  ---
2404
2488
  description: Set up Synkro \u2014 create a starter rule set, review suggestions, and watch a live block
@@ -2456,14 +2540,14 @@ var init_skillParser = __esm({
2456
2540
  function stubHook(surface, optsLiteral) {
2457
2541
  return "#!/usr/bin/env bun\nimport { runStub } from './_synkro-stub-common.ts';\nrunStub(" + JSON.stringify(surface) + ", " + optsLiteral + ");\n";
2458
2542
  }
2459
- var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
2543
+ var STUB_COMMON_TS, STUB_EDIT_PRECHECK_TS, STUB_EDIT_FOLLOWUP_TS, STUB_CWE_PRECHECK_TS, STUB_CVE_PRECHECK_TS, STUB_BASH_JUDGE_TS, STUB_SKILL_JUDGE_TS, STUB_INSTALL_SCAN_TS, STUB_AGENT_JUDGE_TS, STUB_MCP_GATE_TS, STUB_PLAN_JUDGE_TS, STUB_STOP_SUMMARY_TS, STUB_SESSION_START_TS, STUB_TRANSCRIPT_SYNC_TS, STUB_SUBAGENT_START_TS, STUB_SUBAGENT_STOP_TS, STUB_USER_PROMPT_SUBMIT_TS, STUB_BASH_FOLLOWUP_TS, STUB_PROMPT_ROUTE_TS, STUB_TASK_ACTIVATE_INTENT_TS, STUB_CURSOR_BASH_JUDGE_TS, STUB_CURSOR_SKILL_JUDGE_TS, STUB_CURSOR_EDIT_CAPTURE_TS, STUB_CURSOR_AGENT_CAPTURE_TS;
2460
2544
  var init_hookScriptsTs = __esm({
2461
2545
  "cli/installer/hookScriptsTs.ts"() {
2462
2546
  "use strict";
2463
- STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, statSync, openSync, readSync, closeSync } from 'node:fs';
2547
+ STUB_COMMON_TS = String.raw`import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, statSync, realpathSync, openSync, readSync, closeSync } from 'node:fs';
2464
2548
  import { execSync } from 'node:child_process';
2465
2549
  import { homedir } from 'node:os';
2466
- import { join, resolve, isAbsolute } from 'node:path';
2550
+ import { basename, join, resolve, relative, isAbsolute } from 'node:path';
2467
2551
  import { randomUUID, createHash } from 'node:crypto';
2468
2552
 
2469
2553
  const HOME = homedir();
@@ -2622,13 +2706,17 @@ async function cloudMcpGate(payload: any, harness: string): Promise<string> {
2622
2706
  try {
2623
2707
  const toolName = String(payload.tool_name || payload.tool || '');
2624
2708
  const parts = toolName.split('__');
2625
- const server = (parts.length >= 3 && parts[0] === 'mcp') ? parts[1] : '';
2709
+ const input = payload.tool_input && typeof payload.tool_input === 'object' ? payload.tool_input : {};
2710
+ const server = (parts.length >= 3 && parts[0] === 'mcp')
2711
+ ? parts[1]
2712
+ : String(payload.server_name || payload.serverName || payload.server || payload.mcp_server
2713
+ || input.server_name || input.serverName || input.server || input.mcp_server || '');
2626
2714
  if (!server) return failOpen(harness);
2627
2715
  const base = (cfgVal('SYNKRO_GATEWAY_URL') || 'https://api.synkro.sh').replace(/\/+$/, '');
2628
2716
  const resp = await fetch(base + '/api/mcp/gate', {
2629
2717
  method: 'POST',
2630
2718
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + loadMcpJwt() },
2631
- body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') }),
2719
+ body: JSON.stringify({ server: server, tool: parts.slice(2).join('__') || toolName, harness: harness }),
2632
2720
  signal: AbortSignal.timeout(5000),
2633
2721
  });
2634
2722
  if (!resp.ok) return failOpen(harness);
@@ -2725,48 +2813,293 @@ function gatherBaseContent(fp: string, ti: any): string | undefined {
2725
2813
  return '\n'.repeat(linesBefore) + full.slice(start, end);
2726
2814
  }
2727
2815
 
2816
+ function gatherPreEditContentFromPost(fp: string, toolName: string, ti: any, operation: string): string | undefined {
2817
+ if (operation === 'add') return '';
2818
+ if (operation === 'delete') {
2819
+ const patch = String(ti?.patch || '');
2820
+ const removed: string[] = [];
2821
+ for (const line of patch.split('\n')) {
2822
+ if (line.startsWith('*** ') || line.startsWith('@@')) continue;
2823
+ if (line.startsWith('-')) removed.push(line.slice(1));
2824
+ }
2825
+ return removed.length ? removed.join('\n') : undefined;
2826
+ }
2827
+ let full = '';
2828
+ try { full = readFileSync(fp, 'utf-8'); } catch { return undefined; }
2829
+ if (toolName === 'Edit') {
2830
+ const oldString = String(ti?.old_string || '');
2831
+ const newString = String(ti?.new_string || '');
2832
+ if (newString && full.includes(newString)) return full.replace(newString, oldString);
2833
+ return undefined;
2834
+ }
2835
+ if (toolName === 'MultiEdit' && Array.isArray(ti?.edits)) {
2836
+ let before = full;
2837
+ for (const edit of [...ti.edits].reverse()) {
2838
+ const oldString = String(edit?.old_string || '');
2839
+ const newString = String(edit?.new_string || '');
2840
+ if (!newString || !before.includes(newString)) return undefined;
2841
+ before = before.replace(newString, oldString);
2842
+ }
2843
+ return before;
2844
+ }
2845
+ return full;
2846
+ }
2847
+
2728
2848
  // Codex edits arrive as the apply_patch tool with the patch text in
2729
2849
  // tool_input.command (lines: "*** Update/Add/Delete File: PATH", then hunks where
2730
2850
  // "+" adds, "-" removes, " " is context). The edit hooks expect the CC shape
2731
- // {file_path, old_string, new_string}; translate once so filePathFromToolInput,
2732
- // editAnchorString, and gatherBaseContent all work unchanged. A patch spanning
2733
- // more than one file or hunk cannot be reconstructed faithfully as one Edit, so
2734
- // mark it for a clear deny-and-retry response instead of grading partial content.
2851
+ // {file_path, old_string, new_string}; translate every file and hunk into an
2852
+ // explicit edit batch so one apply_patch call never drops evidence.
2735
2853
  export function normalizeCodexApplyPatch(payload: any): void {
2736
2854
  try {
2737
- if (!/apply_patch/i.test(String((payload && payload.tool_name) || ''))) return;
2855
+ if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(String((payload && payload.tool_name) || ''))) return;
2738
2856
  const ti = payload.tool_input;
2739
2857
  const patch = (ti && typeof ti === 'object')
2740
2858
  ? String(ti.command || ti.patch || ti.input || '')
2741
2859
  : (typeof ti === 'string' ? ti : '');
2742
2860
  if (!patch) return;
2743
- const files: string[] = [];
2744
- let hunkCount = 0;
2745
- const oldLines: string[] = [];
2746
- const newLines: string[] = [];
2861
+ type Hunk = { oldLines: string[]; newLines: string[] };
2862
+ type PatchFile = {
2863
+ operation: 'Add' | 'Update' | 'Delete';
2864
+ path: string;
2865
+ moveTarget: string;
2866
+ hunks: Hunk[];
2867
+ fragment: string[];
2868
+ };
2869
+ const files: PatchFile[] = [];
2870
+ let current: PatchFile | null = null;
2871
+ let hunk: Hunk | null = null;
2872
+ const flushHunk = () => {
2873
+ if (current && hunk && (hunk.oldLines.length || hunk.newLines.length)) current.hunks.push(hunk);
2874
+ hunk = null;
2875
+ };
2876
+ const flushFile = () => {
2877
+ flushHunk();
2878
+ if (current) files.push(current);
2879
+ current = null;
2880
+ };
2747
2881
  for (const ln of patch.split('\n')) {
2748
2882
  const mFile = ln.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+?)\s*$/);
2749
- if (mFile) { files.push(mFile[1].trim()); continue; }
2750
- if (ln.indexOf('@@') === 0) { hunkCount++; continue; }
2883
+ if (mFile) {
2884
+ flushFile();
2885
+ const operation = ln.match(/^\*\*\*\s+(Update|Add|Delete)\s+File:/)?.[1] as 'Add' | 'Update' | 'Delete';
2886
+ current = {
2887
+ operation,
2888
+ path: mFile[1].trim(),
2889
+ moveTarget: '',
2890
+ hunks: [],
2891
+ fragment: [ln],
2892
+ };
2893
+ continue;
2894
+ }
2895
+ if (!current) continue;
2896
+ current.fragment.push(ln);
2897
+ const mMove = ln.match(/^\*\*\*\s+Move to:\s+(.+?)\s*$/);
2898
+ if (mMove) { current.moveTarget = mMove[1].trim(); continue; }
2899
+ if (ln.indexOf('@@') === 0) {
2900
+ flushHunk();
2901
+ hunk = { oldLines: [], newLines: [] };
2902
+ const anchor = ln.slice(2).trim();
2903
+ if (anchor) { hunk.oldLines.push(anchor); hunk.newLines.push(anchor); }
2904
+ continue;
2905
+ }
2751
2906
  if (ln.indexOf('*** ') === 0) continue;
2907
+ hunk = hunk || { oldLines: [], newLines: [] };
2752
2908
  const c = ln.charAt(0);
2753
- if (c === '+') newLines.push(ln.slice(1));
2754
- else if (c === '-') oldLines.push(ln.slice(1));
2755
- else if (c === ' ') { oldLines.push(ln.slice(1)); newLines.push(ln.slice(1)); }
2909
+ if (c === '+') hunk.newLines.push(ln.slice(1));
2910
+ else if (c === '-') hunk.oldLines.push(ln.slice(1));
2911
+ else if (c === ' ') { hunk.oldLines.push(ln.slice(1)); hunk.newLines.push(ln.slice(1)); }
2756
2912
  }
2757
- if (files.length !== 1 || hunkCount > 1) {
2913
+ flushFile();
2914
+ if (!files.length) {
2758
2915
  payload.__synkro_codex_patch_error =
2759
- 'Synkro needs Codex edits split into one file and one contiguous hunk. Retry this apply_patch as separate edits.';
2916
+ 'Synkro could not recover a file edit from this apply_patch payload.';
2760
2917
  return;
2761
2918
  }
2762
- if (!files[0]) return;
2763
2919
  const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
2764
- const filePath = !isAbsolute(files[0]) && cwd ? resolve(cwd, files[0]) : files[0];
2765
- payload.tool_name = 'Edit';
2766
- payload.tool_input = { file_path: filePath, old_string: oldLines.join('\n'), new_string: newLines.join('\n') };
2920
+ const normalized = files.map((file) => {
2921
+ const filePath = !isAbsolute(file.path) && cwd ? resolve(cwd, file.path) : file.path;
2922
+ const patchFragment = file.fragment.join('\n');
2923
+ if (file.operation === 'Add') {
2924
+ return {
2925
+ tool_name: 'Write',
2926
+ tool_input: {
2927
+ file_path: filePath,
2928
+ content: file.hunks.flatMap((item) => item.newLines).join('\n'),
2929
+ patch: patchFragment,
2930
+ },
2931
+ __synkro_codex_operation: 'add',
2932
+ };
2933
+ }
2934
+ if (file.operation === 'Delete') {
2935
+ return {
2936
+ tool_name: 'Write',
2937
+ tool_input: { file_path: filePath, content: '', patch: patchFragment },
2938
+ __synkro_codex_operation: 'delete',
2939
+ };
2940
+ }
2941
+ const edits = file.hunks.map((item) => ({
2942
+ old_string: item.oldLines.join('\n'),
2943
+ new_string: item.newLines.join('\n'),
2944
+ }));
2945
+ return {
2946
+ tool_name: edits.length > 1 ? 'MultiEdit' : 'Edit',
2947
+ tool_input: edits.length > 1
2948
+ ? { file_path: filePath, edits, patch: patchFragment }
2949
+ : { file_path: filePath, ...(edits[0] || { old_string: '', new_string: '' }), patch: patchFragment },
2950
+ __synkro_codex_operation: file.moveTarget ? 'rename' : 'modify',
2951
+ ...(file.moveTarget ? { __synkro_codex_move_target: file.moveTarget } : {}),
2952
+ };
2953
+ });
2954
+ payload.__synkro_codex_edits = normalized;
2955
+ Object.assign(payload, normalized[0]);
2767
2956
  } catch { /* leave payload untouched on any parse error */ }
2768
2957
  }
2769
2958
 
2959
+ type RecoveredCodexEdit = { payload: Record<string, any>; baseContent?: string };
2960
+
2961
+ function completedCodexToolRecord(entry: any): any {
2962
+ if (!entry || typeof entry !== 'object') return null;
2963
+ if (entry.type === 'response_item' && entry.payload && typeof entry.payload === 'object') {
2964
+ return entry.payload;
2965
+ }
2966
+ return entry.payload && typeof entry.payload === 'object' ? entry.payload : entry;
2967
+ }
2968
+
2969
+ function completedCodexToolInput(record: any): any {
2970
+ const raw = record?.input ?? record?.arguments ?? record?.command;
2971
+ if (raw && typeof raw === 'object') return raw;
2972
+ if (typeof raw !== 'string') return {};
2973
+ try {
2974
+ const parsed = JSON.parse(raw);
2975
+ if (parsed && typeof parsed === 'object') return parsed;
2976
+ if (typeof parsed === 'string') return { command: parsed };
2977
+ } catch {}
2978
+ return { command: raw };
2979
+ }
2980
+
2981
+ function containedCodexEditPath(repoRoot: string, filePath: string): string {
2982
+ if (!repoRoot || !filePath) return '';
2983
+ try {
2984
+ const lexicalRoot = resolve(repoRoot);
2985
+ const realRoot = realpathSync(lexicalRoot);
2986
+ const lexical = isAbsolute(filePath) ? resolve(filePath) : resolve(lexicalRoot, filePath);
2987
+ const isContained = (rel: string): boolean =>
2988
+ !!rel && rel !== '..' && !rel.startsWith('..' + '/') && !isAbsolute(rel);
2989
+ // Accept an absolute path expressed through either the lexical repo alias
2990
+ // (/var on macOS, a symlinked checkout) or its physical path, but always
2991
+ // continue from the physical root so new and existing files share one
2992
+ // canonical provenance path.
2993
+ let rel = relative(lexicalRoot, lexical);
2994
+ if (!isContained(rel)) {
2995
+ rel = relative(realRoot, lexical);
2996
+ if (!isContained(rel)) return '';
2997
+ }
2998
+ let candidate = resolve(realRoot, rel);
2999
+ // realpathSync requires the leaf to exist. Walk to the nearest existing
3000
+ // ancestor, resolve symlinks there, then append the missing suffix. This
3001
+ // both canonicalizes new files and rejects a new leaf beneath an in-repo
3002
+ // symlink that actually escapes the repository.
3003
+ const missing: string[] = [];
3004
+ let ancestor = candidate;
3005
+ while (!existsSync(ancestor)) {
3006
+ const parent = resolve(ancestor, '..');
3007
+ if (parent === ancestor) return '';
3008
+ missing.unshift(basename(ancestor));
3009
+ ancestor = parent;
3010
+ }
3011
+ candidate = resolve(realpathSync(ancestor), ...missing);
3012
+ const realRel = relative(realRoot, candidate);
3013
+ if (!realRel || realRel.startsWith('..') || isAbsolute(realRel)) return '';
3014
+ return existsSync(candidate) ? realpathSync(candidate) : candidate;
3015
+ } catch {
3016
+ return '';
3017
+ }
3018
+ }
3019
+
3020
+ /**
3021
+ * Recover completed Codex apply_patch calls from a bounded transcript tail.
3022
+ * A tool call is evidence only after a matching tool output exists. Calls are
3023
+ * expanded one event per file, restricted to the repo, and keyed by the stable
3024
+ * Codex call id so replay is idempotent server-side.
3025
+ */
3026
+ export function extractCompletedCodexPatchEdits(
3027
+ transcript: string,
3028
+ repoRoot: string,
3029
+ ): RecoveredCodexEdit[] {
3030
+ if (!transcript || !repoRoot) return [];
3031
+ const calls = new Map<string, { record: any; timestamp: string }>();
3032
+ const completed = new Set<string>();
3033
+ for (const line of transcript.split('\n')) {
3034
+ if (!line.trim()) continue;
3035
+ let entry: any;
3036
+ try { entry = JSON.parse(line); } catch { continue; }
3037
+ const record = completedCodexToolRecord(entry);
3038
+ const type = String(record?.type || '');
3039
+ const callId = String(record?.call_id || record?.tool_call_id || record?.id || '');
3040
+ if (!callId) continue;
3041
+ if (type === 'custom_tool_call_output' || type === 'function_call_output') {
3042
+ completed.add(callId);
3043
+ continue;
3044
+ }
3045
+ if (type !== 'custom_tool_call' && type !== 'function_call') continue;
3046
+ const name = String(record?.name || record?.tool_name || '');
3047
+ if (!/^(?:apply_?patch|functions[._]apply_?patch)$/i.test(name)) continue;
3048
+ calls.set(callId, {
3049
+ record,
3050
+ timestamp: String(entry?.timestamp || record?.timestamp || ''),
3051
+ });
3052
+ }
3053
+
3054
+ const results: RecoveredCodexEdit[] = [];
3055
+ const seen = new Set<string>();
3056
+ const candidates = [...calls.entries()].filter(([callId]) => completed.has(callId)).slice(-100);
3057
+ for (const [callId, call] of candidates) {
3058
+ const toolInput = completedCodexToolInput(call.record);
3059
+ const patch = String(toolInput?.command || toolInput?.patch || toolInput?.input || '');
3060
+ if (!patch || patch.length > 100000) continue;
3061
+ const raw: any = {
3062
+ cwd: repoRoot,
3063
+ tool_name: String(call.record?.name || call.record?.tool_name || 'apply_patch'),
3064
+ tool_input: toolInput,
3065
+ tool_call_id: callId,
3066
+ _ts: call.timestamp || undefined,
3067
+ };
3068
+ normalizeCodexApplyPatch(raw);
3069
+ const edits = Array.isArray(raw.__synkro_codex_edits) ? raw.__synkro_codex_edits : [];
3070
+ for (const edit of edits) {
3071
+ if (results.length >= 50) return results;
3072
+ const fp = containedCodexEditPath(repoRoot, filePathFromToolInput(edit?.tool_input));
3073
+ if (!fp) continue;
3074
+ const dedupeKey = callId + '\u0000' + fp;
3075
+ if (seen.has(dedupeKey)) continue;
3076
+ seen.add(dedupeKey);
3077
+ const payload: any = {
3078
+ cwd: repoRoot,
3079
+ tool_name: edit.tool_name,
3080
+ tool_input: { ...(edit.tool_input || {}), file_path: fp },
3081
+ tool_call_id: callId,
3082
+ _ts: call.timestamp || undefined,
3083
+ __synkro_codex_operation: edit.__synkro_codex_operation,
3084
+ __synkro_codex_move_target: edit.__synkro_codex_move_target,
3085
+ __synkro_edit_source: 'transcript',
3086
+ };
3087
+ const baseContent = gatherPreEditContentFromPost(
3088
+ fp,
3089
+ String(payload.tool_name || ''),
3090
+ payload.tool_input,
3091
+ String(payload.__synkro_codex_operation || ''),
3092
+ );
3093
+ if (baseContent === undefined) continue;
3094
+ results.push({
3095
+ payload,
3096
+ baseContent,
3097
+ });
3098
+ }
3099
+ }
3100
+ return results;
3101
+ }
3102
+
2770
3103
  interface StubOpts {
2771
3104
  needsFile?: boolean;
2772
3105
  needsTranscript?: boolean;
@@ -2776,6 +3109,115 @@ interface StubOpts {
2776
3109
  needsPlan?: boolean;
2777
3110
  telemetry?: boolean;
2778
3111
  harness?: string;
3112
+ postEdit?: boolean;
3113
+ /** Codex SubagentStart/SubagentStop report the parent as session_id and
3114
+ * the child as agent_id. Remap them to the ordinary child-session envelope. */
3115
+ subagent?: boolean;
3116
+ }
3117
+
3118
+ function isContainedFile(root: string, candidate: string): boolean {
3119
+ try {
3120
+ const rel = relative(root, candidate);
3121
+ return !!rel && !rel.startsWith('..') && !isAbsolute(rel) && statSync(candidate).isFile();
3122
+ } catch { return false; }
3123
+ }
3124
+
3125
+ function safeCodexSessionId(value: string): boolean {
3126
+ if (!value || value.length > 200) return false;
3127
+ for (const ch of value) {
3128
+ const code = ch.charCodeAt(0);
3129
+ const alphaNumeric = (code >= 48 && code <= 57)
3130
+ || (code >= 65 && code <= 90)
3131
+ || (code >= 97 && code <= 122);
3132
+ if (!alphaNumeric && ch !== '-' && ch !== '_') return false;
3133
+ }
3134
+ return true;
3135
+ }
3136
+
3137
+ function readJsonlHeader(path: string): string {
3138
+ let fd = -1;
3139
+ try {
3140
+ fd = openSync(path, 'r');
3141
+ const buf = Buffer.alloc(16384);
3142
+ const count = readSync(fd, buf, 0, buf.length, 0);
3143
+ const head = buf.subarray(0, count).toString('utf-8');
3144
+ const newline = head.indexOf('\n');
3145
+ return newline >= 0 ? head.slice(0, newline) : head;
3146
+ } catch { return ''; }
3147
+ finally { if (fd >= 0) { try { closeSync(fd); } catch {} } }
3148
+ }
3149
+
3150
+ /**
3151
+ * Codex hook payloads identify the rollout by session_id but do not always send
3152
+ * transcript_path. Resolve that ID only within CODEX_HOME/sessions (or the
3153
+ * default ~/.codex/sessions), then cache the exact contained file so subsequent
3154
+ * tool hooks do not recursively scan the rollout tree.
3155
+ */
3156
+ export function resolveHookTranscriptPath(payload: any, harness: string, sessionId: string): string {
3157
+ const explicit = [
3158
+ payload?.transcript_path,
3159
+ payload?.rollout_path,
3160
+ payload?.session_path,
3161
+ ].find((value) => typeof value === 'string' && value);
3162
+ if (typeof explicit === 'string') return explicit;
3163
+ if (harness !== 'codex' || !safeCodexSessionId(sessionId)) return '';
3164
+
3165
+ const homeRoots = [
3166
+ process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : '',
3167
+ resolve(join(HOME, '.codex')),
3168
+ ].filter((value, index, all) => !!value && all.indexOf(value) === index);
3169
+ const sessionRoots = homeRoots.map((root) => join(root, 'sessions')).filter((root) => existsSync(root));
3170
+ if (!sessionRoots.length) return '';
3171
+
3172
+ const cachePath = join(
3173
+ HOME,
3174
+ '.synkro',
3175
+ 'codex-transcript-' + createHash('sha256').update(sessionId).digest('hex').slice(0, 16),
3176
+ );
3177
+ try {
3178
+ const cached = resolve(readFileSync(cachePath, 'utf-8').trim());
3179
+ if (sessionRoots.some((root) => isContainedFile(root, cached))) return cached;
3180
+ } catch {}
3181
+
3182
+ const candidates: string[] = [];
3183
+ for (const root of sessionRoots) {
3184
+ let entries: string[] = [];
3185
+ try { entries = readdirSync(root, { recursive: true, encoding: 'utf-8' }) as string[]; } catch {}
3186
+ for (const entry of entries) {
3187
+ if (!entry.endsWith('.jsonl')) continue;
3188
+ const candidate = resolve(join(root, entry));
3189
+ if (isContainedFile(root, candidate)) candidates.push(candidate);
3190
+ }
3191
+ }
3192
+
3193
+ let found = candidates.find((candidate) => candidate.includes(sessionId)) || '';
3194
+ if (!found) {
3195
+ const recent = candidates
3196
+ .map((candidate) => {
3197
+ try { return { candidate, mtime: statSync(candidate).mtimeMs }; }
3198
+ catch { return { candidate, mtime: 0 }; }
3199
+ })
3200
+ .sort((a, b) => b.mtime - a.mtime)
3201
+ .slice(0, 100);
3202
+ for (const item of recent) {
3203
+ try {
3204
+ const first = JSON.parse(readJsonlHeader(item.candidate));
3205
+ const candidateId = String(first?.payload?.session_id || first?.payload?.id || '');
3206
+ if (first?.type === 'session_meta' && candidateId === sessionId) {
3207
+ found = item.candidate;
3208
+ break;
3209
+ }
3210
+ } catch {}
3211
+ }
3212
+ }
3213
+
3214
+ if (found) {
3215
+ try {
3216
+ mkdirSync(join(HOME, '.synkro'), { recursive: true });
3217
+ writeFileSync(cachePath, found, { encoding: 'utf-8', mode: 0o600 });
3218
+ } catch {}
3219
+ }
3220
+ return found;
2779
3221
  }
2780
3222
 
2781
3223
  export async function runStub(surface: string, opts: StubOpts = {}): Promise<void> {
@@ -2792,6 +3234,17 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2792
3234
  // Codex: rewrite apply_patch tool_input into the CC edit shape before any
2793
3235
  // surface reads it (edit/cwe/cve). No-op for CC/Cursor and non-edit tools.
2794
3236
  if (harness === 'codex') normalizeCodexApplyPatch(payload);
3237
+ if (harness === 'codex' && opts.subagent) {
3238
+ const parentSessionId = String(payload?.session_id || '');
3239
+ const childSessionId = String(payload?.agent_id || '');
3240
+ if (parentSessionId && childSessionId) {
3241
+ payload.parent_session_id = parentSessionId;
3242
+ payload.session_id = childSessionId;
3243
+ if (!payload.transcript_path && payload.agent_transcript_path) {
3244
+ payload.transcript_path = payload.agent_transcript_path;
3245
+ }
3246
+ }
3247
+ }
2795
3248
  telemPayload = payload;
2796
3249
  telemCwd = (typeof payload?.cwd === 'string' ? payload.cwd : '') || '';
2797
3250
  telemSessionId = String(payload?.session_id || payload?.conversation_id || '');
@@ -2862,14 +3315,40 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2862
3315
  const envelope: any = { payload, harness, cwd: root || cwd, sessionId, synkroFileText };
2863
3316
 
2864
3317
  if (opts.needsFile) {
2865
- const fp = filePathFromToolInput(payload.tool_input || {});
2866
- if (fp && existsSync(fp)) {
2867
- const bc = gatherBaseContent(fp, payload.tool_input || {});
2868
- if (bc !== undefined) envelope.baseContent = bc;
3318
+ const normalizedEdits = harness === 'codex' && Array.isArray(payload.__synkro_codex_edits)
3319
+ ? payload.__synkro_codex_edits
3320
+ : [{ tool_name: payload.tool_name, tool_input: payload.tool_input }];
3321
+ const editBatch = normalizedEdits.map((edit: any) => {
3322
+ const childPayload = {
3323
+ ...payload,
3324
+ tool_name: edit.tool_name,
3325
+ tool_input: edit.tool_input,
3326
+ __synkro_codex_operation: edit.__synkro_codex_operation,
3327
+ __synkro_codex_move_target: edit.__synkro_codex_move_target,
3328
+ };
3329
+ delete childPayload.__synkro_codex_edits;
3330
+ const fp = filePathFromToolInput(childPayload.tool_input || {});
3331
+ const canRead = fp && (existsSync(fp) || (opts.postEdit && childPayload.__synkro_codex_operation === 'add'));
3332
+ const bc = canRead
3333
+ ? (opts.postEdit
3334
+ ? gatherPreEditContentFromPost(
3335
+ fp,
3336
+ String(childPayload.tool_name || ''),
3337
+ childPayload.tool_input || {},
3338
+ String(childPayload.__synkro_codex_operation || ''),
3339
+ )
3340
+ : gatherBaseContent(fp, childPayload.tool_input || {}))
3341
+ : undefined;
3342
+ return { payload: childPayload, ...(bc !== undefined ? { baseContent: bc } : {}) };
3343
+ });
3344
+ if (editBatch.length) {
3345
+ envelope.editBatch = editBatch;
3346
+ envelope.payload = editBatch[0].payload;
3347
+ if (editBatch[0].baseContent !== undefined) envelope.baseContent = editBatch[0].baseContent;
2869
3348
  }
2870
3349
  }
2871
3350
  if (opts.needsTranscript) {
2872
- const tp = typeof payload.transcript_path === 'string' ? payload.transcript_path : '';
3351
+ const tp = resolveHookTranscriptPath(payload, harness, sessionId);
2873
3352
  if (tp && existsSync(tp)) {
2874
3353
  try {
2875
3354
  const t = readFileSync(tp, 'utf-8');
@@ -2881,6 +3360,10 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2881
3360
  // by ts, so we no longer depend on the whole file for absolute message indices.
2882
3361
  const cap = opts.fullTranscript ? 400000 : 200000;
2883
3362
  envelope.transcript = t.length <= cap ? t : t.slice(t.length - cap);
3363
+ if (harness === 'codex') {
3364
+ const recovered = extractCompletedCodexPatchEdits(envelope.transcript, root || cwd);
3365
+ if (recovered.length) envelope.transcriptEdits = recovered;
3366
+ }
2884
3367
  } catch {}
2885
3368
  }
2886
3369
  }
@@ -2896,7 +3379,8 @@ export async function runStub(surface: string, opts: StubOpts = {}): Promise<voi
2896
3379
  // prompt-submit is the opposite: it's INTERACTIVE (blocks the user's prompt from
2897
3380
  // being sent) under a short 5s hook timeout, so it gets ONE short attempt (below)
2898
3381
  // and a tight per-attempt budget that fits inside 5s — never the 6s×3 telemetry path.
2899
- const timeoutMs = surface === 'bash-followup' ? 32000
3382
+ const timeoutMs = surface === 'cwe-precheck' ? 48000
3383
+ : surface === 'bash-followup' ? 32000
2900
3384
  : surface === 'prompt-submit' ? 3500
2901
3385
  : (opts.telemetry ? 6000 : 28000);
2902
3386
  // Cloud has no local container to reach. Post the SAME envelope to the org's grader
@@ -3061,8 +3545,16 @@ function toolInputSummary(toolName: string, toolInput: any, includeCommand: bool
3061
3545
  // cc_model is derived by the caller (bounded tail read) and passed in; this stays
3062
3546
  // allocation-only so the dormant hot path never touches the transcript.
3063
3547
  function telemPayloadOpts(payload: any, harness: string, cwd: string, sessionId: string, ccModel?: string): any {
3548
+ const normalizedHarness = String(harness || '').trim().toLowerCase().replace(/[\\s-]+/g, '_');
3549
+ const agentKind = normalizedHarness === 'cursor'
3550
+ ? 'cursor'
3551
+ : normalizedHarness === 'codex'
3552
+ ? 'codex'
3553
+ : normalizedHarness === 'cc' || normalizedHarness === 'claude_code'
3554
+ ? 'claude_code'
3555
+ : undefined;
3064
3556
  return {
3065
- agent_kind: harness === 'cursor' ? 'cursor' : harness === 'codex' ? 'codex' : 'claude_code',
3557
+ agent_kind: agentKind,
3066
3558
  cc_session_id: sessionId || undefined,
3067
3559
  cc_tool_use_id: payload?.tool_use_id ? String(payload.tool_use_id) : undefined,
3068
3560
  cc_model: ccModel || undefined,
@@ -3164,6 +3656,7 @@ function emitStubTelemetry(
3164
3656
  }
3165
3657
  `;
3166
3658
  STUB_EDIT_PRECHECK_TS = stubHook("edit-precheck", "{ needsFile: true, needsTranscript: true }");
3659
+ STUB_EDIT_FOLLOWUP_TS = stubHook("edit-followup", "{ needsFile: true, needsTranscript: true, postEdit: true }");
3167
3660
  STUB_CWE_PRECHECK_TS = stubHook("cwe-precheck", "{ needsFile: true, needsTranscript: true }");
3168
3661
  STUB_CVE_PRECHECK_TS = stubHook("cve-precheck", "{ needsFile: true, needsTranscript: true }");
3169
3662
  STUB_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true }");
@@ -3175,6 +3668,8 @@ function emitStubTelemetry(
3175
3668
  STUB_STOP_SUMMARY_TS = stubHook("stop-summary", "{ needsTranscript: true, fullTranscript: true }");
3176
3669
  STUB_SESSION_START_TS = stubHook("session-start", "{ telemetry: true }");
3177
3670
  STUB_TRANSCRIPT_SYNC_TS = stubHook("transcript-sync", "{ needsTranscript: true, fullTranscript: true, telemetry: true }");
3671
+ STUB_SUBAGENT_START_TS = stubHook("subagent-start", "{ telemetry: true, subagent: true }");
3672
+ STUB_SUBAGENT_STOP_TS = stubHook("subagent-stop", "{ needsTranscript: true, fullTranscript: true, telemetry: true, subagent: true }");
3178
3673
  STUB_USER_PROMPT_SUBMIT_TS = stubHook("prompt-submit", "{ telemetry: true }");
3179
3674
  STUB_BASH_FOLLOWUP_TS = stubHook("bash-followup", "{ telemetry: true }");
3180
3675
  STUB_PROMPT_ROUTE_TS = `#!/usr/bin/env bun
@@ -3307,7 +3802,7 @@ try {
3307
3802
  const resp = await fetch('http://127.0.0.1:' + (process.env.SYNKRO_GRADER_HOST_PORT || '18929') + '/submit', {
3308
3803
  method: 'POST',
3309
3804
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
3310
- body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', agent_kind: 'claude_code', hedge: false }),
3805
+ body: JSON.stringify({ role: 'route-classify', payload: t.slice(0, 1000), content: 'x', hedge: false }),
3311
3806
  signal: AbortSignal.timeout(1000),
3312
3807
  });
3313
3808
  if (resp.ok) {
@@ -3346,8 +3841,14 @@ const PORT = process.env.SYNKRO_MCP_PORT || '18931';
3346
3841
  function mcpJwt(): string { try { return readFileSync(join(homedir(), '.synkro', '.mcp-jwt'), 'utf-8').trim(); } catch { return ''; } }
3347
3842
  const chunks: Buffer[] = [];
3348
3843
  for await (const chunk of process.stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
3844
+ let originalToolInput: Record<string, unknown> = {};
3845
+ let hookEventName = 'PreToolUse';
3349
3846
  try {
3350
3847
  const payload = JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');
3848
+ hookEventName = String(payload.hook_event_name || payload.hookEventName || 'PreToolUse');
3849
+ if (payload.tool_input && typeof payload.tool_input === 'object' && !Array.isArray(payload.tool_input)) {
3850
+ originalToolInput = payload.tool_input;
3851
+ }
3351
3852
  await fetch('http://127.0.0.1:' + PORT + '/api/local/task-activate-intent', {
3352
3853
  method: 'POST',
3353
3854
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + mcpJwt() },
@@ -3355,7 +3856,29 @@ try {
3355
3856
  signal: AbortSignal.timeout(5000),
3356
3857
  });
3357
3858
  } catch {}
3358
- process.stdout.write('{}\\n');
3859
+ // PreToolUse enforcement and Codex's native approval are separate lifecycle
3860
+ // stages. The policy gate already allowed this exact activate_standard call;
3861
+ // approve its subsequent PermissionRequest so headless approval_policy=never
3862
+ // can execute it. This script is registered only for the activation matcher,
3863
+ // never as a blanket MCP approval hook.
3864
+ if (process.env.SYNKRO_HOOK_FORMAT === 'codex' && hookEventName === 'PermissionRequest') {
3865
+ process.stdout.write(JSON.stringify({
3866
+ hookSpecificOutput: {
3867
+ hookEventName: 'PermissionRequest',
3868
+ decision: { behavior: 'allow' },
3869
+ },
3870
+ }) + '\\n');
3871
+ } else if (process.env.SYNKRO_HOOK_FORMAT === 'codex') {
3872
+ process.stdout.write(JSON.stringify({
3873
+ hookSpecificOutput: {
3874
+ hookEventName: 'PreToolUse',
3875
+ permissionDecision: 'allow',
3876
+ updatedInput: originalToolInput,
3877
+ },
3878
+ }) + '\\n');
3879
+ } else {
3880
+ process.stdout.write('{}\\n');
3881
+ }
3359
3882
  `;
3360
3883
  STUB_CURSOR_BASH_JUDGE_TS = stubHook("bash-judge", "{ needsTranscript: true, harness: 'cursor' }");
3361
3884
  STUB_CURSOR_SKILL_JUDGE_TS = stubHook("skill-judge", "{ needsFile: true, needsTranscript: true, harness: 'cursor' }");
@@ -3382,9 +3905,9 @@ __export(stub_exports, {
3382
3905
  saveCredentials: () => saveCredentials
3383
3906
  });
3384
3907
  import { createServer } from "http";
3385
- import { writeFileSync as writeFileSync11, readFileSync as readFileSync13, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
3386
- import { homedir as homedir13, platform as platform2 } from "os";
3387
- import { join as join11, dirname as dirname5 } from "path";
3908
+ import { writeFileSync as writeFileSync11, readFileSync as readFileSync14, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync5 } from "fs";
3909
+ import { homedir as homedir14, platform as platform2 } from "os";
3910
+ import { join as join12, dirname as dirname5 } from "path";
3388
3911
  import { execFile } from "child_process";
3389
3912
  import jwt from "jsonwebtoken";
3390
3913
  function openBrowser(url) {
@@ -3423,7 +3946,7 @@ function loadCredentials() {
3423
3946
  return null;
3424
3947
  }
3425
3948
  try {
3426
- const content = readFileSync13(AUTH_FILE, "utf8");
3949
+ const content = readFileSync14(AUTH_FILE, "utf8");
3427
3950
  return JSON.parse(content);
3428
3951
  } catch (error) {
3429
3952
  return null;
@@ -3711,7 +4234,7 @@ var init_stub = __esm({
3711
4234
  PORT = 8100;
3712
4235
  RAW_WEB_AUTH_URL = process.env.SYNKRO_WEB_AUTH_URL;
3713
4236
  SYNKRO_WEB_AUTH_URL = RAW_WEB_AUTH_URL && /^https?:\/\//.test(RAW_WEB_AUTH_URL) ? RAW_WEB_AUTH_URL : "https://app.synkro.sh";
3714
- AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join11(homedir13(), ".synkro", "credentials.json");
4237
+ AUTH_FILE = process.env.SYNKRO_AUTH_FILE || join12(homedir14(), ".synkro", "credentials.json");
3715
4238
  RAW_API_URL = process.env.SYNKRO_CRUD_URL || process.env.SYNKRO_API_URL;
3716
4239
  SYNKRO_API_URL = RAW_API_URL && /^https?:\/\//.test(RAW_API_URL) ? RAW_API_URL : "https://api.synkro.sh";
3717
4240
  ERROR_HTML = `
@@ -3948,9 +4471,9 @@ __export(claudeDesktopTap_exports, {
3948
4471
  runClaudeDesktopTap: () => runClaudeDesktopTap
3949
4472
  });
3950
4473
  import { spawn as spawn2 } from "child_process";
3951
- import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
3952
- import { join as join12 } from "path";
3953
- import { homedir as homedir14 } from "os";
4474
+ import { writeFileSync as writeFileSync12, mkdtempSync, mkdirSync as mkdirSync10, readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
4475
+ import { join as join13 } from "path";
4476
+ import { homedir as homedir15 } from "os";
3954
4477
  function claudeDesktopInstalled() {
3955
4478
  return process.platform === "darwin" && existsSync17("/Applications/Claude.app/Contents/MacOS/Claude");
3956
4479
  }
@@ -4066,7 +4589,7 @@ async function runClaudeDesktopTap(opts = {}) {
4066
4589
  }
4067
4590
  let token = "";
4068
4591
  try {
4069
- token = readFileSync14(JWT_PATH, "utf-8").trim();
4592
+ token = readFileSync15(JWT_PATH, "utf-8").trim();
4070
4593
  } catch {
4071
4594
  }
4072
4595
  if (!token) {
@@ -4087,13 +4610,13 @@ async function runClaudeDesktopTap(opts = {}) {
4087
4610
  } catch (err) {
4088
4611
  console.log(` \u26A0 Could not register Claude Desktop MCP: ${err.message}`);
4089
4612
  }
4090
- const cdRoot = join12(homedir14(), ".synkro", "cd-sessions");
4613
+ const cdRoot = join13(homedir15(), ".synkro", "cd-sessions");
4091
4614
  mkdirSync10(cdRoot, { recursive: true });
4092
- const sessionDir = mkdtempSync(join12(cdRoot, "synkro-cd-"));
4093
- writeFileSync12(join12(sessionDir, "tap.py"), ADDON_PY, "utf-8");
4094
- writeFileSync12(join12(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
4095
- writeFileSync12(join12(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
4096
- const runnerPath = join12(sessionDir, "run.sh");
4615
+ const sessionDir = mkdtempSync(join13(cdRoot, "synkro-cd-"));
4616
+ writeFileSync12(join13(sessionDir, "tap.py"), ADDON_PY, "utf-8");
4617
+ writeFileSync12(join13(sessionDir, "mcp_proxy.py"), MCP_PROXY_PY, { mode: 493 });
4618
+ writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
4619
+ const runnerPath = join13(sessionDir, "run.sh");
4097
4620
  writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
4098
4621
  await new Promise((resolve6) => {
4099
4622
  const child = spawn2("bash", [runnerPath], {
@@ -4127,7 +4650,7 @@ var init_claudeDesktopTap = __esm({
4127
4650
  TURN_VERDICTS_URL = "http://127.0.0.1:18931/api/local/turn-verdicts";
4128
4651
  TURN_VERDICT_URL = "http://127.0.0.1:18931/api/local/turn-verdict";
4129
4652
  MCP_EVENT_URL = "http://127.0.0.1:18931/api/local/mcp/event";
4130
- JWT_PATH = join12(homedir14(), ".synkro", ".mcp-jwt");
4653
+ JWT_PATH = join13(homedir15(), ".synkro", ".mcp-jwt");
4131
4654
  ADDON_PY = `import os, json, threading, urllib.request, urllib.error, re, base64, time, hashlib
4132
4655
  from mitmproxy import http
4133
4656
 
@@ -4264,6 +4787,7 @@ def _emit_mcp_event(server, tool, ev_type, eid, convo_id=""):
4264
4787
  "tool_name": (str(tool)[:120] if tool else None),
4265
4788
  "decision": ("allowed" if ev_type == "tool_call" else None),
4266
4789
  "repo": "claude-desktop",
4790
+ "harness": "claude_desktop",
4267
4791
  }).encode("utf-8")
4268
4792
  _post_json(MCP_EVENT_URL, payload, 5)
4269
4793
  except Exception:
@@ -5243,9 +5767,9 @@ __export(macKeychain_exports, {
5243
5767
  writeCursorApiKey: () => writeCursorApiKey,
5244
5768
  writeRefreshAgent: () => writeRefreshAgent
5245
5769
  });
5246
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync15 } from "fs";
5247
- import { homedir as homedir15, platform as platform3 } from "os";
5248
- import { join as join13 } from "path";
5770
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, chmodSync as chmodSync2, readFileSync as readFileSync16 } from "fs";
5771
+ import { homedir as homedir16, platform as platform3 } from "os";
5772
+ import { join as join14 } from "path";
5249
5773
  import { spawnSync as spawnSync2 } from "child_process";
5250
5774
  function needsKeychainBridge() {
5251
5775
  return platform3() === "darwin";
@@ -5268,7 +5792,7 @@ function exportKeychainCreds() {
5268
5792
  let changed = true;
5269
5793
  try {
5270
5794
  if (existsSync18(CLAUDE_CREDS_FILE)) {
5271
- changed = readFileSync15(CLAUDE_CREDS_FILE, "utf-8") !== blob;
5795
+ changed = readFileSync16(CLAUDE_CREDS_FILE, "utf-8") !== blob;
5272
5796
  }
5273
5797
  } catch {
5274
5798
  }
@@ -5278,7 +5802,7 @@ function exportKeychainCreds() {
5278
5802
  }
5279
5803
  function cursorApiKeyConfigured() {
5280
5804
  try {
5281
- return existsSync18(CURSOR_API_KEY_FILE) && readFileSync15(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
5805
+ return existsSync18(CURSOR_API_KEY_FILE) && readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim().length > 0;
5282
5806
  } catch {
5283
5807
  return false;
5284
5808
  }
@@ -5294,7 +5818,7 @@ function writeCursorApiKey(key) {
5294
5818
  async function validateCursorApiKey() {
5295
5819
  let key;
5296
5820
  try {
5297
- key = readFileSync15(CURSOR_API_KEY_FILE, "utf-8").trim();
5821
+ key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
5298
5822
  } catch {
5299
5823
  return null;
5300
5824
  }
@@ -5315,7 +5839,7 @@ async function validateCursorApiKey() {
5315
5839
  function credsAreStale() {
5316
5840
  if (!existsSync18(CLAUDE_CREDS_FILE)) return true;
5317
5841
  try {
5318
- const raw = readFileSync15(CLAUDE_CREDS_FILE, "utf-8");
5842
+ const raw = readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
5319
5843
  const exp = JSON.parse(raw)?.claudeAiOauth?.expiresAt ?? 0;
5320
5844
  if (!exp) return true;
5321
5845
  return Date.now() >= exp - REFRESH_EXPIRY_BUFFER_SECONDS * 1e3;
@@ -5330,7 +5854,7 @@ function writeRefreshAgent(synkroBinPath) {
5330
5854
  if (platform3() !== "darwin") {
5331
5855
  throw new KeychainExportError("writeRefreshAgent is darwin-only");
5332
5856
  }
5333
- mkdirSync11(join13(homedir15(), "Library", "LaunchAgents"), { recursive: true });
5857
+ mkdirSync11(join14(homedir16(), "Library", "LaunchAgents"), { recursive: true });
5334
5858
  mkdirSync11(SYNKRO_DIR6, { recursive: true });
5335
5859
  const script = `#!/bin/bash
5336
5860
  # Generated by synkro (writeRefreshAgent). Expiry-aware Claude creds refresher.
@@ -5427,7 +5951,7 @@ function refreshCreds() {
5427
5951
  }
5428
5952
  function readExportedCreds() {
5429
5953
  try {
5430
- return readFileSync15(CLAUDE_CREDS_FILE, "utf-8");
5954
+ return readFileSync16(CLAUDE_CREDS_FILE, "utf-8");
5431
5955
  } catch {
5432
5956
  return null;
5433
5957
  }
@@ -5436,16 +5960,16 @@ var SYNKRO_DIR6, CLAUDE_CREDS_DIR, CLAUDE_CREDS_FILE, CURSOR_CREDS_DIR, CURSOR_A
5436
5960
  var init_macKeychain = __esm({
5437
5961
  "cli/local-cc/macKeychain.ts"() {
5438
5962
  "use strict";
5439
- SYNKRO_DIR6 = join13(homedir15(), ".synkro");
5440
- CLAUDE_CREDS_DIR = join13(SYNKRO_DIR6, "claude-creds");
5441
- CLAUDE_CREDS_FILE = join13(CLAUDE_CREDS_DIR, ".credentials.json");
5442
- CURSOR_CREDS_DIR = join13(SYNKRO_DIR6, "cursor-creds");
5443
- CURSOR_API_KEY_FILE = join13(CURSOR_CREDS_DIR, "api-key");
5963
+ SYNKRO_DIR6 = join14(homedir16(), ".synkro");
5964
+ CLAUDE_CREDS_DIR = join14(SYNKRO_DIR6, "claude-creds");
5965
+ CLAUDE_CREDS_FILE = join14(CLAUDE_CREDS_DIR, ".credentials.json");
5966
+ CURSOR_CREDS_DIR = join14(SYNKRO_DIR6, "cursor-creds");
5967
+ CURSOR_API_KEY_FILE = join14(CURSOR_CREDS_DIR, "api-key");
5444
5968
  KEYCHAIN_SERVICE = "Claude Code-credentials";
5445
5969
  LAUNCHD_LABEL = "com.synkro.cli.claude-creds-refresh";
5446
- LAUNCHD_PLIST = join13(homedir15(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
5447
- REFRESH_SCRIPT = join13(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
5448
- REFRESH_LOG = join13(SYNKRO_DIR6, "claude-creds-refresh.log");
5970
+ LAUNCHD_PLIST = join14(homedir16(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
5971
+ REFRESH_SCRIPT = join14(SYNKRO_DIR6, "claude-creds-refresh-loop.sh");
5972
+ REFRESH_LOG = join14(SYNKRO_DIR6, "claude-creds-refresh.log");
5449
5973
  REFRESH_EXPIRY_BUFFER_SECONDS = 120;
5450
5974
  MIN_REFRESH_INTERVAL_SECONDS = 30;
5451
5975
  MAX_REFRESH_INTERVAL_SECONDS = 5 * 60;
@@ -5464,8 +5988,11 @@ var init_macKeychain = __esm({
5464
5988
  var dockerInstall_exports = {};
5465
5989
  __export(dockerInstall_exports, {
5466
5990
  DockerInstallError: () => DockerInstallError,
5991
+ HOST_PGLITE_PORT: () => HOST_PGLITE_PORT,
5992
+ PGLITE_PASSWORD_PATH: () => PGLITE_PASSWORD_PATH,
5467
5993
  SYNKRO_DIR: () => SYNKRO_DIR7,
5468
5994
  assertDockerAvailable: () => assertDockerAvailable,
5995
+ createPgliteScramVerifier: () => createPgliteScramVerifier,
5469
5996
  dockerInstall: () => dockerInstall,
5470
5997
  dockerRemove: () => dockerRemove,
5471
5998
  dockerSafeRestart: () => dockerSafeRestart,
@@ -5474,20 +6001,80 @@ __export(dockerInstall_exports, {
5474
6001
  dockerStatus: () => dockerStatus,
5475
6002
  dockerStop: () => dockerStop,
5476
6003
  dockerUpdate: () => dockerUpdate,
6004
+ ensurePgliteProxyCredentials: () => ensurePgliteProxyCredentials,
5477
6005
  imageTag: () => imageTag,
5478
6006
  normalizeProvider: () => normalizeProvider,
5479
6007
  poolLabel: () => poolLabel,
5480
6008
  readContainerConfig: () => readContainerConfig,
6009
+ resolveConductorProvider: () => resolveConductorProvider,
6010
+ resolveContainerName: () => resolveContainerName,
5481
6011
  resolveGraderPool: () => resolveGraderPool,
5482
6012
  resolveWorkerConfig: () => resolveWorkerConfig,
5483
6013
  splitWorkers: () => splitWorkers,
5484
6014
  waitForContainerReady: () => waitForContainerReady,
5485
6015
  waitForWorkersReady: () => waitForWorkersReady
5486
6016
  });
5487
- import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync16, readdirSync as readdirSync2 } from "fs";
5488
- import { homedir as homedir16 } from "os";
5489
- import { join as join14 } from "path";
6017
+ import {
6018
+ chmodSync as chmodSync3,
6019
+ copyFileSync,
6020
+ existsSync as existsSync19,
6021
+ mkdirSync as mkdirSync12,
6022
+ readFileSync as readFileSync17,
6023
+ readdirSync as readdirSync2,
6024
+ renameSync as renameSync7,
6025
+ writeFileSync as writeFileSync14
6026
+ } from "fs";
6027
+ import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "crypto";
6028
+ import { homedir as homedir17 } from "os";
6029
+ import { join as join15 } from "path";
5490
6030
  import { execSync as execSync3, spawnSync as spawnSync3 } from "child_process";
6031
+ function resolveContainerName(raw = process.env.SYNKRO_CONTAINER_NAME) {
6032
+ const value = String(raw || "").trim();
6033
+ if (!value) return "synkro-server";
6034
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(value)) {
6035
+ throw new Error("SYNKRO_CONTAINER_NAME must be a valid Docker container name (1-128 letters, digits, _, ., or -)");
6036
+ }
6037
+ return value;
6038
+ }
6039
+ function createPgliteScramVerifier(password, salt = randomBytes2(16)) {
6040
+ const iterations = 4096;
6041
+ const saltedPassword = pbkdf2Sync(password, salt, iterations, 32, "sha256");
6042
+ const clientKey = createHmac("sha256", saltedPassword).update("Client Key").digest();
6043
+ const storedKey = createHash2("sha256").update(clientKey).digest();
6044
+ const serverKey = createHmac("sha256", saltedPassword).update("Server Key").digest();
6045
+ return `SCRAM-SHA-256$${iterations}:${salt.toString("base64")}$${storedKey.toString("base64")}:${serverKey.toString("base64")}`;
6046
+ }
6047
+ function ensurePgliteProxyCredentials() {
6048
+ const hasPassword = existsSync19(PGLITE_PASSWORD_PATH) && readFileSync17(PGLITE_PASSWORD_PATH, "utf-8").trim().length > 0;
6049
+ const hasUserlist = existsSync19(PGLITE_USERLIST_PATH) && readFileSync17(PGLITE_USERLIST_PATH, "utf-8").trim().length > 0;
6050
+ if (hasPassword && hasUserlist) {
6051
+ chmodSync3(PGLITE_PASSWORD_PATH, 384);
6052
+ chmodSync3(PGLITE_USERLIST_PATH, 384);
6053
+ return;
6054
+ }
6055
+ const password = randomBytes2(24).toString("base64url");
6056
+ const verifier = createPgliteScramVerifier(password);
6057
+ const suffix = `${process.pid}.${Date.now()}.tmp`;
6058
+ const passwordTmp = `${PGLITE_PASSWORD_PATH}.${suffix}`;
6059
+ const userlistTmp = `${PGLITE_USERLIST_PATH}.${suffix}`;
6060
+ writeFileSync14(passwordTmp, `${password}
6061
+ `, { mode: 384 });
6062
+ writeFileSync14(userlistTmp, `"synkro" "${verifier}"
6063
+ `, { mode: 384 });
6064
+ renameSync7(passwordTmp, PGLITE_PASSWORD_PATH);
6065
+ renameSync7(userlistTmp, PGLITE_USERLIST_PATH);
6066
+ chmodSync3(PGLITE_PASSWORD_PATH, 384);
6067
+ chmodSync3(PGLITE_USERLIST_PATH, 384);
6068
+ }
6069
+ function resolveConductorProvider(pool, counts) {
6070
+ if (pool !== "auto") return pool;
6071
+ const ranked = [
6072
+ ["claude_code", counts.claudeWorkers],
6073
+ ["cursor", counts.cursorWorkers],
6074
+ ["codex", counts.codexWorkers]
6075
+ ];
6076
+ return ranked.reduce((best, candidate) => candidate[1] > best[1] ? candidate : best, ranked[0])[0];
6077
+ }
5491
6078
  function splitWorkers(total, providers) {
5492
6079
  const t = Math.max(0, Math.floor(total));
5493
6080
  const selected = ["claude_code", "cursor", "codex"].filter((provider) => providers.includes(provider));
@@ -5587,9 +6174,9 @@ function readSynkroFileConfig() {
5587
6174
  try {
5588
6175
  const root = execSync3("git rev-parse --show-toplevel 2>/dev/null", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
5589
6176
  if (!root) return { pool: "auto", warnings: [] };
5590
- const fp = join14(root, "synkro.toml");
6177
+ const fp = join15(root, "synkro.toml");
5591
6178
  if (!existsSync19(fp)) return { pool: "auto", warnings: [] };
5592
- const parsed = parseSynkroToml(readFileSync16(fp, "utf-8"));
6179
+ const parsed = parseSynkroToml(readFileSync17(fp, "utf-8"));
5593
6180
  return resolveGraderPool(parsed);
5594
6181
  } catch {
5595
6182
  }
@@ -5634,7 +6221,10 @@ function resolveWorkerConfig(rest) {
5634
6221
  const cw = sc.claudeWorkers || 0;
5635
6222
  const curw = sc.cursorWorkers || 0;
5636
6223
  const codw = sc.codexWorkers || 0;
5637
- if (cw + curw + codw > 0) return { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw, explicit };
6224
+ if (cw + curw + codw > 0) {
6225
+ const counts2 = { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
6226
+ return { ...counts2, conductorProvider: resolveConductorProvider(sc.pool, counts2), explicit };
6227
+ }
5638
6228
  }
5639
6229
  if (sc.pool === "cursor") {
5640
6230
  provs = ["cursor"];
@@ -5647,7 +6237,9 @@ function resolveWorkerConfig(rest) {
5647
6237
  if (provs.length === 0) provs = ["claude_code"];
5648
6238
  }
5649
6239
  }
5650
- return { ...splitWorkers(workers, provs), explicit };
6240
+ const counts = splitWorkers(workers, provs);
6241
+ const pool = provs.length === 1 ? provs[0] : "auto";
6242
+ return { ...counts, conductorProvider: resolveConductorProvider(pool, counts), explicit };
5651
6243
  }
5652
6244
  function imageTag() {
5653
6245
  const registry = process.env.SYNKRO_IMAGE_REGISTRY || "";
@@ -5667,7 +6259,7 @@ function assertDockerAvailable() {
5667
6259
  }
5668
6260
  function claudeCredsHostDir() {
5669
6261
  if (needsKeychainBridge()) return CLAUDE_CREDS_DIR;
5670
- return join14(homedir16(), ".claude");
6262
+ return join15(homedir17(), ".claude");
5671
6263
  }
5672
6264
  function resolveSynkroBin() {
5673
6265
  const which2 = spawnSync3("which", ["synkro"], { encoding: "utf-8", timeout: 5e3 });
@@ -5721,16 +6313,22 @@ async function dockerInstall(opts = {}) {
5721
6313
  const cursorWorkers = opts.cursorWorkers ?? 0;
5722
6314
  const codexWorkers = opts.codexWorkers ?? 0;
5723
6315
  const totalWorkers = claudeWorkers + cursorWorkers + codexWorkers;
5724
- const codexHomeDir = opts.codexHomeDir ?? join14(SYNKRO_DIR7, "codex-local-session");
5725
- if (codexWorkers > 0 && !existsSync19(join14(codexHomeDir, "auth.json"))) {
6316
+ const workerCounts = { claudeWorkers, cursorWorkers, codexWorkers };
6317
+ const conductorProvider = opts.conductorProvider ?? resolveConductorProvider("auto", workerCounts);
6318
+ const usesClaude = claudeWorkers > 0 || conductorProvider === "claude_code";
6319
+ const usesCursor = cursorWorkers > 0 || conductorProvider === "cursor";
6320
+ const usesCodex = codexWorkers > 0 || conductorProvider === "codex";
6321
+ const codexHomeDir = opts.codexHomeDir ?? join15(SYNKRO_DIR7, "codex-local-session");
6322
+ if (usesCodex && !existsSync19(join15(codexHomeDir, "auth.json"))) {
5726
6323
  throw new DockerInstallError(
5727
6324
  "Codex grader credentials are missing. Re-run `synkro install` to authorize an isolated Codex session."
5728
6325
  );
5729
6326
  }
5730
6327
  mkdirSync12(PGDATA_PATH, { recursive: true });
5731
6328
  mkdirSync12(BACKUP_DIR, { recursive: true });
6329
+ ensurePgliteProxyCredentials();
5732
6330
  mkdirSync12(CLAUDE_HOST_STATE_DIR, { recursive: true });
5733
- const hostClaudeJson = join14(homedir16(), ".claude.json");
6331
+ const hostClaudeJson = join15(homedir17(), ".claude.json");
5734
6332
  if (existsSync19(hostClaudeJson)) {
5735
6333
  copyFileSync(hostClaudeJson, CLAUDE_HOST_STATE_FILE);
5736
6334
  }
@@ -5743,14 +6341,14 @@ async function dockerInstall(opts = {}) {
5743
6341
  if (needsKeychainBridge()) {
5744
6342
  const claudeCredsPath = exportKeychainCreds();
5745
6343
  if (!claudeCredsPath) {
5746
- if (claudeWorkers > 0) {
6344
+ if (usesClaude) {
5747
6345
  throw new DockerInstallError(
5748
6346
  "Claude Code keychain entry not found. Run `claude login` (or open Claude Code and sign in) before installing the container."
5749
6347
  );
5750
6348
  }
5751
6349
  console.warn(" \u26A0 Claude Code keychain entry not found \u2014 live usage telemetry stays off until you sign in to Claude Code (grading is unaffected).");
5752
6350
  }
5753
- if (cursorWorkers > 0 && !cursorApiKeyConfigured()) {
6351
+ if (usesCursor && !cursorApiKeyConfigured()) {
5754
6352
  console.warn(" \u26A0 No Cursor API key found \u2014 Cursor grader workers will be idle.");
5755
6353
  console.warn(" Generate a key at cursor.com \u2192 Settings \u2192 API Keys, then:");
5756
6354
  console.warn(` echo 'YOUR_KEY' > ~/.synkro/cursor-creds/api-key && chmod 600 ~/.synkro/cursor-creds/api-key`);
@@ -5763,7 +6361,7 @@ async function dockerInstall(opts = {}) {
5763
6361
  console.warn(` Plist written to ${plist} \u2014 load manually with launchctl bootstrap when ready.`);
5764
6362
  }
5765
6363
  } else {
5766
- mkdirSync12(join14(homedir16(), ".claude"), { recursive: true });
6364
+ mkdirSync12(join15(homedir17(), ".claude"), { recursive: true });
5767
6365
  }
5768
6366
  const imageExistsLocally = () => spawnSync3("docker", ["image", "inspect", image], { stdio: "ignore", timeout: 3e4 }).status === 0;
5769
6367
  const skipPull = process.env.SYNKRO_SKIP_PULL === "1" || process.env.SYNKRO_SKIP_PULL === "true";
@@ -5802,7 +6400,7 @@ async function dockerInstall(opts = {}) {
5802
6400
  "-p",
5803
6401
  `127.0.0.1:${HOST_CWE_PORT}:8930`,
5804
6402
  "-p",
5805
- `127.0.0.1:${HOST_PGLITE_PORT}:5433`,
6403
+ `127.0.0.1:${HOST_PGLITE_PORT}:5434`,
5806
6404
  "-v",
5807
6405
  `${PGDATA_PATH}:/data/pgdata`,
5808
6406
  "-v",
@@ -5816,13 +6414,13 @@ async function dockerInstall(opts = {}) {
5816
6414
  "-v",
5817
6415
  `${credsDir}:/home/synkro/.claude:rw`,
5818
6416
  "-v",
5819
- `${join14(homedir16(), ".claude")}:/data/claude-host:ro`,
6417
+ `${join15(homedir17(), ".claude")}:/data/claude-host:ro`,
5820
6418
  "-v",
5821
6419
  `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
5822
6420
  // Cursor creds — mounted RW so the in-container refresher can rotate the
5823
6421
  // access token in place. Only mounted when the install includes Cursor.
5824
- ...cursorWorkers > 0 ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
5825
- ...codexWorkers > 0 ? ["-v", `${codexHomeDir}:/home/synkro/.codex:rw`] : [],
6422
+ ...usesCursor ? ["-v", `${CURSOR_CREDS_DIR}:/home/synkro/.cursor-creds:rw`] : [],
6423
+ ...usesCodex ? ["-v", `${codexHomeDir}:/home/synkro/.codex:rw`] : [],
5826
6424
  "-e",
5827
6425
  `WORKERS_PER_POOL=${totalWorkers}`,
5828
6426
  "-e",
@@ -5831,9 +6429,13 @@ async function dockerInstall(opts = {}) {
5831
6429
  `CURSOR_WORKERS=${cursorWorkers}`,
5832
6430
  "-e",
5833
6431
  `CODEX_WORKERS=${codexWorkers}`,
6432
+ "-e",
6433
+ `SYNKRO_CONDUCTOR_PROVIDER=${conductorProvider}`,
5834
6434
  // Pass through the batch-size lever if the operator set it. Defaults
5835
6435
  // inside the container to 5; clamped to [1, 20] by synkro-server.ts.
5836
6436
  ...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
6437
+ // Full verifier prompt/response tracing is explicit opt-in because it contains source code.
6438
+ ...process.env.SYNKRO_VERIFY_TRACE === "1" ? ["-e", "SYNKRO_VERIFY_TRACE=1"] : [],
5837
6439
  // Cursor grading model — tunable like SYNKRO_MAX_BATCH_SIZE.
5838
6440
  ...process.env.SYNKRO_CURSOR_MODEL ? ["-e", `SYNKRO_CURSOR_MODEL=${process.env.SYNKRO_CURSOR_MODEL}`] : [],
5839
6441
  // Fix-poll kill switch. Default ON in the image; a benchmark/headless run
@@ -5865,7 +6467,14 @@ async function dockerInstall(opts = {}) {
5865
6467
  if (run.status !== 0) {
5866
6468
  throw new DockerInstallError(`docker run failed (image ${image})`);
5867
6469
  }
5868
- return { image, hostMcpPort: HOST_MCP_PORT, hostGraderPort: HOST_GRADER_PORT, hostCwePort: HOST_CWE_PORT, hostPglitePort: HOST_PGLITE_PORT };
6470
+ return {
6471
+ image,
6472
+ hostMcpPort: HOST_MCP_PORT,
6473
+ hostGraderPort: HOST_GRADER_PORT,
6474
+ hostCwePort: HOST_CWE_PORT,
6475
+ hostPglitePort: HOST_PGLITE_PORT,
6476
+ pglitePasswordPath: PGLITE_PASSWORD_PATH
6477
+ };
5869
6478
  }
5870
6479
  async function waitForContainerReady(timeoutMs = 6e4) {
5871
6480
  const start = Date.now();
@@ -5920,7 +6529,7 @@ function dockerStatus() {
5920
6529
  return {
5921
6530
  running: true,
5922
6531
  image: imageTag(),
5923
- healthz: `http://127.0.0.1:${HOST_MCP_PORT}/`
6532
+ healthz: `http://127.0.0.1:${HOST_MCP_PORT}/health`
5924
6533
  };
5925
6534
  }
5926
6535
  function readContainerConfig() {
@@ -5949,6 +6558,7 @@ function readContainerConfig() {
5949
6558
  claudeWorkers: num(get("CLAUDE_WORKERS")),
5950
6559
  cursorWorkers: num(get("CURSOR_WORKERS")),
5951
6560
  codexWorkers: num(get("CODEX_WORKERS")),
6561
+ conductorProvider: normalizeProvider(get("SYNKRO_CONDUCTOR_PROVIDER") || "") || void 0,
5952
6562
  connectedRepo: get("SYNKRO_CONNECTED_REPO") || void 0
5953
6563
  };
5954
6564
  }
@@ -6066,23 +6676,34 @@ function checkPgdata() {
6066
6676
  if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
6067
6677
  return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
6068
6678
  }
6069
- var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
6679
+ var SYNKRO_DIR7, MCP_JWT_PATH, PGDATA_PATH, PGLITE_PASSWORD_PATH, PGLITE_USERLIST_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, HOST_PGLITE_PORT, CONTAINER_NAME, defaultImageVersion, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
6070
6680
  var init_dockerInstall = __esm({
6071
6681
  "cli/local-cc/dockerInstall.ts"() {
6072
6682
  "use strict";
6073
6683
  init_agentDetect();
6074
6684
  init_macKeychain();
6075
- SYNKRO_DIR7 = join14(homedir16(), ".synkro");
6076
- MCP_JWT_PATH = join14(SYNKRO_DIR7, ".mcp-jwt");
6077
- PGDATA_PATH = join14(SYNKRO_DIR7, "pgdata");
6078
- CLAUDE_HOST_STATE_DIR = join14(SYNKRO_DIR7, "claude-host-state");
6079
- CLAUDE_HOST_STATE_FILE = join14(CLAUDE_HOST_STATE_DIR, ".claude.json");
6685
+ SYNKRO_DIR7 = join15(homedir17(), ".synkro");
6686
+ MCP_JWT_PATH = join15(SYNKRO_DIR7, ".mcp-jwt");
6687
+ PGDATA_PATH = join15(SYNKRO_DIR7, "pgdata");
6688
+ PGLITE_PASSWORD_PATH = join15(SYNKRO_DIR7, ".pglite-password");
6689
+ PGLITE_USERLIST_PATH = join15(SYNKRO_DIR7, ".pglite-userlist");
6690
+ CLAUDE_HOST_STATE_DIR = join15(SYNKRO_DIR7, "claude-host-state");
6691
+ CLAUDE_HOST_STATE_FILE = join15(CLAUDE_HOST_STATE_DIR, ".claude.json");
6080
6692
  HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
6081
6693
  HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
6082
6694
  HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
6083
6695
  HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
6084
- CONTAINER_NAME = "synkro-server";
6085
- DEFAULT_IMAGE = "ghcr.io/synkro-sh/synkro-server:latest";
6696
+ CONTAINER_NAME = resolveContainerName();
6697
+ defaultImageVersion = () => {
6698
+ if (true) return "1.7.89";
6699
+ try {
6700
+ const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
6701
+ if (pkg.version) return pkg.version;
6702
+ } catch {
6703
+ }
6704
+ return process.env.npm_package_version || "0.0.0-dev";
6705
+ };
6706
+ DEFAULT_IMAGE = `ghcr.io/synkro-sh/synkro-server:${defaultImageVersion()}`;
6086
6707
  DockerInstallError = class extends Error {
6087
6708
  constructor(message, cause) {
6088
6709
  super(message);
@@ -6091,17 +6712,17 @@ var init_dockerInstall = __esm({
6091
6712
  }
6092
6713
  cause;
6093
6714
  };
6094
- BACKUP_DIR = join14(SYNKRO_DIR7, "pgdata-backups");
6715
+ BACKUP_DIR = join15(SYNKRO_DIR7, "pgdata-backups");
6095
6716
  }
6096
6717
  });
6097
6718
 
6098
6719
  // cli/local-cc/setupToken.ts
6099
6720
  import { spawn as nodeSpawn } from "child_process";
6100
- import { readFileSync as readFileSync17, unlinkSync as unlinkSync6 } from "fs";
6101
- import { homedir as homedir17, platform as platform4 } from "os";
6102
- import { join as join15 } from "path";
6721
+ import { readFileSync as readFileSync18, unlinkSync as unlinkSync6 } from "fs";
6722
+ import { homedir as homedir18, platform as platform4 } from "os";
6723
+ import { join as join16 } from "path";
6103
6724
  function captureClaudeSetupToken() {
6104
- const tmpFile = join15(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
6725
+ const tmpFile = join16(SYNKRO_DIR8, `token-capture-${Date.now()}.raw`);
6105
6726
  const isMac = platform4() === "darwin";
6106
6727
  const bin = "script";
6107
6728
  const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
@@ -6115,7 +6736,7 @@ function captureClaudeSetupToken() {
6115
6736
  proc.on("close", (code) => {
6116
6737
  let raw = "";
6117
6738
  try {
6118
- raw = readFileSync17(tmpFile, "utf-8");
6739
+ raw = readFileSync18(tmpFile, "utf-8");
6119
6740
  } catch (e) {
6120
6741
  reject(new Error(`Could not read script output file: ${e.message}`));
6121
6742
  return;
@@ -6181,15 +6802,15 @@ var SYNKRO_DIR8;
6181
6802
  var init_setupToken = __esm({
6182
6803
  "cli/local-cc/setupToken.ts"() {
6183
6804
  "use strict";
6184
- SYNKRO_DIR8 = join15(homedir17(), ".synkro");
6805
+ SYNKRO_DIR8 = join16(homedir18(), ".synkro");
6185
6806
  }
6186
6807
  });
6187
6808
 
6188
6809
  // cli/local-cc/codexCloudSetup.ts
6189
6810
  import { spawn as nodeSpawn2, spawnSync as spawnSync4 } from "child_process";
6190
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
6191
- import { homedir as homedir18 } from "os";
6192
- import { join as join16 } from "path";
6811
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync13, rmSync, existsSync as existsSync20 } from "fs";
6812
+ import { homedir as homedir19 } from "os";
6813
+ import { join as join17 } from "path";
6193
6814
  function findCodexBinary() {
6194
6815
  if (process.env.SYNKRO_CODEX_BIN) return process.env.SYNKRO_CODEX_BIN;
6195
6816
  const r = spawnSync4("which", ["codex"], { encoding: "utf-8" });
@@ -6198,7 +6819,7 @@ function findCodexBinary() {
6198
6819
  }
6199
6820
  function runCodexLogin(codexBin, codexHome) {
6200
6821
  mkdirSync13(codexHome, { recursive: true, mode: 448 });
6201
- writeFileSync14(join16(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
6822
+ writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
6202
6823
  return new Promise((resolve6, reject) => {
6203
6824
  const proc = nodeSpawn2(codexBin, ["login"], {
6204
6825
  stdio: "inherit",
@@ -6230,13 +6851,13 @@ async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
6230
6851
  } catch (e) {
6231
6852
  return { ok: false, error: `Codex login failed: ${e.message}` };
6232
6853
  }
6233
- const authPath = join16(CODEX_CLOUD_HOME, "auth.json");
6854
+ const authPath = join17(CODEX_CLOUD_HOME, "auth.json");
6234
6855
  if (!existsSync20(authPath)) {
6235
6856
  return { ok: false, error: "codex login completed but no auth.json was written \u2014 did the browser approval finish?" };
6236
6857
  }
6237
6858
  let auth;
6238
6859
  try {
6239
- auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6860
+ auth = JSON.parse(readFileSync19(authPath, "utf-8"));
6240
6861
  } catch (e) {
6241
6862
  return { ok: false, error: `could not read codex auth.json: ${e.message}` };
6242
6863
  }
@@ -6270,7 +6891,7 @@ async function setupCodexLocal(onStatus) {
6270
6891
  if (!codexBin) {
6271
6892
  return { ok: false, error: "Codex CLI not found on PATH. Install Codex, then re-run `synkro install`." };
6272
6893
  }
6273
- const authPath = join16(CODEX_LOCAL_HOME, "auth.json");
6894
+ const authPath = join17(CODEX_LOCAL_HOME, "auth.json");
6274
6895
  if (!existsSync20(authPath)) {
6275
6896
  onStatus?.("Opening your browser to authorize an isolated Codex session for the local grader\u2026");
6276
6897
  try {
@@ -6280,7 +6901,7 @@ async function setupCodexLocal(onStatus) {
6280
6901
  }
6281
6902
  }
6282
6903
  try {
6283
- const auth = JSON.parse(readFileSync18(authPath, "utf-8"));
6904
+ const auth = JSON.parse(readFileSync19(authPath, "utf-8"));
6284
6905
  if (!auth.tokens?.refresh_token) throw new Error("auth.json has no refresh token");
6285
6906
  } catch (e) {
6286
6907
  return { ok: false, error: `Codex local grader auth is invalid: ${e.message}` };
@@ -6292,9 +6913,9 @@ var SYNKRO_DIR9, CODEX_CLOUD_HOME, CODEX_LOCAL_HOME;
6292
6913
  var init_codexCloudSetup = __esm({
6293
6914
  "cli/local-cc/codexCloudSetup.ts"() {
6294
6915
  "use strict";
6295
- SYNKRO_DIR9 = join16(homedir18(), ".synkro");
6296
- CODEX_CLOUD_HOME = join16(SYNKRO_DIR9, "codex-cloud-session");
6297
- CODEX_LOCAL_HOME = join16(SYNKRO_DIR9, "codex-local-session");
6916
+ SYNKRO_DIR9 = join17(homedir19(), ".synkro");
6917
+ CODEX_CLOUD_HOME = join17(SYNKRO_DIR9, "codex-cloud-session");
6918
+ CODEX_LOCAL_HOME = join17(SYNKRO_DIR9, "codex-local-session");
6298
6919
  }
6299
6920
  });
6300
6921
 
@@ -6316,21 +6937,21 @@ __export(ptyShim_exports, {
6316
6937
  import {
6317
6938
  existsSync as existsSync21,
6318
6939
  mkdirSync as mkdirSync14,
6319
- writeFileSync as writeFileSync15,
6320
- chmodSync as chmodSync3,
6321
- readFileSync as readFileSync19,
6940
+ writeFileSync as writeFileSync16,
6941
+ chmodSync as chmodSync4,
6942
+ readFileSync as readFileSync20,
6322
6943
  rmSync as rmSync2,
6323
6944
  realpathSync,
6324
6945
  symlinkSync,
6325
6946
  lstatSync,
6326
6947
  readdirSync as readdirSync3
6327
6948
  } from "fs";
6328
- import { homedir as homedir19 } from "os";
6329
- import { join as join17 } from "path";
6949
+ import { homedir as homedir20 } from "os";
6950
+ import { join as join18 } from "path";
6330
6951
  import { spawnSync as spawnSync5, spawn as spawn3 } from "child_process";
6331
6952
  function rcFiles() {
6332
- const h = homedir19();
6333
- return [join17(h, ".zshrc"), join17(h, ".bashrc"), join17(h, ".bash_profile")];
6953
+ const h = homedir20();
6954
+ return [join18(h, ".zshrc"), join18(h, ".bashrc"), join18(h, ".bash_profile")];
6334
6955
  }
6335
6956
  function resolveRealClaude() {
6336
6957
  const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
@@ -6342,7 +6963,7 @@ function resolveRealClaude() {
6342
6963
  return p;
6343
6964
  }
6344
6965
  }
6345
- for (const c of [join17(homedir19(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6966
+ for (const c of [join18(homedir20(), ".local", "bin", "claude"), "/usr/local/bin/claude", "/opt/homebrew/bin/claude"]) {
6346
6967
  if (existsSync21(c)) {
6347
6968
  try {
6348
6969
  return realpathSync(c);
@@ -6357,7 +6978,7 @@ function findClaudeLink() {
6357
6978
  const r = spawnSync5("bash", ["-lc", `PATH="$(printf '%s' "$PATH" | sed "s#$HOME/.synkro/bin:##g")" command -v claude`], { encoding: "utf-8" });
6358
6979
  let linkPath = (r.stdout || "").trim();
6359
6980
  if (!linkPath) {
6360
- const c = join17(homedir19(), ".local", "bin", "claude");
6981
+ const c = join18(homedir20(), ".local", "bin", "claude");
6361
6982
  if (existsSync21(c)) linkPath = c;
6362
6983
  else return null;
6363
6984
  }
@@ -6371,7 +6992,7 @@ function findClaudeLink() {
6371
6992
  }
6372
6993
  function isOurShim(path) {
6373
6994
  try {
6374
- return readFileSync19(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6995
+ return readFileSync20(path, "utf-8").slice(0, 300).includes("Synkro pty shim");
6375
6996
  } catch {
6376
6997
  return false;
6377
6998
  }
@@ -6384,7 +7005,7 @@ function shadowClaude() {
6384
7005
  }
6385
7006
  const { linkPath, realTarget } = found;
6386
7007
  if (isOurShim(linkPath)) {
6387
- console.log(` \xB7 ${linkPath.replace(homedir19(), "~")} already shadowed`);
7008
+ console.log(` \xB7 ${linkPath.replace(homedir20(), "~")} already shadowed`);
6388
7009
  return;
6389
7010
  }
6390
7011
  let wasSymlink = false;
@@ -6394,14 +7015,14 @@ function shadowClaude() {
6394
7015
  }
6395
7016
  const state = { linkPath, realTarget, wasSymlink };
6396
7017
  try {
6397
- writeFileSync15(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
7018
+ writeFileSync16(SHADOW_STATE_FILE, JSON.stringify(state), "utf-8");
6398
7019
  } catch {
6399
7020
  }
6400
7021
  try {
6401
7022
  rmSync2(linkPath, { force: true });
6402
- writeFileSync15(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
6403
- chmodSync3(linkPath, 493);
6404
- console.log(` \u2713 shadowed ${linkPath.replace(homedir19(), "~")} \u2192 shim (real: ${realTarget.replace(homedir19(), "~")})`);
7023
+ writeFileSync16(linkPath, SHIM_SOURCE.replace("__BAKED_CLAUDE__", realTarget), "utf-8");
7024
+ chmodSync4(linkPath, 493);
7025
+ console.log(` \u2713 shadowed ${linkPath.replace(homedir20(), "~")} \u2192 shim (real: ${realTarget.replace(homedir20(), "~")})`);
6405
7026
  } catch (e) {
6406
7027
  console.warn(` \u26A0 could not shadow ${linkPath}: ${e.message}`);
6407
7028
  }
@@ -6409,7 +7030,7 @@ function shadowClaude() {
6409
7030
  function unshadowClaude() {
6410
7031
  let state;
6411
7032
  try {
6412
- state = JSON.parse(readFileSync19(SHADOW_STATE_FILE, "utf-8"));
7033
+ state = JSON.parse(readFileSync20(SHADOW_STATE_FILE, "utf-8"));
6413
7034
  } catch {
6414
7035
  return;
6415
7036
  }
@@ -6418,7 +7039,7 @@ function unshadowClaude() {
6418
7039
  if (existsSync21(state.linkPath) && !isOurShim(state.linkPath)) return;
6419
7040
  rmSync2(state.linkPath, { force: true });
6420
7041
  symlinkSync(state.realTarget, state.linkPath);
6421
- console.log(`\u2713 restored ${state.linkPath.replace(homedir19(), "~")} \u2192 ${state.realTarget.replace(homedir19(), "~")}`);
7042
+ console.log(`\u2713 restored ${state.linkPath.replace(homedir20(), "~")} \u2192 ${state.realTarget.replace(homedir20(), "~")}`);
6422
7043
  } catch (e) {
6423
7044
  console.warn(` \u26A0 could not restore claude: ${e.message} \u2014 run: ln -sf ${state.realTarget} ${state.linkPath}`);
6424
7045
  }
@@ -6429,13 +7050,13 @@ function addPathBlock() {
6429
7050
  if (!existsSync21(rc) && rc.endsWith(".bash_profile")) continue;
6430
7051
  let body = "";
6431
7052
  try {
6432
- body = readFileSync19(rc, "utf-8");
7053
+ body = readFileSync20(rc, "utf-8");
6433
7054
  } catch {
6434
7055
  }
6435
7056
  const cleaned = stripBlock(body);
6436
7057
  const next = cleaned.replace(/\n*$/, "") + (cleaned ? "\n\n" : "") + RC_BLOCK + "\n";
6437
7058
  try {
6438
- writeFileSync15(rc, next, "utf-8");
7059
+ writeFileSync16(rc, next, "utf-8");
6439
7060
  touched.push(rc);
6440
7061
  } catch {
6441
7062
  }
@@ -6454,12 +7075,12 @@ function installPtyShim() {
6454
7075
  mkdirSync14(SHIM_BIN_DIR, { recursive: true });
6455
7076
  mkdirSync14(PTY_STATE_DIR, { recursive: true });
6456
7077
  const real = resolveRealClaude();
6457
- writeFileSync15(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
6458
- chmodSync3(SHIM_PATH, 493);
7078
+ writeFileSync16(SHIM_PATH, SHIM_SOURCE.replace("__BAKED_CLAUDE__", real), "utf-8");
7079
+ chmodSync4(SHIM_PATH, 493);
6459
7080
  const touched = addPathBlock();
6460
7081
  console.log(` \u2713 pty routing shim installed (real claude: ${real})`);
6461
7082
  shadowClaude();
6462
- if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir19(), "~")).join(", ")}`);
7083
+ if (touched.length) console.log(` added ~/.synkro/bin to PATH in: ${touched.map((t) => t.replace(homedir20(), "~")).join(", ")}`);
6463
7084
  }
6464
7085
  function uninstallPtyShim() {
6465
7086
  try {
@@ -6477,10 +7098,10 @@ function uninstallPtyShim() {
6477
7098
  for (const rc of rcFiles()) {
6478
7099
  if (!existsSync21(rc)) continue;
6479
7100
  try {
6480
- const body = readFileSync19(rc, "utf-8");
7101
+ const body = readFileSync20(rc, "utf-8");
6481
7102
  if (body.includes(RC_BEGIN)) {
6482
- writeFileSync15(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
6483
- cleaned.push(rc.replace(homedir19(), "~"));
7103
+ writeFileSync16(rc, stripBlock(body).replace(/\n{3,}/g, "\n\n"), "utf-8");
7104
+ cleaned.push(rc.replace(homedir20(), "~"));
6484
7105
  }
6485
7106
  } catch {
6486
7107
  }
@@ -6512,7 +7133,7 @@ function listSessions(opts = {}) {
6512
7133
  const out = [];
6513
7134
  for (const f of files) {
6514
7135
  try {
6515
- const r = JSON.parse(readFileSync19(join17(SESSIONS_DIR, f), "utf-8"));
7136
+ const r = JSON.parse(readFileSync20(join18(SESSIONS_DIR, f), "utf-8"));
6516
7137
  if (!r || !r.session_id) continue;
6517
7138
  if (liveOnly) {
6518
7139
  const s = r.tmux_session;
@@ -6541,7 +7162,7 @@ function resolveTargetSession(override) {
6541
7162
  const own = currentTmuxSession();
6542
7163
  const fromFile = (() => {
6543
7164
  try {
6544
- return readFileSync19(ACTIVE_SESSION_FILE, "utf-8").trim();
7165
+ return readFileSync20(ACTIVE_SESSION_FILE, "utf-8").trim();
6545
7166
  } catch {
6546
7167
  return "";
6547
7168
  }
@@ -6567,7 +7188,7 @@ function injectModel(model, sessionOverride) {
6567
7188
  sendKeys("Escape");
6568
7189
  sendKeys("-l", `/model ${model}`);
6569
7190
  sendKeys("Enter");
6570
- const pidFile = join17(PTY_STATE_DIR, `poll-${session}.pid`);
7191
+ const pidFile = join18(PTY_STATE_DIR, `poll-${session}.pid`);
6571
7192
  try {
6572
7193
  mkdirSync14(PTY_STATE_DIR, { recursive: true });
6573
7194
  } catch {
@@ -6581,13 +7202,13 @@ var SYNKRO_DIR10, SHIM_BIN_DIR, SHIM_PATH, PTY_STATE_DIR, ACTIVE_SESSION_FILE, S
6581
7202
  var init_ptyShim = __esm({
6582
7203
  "cli/local-cc/ptyShim.ts"() {
6583
7204
  "use strict";
6584
- SYNKRO_DIR10 = join17(homedir19(), ".synkro");
6585
- SHIM_BIN_DIR = join17(SYNKRO_DIR10, "bin");
6586
- SHIM_PATH = join17(SHIM_BIN_DIR, "claude");
6587
- PTY_STATE_DIR = join17(SYNKRO_DIR10, "pty");
6588
- ACTIVE_SESSION_FILE = join17(PTY_STATE_DIR, "active");
6589
- SHADOW_STATE_FILE = join17(PTY_STATE_DIR, "shadow.json");
6590
- SESSIONS_DIR = join17(PTY_STATE_DIR, "sessions");
7205
+ SYNKRO_DIR10 = join18(homedir20(), ".synkro");
7206
+ SHIM_BIN_DIR = join18(SYNKRO_DIR10, "bin");
7207
+ SHIM_PATH = join18(SHIM_BIN_DIR, "claude");
7208
+ PTY_STATE_DIR = join18(SYNKRO_DIR10, "pty");
7209
+ ACTIVE_SESSION_FILE = join18(PTY_STATE_DIR, "active");
7210
+ SHADOW_STATE_FILE = join18(PTY_STATE_DIR, "shadow.json");
7211
+ SESSIONS_DIR = join18(PTY_STATE_DIR, "sessions");
6591
7212
  SHIM_SESSION_PREFIX = "synkro-cc-";
6592
7213
  RC_BEGIN = "# >>> synkro pty shim (managed \u2014 do not edit) >>>";
6593
7214
  RC_END = "# <<< synkro pty shim <<<";
@@ -6645,6 +7266,202 @@ exit $RC
6645
7266
  }
6646
7267
  });
6647
7268
 
7269
+ // cli/installer/graderSmoke.ts
7270
+ function isPrimerAckVerdict(value) {
7271
+ return /<category>\s*primer_ack\s*<\/category>/i.test(value) || /<reason>\s*(?:batch )?primer received\s*<\/reason>/i.test(value);
7272
+ }
7273
+ function isValidRiskyEditSmokeVerdict(value) {
7274
+ return /<synkro-verdict(?:\s[^>]*)?>[\s\S]*<\/synkro-verdict>/i.test(value) && /<ok>\s*false\s*<\/ok>/i.test(value) && !isPrimerAckVerdict(value);
7275
+ }
7276
+ var init_graderSmoke = __esm({
7277
+ "cli/installer/graderSmoke.ts"() {
7278
+ "use strict";
7279
+ }
7280
+ });
7281
+
7282
+ // cli/scanning/codexTranscriptUsage.ts
7283
+ function rawCounters(value) {
7284
+ return {
7285
+ input: finiteCounter(value?.input_tokens),
7286
+ output: finiteCounter(value?.output_tokens),
7287
+ cacheCreation: finiteCounter(value?.cache_write_input_tokens),
7288
+ cacheRead: finiteCounter(value?.cached_input_tokens)
7289
+ };
7290
+ }
7291
+ function counterDelta(current, previous) {
7292
+ return current >= previous ? current - previous : current;
7293
+ }
7294
+ function normalizedUsage(raw) {
7295
+ return {
7296
+ // Codex input_tokens includes cached/cache-write details. Store only the
7297
+ // uncached remainder in input_tokens so totals and pricing never count the
7298
+ // same prompt tokens twice.
7299
+ input_tokens: Math.max(0, raw.input - raw.cacheCreation - raw.cacheRead),
7300
+ output_tokens: raw.output,
7301
+ cache_creation_input_tokens: raw.cacheCreation,
7302
+ cache_read_input_tokens: raw.cacheRead
7303
+ };
7304
+ }
7305
+ function addUsage(a, b) {
7306
+ return {
7307
+ input_tokens: (a?.input_tokens || 0) + b.input_tokens,
7308
+ output_tokens: (a?.output_tokens || 0) + b.output_tokens,
7309
+ cache_creation_input_tokens: (a?.cache_creation_input_tokens || 0) + b.cache_creation_input_tokens,
7310
+ cache_read_input_tokens: (a?.cache_read_input_tokens || 0) + b.cache_read_input_tokens
7311
+ };
7312
+ }
7313
+ function parseCodexTranscriptUsage(transcript, options = {}) {
7314
+ if (!transcript) return null;
7315
+ const lines = transcript.split("\n");
7316
+ const hasCanonicalAssistant = lines.some((line) => {
7317
+ try {
7318
+ const entry = JSON.parse(line);
7319
+ return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
7320
+ } catch {
7321
+ return false;
7322
+ }
7323
+ });
7324
+ const turnsByLine = /* @__PURE__ */ new Map();
7325
+ let model = "";
7326
+ let lastAssistantLine = -1;
7327
+ let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
7328
+ let latest = null;
7329
+ let sawSnapshot = false;
7330
+ for (let i = 0; i < lines.length; i++) {
7331
+ const line = lines[i].trim();
7332
+ if (!line) continue;
7333
+ let entry;
7334
+ try {
7335
+ entry = JSON.parse(line);
7336
+ } catch {
7337
+ continue;
7338
+ }
7339
+ if (entry?.type === "turn_context" && typeof entry?.payload?.model === "string") {
7340
+ model = entry.payload.model;
7341
+ continue;
7342
+ }
7343
+ if (!hasCanonicalAssistant && entry?.type === "response_item" && entry?.payload?.type === "message" && entry?.payload?.role === "assistant") {
7344
+ lastAssistantLine = i;
7345
+ continue;
7346
+ }
7347
+ if (entry?.type !== "event_msg") continue;
7348
+ if (entry?.payload?.type === "agent_message") {
7349
+ lastAssistantLine = i;
7350
+ continue;
7351
+ }
7352
+ if (entry?.payload?.type !== "token_count") continue;
7353
+ const eventModel = entry?.payload?.model ?? entry?.model;
7354
+ if (typeof eventModel === "string" && eventModel) model = eventModel;
7355
+ const total = entry?.payload?.info?.total_token_usage;
7356
+ if (!total || typeof total !== "object") continue;
7357
+ const current = rawCounters(total);
7358
+ latest = current;
7359
+ if (options.partial && !sawSnapshot) {
7360
+ previous = current;
7361
+ sawSnapshot = true;
7362
+ continue;
7363
+ }
7364
+ sawSnapshot = true;
7365
+ const delta = normalizedUsage({
7366
+ input: counterDelta(current.input, previous.input),
7367
+ output: counterDelta(current.output, previous.output),
7368
+ cacheCreation: counterDelta(current.cacheCreation, previous.cacheCreation),
7369
+ cacheRead: counterDelta(current.cacheRead, previous.cacheRead)
7370
+ });
7371
+ previous = current;
7372
+ if (lastAssistantLine >= 0) {
7373
+ const prior = turnsByLine.get(lastAssistantLine);
7374
+ turnsByLine.set(lastAssistantLine, {
7375
+ lineIndex: lastAssistantLine,
7376
+ model,
7377
+ usage: addUsage(prior?.usage, delta)
7378
+ });
7379
+ }
7380
+ }
7381
+ if (model) {
7382
+ for (const turn of turnsByLine.values()) {
7383
+ if (!turn.model) turn.model = model;
7384
+ }
7385
+ }
7386
+ if (!latest) return null;
7387
+ return { model, total: normalizedUsage(latest), turnsByLine };
7388
+ }
7389
+ var finiteCounter;
7390
+ var init_codexTranscriptUsage = __esm({
7391
+ "cli/scanning/codexTranscriptUsage.ts"() {
7392
+ "use strict";
7393
+ finiteCounter = (value) => {
7394
+ const n = Number(value);
7395
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
7396
+ };
7397
+ }
7398
+ });
7399
+
7400
+ // cli/scanning/codexTranscriptMessages.ts
7401
+ import { createHash as createHash3 } from "crypto";
7402
+ function textContent(content) {
7403
+ if (typeof content === "string") return content.trim();
7404
+ if (!Array.isArray(content)) return "";
7405
+ return content.map((block) => {
7406
+ if (typeof block === "string") return block;
7407
+ return block?.type === "text" || block?.type === "input_text" || block?.type === "output_text" ? String(block.text || "") : "";
7408
+ }).join(" ").trim();
7409
+ }
7410
+ function isCodexConversationNoise(content) {
7411
+ const value = content.trimStart();
7412
+ return value.startsWith("[synkro:") || value.startsWith("claude-code: {") || value.startsWith("RAW claude-code agent:") || value.startsWith("Guard: (R") || value.startsWith("<developer") || value.startsWith("<system") || value.startsWith("<permissions instructions>") || value.startsWith("<app-context>") || value.startsWith("<environment_context>") || value.startsWith("<recommended_plugins>");
7413
+ }
7414
+ function parseCodexConversationMessages(transcript) {
7415
+ if (!transcript) return [];
7416
+ const entries = [];
7417
+ for (const [lineIndex, line] of transcript.split("\n").entries()) {
7418
+ const value = line.trim();
7419
+ if (!value) continue;
7420
+ try {
7421
+ entries.push({ lineIndex, entry: JSON.parse(value) });
7422
+ } catch {
7423
+ }
7424
+ }
7425
+ const hasCanonicalMessages = entries.some(({ entry }) => entry?.type === "event_msg" && (entry?.payload?.type === "user_message" || entry?.payload?.type === "agent_message"));
7426
+ const messages = [];
7427
+ for (const { lineIndex, entry } of entries) {
7428
+ let role = null;
7429
+ let content;
7430
+ let itemId = "";
7431
+ const eventType = entry?.type === "event_msg" ? entry?.payload?.type : "";
7432
+ if (eventType === "user_message" || eventType === "agent_message") {
7433
+ role = eventType === "user_message" ? "user" : "assistant";
7434
+ content = entry?.payload?.message;
7435
+ } else if (!hasCanonicalMessages && entry?.type === "response_item" && entry?.payload?.type === "message") {
7436
+ const responseRole = entry?.payload?.role;
7437
+ if (responseRole !== "user" && responseRole !== "assistant") continue;
7438
+ role = responseRole;
7439
+ content = entry?.payload?.content;
7440
+ itemId = typeof entry?.payload?.id === "string" ? entry.payload.id : "";
7441
+ } else {
7442
+ continue;
7443
+ }
7444
+ if (!role) continue;
7445
+ const text = textContent(content);
7446
+ if (!text || isCodexConversationNoise(text)) continue;
7447
+ const timestamp = typeof entry?.timestamp === "string" && entry.timestamp ? entry.timestamp : void 0;
7448
+ const uuid = typeof entry?.uuid === "string" && entry.uuid ? entry.uuid : itemId || createHash3("sha256").update((timestamp || "") + "\0" + role + "\0" + text).digest("hex").slice(0, 32);
7449
+ messages.push({
7450
+ lineIndex,
7451
+ role,
7452
+ content: text.slice(0, 8e3),
7453
+ uuid,
7454
+ ...timestamp ? { timestamp } : {}
7455
+ });
7456
+ }
7457
+ return messages;
7458
+ }
7459
+ var init_codexTranscriptMessages = __esm({
7460
+ "cli/scanning/codexTranscriptMessages.ts"() {
7461
+ "use strict";
7462
+ }
7463
+ });
7464
+
6648
7465
  // cli/commands/install.ts
6649
7466
  var install_exports = {};
6650
7467
  __export(install_exports, {
@@ -6660,12 +7477,12 @@ __export(install_exports, {
6660
7477
  syncSkillFiles: () => syncSkillFiles,
6661
7478
  writeHookScripts: () => writeHookScripts
6662
7479
  });
6663
- import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync16, chmodSync as chmodSync4, readFileSync as readFileSync20, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
6664
- import { homedir as homedir20 } from "os";
6665
- import { join as join18, isAbsolute, resolve as resolve4 } from "path";
7480
+ import { existsSync as existsSync22, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17, chmodSync as chmodSync5, readFileSync as readFileSync21, readdirSync as readdirSync4, unlinkSync as unlinkSync7, statSync as statSync2 } from "fs";
7481
+ import { homedir as homedir21 } from "os";
7482
+ import { join as join19, isAbsolute, resolve as resolve4 } from "path";
6666
7483
  import { execSync as execSync4, spawn as spawn4 } from "child_process";
6667
7484
  import { createInterface as createInterface2 } from "readline";
6668
- import { createHash as createHash2 } from "crypto";
7485
+ import { createHash as createHash4 } from "crypto";
6669
7486
  function resolvePersistedHookMode() {
6670
7487
  return "stub";
6671
7488
  }
@@ -6793,34 +7610,37 @@ function ensureSynkroDir() {
6793
7610
  mkdirSync15(HOOKS_DIR, { recursive: true });
6794
7611
  mkdirSync15(BIN_DIR, { recursive: true });
6795
7612
  mkdirSync15(OFFSETS_DIR, { recursive: true });
6796
- mkdirSync15(join18(SYNKRO_DIR11, "sessions"), { recursive: true });
7613
+ mkdirSync15(join19(SYNKRO_DIR11, "sessions"), { recursive: true });
6797
7614
  }
6798
7615
  function writeHookScripts() {
6799
- const installExtractCorePath = join18(HOOKS_DIR, "installExtractCore.ts");
6800
- const bashScriptPath = join18(HOOKS_DIR, "cc-bash-judge.ts");
6801
- const skillJudgeScriptPath = join18(HOOKS_DIR, "cc-skill-judge.ts");
6802
- const cursorSkillJudgePath = join18(HOOKS_DIR, "cursor-skill-judge.ts");
6803
- const bashFollowupScriptPath = join18(HOOKS_DIR, "cc-bash-followup.ts");
6804
- const editPrecheckScriptPath = join18(HOOKS_DIR, "cc-edit-precheck.ts");
6805
- const cwePrecheckScriptPath = join18(HOOKS_DIR, "cc-cwe-precheck.ts");
6806
- const cvePrecheckScriptPath = join18(HOOKS_DIR, "cc-cve-precheck.ts");
6807
- const planJudgeScriptPath = join18(HOOKS_DIR, "cc-plan-judge.ts");
6808
- const agentJudgeScriptPath = join18(HOOKS_DIR, "cc-agent-judge.ts");
6809
- const stopSummaryScriptPath = join18(HOOKS_DIR, "cc-stop-summary.ts");
6810
- const sessionStartScriptPath = join18(HOOKS_DIR, "cc-session-start.ts");
6811
- const transcriptSyncScriptPath = join18(HOOKS_DIR, "cc-transcript-sync.ts");
6812
- const userPromptSubmitScriptPath = join18(HOOKS_DIR, "cc-user-prompt-submit.ts");
6813
- const promptRouteScriptPath = join18(HOOKS_DIR, "cc-prompt-route.ts");
6814
- const commonScriptPath = join18(HOOKS_DIR, "_synkro-common.ts");
6815
- const commonBashScriptPath = join18(HOOKS_DIR, "_synkro-common.sh");
6816
- const installScanScriptPath = join18(HOOKS_DIR, "cc-install-scan.ts");
6817
- const cursorBashJudgePath = join18(HOOKS_DIR, "cursor-bash-judge.ts");
6818
- const cursorEditCapturePath = join18(HOOKS_DIR, "cursor-edit-capture.ts");
6819
- const cursorAgentCapturePath = join18(HOOKS_DIR, "cursor-agent-capture.ts");
6820
- const mcpStdioProxyPath = join18(HOOKS_DIR, "mcp-stdio-proxy.ts");
6821
- const taskActivateIntentScriptPath = join18(HOOKS_DIR, "cc-task-activate-intent.ts");
6822
- const mcpGateScriptPath = join18(HOOKS_DIR, "cc-mcp-gate.ts");
6823
- const stubCommonPath = join18(HOOKS_DIR, "_synkro-stub-common.ts");
7616
+ const installExtractCorePath = join19(HOOKS_DIR, "installExtractCore.ts");
7617
+ const bashScriptPath = join19(HOOKS_DIR, "cc-bash-judge.ts");
7618
+ const skillJudgeScriptPath = join19(HOOKS_DIR, "cc-skill-judge.ts");
7619
+ const cursorSkillJudgePath = join19(HOOKS_DIR, "cursor-skill-judge.ts");
7620
+ const bashFollowupScriptPath = join19(HOOKS_DIR, "cc-bash-followup.ts");
7621
+ const editPrecheckScriptPath = join19(HOOKS_DIR, "cc-edit-precheck.ts");
7622
+ const editFollowupScriptPath = join19(HOOKS_DIR, "cc-edit-followup.ts");
7623
+ const cwePrecheckScriptPath = join19(HOOKS_DIR, "cc-cwe-precheck.ts");
7624
+ const cvePrecheckScriptPath = join19(HOOKS_DIR, "cc-cve-precheck.ts");
7625
+ const planJudgeScriptPath = join19(HOOKS_DIR, "cc-plan-judge.ts");
7626
+ const agentJudgeScriptPath = join19(HOOKS_DIR, "cc-agent-judge.ts");
7627
+ const stopSummaryScriptPath = join19(HOOKS_DIR, "cc-stop-summary.ts");
7628
+ const sessionStartScriptPath = join19(HOOKS_DIR, "cc-session-start.ts");
7629
+ const transcriptSyncScriptPath = join19(HOOKS_DIR, "cc-transcript-sync.ts");
7630
+ const subagentStartScriptPath = join19(HOOKS_DIR, "codex-subagent-start.ts");
7631
+ const subagentStopScriptPath = join19(HOOKS_DIR, "codex-subagent-stop.ts");
7632
+ const userPromptSubmitScriptPath = join19(HOOKS_DIR, "cc-user-prompt-submit.ts");
7633
+ const promptRouteScriptPath = join19(HOOKS_DIR, "cc-prompt-route.ts");
7634
+ const commonScriptPath = join19(HOOKS_DIR, "_synkro-common.ts");
7635
+ const commonBashScriptPath = join19(HOOKS_DIR, "_synkro-common.sh");
7636
+ const installScanScriptPath = join19(HOOKS_DIR, "cc-install-scan.ts");
7637
+ const cursorBashJudgePath = join19(HOOKS_DIR, "cursor-bash-judge.ts");
7638
+ const cursorEditCapturePath = join19(HOOKS_DIR, "cursor-edit-capture.ts");
7639
+ const cursorAgentCapturePath = join19(HOOKS_DIR, "cursor-agent-capture.ts");
7640
+ const mcpStdioProxyPath = join19(HOOKS_DIR, "mcp-stdio-proxy.ts");
7641
+ const taskActivateIntentScriptPath = join19(HOOKS_DIR, "cc-task-activate-intent.ts");
7642
+ const mcpGateScriptPath = join19(HOOKS_DIR, "cc-mcp-gate.ts");
7643
+ const stubCommonPath = join19(HOOKS_DIR, "_synkro-stub-common.ts");
6824
7644
  const stubFiles = [
6825
7645
  [stubCommonPath, STUB_COMMON_TS],
6826
7646
  [bashScriptPath, STUB_BASH_JUDGE_TS],
@@ -6828,6 +7648,7 @@ function writeHookScripts() {
6828
7648
  [cursorSkillJudgePath, STUB_CURSOR_SKILL_JUDGE_TS],
6829
7649
  [bashFollowupScriptPath, STUB_BASH_FOLLOWUP_TS],
6830
7650
  [editPrecheckScriptPath, STUB_EDIT_PRECHECK_TS],
7651
+ [editFollowupScriptPath, STUB_EDIT_FOLLOWUP_TS],
6831
7652
  [cwePrecheckScriptPath, STUB_CWE_PRECHECK_TS],
6832
7653
  [cvePrecheckScriptPath, STUB_CVE_PRECHECK_TS],
6833
7654
  [planJudgeScriptPath, STUB_PLAN_JUDGE_TS],
@@ -6835,6 +7656,8 @@ function writeHookScripts() {
6835
7656
  [stopSummaryScriptPath, STUB_STOP_SUMMARY_TS],
6836
7657
  [sessionStartScriptPath, STUB_SESSION_START_TS],
6837
7658
  [transcriptSyncScriptPath, STUB_TRANSCRIPT_SYNC_TS],
7659
+ [subagentStartScriptPath, STUB_SUBAGENT_START_TS],
7660
+ [subagentStopScriptPath, STUB_SUBAGENT_STOP_TS],
6838
7661
  [userPromptSubmitScriptPath, STUB_USER_PROMPT_SUBMIT_TS],
6839
7662
  [promptRouteScriptPath, STUB_PROMPT_ROUTE_TS],
6840
7663
  [installScanScriptPath, STUB_INSTALL_SCAN_TS],
@@ -6845,14 +7668,14 @@ function writeHookScripts() {
6845
7668
  [cursorAgentCapturePath, STUB_CURSOR_AGENT_CAPTURE_TS]
6846
7669
  ];
6847
7670
  for (const [p, content] of stubFiles) {
6848
- writeFileSync16(p, content, "utf-8");
6849
- chmodSync4(p, 493);
7671
+ writeFileSync17(p, content, "utf-8");
7672
+ chmodSync5(p, 493);
6850
7673
  }
6851
- writeFileSync16(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
6852
- chmodSync4(mcpStdioProxyPath, 493);
7674
+ writeFileSync17(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
7675
+ chmodSync5(mcpStdioProxyPath, 493);
6853
7676
  for (const stale of ["_synkro-common.ts", "_synkro-common.sh", "installExtractCore.ts"]) {
6854
7677
  try {
6855
- unlinkSync7(join18(HOOKS_DIR, stale));
7678
+ unlinkSync7(join19(HOOKS_DIR, stale));
6856
7679
  } catch {
6857
7680
  }
6858
7681
  }
@@ -6862,6 +7685,7 @@ function writeHookScripts() {
6862
7685
  cursorSkillJudgeScript: cursorSkillJudgePath,
6863
7686
  bashFollowupScript: bashFollowupScriptPath,
6864
7687
  editPrecheckScript: editPrecheckScriptPath,
7688
+ editFollowupScript: editFollowupScriptPath,
6865
7689
  cwePrecheckScript: cwePrecheckScriptPath,
6866
7690
  cvePrecheckScript: cvePrecheckScriptPath,
6867
7691
  planJudgeScript: planJudgeScriptPath,
@@ -6869,6 +7693,8 @@ function writeHookScripts() {
6869
7693
  stopSummaryScript: stopSummaryScriptPath,
6870
7694
  sessionStartScript: sessionStartScriptPath,
6871
7695
  transcriptSyncScript: transcriptSyncScriptPath,
7696
+ subagentStartScript: subagentStartScriptPath,
7697
+ subagentStopScript: subagentStopScriptPath,
6872
7698
  userPromptSubmitScript: userPromptSubmitScriptPath,
6873
7699
  promptRouteScript: promptRouteScriptPath,
6874
7700
  installScanScript: installScanScriptPath,
@@ -6892,7 +7718,7 @@ function resolveSynkroBundle() {
6892
7718
  return null;
6893
7719
  }
6894
7720
  function writeConfigEnv(opts) {
6895
- const credsPath = join18(SYNKRO_DIR11, "credentials.json");
7721
+ const credsPath = join19(SYNKRO_DIR11, "credentials.json");
6896
7722
  const safeGateway = sanitizeConfigValue(opts.gatewayUrl);
6897
7723
  const safeUserId = sanitizeConfigValue(opts.userId);
6898
7724
  const safeOrgId = sanitizeConfigValue(opts.orgId);
@@ -6908,7 +7734,7 @@ function writeConfigEnv(opts) {
6908
7734
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
6909
7735
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
6910
7736
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
6911
- `SYNKRO_VERSION=${shellQuoteSingle2("1.7.87")}`
7737
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.7.89")}`
6912
7738
  ];
6913
7739
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
6914
7740
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
@@ -6928,12 +7754,12 @@ function writeConfigEnv(opts) {
6928
7754
  lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
6929
7755
  lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
6930
7756
  lines.push("");
6931
- writeFileSync16(CONFIG_PATH4, lines.join("\n"), "utf-8");
6932
- chmodSync4(CONFIG_PATH4, 384);
7757
+ writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
7758
+ chmodSync5(CONFIG_PATH4, 384);
6933
7759
  }
6934
7760
  function persistedTranscriptConsent(source) {
6935
7761
  try {
6936
- const env = readFileSync20(CONFIG_PATH4, "utf-8");
7762
+ const env = readFileSync21(CONFIG_PATH4, "utf-8");
6937
7763
  const specific = env.match(new RegExp(`^SYNKRO_TRANSCRIPT_CONSENT_${source}='(yes|no)'`, "m"));
6938
7764
  if (specific) return specific[1] === "yes";
6939
7765
  if (source !== "CODEX") {
@@ -6956,7 +7782,7 @@ async function getOrMintCloudToken(gatewayUrl) {
6956
7782
  assertGatewayAllowed(gatewayUrl);
6957
7783
  let stored = "";
6958
7784
  try {
6959
- stored = readFileSync20(CLOUD_JWT_PATH, "utf-8").trim();
7785
+ stored = readFileSync21(CLOUD_JWT_PATH, "utf-8").trim();
6960
7786
  } catch {
6961
7787
  }
6962
7788
  if (stored && !jwtExpired(stored)) return stored;
@@ -6972,7 +7798,7 @@ async function getOrMintCloudToken(gatewayUrl) {
6972
7798
  throw new Error(`cloud-token mint failed (${resp.status}): ${t.slice(0, 200)}`);
6973
7799
  }
6974
7800
  const { token } = await resp.json();
6975
- writeFileSync16(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
7801
+ writeFileSync17(CLOUD_JWT_PATH, token + "\n", { mode: 384 });
6976
7802
  return token;
6977
7803
  }
6978
7804
  async function provisionCloudContainer(opts) {
@@ -6997,9 +7823,13 @@ async function provisionCloudContainer(opts) {
6997
7823
  ({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
6998
7824
  }
6999
7825
  if (claudeWorkers + cursorWorkers + codexWorkers === 0) codexWorkers = totalWorkers;
7000
- const selectedKind = codexWorkers > 0 && claudeWorkers === 0 && cursorWorkers === 0 ? "codex" : cursorWorkers > 0 && claudeWorkers === 0 ? "cursor" : "claude_code";
7826
+ const selectedKind = resolveConductorProvider(sf?.grader.pool || "auto", {
7827
+ claudeWorkers,
7828
+ cursorWorkers,
7829
+ codexWorkers
7830
+ });
7001
7831
  let setupToken = "";
7002
- if (claudeWorkers > 0) {
7832
+ if (claudeWorkers > 0 || selectedKind === "claude_code") {
7003
7833
  try {
7004
7834
  console.log(" Authorize your Claude account for the hosted Claude worker \u2014");
7005
7835
  console.log(" a browser window will open for approval...\n");
@@ -7050,9 +7880,9 @@ async function provisionCloudContainer(opts) {
7050
7880
  if (opts.hasCursor) harness.push("cursor");
7051
7881
  if (opts.hasCodex) harness.push("codex");
7052
7882
  let cursorApiKey = "";
7053
- if (cursorWorkers > 0) {
7883
+ if (cursorWorkers > 0 || selectedKind === "cursor") {
7054
7884
  try {
7055
- cursorApiKey = readFileSync20(join18(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
7885
+ cursorApiKey = readFileSync21(join19(SYNKRO_DIR11, "cursor-creds", "api-key"), "utf-8").trim();
7056
7886
  } catch {
7057
7887
  }
7058
7888
  }
@@ -7062,7 +7892,7 @@ async function provisionCloudContainer(opts) {
7062
7892
  } catch (e) {
7063
7893
  console.warn(` (cloud token unavailable, using session token: ${e.message})`);
7064
7894
  }
7065
- if (codexWorkers > 0) {
7895
+ if (codexWorkers > 0 || selectedKind === "codex") {
7066
7896
  const codex = await setupCodexCloud(opts.gatewayUrl, opts.jwt, (message) => console.log(` ${message}`));
7067
7897
  if (!codex.ok) {
7068
7898
  console.error(` \u2717 ${codex.error || "Codex cloud authorization failed"}`);
@@ -7081,6 +7911,7 @@ async function provisionCloudContainer(opts) {
7081
7911
  claude_workers: claudeWorkers,
7082
7912
  cursor_workers: cursorWorkers,
7083
7913
  codex_workers: codexWorkers,
7914
+ conductor_provider: selectedKind,
7084
7915
  cursor_api_key: cursorApiKey,
7085
7916
  // never logged; gateway stores it as the org secret
7086
7917
  connected_repo: repo,
@@ -7206,11 +8037,12 @@ async function verifyCloudGrader(jwt2, requestedKind) {
7206
8037
  body = JSON.parse(raw);
7207
8038
  } catch {
7208
8039
  }
7209
- if (r.ok && typeof body.result === "string" && body.result.includes("<synkro-verdict>")) {
8040
+ if (r.ok && typeof body.result === "string" && isValidRiskyEditSmokeVerdict(body.result)) {
7210
8041
  console.log(" \u2713 smoke grade passed \u2014 cloud grading is live\n");
7211
8042
  return true;
7212
8043
  }
7213
- const detail = (body.error || raw || "").trim().slice(0, 300) || "(empty response)";
8044
+ const invalidVerdict = r.ok && typeof body.result === "string" ? `invalid smoke verdict: ${body.result}` : "";
8045
+ const detail = (body.error || invalidVerdict || raw || "").trim().slice(0, 300) || "(empty response)";
7214
8046
  console.warn(` \u2717 smoke grade FAILED (HTTP ${r.status}): ${detail}`);
7215
8047
  if (body.error_code) {
7216
8048
  console.warn(` \u25B8 ${body.error_code}: ${body.hint || ""}`);
@@ -7236,7 +8068,7 @@ async function verifyCloudGrader(jwt2, requestedKind) {
7236
8068
  function readPersistedDeployLocation() {
7237
8069
  try {
7238
8070
  if (existsSync22(CONFIG_PATH4)) {
7239
- const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
8071
+ const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOY_LOCATION='([^']*)'/m);
7240
8072
  if (m?.[1] === "cloud") return "cloud";
7241
8073
  }
7242
8074
  } catch {
@@ -7245,7 +8077,7 @@ function readPersistedDeployLocation() {
7245
8077
  }
7246
8078
  function updateConfigEnvLocation(location) {
7247
8079
  if (!existsSync22(CONFIG_PATH4)) return;
7248
- let env = readFileSync20(CONFIG_PATH4, "utf-8");
8080
+ let env = readFileSync21(CONFIG_PATH4, "utf-8");
7249
8081
  const set = (k, v) => {
7250
8082
  const re = new RegExp(`^${k}=.*$`, "m");
7251
8083
  const line = `${k}='${v}'`;
@@ -7253,14 +8085,14 @@ function updateConfigEnvLocation(location) {
7253
8085
  };
7254
8086
  set("SYNKRO_DEPLOY_LOCATION", location);
7255
8087
  set("SYNKRO_STORAGE_MODE", location === "cloud" ? "cloud" : "local");
7256
- writeFileSync16(CONFIG_PATH4, env, "utf-8");
7257
- chmodSync4(CONFIG_PATH4, 384);
8088
+ writeFileSync17(CONFIG_PATH4, env, "utf-8");
8089
+ chmodSync5(CONFIG_PATH4, 384);
7258
8090
  }
7259
8091
  async function applyMcpConfig(opts) {
7260
8092
  if (!opts.hasClaudeCode && !opts.hasCursor && !opts.hasCodex) return;
7261
8093
  let mcpJwt2 = "";
7262
8094
  try {
7263
- mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8095
+ mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
7264
8096
  } catch {
7265
8097
  }
7266
8098
  if (!mcpJwt2) {
@@ -7391,7 +8223,7 @@ function resolveDeploymentMode() {
7391
8223
  if (envOverride === "bare-host" || envOverride === "docker") return envOverride;
7392
8224
  try {
7393
8225
  if (existsSync22(CONFIG_PATH4)) {
7394
- const m = readFileSync20(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
8226
+ const m = readFileSync21(CONFIG_PATH4, "utf-8").match(/^SYNKRO_DEPLOYMENT_MODE='([^']*)'/m);
7395
8227
  const val = m?.[1]?.toLowerCase();
7396
8228
  if (val === "bare-host" || val === "docker") return val;
7397
8229
  }
@@ -7418,16 +8250,16 @@ function collectLocalMetadata(includeClaudeCode = true) {
7418
8250
  meta.cc_version = execSync4("claude --version", { encoding: "utf-8", timeout: 5e3 }).trim().split("\n")[0];
7419
8251
  } catch {
7420
8252
  }
7421
- const claudeDir = join18(homedir20(), ".claude");
8253
+ const claudeDir = join19(homedir21(), ".claude");
7422
8254
  try {
7423
- const settings = JSON.parse(readFileSync20(join18(claudeDir, "settings.json"), "utf-8"));
8255
+ const settings = JSON.parse(readFileSync21(join19(claudeDir, "settings.json"), "utf-8"));
7424
8256
  const plugins = Object.keys(settings.enabledPlugins ?? {}).filter((k) => settings.enabledPlugins[k]);
7425
8257
  if (plugins.length) meta.enabled_plugins = plugins;
7426
8258
  if (settings.permissions?.defaultMode) meta.permissions_mode = settings.permissions.defaultMode;
7427
8259
  } catch {
7428
8260
  }
7429
8261
  try {
7430
- const mcpCache = JSON.parse(readFileSync20(join18(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
8262
+ const mcpCache = JSON.parse(readFileSync21(join19(claudeDir, "mcp-needs-auth-cache.json"), "utf-8"));
7431
8263
  const mcpNames = Object.keys(mcpCache);
7432
8264
  if (mcpNames.length) meta.mcp_servers = mcpNames;
7433
8265
  } catch {
@@ -7439,10 +8271,10 @@ function collectLocalMetadata(includeClaudeCode = true) {
7439
8271
  } catch {
7440
8272
  }
7441
8273
  try {
7442
- const sessionsDir = join18(claudeDir, "sessions");
8274
+ const sessionsDir = join19(claudeDir, "sessions");
7443
8275
  const files = readdirSync4(sessionsDir).filter((f) => f.endsWith(".json")).slice(-5);
7444
8276
  for (const f of files) {
7445
- const s = JSON.parse(readFileSync20(join18(sessionsDir, f), "utf-8"));
8277
+ const s = JSON.parse(readFileSync21(join19(sessionsDir, f), "utf-8"));
7446
8278
  if (s.version) {
7447
8279
  meta.cc_version = meta.cc_version || s.version;
7448
8280
  break;
@@ -7644,7 +8476,7 @@ async function installCommand(opts = {}) {
7644
8476
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
7645
8477
  emit("install", {
7646
8478
  phase: "started",
7647
- cli_version_to: "1.7.87",
8479
+ cli_version_to: "1.7.89",
7648
8480
  agents_detected: agents.map((a) => a.kind),
7649
8481
  with_github: false,
7650
8482
  with_local_cc: false,
@@ -7656,9 +8488,9 @@ async function installCommand(opts = {}) {
7656
8488
  const scripts = writeHookScripts();
7657
8489
  console.log("Wrote hook scripts to ~/.synkro/hooks/\n");
7658
8490
  for (const mode of ["edit", "bash"]) {
7659
- const pidFile = join18(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
8491
+ const pidFile = join19(SYNKRO_DIR11, "daemon", mode, "daemon.pid");
7660
8492
  try {
7661
- const pid = parseInt(readFileSync20(pidFile, "utf-8").trim(), 10);
8493
+ const pid = parseInt(readFileSync21(pidFile, "utf-8").trim(), 10);
7662
8494
  if (pid > 0) {
7663
8495
  process.kill(pid, "SIGTERM");
7664
8496
  console.log(`Stopped stale ${mode} grader daemon (pid ${pid})`);
@@ -7721,12 +8553,15 @@ async function installCommand(opts = {}) {
7721
8553
  skillJudgeScriptPath: scripts.skillJudgeScript,
7722
8554
  bashFollowupScriptPath: scripts.bashFollowupScript,
7723
8555
  editPrecheckScriptPath: scripts.editPrecheckScript,
8556
+ editFollowupScriptPath: scripts.editFollowupScript,
7724
8557
  cwePrecheckScriptPath: scripts.cwePrecheckScript,
7725
8558
  cvePrecheckScriptPath: scripts.cvePrecheckScript,
7726
8559
  agentJudgeScriptPath: scripts.agentJudgeScript,
7727
8560
  stopSummaryScriptPath: scripts.stopSummaryScript,
7728
8561
  sessionStartScriptPath: scripts.sessionStartScript,
7729
8562
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
8563
+ subagentStartScriptPath: scripts.subagentStartScript,
8564
+ subagentStopScriptPath: scripts.subagentStopScript,
7730
8565
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
7731
8566
  promptRouteScriptPath: scripts.promptRouteScript,
7732
8567
  installScanScriptPath: scripts.installScanScript,
@@ -7776,7 +8611,7 @@ async function installCommand(opts = {}) {
7776
8611
  if (mintResp.ok) {
7777
8612
  const minted = await mintResp.json();
7778
8613
  mcpJwt2 = minted.token;
7779
- writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
8614
+ writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), mcpJwt2 + "\n", { mode: 384 });
7780
8615
  } else {
7781
8616
  console.warn(" \u26A0 Could not mint MCP token \u2014 local server will reject requests until re-installed.");
7782
8617
  }
@@ -7804,7 +8639,7 @@ async function installCommand(opts = {}) {
7804
8639
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7805
8640
  }
7806
8641
  const minted = await mintResp.json();
7807
- writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
8642
+ writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7808
8643
  const mcp = installMcpConfig({ gatewayUrl, bearerToken: minted.token });
7809
8644
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7810
8645
  console.log(` url: ${mcp.url}`);
@@ -7826,7 +8661,7 @@ async function installCommand(opts = {}) {
7826
8661
  if (hasCursor && !opts.noMcp) {
7827
8662
  try {
7828
8663
  if (useLocalMcp) {
7829
- const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
8664
+ const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
7830
8665
  if (!existsSync22(jwtPath)) {
7831
8666
  const mintResp = await fetch(`${gatewayUrl}/api/v1/cli/mcp-token`, {
7832
8667
  method: "POST",
@@ -7835,7 +8670,7 @@ async function installCommand(opts = {}) {
7835
8670
  });
7836
8671
  if (mintResp.ok) {
7837
8672
  const minted = await mintResp.json();
7838
- writeFileSync16(jwtPath, minted.token + "\n", { mode: 384 });
8673
+ writeFileSync17(jwtPath, minted.token + "\n", { mode: 384 });
7839
8674
  }
7840
8675
  }
7841
8676
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: "", local: true });
@@ -7855,7 +8690,7 @@ async function installCommand(opts = {}) {
7855
8690
  throw new Error(`mcp-token mint failed (${mintResp.status}): ${errText.slice(0, 200)}`);
7856
8691
  }
7857
8692
  const minted = await mintResp.json();
7858
- writeFileSync16(join18(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
8693
+ writeFileSync17(join19(SYNKRO_DIR11, ".mcp-jwt"), minted.token + "\n", { mode: 384 });
7859
8694
  const mcp = installCursorMcpConfig({ gatewayUrl, bearerToken: minted.token });
7860
8695
  console.log(`Registered Synkro guardrails MCP server in ${mcp.path}`);
7861
8696
  console.log(` url: ${mcp.url}`);
@@ -7868,10 +8703,10 @@ async function installCommand(opts = {}) {
7868
8703
  }
7869
8704
  if (hasCodex && !opts.noMcp) {
7870
8705
  try {
7871
- const jwtPath = join18(SYNKRO_DIR11, ".mcp-jwt");
8706
+ const jwtPath = join19(SYNKRO_DIR11, ".mcp-jwt");
7872
8707
  let mcpJwt2 = "";
7873
8708
  try {
7874
- mcpJwt2 = readFileSync20(jwtPath, "utf-8").trim();
8709
+ mcpJwt2 = readFileSync21(jwtPath, "utf-8").trim();
7875
8710
  } catch {
7876
8711
  }
7877
8712
  if (!mcpJwt2) {
@@ -7889,7 +8724,7 @@ async function installCommand(opts = {}) {
7889
8724
  }
7890
8725
  const minted = await mintResp.json();
7891
8726
  mcpJwt2 = minted.token;
7892
- writeFileSync16(jwtPath, mcpJwt2 + "\n", { mode: 384 });
8727
+ writeFileSync17(jwtPath, mcpJwt2 + "\n", { mode: 384 });
7893
8728
  }
7894
8729
  const mcp = installCodexMcpConfig({
7895
8730
  gatewayUrl,
@@ -7985,8 +8820,13 @@ async function installCommand(opts = {}) {
7985
8820
  ({ claudeWorkers, cursorWorkers, codexWorkers } = splitWorkers(totalWorkers, providers));
7986
8821
  if (synkroFilePool !== "auto") console.log(` synkro.toml: grader pool set to ${poolLabel(synkroFilePool)}`);
7987
8822
  }
8823
+ const conductorProvider = resolveConductorProvider(sf?.grader.pool || "auto", {
8824
+ claudeWorkers,
8825
+ cursorWorkers,
8826
+ codexWorkers
8827
+ });
7988
8828
  let codexHomeDir;
7989
- if (codexWorkers > 0) {
8829
+ if (codexWorkers > 0 || conductorProvider === "codex") {
7990
8830
  const codexSetup = await setupCodexLocal((message) => console.log(` ${message}`));
7991
8831
  if (!codexSetup.ok || !codexSetup.home) {
7992
8832
  console.error(` \u2717 ${codexSetup.error || "Codex grader authorization failed"}`);
@@ -7996,14 +8836,15 @@ async function installCommand(opts = {}) {
7996
8836
  }
7997
8837
  console.log(` worker pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex`);
7998
8838
  const connectedRepo = detectGitRepo2() || void 0;
7999
- const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, codexHomeDir, connectedRepo });
8839
+ const { image, hostMcpPort, hostGraderPort, hostCwePort, hostPglitePort, pglitePasswordPath } = await dockerInstall({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, codexHomeDir, connectedRepo });
8000
8840
  console.log(` \u2713 pulled ${image}`);
8001
- console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort} pglite=${hostPglitePort}`);
8841
+ console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);
8842
+ console.log(` PGLite: postgresql://synkro@127.0.0.1:${hostPglitePort}/postgres (password: ${pglitePasswordPath})`);
8002
8843
  console.log(" waiting for container to be ready...");
8003
8844
  const ready = await waitForContainerReady(6e4);
8004
8845
  if (ready) {
8005
8846
  console.log(" \u2713 container ready");
8006
- const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8847
+ const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8007
8848
  try {
8008
8849
  const ingestResp = await fetch(`http://127.0.0.1:${hostMcpPort}/api/ingest`, {
8009
8850
  method: "POST",
@@ -8046,7 +8887,7 @@ async function installCommand(opts = {}) {
8046
8887
  try {
8047
8888
  let mcpToken = "";
8048
8889
  try {
8049
- mcpToken = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8890
+ mcpToken = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8050
8891
  } catch {
8051
8892
  }
8052
8893
  if (mcpToken) {
@@ -8214,8 +9055,8 @@ function writeSynkroFileIfMissing(opts) {
8214
9055
  try {
8215
9056
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8216
9057
  if (!root) return;
8217
- if (root === homedir20()) return;
8218
- const fp = join18(root, "synkro.toml");
9058
+ if (root === homedir21()) return;
9059
+ const fp = join19(root, "synkro.toml");
8219
9060
  let hasFile = false;
8220
9061
  try {
8221
9062
  hasFile = statSync2(fp).isFile();
@@ -8247,7 +9088,7 @@ function writeSynkroFileIfMissing(opts) {
8247
9088
  "cve = true",
8248
9089
  ""
8249
9090
  ].join("\n");
8250
- writeFileSync16(fp, toml, "utf-8");
9091
+ writeFileSync17(fp, toml, "utf-8");
8251
9092
  console.log(` synkro.toml: wrote ${fp} (pool=${pool}, mode=${mode})`);
8252
9093
  } catch {
8253
9094
  }
@@ -8255,12 +9096,12 @@ function writeSynkroFileIfMissing(opts) {
8255
9096
  function updateSynkroTomlLocation(location) {
8256
9097
  try {
8257
9098
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8258
- if (!root || root === homedir20()) return;
8259
- const fp = join18(root, "synkro.toml");
9099
+ if (!root || root === homedir21()) return;
9100
+ const fp = join19(root, "synkro.toml");
8260
9101
  let txt = "";
8261
9102
  try {
8262
9103
  if (!statSync2(fp).isFile()) return;
8263
- txt = readFileSync20(fp, "utf-8");
9104
+ txt = readFileSync21(fp, "utf-8");
8264
9105
  } catch {
8265
9106
  return;
8266
9107
  }
@@ -8268,7 +9109,7 @@ function updateSynkroTomlLocation(location) {
8268
9109
  if (!re.test(txt)) return;
8269
9110
  const next = txt.replace(re, `$1"${location}"`);
8270
9111
  if (next !== txt) {
8271
- writeFileSync16(fp, next, "utf-8");
9112
+ writeFileSync17(fp, next, "utf-8");
8272
9113
  console.log(` synkro.toml: [grader] location = "${location}"`);
8273
9114
  }
8274
9115
  } catch {
@@ -8278,9 +9119,9 @@ function readFullSynkroFile() {
8278
9119
  try {
8279
9120
  const root = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8", timeout: 3e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
8280
9121
  if (!root) return null;
8281
- const fp = join18(root, "synkro.toml");
9122
+ const fp = join19(root, "synkro.toml");
8282
9123
  if (!existsSync22(fp)) return null;
8283
- const parsed = parseSynkroToml2(readFileSync20(fp, "utf-8"));
9124
+ const parsed = parseSynkroToml2(readFileSync21(fp, "utf-8"));
8284
9125
  const valid = ["claude-code", "cursor", "codex"];
8285
9126
  const harness = Array.isArray(parsed.harness) ? parsed.harness.filter((h) => valid.includes(h)) : ["claude-code", "cursor"];
8286
9127
  const resolved = resolveGraderPool(parsed);
@@ -8320,7 +9161,7 @@ function reconcileHarness() {
8320
9161
  console.log(`synkro.toml: harness=[${sf.harness.join(", ")}] pool=${poolLabel(sf.grader.pool)} mode=${sf.grader.mode}`);
8321
9162
  const scripts = writeHookScripts();
8322
9163
  console.log("Wrote hook scripts to ~/.synkro/hooks/");
8323
- const ccSettings = join18(homedir20(), ".claude", "settings.json");
9164
+ const ccSettings = join19(homedir21(), ".claude", "settings.json");
8324
9165
  if (wantCC) {
8325
9166
  installCCHooks(ccSettings, {
8326
9167
  bashJudgeScriptPath: scripts.bashScript,
@@ -8343,7 +9184,7 @@ function reconcileHarness() {
8343
9184
  });
8344
9185
  console.log(" \u2713 Claude Code hooks registered");
8345
9186
  try {
8346
- const mcpJwt2 = readFileSync20(join18(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
9187
+ const mcpJwt2 = readFileSync21(join19(SYNKRO_DIR11, ".mcp-jwt"), "utf-8").trim();
8347
9188
  if (mcpJwt2) {
8348
9189
  installMcpConfig({ gatewayUrl: "", bearerToken: mcpJwt2, local: true });
8349
9190
  console.log(" \u2713 Claude Code MCP registered");
@@ -8355,7 +9196,7 @@ function reconcileHarness() {
8355
9196
  if (uninstallMcpConfig()) console.log(" \u2717 Claude Code MCP removed");
8356
9197
  if (uninstallClaudeDesktopMcpConfig()) console.log(" \u2717 Claude Desktop MCP removed");
8357
9198
  }
8358
- const cursorHooks = join18(homedir20(), ".cursor", "hooks.json");
9199
+ const cursorHooks = join19(homedir21(), ".cursor", "hooks.json");
8359
9200
  if (wantCursor) {
8360
9201
  installCursorHooks(cursorHooks, {
8361
9202
  bashJudgeScriptPath: scripts.cursorBashJudgeScript,
@@ -8386,19 +9227,22 @@ function reconcileHarness() {
8386
9227
  if (uninstallCursorHooks(cursorHooks)) console.log(" \u2717 Cursor hooks removed");
8387
9228
  if (uninstallCursorMcpConfig()) console.log(" \u2717 Cursor MCP removed");
8388
9229
  }
8389
- const codexHooks = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "hooks.json");
9230
+ const codexHooks = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "hooks.json");
8390
9231
  if (wantCodex) {
8391
9232
  installCodexHooks(codexHooks, {
8392
9233
  bashJudgeScriptPath: scripts.bashScript,
8393
9234
  skillJudgeScriptPath: scripts.skillJudgeScript,
8394
9235
  bashFollowupScriptPath: scripts.bashFollowupScript,
8395
9236
  editPrecheckScriptPath: scripts.editPrecheckScript,
9237
+ editFollowupScriptPath: scripts.editFollowupScript,
8396
9238
  cwePrecheckScriptPath: scripts.cwePrecheckScript,
8397
9239
  cvePrecheckScriptPath: scripts.cvePrecheckScript,
8398
9240
  agentJudgeScriptPath: scripts.agentJudgeScript,
8399
9241
  stopSummaryScriptPath: scripts.stopSummaryScript,
8400
9242
  sessionStartScriptPath: scripts.sessionStartScript,
8401
9243
  transcriptSyncScriptPath: scripts.transcriptSyncScript,
9244
+ subagentStartScriptPath: scripts.subagentStartScript,
9245
+ subagentStopScriptPath: scripts.subagentStopScript,
8402
9246
  userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
8403
9247
  promptRouteScriptPath: scripts.promptRouteScript,
8404
9248
  installScanScriptPath: scripts.installScanScript,
@@ -8421,7 +9265,10 @@ function reconcileHarness() {
8421
9265
  const cw = Math.max(0, Math.floor(sf.workers.claude || 0));
8422
9266
  const curw = Math.max(0, Math.floor(sf.workers.cursor || 0));
8423
9267
  const codw = Math.max(0, Math.floor(sf.workers.codex || 0));
8424
- if (cw + curw + codw > 0) return { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
9268
+ if (cw + curw + codw > 0) {
9269
+ const counts2 = { claudeWorkers: cw, cursorWorkers: curw, codexWorkers: codw };
9270
+ return { ...counts2, conductorProvider: resolveConductorProvider(sf.grader.pool, counts2) };
9271
+ }
8425
9272
  }
8426
9273
  const total = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
8427
9274
  const providers = [];
@@ -8437,7 +9284,8 @@ function reconcileHarness() {
8437
9284
  if (wantCodex) providers.push("codex");
8438
9285
  }
8439
9286
  if (providers.length === 0) providers.push("claude_code");
8440
- return splitWorkers(total, providers);
9287
+ const counts = splitWorkers(total, providers);
9288
+ return { ...counts, conductorProvider: resolveConductorProvider(sf.grader.pool, counts) };
8441
9289
  }
8442
9290
  async function ingestSkillTasks(tasks, mcpPort) {
8443
9291
  const summary = { ingested: 0, rules: 0, rejected: [] };
@@ -8474,7 +9322,7 @@ async function syncSkillFiles() {
8474
9322
  if (resolved.length === 0) return;
8475
9323
  const mcpPort = process.env.SYNKRO_MCP_PORT || "18931";
8476
9324
  const tasks = resolved.map((fp) => {
8477
- const content = readFileSync20(fp, "utf-8");
9325
+ const content = readFileSync21(fp, "utf-8");
8478
9326
  const source = `skill:${fp.split("/").pop()}`;
8479
9327
  if (!content.trim()) {
8480
9328
  console.log(` \u2298 skill ${source}: empty file, skipped`);
@@ -8499,11 +9347,11 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8499
9347
  } catch {
8500
9348
  }
8501
9349
  };
8502
- add(join18(homedir20(), ".claude", "skills"));
8503
- add(join18(homedir20(), ".agents", "skills"));
9350
+ add(join19(homedir21(), ".claude", "skills"));
9351
+ add(join19(homedir21(), ".agents", "skills"));
8504
9352
  if (repoRoot2) {
8505
- add(join18(repoRoot2, ".claude", "skills"));
8506
- add(join18(repoRoot2, ".agents", "skills"));
9353
+ add(join19(repoRoot2, ".claude", "skills"));
9354
+ add(join19(repoRoot2, ".agents", "skills"));
8507
9355
  }
8508
9356
  const out = [];
8509
9357
  const seen = /* @__PURE__ */ new Set();
@@ -8514,12 +9362,12 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8514
9362
  try {
8515
9363
  const st = statSync2(file);
8516
9364
  if (!st.isFile() || st.size > 2e5) return;
8517
- content = readFileSync20(file, "utf-8");
9365
+ content = readFileSync21(file, "utf-8");
8518
9366
  } catch {
8519
9367
  return;
8520
9368
  }
8521
9369
  if (!content.trim()) return;
8522
- const hash = createHash2("sha256").update(content).digest("hex");
9370
+ const hash = createHash4("sha256").update(content).digest("hex");
8523
9371
  if (seen.has(hash) || excludeHashes.has(hash)) return;
8524
9372
  seen.add(hash);
8525
9373
  const ingested = ingestedHashes.has(hash) || ingestedNames.has(normSkillName(name));
@@ -8534,7 +9382,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8534
9382
  }
8535
9383
  for (const entry of entries) {
8536
9384
  if (entry.startsWith(".")) continue;
8537
- const full = join18(root, entry);
9385
+ const full = join19(root, entry);
8538
9386
  let st;
8539
9387
  try {
8540
9388
  st = statSync2(full);
@@ -8542,7 +9390,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8542
9390
  continue;
8543
9391
  }
8544
9392
  if (st.isDirectory()) {
8545
- const skillMd = join18(full, "SKILL.md");
9393
+ const skillMd = join19(full, "SKILL.md");
8546
9394
  if (existsSync22(skillMd)) consider(skillMd, entry);
8547
9395
  } else if (/\.mdx?$/i.test(entry) && entry.toUpperCase() !== "README.MD") {
8548
9396
  consider(full, entry.replace(/\.mdx?$/i, ""));
@@ -8552,7 +9400,7 @@ function discoverSkillFiles(repoRoot2, excludeHashes, ingestedHashes, ingestedNa
8552
9400
  return out;
8553
9401
  }
8554
9402
  function discoverySetHash(found) {
8555
- return createHash2("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
9403
+ return createHash4("sha256").update(found.map((f) => f.hash).sort().join(",")).digest("hex");
8556
9404
  }
8557
9405
  async function promptSkillDiscovery(found) {
8558
9406
  if (!process.stdin.isTTY || found.length === 0) return [];
@@ -8592,7 +9440,7 @@ async function discoverAndIngestSkills() {
8592
9440
  if (sf?.skills?.length) {
8593
9441
  for (const fp of resolveSkillPaths(sf.skills, sf._repoRoot)) {
8594
9442
  try {
8595
- excludeHashes.add(createHash2("sha256").update(readFileSync20(fp, "utf-8")).digest("hex"));
9443
+ excludeHashes.add(createHash4("sha256").update(readFileSync21(fp, "utf-8")).digest("hex"));
8596
9444
  } catch {
8597
9445
  }
8598
9446
  }
@@ -8623,13 +9471,13 @@ async function discoverAndIngestSkills() {
8623
9471
  const setHash = discoverySetHash(selectable);
8624
9472
  let prev = "";
8625
9473
  try {
8626
- prev = readFileSync20(SKILLS_DISCOVERED_PATH, "utf-8").trim();
9474
+ prev = readFileSync21(SKILLS_DISCOVERED_PATH, "utf-8").trim();
8627
9475
  } catch {
8628
9476
  }
8629
9477
  if (prev === setHash) return;
8630
9478
  const picks = await promptSkillDiscovery(found);
8631
9479
  try {
8632
- writeFileSync16(SKILLS_DISCOVERED_PATH, setHash);
9480
+ writeFileSync17(SKILLS_DISCOVERED_PATH, setHash);
8633
9481
  } catch {
8634
9482
  }
8635
9483
  if (picks.length === 0) {
@@ -8668,9 +9516,9 @@ function ensureReachabilityGitHook() {
8668
9516
  const root = run("git rev-parse --show-toplevel");
8669
9517
  if (!root) return null;
8670
9518
  let hooksDir = run("git config --get core.hooksPath");
8671
- hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join18(root, hooksDir) : join18(root, ".git", "hooks");
9519
+ hooksDir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : join19(root, hooksDir) : join19(root, ".git", "hooks");
8672
9520
  if (!existsSync22(hooksDir)) mkdirSync15(hooksDir, { recursive: true });
8673
- const hookPath = join18(hooksDir, "post-commit");
9521
+ const hookPath = join19(hooksDir, "post-commit");
8674
9522
  const resolvedBin = resolveSynkroBinPath();
8675
9523
  const invoke = resolvedBin ? `"${resolvedBin}" reachability-scan --quiet` : "true";
8676
9524
  const START = "# >>> synkro reachability (managed) >>>";
@@ -8684,23 +9532,23 @@ function ensureReachabilityGitHook() {
8684
9532
  ].join("\n");
8685
9533
  const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8686
9534
  if (!existsSync22(hookPath)) {
8687
- writeFileSync16(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
9535
+ writeFileSync17(hookPath, "#!/bin/sh\n" + block + "\n", { mode: 493 });
8688
9536
  return "installed";
8689
9537
  }
8690
- let cur = readFileSync20(hookPath, "utf-8");
9538
+ let cur = readFileSync21(hookPath, "utf-8");
8691
9539
  if (cur.includes(START)) {
8692
9540
  cur = cur.replace(new RegExp(esc(START) + "[\\s\\S]*?" + esc(END), "m"), block);
8693
- writeFileSync16(hookPath, cur);
9541
+ writeFileSync17(hookPath, cur);
8694
9542
  try {
8695
- chmodSync4(hookPath, 493);
9543
+ chmodSync5(hookPath, 493);
8696
9544
  } catch {
8697
9545
  }
8698
9546
  return "updated";
8699
9547
  }
8700
9548
  const sep = cur.endsWith("\n") ? "" : "\n";
8701
- writeFileSync16(hookPath, cur + sep + "\n" + block + "\n");
9549
+ writeFileSync17(hookPath, cur + sep + "\n" + block + "\n");
8702
9550
  try {
8703
- chmodSync4(hookPath, 493);
9551
+ chmodSync5(hookPath, 493);
8704
9552
  } catch {
8705
9553
  }
8706
9554
  return "updated";
@@ -8726,7 +9574,7 @@ function detectGitRepo2() {
8726
9574
  function getClaudeProjectsFolder() {
8727
9575
  const cwd = process.cwd();
8728
9576
  const sanitized = "-" + cwd.replace(/\//g, "-");
8729
- const projectsDir = join18(homedir20(), ".claude", "projects", sanitized);
9577
+ const projectsDir = join19(homedir21(), ".claude", "projects", sanitized);
8730
9578
  return existsSync22(projectsDir) ? projectsDir : null;
8731
9579
  }
8732
9580
  function extractSessionInsights(projectsDir) {
@@ -8734,9 +9582,9 @@ function extractSessionInsights(projectsDir) {
8734
9582
  const files = readdirSync4(projectsDir).filter((f) => f.endsWith(".jsonl"));
8735
9583
  for (const file of files) {
8736
9584
  const sessionId = file.replace(".jsonl", "");
8737
- const filePath = join18(projectsDir, file);
9585
+ const filePath = join19(projectsDir, file);
8738
9586
  try {
8739
- const content = readFileSync20(filePath, "utf-8");
9587
+ const content = readFileSync21(filePath, "utf-8");
8740
9588
  const lines = content.split("\n").filter(Boolean);
8741
9589
  for (let i = 0; i < lines.length; i++) {
8742
9590
  try {
@@ -8812,7 +9660,7 @@ function extractTextContent(content) {
8812
9660
  return "";
8813
9661
  }
8814
9662
  function getCodexTranscriptFiles(repo) {
8815
- const sessionsDir = join18(process.env.CODEX_HOME || join18(homedir20(), ".codex"), "sessions");
9663
+ const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
8816
9664
  if (!existsSync22(sessionsDir)) return [];
8817
9665
  let relative = [];
8818
9666
  try {
@@ -8820,9 +9668,9 @@ function getCodexTranscriptFiles(repo) {
8820
9668
  } catch {
8821
9669
  return [];
8822
9670
  }
8823
- return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join18(sessionsDir, p)).filter((filePath) => {
9671
+ return relative.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
8824
9672
  try {
8825
- const first = readFileSync20(filePath, "utf-8").split("\n", 1)[0];
9673
+ const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
8826
9674
  const meta = JSON.parse(first);
8827
9675
  const cwd = typeof meta?.payload?.cwd === "string" ? resolve4(meta.payload.cwd) : "";
8828
9676
  const root = resolve4(repo);
@@ -8832,38 +9680,45 @@ function getCodexTranscriptFiles(repo) {
8832
9680
  }
8833
9681
  });
8834
9682
  }
9683
+ function isJsonSyntaxError(error) {
9684
+ return error instanceof SyntaxError;
9685
+ }
8835
9686
  function parseCodexTranscriptFile(filePath) {
8836
- const lines = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean);
9687
+ const transcript = readFileSync21(filePath, "utf-8");
9688
+ const lines = transcript.split("\n");
9689
+ const transcriptUsage = parseCodexTranscriptUsage(transcript);
8837
9690
  let sessionId = "";
8838
9691
  let model = "";
8839
- const messages = [];
8840
- for (let i = 0; i < lines.length; i++) {
9692
+ for (const line of lines) {
8841
9693
  try {
8842
- const entry = JSON.parse(lines[i]);
9694
+ const entry = JSON.parse(line);
8843
9695
  if (entry.type === "session_meta") {
8844
9696
  sessionId = String(entry.payload?.session_id || entry.payload?.id || sessionId);
8845
- continue;
8846
- }
8847
- if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
9697
+ } else if (entry.type === "turn_context" && typeof entry.payload?.model === "string") {
8848
9698
  model = entry.payload.model;
8849
- continue;
8850
9699
  }
8851
- if (entry.type !== "event_msg") continue;
8852
- const eventType = entry.payload?.type;
8853
- if (eventType !== "user_message" && eventType !== "agent_message") continue;
8854
- const text = String(entry.payload?.message || "").slice(0, 8e3);
8855
- if (!text) continue;
8856
- const type = eventType === "user_message" ? "user" : "assistant";
8857
- messages.push({
8858
- message_index: i,
8859
- type,
8860
- content: text,
8861
- ...type === "assistant" && model ? { model } : {}
8862
- });
8863
- } catch {
9700
+ } catch (error) {
9701
+ if (!isJsonSyntaxError(error)) throw error;
8864
9702
  }
8865
9703
  }
8866
- return { sessionId, messages };
9704
+ const messages = parseCodexConversationMessages(transcript).map((message) => {
9705
+ const turnUsage = message.role === "assistant" ? transcriptUsage?.turnsByLine.get(message.lineIndex) : void 0;
9706
+ return {
9707
+ message_index: message.lineIndex,
9708
+ uuid: message.uuid,
9709
+ type: message.role,
9710
+ content: message.content,
9711
+ ...message.role === "assistant" && (turnUsage?.model || model) ? { model: turnUsage?.model || model } : {},
9712
+ ...turnUsage ? { usage: turnUsage.usage } : {},
9713
+ ...message.timestamp ? { timestamp: message.timestamp } : {}
9714
+ };
9715
+ });
9716
+ return {
9717
+ sessionId,
9718
+ messages,
9719
+ model: transcriptUsage?.model || model,
9720
+ usage: transcriptUsage?.total
9721
+ };
8867
9722
  }
8868
9723
  async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8869
9724
  const files = getCodexTranscriptFiles(repo);
@@ -8880,7 +9735,15 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8880
9735
  const resp = await fetch(`http://127.0.0.1:${mcpPort}/api/conversation-sync`, {
8881
9736
  method: "POST",
8882
9737
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${mcpToken}` },
8883
- body: JSON.stringify({ session_id: parsed.sessionId, repo, messages }),
9738
+ body: JSON.stringify({
9739
+ session_id: parsed.sessionId,
9740
+ repo,
9741
+ messages,
9742
+ session_usage: parsed.usage,
9743
+ model: parsed.model,
9744
+ harness: "codex",
9745
+ usage_cumulative: true
9746
+ }),
8884
9747
  signal: AbortSignal.timeout(15e3)
8885
9748
  });
8886
9749
  if (resp.ok) {
@@ -8888,7 +9751,7 @@ async function syncCodexTranscriptsLocal(mcpPort, mcpToken, repo) {
8888
9751
  totalSessions++;
8889
9752
  totalMessages += result.ingested ?? messages.length;
8890
9753
  }
8891
- writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
9754
+ writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(files[i], "utf-8").split("\n").filter(Boolean).length), "utf-8");
8892
9755
  } catch {
8893
9756
  }
8894
9757
  if ((i + 1) % 10 === 0 || i === files.length - 1) {
@@ -8902,14 +9765,14 @@ function cursorProjectSlug(workspaceRoot) {
8902
9765
  return workspaceRoot.replace(/^[/]+/, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8903
9766
  }
8904
9767
  function getCursorTranscriptsDir() {
8905
- const dir = join18(homedir20(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
9768
+ const dir = join19(homedir21(), ".cursor", "projects", cursorProjectSlug(process.cwd()), "agent-transcripts");
8906
9769
  return existsSync22(dir) ? dir : null;
8907
9770
  }
8908
9771
  function isSafeConvId(id) {
8909
9772
  return /^[A-Za-z0-9_-]+$/.test(id);
8910
9773
  }
8911
9774
  function parseCursorTranscriptFile(filePath) {
8912
- const content = readFileSync20(filePath, "utf-8");
9775
+ const content = readFileSync21(filePath, "utf-8");
8913
9776
  const lines = content.split("\n").filter(Boolean);
8914
9777
  const messages = [];
8915
9778
  for (let i = 0; i < lines.length; i++) {
@@ -8941,7 +9804,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8941
9804
  for (let i = 0; i < convDirs.length; i++) {
8942
9805
  const convId = convDirs[i];
8943
9806
  if (!isSafeConvId(convId)) continue;
8944
- const filePath = join18(dir, convId, `${convId}.jsonl`);
9807
+ const filePath = join19(dir, convId, `${convId}.jsonl`);
8945
9808
  if (!existsSync22(filePath)) continue;
8946
9809
  try {
8947
9810
  const all = parseCursorTranscriptFile(filePath);
@@ -8964,8 +9827,8 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8964
9827
  process.stdout.write(`\r Progress: ${i + 1}/${convDirs.length} sessions (${totalMessages} messages embedded) `);
8965
9828
  }
8966
9829
  try {
8967
- const lc = readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length;
8968
- writeFileSync16(join18(OFFSETS_DIR, convId), String(lc), "utf-8");
9830
+ const lc = readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length;
9831
+ writeFileSync17(join19(OFFSETS_DIR, convId), String(lc), "utf-8");
8969
9832
  } catch {
8970
9833
  }
8971
9834
  }
@@ -8973,7 +9836,7 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
8973
9836
  return { sessions: totalSessions, messages: totalMessages };
8974
9837
  }
8975
9838
  function parseTranscriptFile(filePath) {
8976
- const content = readFileSync20(filePath, "utf-8");
9839
+ const content = readFileSync21(filePath, "utf-8");
8977
9840
  const lines = content.split("\n").filter(Boolean);
8978
9841
  const messages = [];
8979
9842
  for (let i = 0; i < lines.length; i++) {
@@ -9021,7 +9884,7 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
9021
9884
  for (let i = 0; i < files.length; i++) {
9022
9885
  const file = files[i];
9023
9886
  const sessionId = file.replace(".jsonl", "");
9024
- const filePath = join18(projectsDir, file);
9887
+ const filePath = join19(projectsDir, file);
9025
9888
  try {
9026
9889
  const allMessages = parseTranscriptFile(filePath);
9027
9890
  const messages = allMessages.length > 500 ? allMessages.slice(-500) : allMessages;
@@ -9043,9 +9906,9 @@ async function syncTranscriptsLocal(mcpPort, mcpToken, repo) {
9043
9906
  process.stdout.write(`\r Progress: ${i + 1}/${files.length} sessions (${totalMessages} messages embedded) `);
9044
9907
  }
9045
9908
  try {
9046
- const content = readFileSync20(join18(projectsDir, file), "utf-8");
9909
+ const content = readFileSync21(join19(projectsDir, file), "utf-8");
9047
9910
  const lineCount = content.split("\n").filter(Boolean).length;
9048
- writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9911
+ writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9049
9912
  } catch {
9050
9913
  }
9051
9914
  }
@@ -9066,7 +9929,7 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
9066
9929
  const sessions = [];
9067
9930
  for (const file of batch) {
9068
9931
  const sessionId = file.replace(".jsonl", "");
9069
- const filePath = join18(projectsDir, file);
9932
+ const filePath = join19(projectsDir, file);
9070
9933
  try {
9071
9934
  const allMessages = parseTranscriptFile(filePath);
9072
9935
  const messages = allMessages.length > maxMessagesPerSession ? allMessages.slice(-maxMessagesPerSession) : allMessages;
@@ -9095,11 +9958,11 @@ async function syncTranscriptsBulk(gatewayUrl, token, repo) {
9095
9958
  }
9096
9959
  for (const file of batch) {
9097
9960
  const sessionId = file.replace(".jsonl", "");
9098
- const filePath = join18(projectsDir, file);
9961
+ const filePath = join19(projectsDir, file);
9099
9962
  try {
9100
- const content = readFileSync20(filePath, "utf-8");
9963
+ const content = readFileSync21(filePath, "utf-8");
9101
9964
  const lineCount = content.split("\n").filter(Boolean).length;
9102
- writeFileSync16(join18(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9965
+ writeFileSync17(join19(OFFSETS_DIR, sessionId), String(lineCount), "utf-8");
9103
9966
  } catch {
9104
9967
  }
9105
9968
  }
@@ -9117,7 +9980,16 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
9117
9980
  try {
9118
9981
  const parsed = parseCodexTranscriptFile(filePath);
9119
9982
  const messages = parsed.messages.length > 500 ? parsed.messages.slice(-500) : parsed.messages;
9120
- if (parsed.sessionId && messages.length > 0) sessions.push({ cc_session_id: parsed.sessionId, messages });
9983
+ if (parsed.sessionId && messages.length > 0) {
9984
+ sessions.push({
9985
+ cc_session_id: parsed.sessionId,
9986
+ messages,
9987
+ model: parsed.model,
9988
+ session_usage: parsed.usage,
9989
+ harness: "codex",
9990
+ usage_cumulative: true
9991
+ });
9992
+ }
9121
9993
  } catch {
9122
9994
  }
9123
9995
  }
@@ -9139,7 +10011,7 @@ async function syncCodexTranscriptsBulk(gatewayUrl, token, repo) {
9139
10011
  try {
9140
10012
  const parsed = parseCodexTranscriptFile(filePath);
9141
10013
  if (parsed.sessionId) {
9142
- writeFileSync16(join18(OFFSETS_DIR, parsed.sessionId), String(readFileSync20(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
10014
+ writeFileSync17(join19(OFFSETS_DIR, parsed.sessionId), String(readFileSync21(filePath, "utf-8").split("\n").filter(Boolean).length), "utf-8");
9143
10015
  }
9144
10016
  } catch {
9145
10017
  }
@@ -9169,10 +10041,13 @@ var init_install = __esm({
9169
10041
  init_setupToken();
9170
10042
  init_codexCloudSetup();
9171
10043
  init_ptyShim();
9172
- SYNKRO_DIR11 = join18(homedir20(), ".synkro");
9173
- HOOKS_DIR = join18(SYNKRO_DIR11, "hooks");
9174
- BIN_DIR = join18(SYNKRO_DIR11, "bin");
9175
- CONFIG_PATH4 = join18(SYNKRO_DIR11, "config.env");
10044
+ init_graderSmoke();
10045
+ init_codexTranscriptUsage();
10046
+ init_codexTranscriptMessages();
10047
+ SYNKRO_DIR11 = join19(homedir21(), ".synkro");
10048
+ HOOKS_DIR = join19(SYNKRO_DIR11, "hooks");
10049
+ BIN_DIR = join19(SYNKRO_DIR11, "bin");
10050
+ CONFIG_PATH4 = join19(SYNKRO_DIR11, "config.env");
9176
10051
  MCP_STDIO_PROXY_SRC = `#!/usr/bin/env bun
9177
10052
  import { readFileSync } from 'node:fs';
9178
10053
  import { homedir } from 'node:os';
@@ -9283,23 +10158,23 @@ rl.on('line', async (line) => {
9283
10158
  }
9284
10159
  });
9285
10160
  `;
9286
- OFFSETS_DIR = join18(SYNKRO_DIR11, ".transcript-offsets");
9287
- CLOUD_JWT_PATH = join18(SYNKRO_DIR11, ".cloud-jwt");
9288
- SKILLS_DISCOVERED_PATH = join18(SYNKRO_DIR11, ".skills-discovered");
10161
+ OFFSETS_DIR = join19(SYNKRO_DIR11, ".transcript-offsets");
10162
+ CLOUD_JWT_PATH = join19(SYNKRO_DIR11, ".cloud-jwt");
10163
+ SKILLS_DISCOVERED_PATH = join19(SYNKRO_DIR11, ".skills-discovered");
9289
10164
  }
9290
10165
  });
9291
10166
 
9292
10167
  // cli/local-cc/install.ts
9293
- import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync17, readFileSync as readFileSync21, chmodSync as chmodSync5, copyFileSync as copyFileSync2, renameSync as renameSync7, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
9294
- import { join as join19 } from "path";
9295
- import { homedir as homedir21 } from "os";
10168
+ import { existsSync as existsSync23, mkdirSync as mkdirSync16, writeFileSync as writeFileSync18, readFileSync as readFileSync22, chmodSync as chmodSync6, copyFileSync as copyFileSync2, renameSync as renameSync8, unlinkSync as unlinkSync8, openSync as openSync2, fsyncSync, closeSync as closeSync2 } from "fs";
10169
+ import { join as join20 } from "path";
10170
+ import { homedir as homedir22 } from "os";
9296
10171
  import { spawnSync as spawnSync7 } from "child_process";
9297
10172
  function writePluginFiles() {
9298
10173
  for (const c of CHANNELS) {
9299
10174
  mkdirSync16(c.sessionDir, { recursive: true });
9300
10175
  mkdirSync16(c.pluginSettingsDir, { recursive: true });
9301
- writeFileSync17(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
9302
- writeFileSync17(
10176
+ writeFileSync18(c.pluginPkgPath, PLUGIN_PACKAGE_JSON, "utf-8");
10177
+ writeFileSync18(
9303
10178
  c.pluginSettingsPath,
9304
10179
  JSON.stringify({
9305
10180
  fastMode: true,
@@ -9314,8 +10189,8 @@ function writePluginFiles() {
9314
10189
  }, null, 2) + "\n",
9315
10190
  "utf-8"
9316
10191
  );
9317
- writeFileSync17(c.runScriptPath, c.runScriptSource, "utf-8");
9318
- chmodSync5(c.runScriptPath, 493);
10192
+ writeFileSync18(c.runScriptPath, c.runScriptSource, "utf-8");
10193
+ chmodSync6(c.runScriptPath, 493);
9319
10194
  }
9320
10195
  }
9321
10196
  function runBunInstall() {
@@ -9336,7 +10211,7 @@ function safelyMutateClaudeJson(mutator) {
9336
10211
  if (!existsSync23(CLAUDE_JSON_PATH)) {
9337
10212
  return;
9338
10213
  }
9339
- const originalText = readFileSync21(CLAUDE_JSON_PATH, "utf-8");
10214
+ const originalText = readFileSync22(CLAUDE_JSON_PATH, "utf-8");
9340
10215
  let parsed;
9341
10216
  try {
9342
10217
  parsed = JSON.parse(originalText);
@@ -9368,14 +10243,14 @@ function safelyMutateClaudeJson(mutator) {
9368
10243
  copyFileSync2(CLAUDE_JSON_PATH, CLAUDE_JSON_BACKUP_PATH);
9369
10244
  const tmpPath = `${CLAUDE_JSON_PATH}.synkro-tmp.${process.pid}`;
9370
10245
  try {
9371
- writeFileSync17(tmpPath, newText, "utf-8");
10246
+ writeFileSync18(tmpPath, newText, "utf-8");
9372
10247
  const fd = openSync2(tmpPath, "r");
9373
10248
  try {
9374
10249
  fsyncSync(fd);
9375
10250
  } finally {
9376
10251
  closeSync2(fd);
9377
10252
  }
9378
- renameSync7(tmpPath, CLAUDE_JSON_PATH);
10253
+ renameSync8(tmpPath, CLAUDE_JSON_PATH);
9379
10254
  } catch (err) {
9380
10255
  try {
9381
10256
  unlinkSync8(tmpPath);
@@ -9401,7 +10276,7 @@ function writeProjectMcpJson() {
9401
10276
  }
9402
10277
  }
9403
10278
  };
9404
- writeFileSync17(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
10279
+ writeFileSync18(c.projectMcpPath, JSON.stringify(mcp, null, 2) + "\n", "utf-8");
9405
10280
  }
9406
10281
  }
9407
10282
  function patchClaudeJson() {
@@ -9478,42 +10353,42 @@ var CLAUDE_JSON_BACKUP_PATH, SESSION_DIR, PLUGIN_PATH, PLUGIN_PKG_PATH, PLUGIN_S
9478
10353
  var init_install2 = __esm({
9479
10354
  "cli/local-cc/install.ts"() {
9480
10355
  "use strict";
9481
- CLAUDE_JSON_BACKUP_PATH = join19(homedir21(), ".claude.json.synkro-bak");
9482
- SESSION_DIR = join19(homedir21(), ".synkro", "cc_sessions");
9483
- PLUGIN_PATH = join19(SESSION_DIR, "synkro-channel.ts");
9484
- PLUGIN_PKG_PATH = join19(SESSION_DIR, "package.json");
9485
- PLUGIN_SETTINGS_DIR = join19(SESSION_DIR, ".claude");
9486
- PLUGIN_SETTINGS_PATH = join19(PLUGIN_SETTINGS_DIR, "settings.json");
9487
- PROJECT_MCP_PATH = join19(SESSION_DIR, ".mcp.json");
9488
- CLAUDE_JSON_PATH = join19(homedir21(), ".claude.json");
9489
- RUN_SCRIPT_PATH = join19(SESSION_DIR, "run-claude.sh");
10356
+ CLAUDE_JSON_BACKUP_PATH = join20(homedir22(), ".claude.json.synkro-bak");
10357
+ SESSION_DIR = join20(homedir22(), ".synkro", "cc_sessions");
10358
+ PLUGIN_PATH = join20(SESSION_DIR, "synkro-channel.ts");
10359
+ PLUGIN_PKG_PATH = join20(SESSION_DIR, "package.json");
10360
+ PLUGIN_SETTINGS_DIR = join20(SESSION_DIR, ".claude");
10361
+ PLUGIN_SETTINGS_PATH = join20(PLUGIN_SETTINGS_DIR, "settings.json");
10362
+ PROJECT_MCP_PATH = join20(SESSION_DIR, ".mcp.json");
10363
+ CLAUDE_JSON_PATH = join20(homedir22(), ".claude.json");
10364
+ RUN_SCRIPT_PATH = join20(SESSION_DIR, "run-claude.sh");
9490
10365
  TMUX_SESSION_NAME = "synkro-local-cc";
9491
10366
  CHANNEL_1_PORT = 8941;
9492
- SESSION_DIR_2 = join19(homedir21(), ".synkro", "cc_sessions_2");
9493
- PLUGIN_PATH_2 = join19(SESSION_DIR_2, "synkro-channel.ts");
9494
- PLUGIN_PKG_PATH_2 = join19(SESSION_DIR_2, "package.json");
9495
- PLUGIN_SETTINGS_DIR_2 = join19(SESSION_DIR_2, ".claude");
9496
- PLUGIN_SETTINGS_PATH_2 = join19(PLUGIN_SETTINGS_DIR_2, "settings.json");
9497
- PROJECT_MCP_PATH_2 = join19(SESSION_DIR_2, ".mcp.json");
9498
- RUN_SCRIPT_PATH_2 = join19(SESSION_DIR_2, "run-claude.sh");
10367
+ SESSION_DIR_2 = join20(homedir22(), ".synkro", "cc_sessions_2");
10368
+ PLUGIN_PATH_2 = join20(SESSION_DIR_2, "synkro-channel.ts");
10369
+ PLUGIN_PKG_PATH_2 = join20(SESSION_DIR_2, "package.json");
10370
+ PLUGIN_SETTINGS_DIR_2 = join20(SESSION_DIR_2, ".claude");
10371
+ PLUGIN_SETTINGS_PATH_2 = join20(PLUGIN_SETTINGS_DIR_2, "settings.json");
10372
+ PROJECT_MCP_PATH_2 = join20(SESSION_DIR_2, ".mcp.json");
10373
+ RUN_SCRIPT_PATH_2 = join20(SESSION_DIR_2, "run-claude.sh");
9499
10374
  TMUX_SESSION_NAME_2 = "synkro-local-cc-2";
9500
10375
  CHANNEL_2_PORT = 8951;
9501
- SESSION_DIR_3 = join19(homedir21(), ".synkro", "cc_sessions_3");
9502
- PLUGIN_PATH_3 = join19(SESSION_DIR_3, "synkro-channel.ts");
9503
- PLUGIN_PKG_PATH_3 = join19(SESSION_DIR_3, "package.json");
9504
- PLUGIN_SETTINGS_DIR_3 = join19(SESSION_DIR_3, ".claude");
9505
- PLUGIN_SETTINGS_PATH_3 = join19(PLUGIN_SETTINGS_DIR_3, "settings.json");
9506
- PROJECT_MCP_PATH_3 = join19(SESSION_DIR_3, ".mcp.json");
9507
- RUN_SCRIPT_PATH_3 = join19(SESSION_DIR_3, "run-claude.sh");
10376
+ SESSION_DIR_3 = join20(homedir22(), ".synkro", "cc_sessions_3");
10377
+ PLUGIN_PATH_3 = join20(SESSION_DIR_3, "synkro-channel.ts");
10378
+ PLUGIN_PKG_PATH_3 = join20(SESSION_DIR_3, "package.json");
10379
+ PLUGIN_SETTINGS_DIR_3 = join20(SESSION_DIR_3, ".claude");
10380
+ PLUGIN_SETTINGS_PATH_3 = join20(PLUGIN_SETTINGS_DIR_3, "settings.json");
10381
+ PROJECT_MCP_PATH_3 = join20(SESSION_DIR_3, ".mcp.json");
10382
+ RUN_SCRIPT_PATH_3 = join20(SESSION_DIR_3, "run-claude.sh");
9508
10383
  TMUX_SESSION_NAME_3 = "synkro-local-cc-3";
9509
10384
  CHANNEL_3_PORT = 8942;
9510
- SESSION_DIR_4 = join19(homedir21(), ".synkro", "cc_sessions_4");
9511
- PLUGIN_PATH_4 = join19(SESSION_DIR_4, "synkro-channel.ts");
9512
- PLUGIN_PKG_PATH_4 = join19(SESSION_DIR_4, "package.json");
9513
- PLUGIN_SETTINGS_DIR_4 = join19(SESSION_DIR_4, ".claude");
9514
- PLUGIN_SETTINGS_PATH_4 = join19(PLUGIN_SETTINGS_DIR_4, "settings.json");
9515
- PROJECT_MCP_PATH_4 = join19(SESSION_DIR_4, ".mcp.json");
9516
- RUN_SCRIPT_PATH_4 = join19(SESSION_DIR_4, "run-claude.sh");
10385
+ SESSION_DIR_4 = join20(homedir22(), ".synkro", "cc_sessions_4");
10386
+ PLUGIN_PATH_4 = join20(SESSION_DIR_4, "synkro-channel.ts");
10387
+ PLUGIN_PKG_PATH_4 = join20(SESSION_DIR_4, "package.json");
10388
+ PLUGIN_SETTINGS_DIR_4 = join20(SESSION_DIR_4, ".claude");
10389
+ PLUGIN_SETTINGS_PATH_4 = join20(PLUGIN_SETTINGS_DIR_4, "settings.json");
10390
+ PROJECT_MCP_PATH_4 = join20(SESSION_DIR_4, ".mcp.json");
10391
+ RUN_SCRIPT_PATH_4 = join20(SESSION_DIR_4, "run-claude.sh");
9517
10392
  TMUX_SESSION_NAME_4 = "synkro-local-cc-4";
9518
10393
  CHANNEL_4_PORT = 8952;
9519
10394
  RUN_SCRIPT_SOURCE = `#!/usr/bin/env bash
@@ -9788,8 +10663,8 @@ __export(disconnect_exports, {
9788
10663
  disconnectCommand: () => disconnectCommand
9789
10664
  });
9790
10665
  import { existsSync as existsSync24, rmSync as rmSync3, readdirSync as readdirSync5 } from "fs";
9791
- import { homedir as homedir22 } from "os";
9792
- import { join as join20 } from "path";
10666
+ import { homedir as homedir23 } from "os";
10667
+ import { join as join21 } from "path";
9793
10668
  import { spawnSync as spawnSync8 } from "child_process";
9794
10669
  import { createInterface as createInterface3 } from "readline";
9795
10670
  async function tearDownLocalCC() {
@@ -9904,13 +10779,13 @@ async function disconnectCommand(args2 = [], opts = {}) {
9904
10779
  console.log(`\u2713 wiped ${SYNKRO_DIR12} entirely \u2014 including all scan data and backups`);
9905
10780
  } else {
9906
10781
  const keep = /* @__PURE__ */ new Set([
9907
- join20(SYNKRO_DIR12, "pgdata"),
9908
- join20(SYNKRO_DIR12, "pgdata-backups"),
9909
- join20(SYNKRO_DIR12, ".transcript-offsets")
10782
+ join21(SYNKRO_DIR12, "pgdata"),
10783
+ join21(SYNKRO_DIR12, "pgdata-backups"),
10784
+ join21(SYNKRO_DIR12, ".transcript-offsets")
9910
10785
  ]);
9911
10786
  const preserved = [];
9912
10787
  for (const entry of readdirSync5(SYNKRO_DIR12)) {
9913
- const full = join20(SYNKRO_DIR12, entry);
10788
+ const full = join21(SYNKRO_DIR12, entry);
9914
10789
  if (keep.has(full)) {
9915
10790
  preserved.push(entry);
9916
10791
  continue;
@@ -9948,14 +10823,14 @@ var init_disconnect = __esm({
9948
10823
  init_dockerInstall();
9949
10824
  init_macKeychain();
9950
10825
  init_telemetry();
9951
- SYNKRO_DIR12 = join20(homedir22(), ".synkro");
10826
+ SYNKRO_DIR12 = join21(homedir23(), ".synkro");
9952
10827
  }
9953
10828
  });
9954
10829
 
9955
10830
  // cli/local-cc/turnLog.ts
9956
- import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync22, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
9957
- import { dirname as dirname7, join as join21 } from "path";
9958
- import { homedir as homedir23 } from "os";
10831
+ import { appendFileSync as appendFileSync3, existsSync as existsSync25, mkdirSync as mkdirSync17, openSync as openSync3, readFileSync as readFileSync23, readSync, closeSync as closeSync3, statSync as statSync3, watchFile, unwatchFile } from "fs";
10832
+ import { dirname as dirname7, join as join22 } from "path";
10833
+ import { homedir as homedir24 } from "os";
9959
10834
  function truncate(s, max = PREVIEW_MAX) {
9960
10835
  if (s.length <= max) return s;
9961
10836
  return s.slice(0, max) + "\u2026 [+" + (s.length - max) + " chars]";
@@ -9995,7 +10870,7 @@ function readRecentTurns(n = 20) {
9995
10870
  try {
9996
10871
  const size = statSync3(TURN_LOG_PATH).size;
9997
10872
  if (size === 0) return [];
9998
- const text = readFileSync22(TURN_LOG_PATH, "utf-8");
10873
+ const text = readFileSync23(TURN_LOG_PATH, "utf-8");
9999
10874
  const lines = text.split("\n").filter(Boolean);
10000
10875
  const lastN = lines.slice(-n).reverse();
10001
10876
  return lastN.map((line) => {
@@ -10074,7 +10949,7 @@ var TURN_LOG_PATH, PREVIEW_MAX;
10074
10949
  var init_turnLog = __esm({
10075
10950
  "cli/local-cc/turnLog.ts"() {
10076
10951
  "use strict";
10077
- TURN_LOG_PATH = join21(homedir23(), ".synkro", "cc_sessions", "turns.log");
10952
+ TURN_LOG_PATH = join22(homedir24(), ".synkro", "cc_sessions", "turns.log");
10078
10953
  PREVIEW_MAX = 400;
10079
10954
  }
10080
10955
  });
@@ -10228,8 +11103,8 @@ __export(scanPr_exports, {
10228
11103
  scanPrCommand: () => scanPrCommand
10229
11104
  });
10230
11105
  import { execSync as execSync5, spawn as spawn5 } from "child_process";
10231
- import { readFileSync as readFileSync23, existsSync as existsSync26 } from "fs";
10232
- import { join as join22 } from "path";
11106
+ import { readFileSync as readFileSync24, existsSync as existsSync26 } from "fs";
11107
+ import { join as join23 } from "path";
10233
11108
  function parseMatchSpec(condition) {
10234
11109
  if (!condition.startsWith("match_spec:")) return null;
10235
11110
  try {
@@ -10708,10 +11583,10 @@ function shouldFail(findings, threshold) {
10708
11583
  return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
10709
11584
  }
10710
11585
  function readRepoDeps() {
10711
- const pkgPath = join22(process.cwd(), "package.json");
11586
+ const pkgPath = join23(process.cwd(), "package.json");
10712
11587
  if (!existsSync26(pkgPath)) return {};
10713
11588
  try {
10714
- const pkg = JSON.parse(readFileSync23(pkgPath, "utf-8"));
11589
+ const pkg = JSON.parse(readFileSync24(pkgPath, "utf-8"));
10715
11590
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10716
11591
  } catch {
10717
11592
  return {};
@@ -10955,15 +11830,15 @@ var routeDecide_exports = {};
10955
11830
  __export(routeDecide_exports, {
10956
11831
  routeDecide: () => routeDecide
10957
11832
  });
10958
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
10959
- import { homedir as homedir24 } from "os";
10960
- import { join as join23 } from "path";
11833
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11834
+ import { homedir as homedir25 } from "os";
11835
+ import { join as join24 } from "path";
10961
11836
  function safeSid(sid) {
10962
11837
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
10963
11838
  }
10964
11839
  function loadMcpJwt() {
10965
11840
  try {
10966
- return readFileSync24(join23(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
11841
+ return readFileSync25(join24(SYNKRO_DIR13, ".mcp-jwt"), "utf-8").trim();
10967
11842
  } catch {
10968
11843
  return "";
10969
11844
  }
@@ -10973,7 +11848,7 @@ async function routeDecide(sessionId) {
10973
11848
  const sid = safeSid(sessionId);
10974
11849
  let prompt = "";
10975
11850
  try {
10976
- const rec = JSON.parse(readFileSync24(join23(SESSIONS_DIR2, sid + ".json"), "utf-8"));
11851
+ const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR2, sid + ".json"), "utf-8"));
10977
11852
  prompt = String(rec.last_prompt || "").trim();
10978
11853
  } catch {
10979
11854
  return;
@@ -10984,7 +11859,7 @@ async function routeDecide(sessionId) {
10984
11859
  const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT}/submit`, {
10985
11860
  method: "POST",
10986
11861
  headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt() },
10987
- body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, agent_kind: "claude_code", hedge: false }),
11862
+ body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
10988
11863
  // First call may spin up the route lane (a haiku worker boot), so allow headroom.
10989
11864
  signal: AbortSignal.timeout(12e4)
10990
11865
  });
@@ -10996,19 +11871,19 @@ async function routeDecide(sessionId) {
10996
11871
  return;
10997
11872
  }
10998
11873
  if (!model || !VALID_MODELS.has(model)) return;
10999
- const lastFile = join23(PTY_DIR, "route-last-" + sid);
11874
+ const lastFile = join24(PTY_DIR, "route-last-" + sid);
11000
11875
  let last = "";
11001
11876
  try {
11002
- last = readFileSync24(lastFile, "utf-8").trim();
11877
+ last = readFileSync25(lastFile, "utf-8").trim();
11003
11878
  } catch {
11004
11879
  }
11005
11880
  if (model === last) return;
11006
11881
  try {
11007
- writeFileSync18(lastFile, model);
11882
+ writeFileSync19(lastFile, model);
11008
11883
  } catch {
11009
11884
  }
11010
11885
  try {
11011
- writeFileSync18(join23(PTY_DIR, "route-" + sid), model);
11886
+ writeFileSync19(join24(PTY_DIR, "route-" + sid), model);
11012
11887
  } catch {
11013
11888
  }
11014
11889
  }
@@ -11016,9 +11891,9 @@ var SYNKRO_DIR13, PTY_DIR, SESSIONS_DIR2, VALID_MODELS, GRADER_HOST_PORT;
11016
11891
  var init_routeDecide = __esm({
11017
11892
  "cli/local-cc/routeDecide.ts"() {
11018
11893
  "use strict";
11019
- SYNKRO_DIR13 = join23(homedir24(), ".synkro");
11020
- PTY_DIR = join23(SYNKRO_DIR13, "pty");
11021
- SESSIONS_DIR2 = join23(PTY_DIR, "sessions");
11894
+ SYNKRO_DIR13 = join24(homedir25(), ".synkro");
11895
+ PTY_DIR = join24(SYNKRO_DIR13, "pty");
11896
+ SESSIONS_DIR2 = join24(PTY_DIR, "sessions");
11022
11897
  VALID_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
11023
11898
  GRADER_HOST_PORT = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
11024
11899
  }
@@ -11029,9 +11904,9 @@ var routeOrchestrate_exports = {};
11029
11904
  __export(routeOrchestrate_exports, {
11030
11905
  routeAndResubmit: () => routeAndResubmit
11031
11906
  });
11032
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
11033
- import { homedir as homedir25 } from "os";
11034
- import { join as join24 } from "path";
11907
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync20 } from "fs";
11908
+ import { homedir as homedir26 } from "os";
11909
+ import { join as join25 } from "path";
11035
11910
  import { spawnSync as spawnSync9 } from "child_process";
11036
11911
  function safeSid2(sid) {
11037
11912
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
@@ -11041,7 +11916,7 @@ function safeSession(s) {
11041
11916
  }
11042
11917
  function loadMcpJwt2() {
11043
11918
  try {
11044
- return readFileSync25(join24(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
11919
+ return readFileSync26(join25(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
11045
11920
  } catch {
11046
11921
  return "";
11047
11922
  }
@@ -11051,7 +11926,7 @@ async function classifyTask(prompt) {
11051
11926
  const resp = await fetch(`http://127.0.0.1:${GRADER_HOST_PORT2}/submit`, {
11052
11927
  method: "POST",
11053
11928
  headers: { "Content-Type": "application/json", Authorization: "Bearer " + loadMcpJwt2() },
11054
- body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, agent_kind: "claude_code", hedge: false }),
11929
+ body: JSON.stringify({ role: "route-classify", payload: prompt, content: prompt, hedge: false }),
11055
11930
  signal: AbortSignal.timeout(6e4)
11056
11931
  });
11057
11932
  if (!resp.ok) return null;
@@ -11067,7 +11942,7 @@ async function classifyTask(prompt) {
11067
11942
  }
11068
11943
  function lastRoutedModel(sid) {
11069
11944
  try {
11070
- const v = readFileSync25(join24(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
11945
+ const v = readFileSync26(join25(PTY_DIR2, "route-last-" + sid), "utf-8").trim();
11071
11946
  return VALID_MODELS2.has(v) ? v : "";
11072
11947
  } catch {
11073
11948
  return "";
@@ -11077,12 +11952,12 @@ function resolveSession(sid, tmuxSession) {
11077
11952
  const candidates = [];
11078
11953
  if (tmuxSession) candidates.push(tmuxSession);
11079
11954
  try {
11080
- const rec = JSON.parse(readFileSync25(join24(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
11955
+ const rec = JSON.parse(readFileSync26(join25(SESSIONS_DIR3, safeSid2(sid) + ".json"), "utf-8"));
11081
11956
  if (rec.tmux_session) candidates.push(rec.tmux_session);
11082
11957
  } catch {
11083
11958
  }
11084
11959
  try {
11085
- candidates.push(readFileSync25(ACTIVE_SESSION_FILE2, "utf-8").trim());
11960
+ candidates.push(readFileSync26(ACTIVE_SESSION_FILE2, "utf-8").trim());
11086
11961
  } catch {
11087
11962
  }
11088
11963
  for (const c of candidates) if (c && safeSession(c)) return c;
@@ -11117,12 +11992,12 @@ async function routeAndResubmit(sessionId, task, tmuxSession, forceModel) {
11117
11992
  }
11118
11993
  await wait(500);
11119
11994
  try {
11120
- writeFileSync19(join24(PTY_DIR2, "route-last-" + sid), picked);
11995
+ writeFileSync20(join25(PTY_DIR2, "route-last-" + sid), picked);
11121
11996
  } catch {
11122
11997
  }
11123
11998
  }
11124
11999
  try {
11125
- writeFileSync19(join24(PTY_DIR2, "route-guard-" + sid), "1");
12000
+ writeFileSync20(join25(PTY_DIR2, "route-guard-" + sid), "1");
11126
12001
  } catch {
11127
12002
  }
11128
12003
  sk("C-u");
@@ -11136,10 +12011,10 @@ var SYNKRO_DIR14, PTY_DIR2, SESSIONS_DIR3, ACTIVE_SESSION_FILE2, VALID_MODELS2,
11136
12011
  var init_routeOrchestrate = __esm({
11137
12012
  "cli/local-cc/routeOrchestrate.ts"() {
11138
12013
  "use strict";
11139
- SYNKRO_DIR14 = join24(homedir25(), ".synkro");
11140
- PTY_DIR2 = join24(SYNKRO_DIR14, "pty");
11141
- SESSIONS_DIR3 = join24(PTY_DIR2, "sessions");
11142
- ACTIVE_SESSION_FILE2 = join24(PTY_DIR2, "active");
12014
+ SYNKRO_DIR14 = join25(homedir26(), ".synkro");
12015
+ PTY_DIR2 = join25(SYNKRO_DIR14, "pty");
12016
+ SESSIONS_DIR3 = join25(PTY_DIR2, "sessions");
12017
+ ACTIVE_SESSION_FILE2 = join25(PTY_DIR2, "active");
11143
12018
  VALID_MODELS2 = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-sonnet-5", "claude-opus-4-8"]);
11144
12019
  GRADER_HOST_PORT2 = process.env.SYNKRO_GRADER_HOST_PORT || "18929";
11145
12020
  wait = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -11151,14 +12026,14 @@ var routingToggle_exports = {};
11151
12026
  __export(routingToggle_exports, {
11152
12027
  routingCommand: () => routingCommand
11153
12028
  });
11154
- import { writeFileSync as writeFileSync20, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
11155
- import { homedir as homedir26 } from "os";
11156
- import { join as join25 } from "path";
12029
+ import { writeFileSync as writeFileSync21, unlinkSync as unlinkSync9, existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
12030
+ import { homedir as homedir27 } from "os";
12031
+ import { join as join26 } from "path";
11157
12032
  function safeSid3(sid) {
11158
12033
  return sid.replace(/[^A-Za-z0-9_-]/g, "_");
11159
12034
  }
11160
12035
  function markerFor(session) {
11161
- return session ? join25(PTY_DIR3, "routing-on-" + safeSid3(session)) : join25(PTY_DIR3, "routing-on");
12036
+ return session ? join26(PTY_DIR3, "routing-on-" + safeSid3(session)) : join26(PTY_DIR3, "routing-on");
11162
12037
  }
11163
12038
  function routingCommand(args2) {
11164
12039
  const sub = (args2[0] || "status").trim();
@@ -11167,7 +12042,7 @@ function routingCommand(args2) {
11167
12042
  if (si >= 0 && args2[si + 1]) session = args2[si + 1].trim();
11168
12043
  if (sub === "on") {
11169
12044
  try {
11170
- writeFileSync20(markerFor(session), "1");
12045
+ writeFileSync21(markerFor(session), "1");
11171
12046
  } catch (e) {
11172
12047
  console.error("routing on failed:", String(e));
11173
12048
  return;
@@ -11190,7 +12065,7 @@ function routingCommand(args2) {
11190
12065
  for (const f of safeList()) {
11191
12066
  if (f === "routing-on" || f.startsWith("routing-on-")) {
11192
12067
  try {
11193
- unlinkSync9(join25(PTY_DIR3, f));
12068
+ unlinkSync9(join26(PTY_DIR3, f));
11194
12069
  removed++;
11195
12070
  } catch {
11196
12071
  }
@@ -11222,14 +12097,14 @@ var PTY_DIR3;
11222
12097
  var init_routingToggle = __esm({
11223
12098
  "cli/local-cc/routingToggle.ts"() {
11224
12099
  "use strict";
11225
- PTY_DIR3 = join25(homedir26(), ".synkro", "pty");
12100
+ PTY_DIR3 = join26(homedir27(), ".synkro", "pty");
11226
12101
  }
11227
12102
  });
11228
12103
 
11229
12104
  // cli/local-cc/pueue.ts
11230
12105
  import { execFileSync as execFileSync4, spawnSync as spawnSync10, spawn as spawn6 } from "child_process";
11231
- import { homedir as homedir27 } from "os";
11232
- import { join as join26 } from "path";
12106
+ import { homedir as homedir28 } from "os";
12107
+ import { join as join27 } from "path";
11233
12108
  import { connect as connect2 } from "net";
11234
12109
  function pueueAvailable() {
11235
12110
  const r = spawnSync10("pueue", ["--version"], { encoding: "utf-8" });
@@ -11295,7 +12170,7 @@ function startTask(opts = {}) {
11295
12170
  spawnSync10("pueue", ["remove", String(existing.id)], { encoding: "utf-8" });
11296
12171
  existing = findTask(ch);
11297
12172
  }
11298
- const runScript = join26(cwd, "run-claude.sh");
12173
+ const runScript = join27(cwd, "run-claude.sh");
11299
12174
  const args2 = [
11300
12175
  "add",
11301
12176
  "--label",
@@ -11425,12 +12300,12 @@ var init_pueue = __esm({
11425
12300
  "use strict";
11426
12301
  TASK_LABEL = "synkro-local-cc";
11427
12302
  TMUX_SESSION = "synkro-local-cc";
11428
- SESSION_DIR2 = join26(homedir27(), ".synkro", "cc_sessions");
12303
+ SESSION_DIR2 = join27(homedir28(), ".synkro", "cc_sessions");
11429
12304
  TASK_LABEL_2 = "synkro-local-cc-2";
11430
12305
  TMUX_SESSION_2 = "synkro-local-cc-2";
11431
- SESSION_DIR_22 = join26(homedir27(), ".synkro", "cc_sessions_2");
11432
- SESSION_DIR_32 = join26(homedir27(), ".synkro", "cc_sessions_3");
11433
- SESSION_DIR_42 = join26(homedir27(), ".synkro", "cc_sessions_4");
12306
+ SESSION_DIR_22 = join27(homedir28(), ".synkro", "cc_sessions_2");
12307
+ SESSION_DIR_32 = join27(homedir28(), ".synkro", "cc_sessions_3");
12308
+ SESSION_DIR_42 = join27(homedir28(), ".synkro", "cc_sessions_4");
11434
12309
  PueueError = class extends Error {
11435
12310
  constructor(message, cause) {
11436
12311
  super(message);
@@ -11445,13 +12320,13 @@ var init_pueue = __esm({
11445
12320
  });
11446
12321
 
11447
12322
  // cli/local-cc/settings.ts
11448
- import { existsSync as existsSync28, readFileSync as readFileSync26 } from "fs";
11449
- import { homedir as homedir28 } from "os";
11450
- import { join as join27 } from "path";
12323
+ import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
12324
+ import { homedir as homedir29 } from "os";
12325
+ import { join as join28 } from "path";
11451
12326
  function isLocalCCEnabled() {
11452
12327
  if (!existsSync28(CONFIG_PATH5)) return false;
11453
12328
  try {
11454
- const content = readFileSync26(CONFIG_PATH5, "utf-8");
12329
+ const content = readFileSync27(CONFIG_PATH5, "utf-8");
11455
12330
  const match = content.match(/^SYNKRO_LOCAL_INFERENCE='([^']*)'/m);
11456
12331
  return match?.[1] === "yes";
11457
12332
  } catch {
@@ -11462,7 +12337,7 @@ var CONFIG_PATH5;
11462
12337
  var init_settings = __esm({
11463
12338
  "cli/local-cc/settings.ts"() {
11464
12339
  "use strict";
11465
- CONFIG_PATH5 = join27(homedir28(), ".synkro", "config.env");
12340
+ CONFIG_PATH5 = join28(homedir29(), ".synkro", "config.env");
11466
12341
  }
11467
12342
  });
11468
12343
 
@@ -11472,10 +12347,10 @@ __export(localCc_exports, {
11472
12347
  localCcCommand: () => localCcCommand
11473
12348
  });
11474
12349
  import { spawnSync as spawnSync11 } from "child_process";
11475
- import { homedir as homedir29 } from "os";
11476
- import { join as join28 } from "path";
12350
+ import { homedir as homedir30 } from "os";
12351
+ import { join as join29 } from "path";
11477
12352
  import { readFileSync as fsReadFileSync, existsSync as fsExistsSync } from "fs";
11478
- import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
12353
+ import { existsSync as existsSync29, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
11479
12354
  function deploymentMode() {
11480
12355
  const env = (process.env.SYNKRO_DEPLOYMENT_MODE || "").toLowerCase();
11481
12356
  if (env === "docker") return "docker";
@@ -11582,14 +12457,14 @@ TROUBLESHOOTING
11582
12457
  }
11583
12458
  function readGatewayUrl() {
11584
12459
  if (existsSync29(CONFIG_PATH6)) {
11585
- const m = readFileSync27(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
12460
+ const m = readFileSync28(CONFIG_PATH6, "utf-8").match(/^SYNKRO_GATEWAY_URL='([^']*)'/m);
11586
12461
  if (m) return m[1];
11587
12462
  }
11588
12463
  return "https://api.synkro.sh";
11589
12464
  }
11590
12465
  function updateLocalInferenceFlag(enabled) {
11591
12466
  if (!existsSync29(CONFIG_PATH6)) return;
11592
- let content = readFileSync27(CONFIG_PATH6, "utf-8");
12467
+ let content = readFileSync28(CONFIG_PATH6, "utf-8");
11593
12468
  const flag = enabled ? "yes" : "no";
11594
12469
  if (content.includes("SYNKRO_LOCAL_INFERENCE=")) {
11595
12470
  content = content.replace(/^SYNKRO_LOCAL_INFERENCE='[^']*'/m, `SYNKRO_LOCAL_INFERENCE='${flag}'`);
@@ -11598,7 +12473,7 @@ function updateLocalInferenceFlag(enabled) {
11598
12473
  SYNKRO_LOCAL_INFERENCE='${flag}'
11599
12474
  `;
11600
12475
  }
11601
- writeFileSync21(CONFIG_PATH6, content, "utf-8");
12476
+ writeFileSync22(CONFIG_PATH6, content, "utf-8");
11602
12477
  }
11603
12478
  async function setServerGradingProvider(provider) {
11604
12479
  await ensureValidToken();
@@ -11627,7 +12502,7 @@ async function cmdStatus() {
11627
12502
  } else {
11628
12503
  console.log(`synkro-server container: running (${status.image})`);
11629
12504
  try {
11630
- const r = await fetch(`${status.healthz}healthz`, { signal: AbortSignal.timeout(3e3) });
12505
+ const r = await fetch(status.healthz, { signal: AbortSignal.timeout(3e3) });
11631
12506
  console.log(`Health probe: ${r.ok ? "ok" : `HTTP ${r.status}`}`);
11632
12507
  } catch (err) {
11633
12508
  console.log(`Health probe: ${err.message}`);
@@ -11719,9 +12594,9 @@ async function warmChannels(ready1, ready2) {
11719
12594
  async function cmdStart(rest = []) {
11720
12595
  if (inDockerMode()) {
11721
12596
  if (rest.length > 0) {
11722
- const { claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest);
12597
+ const { claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest);
11723
12598
  console.log(`Starting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)...`);
11724
- await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
12599
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider });
11725
12600
  const ready3 = await waitForContainerReady(6e4);
11726
12601
  console.log(ready3 ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
11727
12602
  return;
@@ -11768,18 +12643,19 @@ async function cmdRestart(rest = []) {
11768
12643
  let claudeWorkers;
11769
12644
  let cursorWorkers;
11770
12645
  let codexWorkers;
12646
+ let conductorProvider;
11771
12647
  if (explicit) {
11772
- ({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
12648
+ ({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest));
11773
12649
  } else {
11774
12650
  const reconciled = reconcileHarness();
11775
12651
  if (reconciled) {
11776
- ({ claudeWorkers, cursorWorkers, codexWorkers } = reconciled);
12652
+ ({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = reconciled);
11777
12653
  } else {
11778
- ({ claudeWorkers, cursorWorkers, codexWorkers } = resolveWorkerConfig(rest));
12654
+ ({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider } = resolveWorkerConfig(rest));
11779
12655
  }
11780
12656
  }
11781
12657
  console.log(`Restarting synkro-server container (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex, pulling latest image)...`);
11782
- await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers });
12658
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider });
11783
12659
  const ready = await waitForContainerReady(6e4);
11784
12660
  console.log(ready ? "\u2713 container ready" : "\u26A0 container did not pass /healthz within 60s");
11785
12661
  if (ready) {
@@ -12031,8 +12907,8 @@ var init_localCc = __esm({
12031
12907
  init_install();
12032
12908
  init_client2();
12033
12909
  init_stub();
12034
- SYNKRO_CONFIG_PATH = join28(homedir29(), ".synkro", "config.env");
12035
- CONFIG_PATH6 = join28(homedir29(), ".synkro", "config.env");
12910
+ SYNKRO_CONFIG_PATH = join29(homedir30(), ".synkro", "config.env");
12911
+ CONFIG_PATH6 = join29(homedir30(), ".synkro", "config.env");
12036
12912
  }
12037
12913
  });
12038
12914
 
@@ -12041,14 +12917,14 @@ var import_exports = {};
12041
12917
  __export(import_exports, {
12042
12918
  importCommand: () => importCommand
12043
12919
  });
12044
- import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync7 } from "fs";
12045
- import { homedir as homedir30 } from "os";
12046
- import { join as join29 } from "path";
12920
+ import { existsSync as existsSync30, readFileSync as readFileSync29, readdirSync as readdirSync7 } from "fs";
12921
+ import { homedir as homedir31 } from "os";
12922
+ import { join as join30 } from "path";
12047
12923
  import { execSync as execSync6 } from "child_process";
12048
12924
  import { createInterface as createInterface4 } from "readline";
12049
12925
  function readMcpJwt() {
12050
12926
  try {
12051
- return readFileSync28(join29(homedir30(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12927
+ return readFileSync29(join30(homedir31(), ".synkro", ".mcp-jwt"), "utf-8").trim();
12052
12928
  } catch {
12053
12929
  return "";
12054
12930
  }
@@ -12056,7 +12932,7 @@ function readMcpJwt() {
12056
12932
  function readConfigEnv2() {
12057
12933
  const out = {};
12058
12934
  try {
12059
- for (const line of readFileSync28(CONFIG_PATH7, "utf-8").split("\n")) {
12935
+ for (const line of readFileSync29(CONFIG_PATH7, "utf-8").split("\n")) {
12060
12936
  const t = line.trim();
12061
12937
  if (!t || t.startsWith("#")) continue;
12062
12938
  const eq = t.indexOf("=");
@@ -12068,7 +12944,7 @@ function readConfigEnv2() {
12068
12944
  }
12069
12945
  function projectsFolder() {
12070
12946
  const sanitized = process.cwd().replace(/\//g, "-");
12071
- const dir = join29(homedir30(), ".claude", "projects", sanitized);
12947
+ const dir = join30(homedir31(), ".claude", "projects", sanitized);
12072
12948
  return existsSync30(dir) ? dir : null;
12073
12949
  }
12074
12950
  function repoName() {
@@ -12108,7 +12984,7 @@ function extractToolResultText(content, e) {
12108
12984
  return t;
12109
12985
  }
12110
12986
  function parseSession(filePath, sessionId) {
12111
- const lines = readFileSync28(filePath, "utf-8").split("\n").filter(Boolean);
12987
+ const lines = readFileSync29(filePath, "utf-8").split("\n").filter(Boolean);
12112
12988
  const messages = [];
12113
12989
  const actions = [];
12114
12990
  let step = 0;
@@ -12188,7 +13064,7 @@ async function importCommand() {
12188
13064
  return;
12189
13065
  }
12190
13066
  }
12191
- const sessions = files.map((f) => parseSession(join29(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
13067
+ const sessions = files.map((f) => parseSession(join30(dir, f), f.replace(".jsonl", ""))).filter((s) => s.messages.length > 0);
12192
13068
  const totalMsgs = sessions.reduce((n, s) => n + s.messages.length, 0);
12193
13069
  let ok = 0, fail = 0;
12194
13070
  if (isCloud) {
@@ -12263,7 +13139,7 @@ var init_import = __esm({
12263
13139
  "cli/commands/import.ts"() {
12264
13140
  "use strict";
12265
13141
  init_stub();
12266
- CONFIG_PATH7 = join29(homedir30(), ".synkro", "config.env");
13142
+ CONFIG_PATH7 = join30(homedir31(), ".synkro", "config.env");
12267
13143
  }
12268
13144
  });
12269
13145
 
@@ -12305,10 +13181,10 @@ var init_packVerify = __esm({
12305
13181
  });
12306
13182
 
12307
13183
  // cli/installer/lockfile.ts
12308
- import { existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync22 } from "fs";
12309
- import { join as join30 } from "path";
13184
+ import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "fs";
13185
+ import { join as join31 } from "path";
12310
13186
  function lockPath(repoRoot2) {
12311
- return join30(repoRoot2, LOCK_FILE);
13187
+ return join31(repoRoot2, LOCK_FILE);
12312
13188
  }
12313
13189
  function writeLockfile(repoRoot2, entries) {
12314
13190
  const sorted = [...entries].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -12326,7 +13202,7 @@ function writeLockfile(repoRoot2, entries) {
12326
13202
  ""
12327
13203
  ])
12328
13204
  ].join("\n");
12329
- writeFileSync22(lockPath(repoRoot2), body, "utf-8");
13205
+ writeFileSync23(lockPath(repoRoot2), body, "utf-8");
12330
13206
  }
12331
13207
  var LOCK_FILE;
12332
13208
  var init_lockfile = __esm({
@@ -12341,9 +13217,9 @@ var sync_exports = {};
12341
13217
  __export(sync_exports, {
12342
13218
  syncCommand: () => syncCommand
12343
13219
  });
12344
- import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync23 } from "fs";
12345
- import { homedir as homedir31 } from "os";
12346
- import { join as join31 } from "path";
13220
+ import { existsSync as existsSync32, mkdirSync as mkdirSync18, readdirSync as readdirSync8, rmSync as rmSync4, writeFileSync as writeFileSync24 } from "fs";
13221
+ import { homedir as homedir32 } from "os";
13222
+ import { join as join32 } from "path";
12347
13223
  function cacheKey(ref, version) {
12348
13224
  return ref.replace(/\//g, "__").replace(/[^\w.@-]/g, "_") + "@" + version + ".json";
12349
13225
  }
@@ -12374,7 +13250,7 @@ async function syncCommand(_args = []) {
12374
13250
  }
12375
13251
  const gateway = (process.env.SYNKRO_GATEWAY_URL || "https://api.synkro.sh").replace(/\/$/, "");
12376
13252
  const cloud = process.env.SYNKRO_DEPLOY_LOCATION === "cloud";
12377
- const cacheDir = join31(homedir31(), ".synkro", "cache", "packs");
13253
+ const cacheDir = join32(homedir32(), ".synkro", "cache", "packs");
12378
13254
  if (!cloud) mkdirSync18(cacheDir, { recursive: true });
12379
13255
  console.log(`Syncing ${refs.length} standard(s) from the registry\u2026`);
12380
13256
  const lock = [];
@@ -12403,7 +13279,7 @@ async function syncCommand(_args = []) {
12403
13279
  if (!cloud) {
12404
13280
  const fname = cacheKey(ref, data.version);
12405
13281
  keptCacheFiles.add(fname);
12406
- writeFileSync23(join31(cacheDir, fname), JSON.stringify({
13282
+ writeFileSync24(join32(cacheDir, fname), JSON.stringify({
12407
13283
  ref,
12408
13284
  version: data.version,
12409
13285
  digest: data.digest,
@@ -12419,7 +13295,7 @@ async function syncCommand(_args = []) {
12419
13295
  for (const f of readdirSync8(cacheDir)) {
12420
13296
  if (f.endsWith(".json") && !keptCacheFiles.has(f)) {
12421
13297
  try {
12422
- rmSync4(join31(cacheDir, f));
13298
+ rmSync4(join32(cacheDir, f));
12423
13299
  } catch {
12424
13300
  }
12425
13301
  }
@@ -12448,13 +13324,13 @@ var whoami_exports = {};
12448
13324
  __export(whoami_exports, {
12449
13325
  whoamiCommand: () => whoamiCommand
12450
13326
  });
12451
- import { readFileSync as readFileSync30, existsSync as existsSync33 } from "fs";
12452
- import { join as join32 } from "path";
12453
- import { homedir as homedir32 } from "os";
13327
+ import { readFileSync as readFileSync31, existsSync as existsSync33 } from "fs";
13328
+ import { join as join33 } from "path";
13329
+ import { homedir as homedir33 } from "os";
12454
13330
  function readConfigEnv3() {
12455
13331
  if (!existsSync33(CONFIG_PATH8)) return {};
12456
13332
  const out = {};
12457
- for (const line of readFileSync30(CONFIG_PATH8, "utf-8").split("\n")) {
13333
+ for (const line of readFileSync31(CONFIG_PATH8, "utf-8").split("\n")) {
12458
13334
  const t = line.trim();
12459
13335
  if (!t || t.startsWith("#")) continue;
12460
13336
  const eq = t.indexOf("=");
@@ -12465,7 +13341,7 @@ function readConfigEnv3() {
12465
13341
  function jwtStatus() {
12466
13342
  try {
12467
13343
  if (!existsSync33(JWT_PATH2)) return { status: "none" };
12468
- const jwt2 = readFileSync30(JWT_PATH2, "utf-8").trim();
13344
+ const jwt2 = readFileSync31(JWT_PATH2, "utf-8").trim();
12469
13345
  if (!jwt2) return { status: "none" };
12470
13346
  const payload = jwt2.split(".")[1];
12471
13347
  if (!payload) return { status: "valid" };
@@ -12527,9 +13403,9 @@ var SYNKRO_DIR15, CONFIG_PATH8, JWT_PATH2, GRADING_LABEL;
12527
13403
  var init_whoami = __esm({
12528
13404
  "cli/commands/whoami.ts"() {
12529
13405
  "use strict";
12530
- SYNKRO_DIR15 = join32(homedir32(), ".synkro");
12531
- CONFIG_PATH8 = join32(SYNKRO_DIR15, "config.env");
12532
- JWT_PATH2 = join32(SYNKRO_DIR15, ".mcp-jwt");
13406
+ SYNKRO_DIR15 = join33(homedir33(), ".synkro");
13407
+ CONFIG_PATH8 = join33(SYNKRO_DIR15, "config.env");
13408
+ JWT_PATH2 = join33(SYNKRO_DIR15, ".mcp-jwt");
12533
13409
  GRADING_LABEL = {
12534
13410
  local: "on-device worker pool",
12535
13411
  cloud: "Synkro Cloud worker pool",
@@ -12574,12 +13450,12 @@ __export(linear_exports, {
12574
13450
  formatLinks: () => formatLinks,
12575
13451
  linearCommand: () => linearCommand
12576
13452
  });
12577
- import { readFileSync as readFileSync31 } from "fs";
12578
- import { homedir as homedir33 } from "os";
12579
- import { join as join33 } from "path";
13453
+ import { readFileSync as readFileSync32 } from "fs";
13454
+ import { homedir as homedir34 } from "os";
13455
+ import { join as join34 } from "path";
12580
13456
  function mcpJwt() {
12581
13457
  try {
12582
- return readFileSync31(join33(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
13458
+ return readFileSync32(join34(SYNKRO_DIR16, ".mcp-jwt"), "utf-8").trim();
12583
13459
  } catch {
12584
13460
  return "";
12585
13461
  }
@@ -12618,7 +13494,7 @@ var SYNKRO_DIR16, PORT2, BASE;
12618
13494
  var init_linear = __esm({
12619
13495
  "cli/commands/linear.ts"() {
12620
13496
  "use strict";
12621
- SYNKRO_DIR16 = join33(homedir33(), ".synkro");
13497
+ SYNKRO_DIR16 = join34(homedir34(), ".synkro");
12622
13498
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
12623
13499
  BASE = `http://127.0.0.1:${PORT2}`;
12624
13500
  }
@@ -12626,7 +13502,7 @@ var init_linear = __esm({
12626
13502
 
12627
13503
  // cli/scanning/cveReachability.ts
12628
13504
  import { parse } from "@babel/parser";
12629
- import { readFileSync as readFileSync32 } from "fs";
13505
+ import { readFileSync as readFileSync33 } from "fs";
12630
13506
  function walk(node, visit) {
12631
13507
  if (!node || typeof node.type !== "string") return;
12632
13508
  visit(node);
@@ -12768,9 +13644,9 @@ var init_cveReachability = __esm({
12768
13644
 
12769
13645
  // cli/reachability/reachabilityScan.ts
12770
13646
  import { spawnSync as spawnSync12, execFileSync as execFileSync5 } from "child_process";
12771
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync24, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
12772
- import { join as join34 } from "path";
12773
- import { homedir as homedir34 } from "os";
13647
+ import { readFileSync as readFileSync34, writeFileSync as writeFileSync25, existsSync as existsSync34, readdirSync as readdirSync9 } from "fs";
13648
+ import { join as join35 } from "path";
13649
+ import { homedir as homedir35 } from "os";
12774
13650
  import { createRequire } from "module";
12775
13651
  function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12776
13652
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "coverage", ".next", ".turbo", "out", ".cache", ".synkro", ".claude", "vendor", "__tests__", "test-results"]);
@@ -12787,7 +13663,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12787
13663
  }
12788
13664
  for (const e of ents) {
12789
13665
  if (files.length >= maxFiles) break;
12790
- const full = join34(dir, e.name);
13666
+ const full = join35(dir, e.name);
12791
13667
  if (e.isDirectory()) {
12792
13668
  if (!SKIP.has(e.name) && !e.name.startsWith(".")) stack.push(full);
12793
13669
  continue;
@@ -12795,7 +13671,7 @@ function walkSourceFiles(repoRoot2, maxFiles = 4e3, maxBytes = 5e5) {
12795
13671
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
12796
13672
  const rel = full.startsWith(repoRoot2 + "/") ? full.slice(repoRoot2.length + 1) : full;
12797
13673
  try {
12798
- const content = readFileSync33(full, "utf8");
13674
+ const content = readFileSync34(full, "utf8");
12799
13675
  if (content.length <= maxBytes) files.push({ path: rel, content });
12800
13676
  } catch {
12801
13677
  }
@@ -12814,12 +13690,12 @@ function cleanVersion(spec) {
12814
13690
  function gatherManifestVersions(repoRoot2) {
12815
13691
  const out = {};
12816
13692
  const dirs = [repoRoot2];
12817
- const pkgsDir = join34(repoRoot2, "packages");
13693
+ const pkgsDir = join35(repoRoot2, "packages");
12818
13694
  if (existsSync34(pkgsDir)) {
12819
13695
  try {
12820
13696
  for (const d of readdirSync9(pkgsDir)) {
12821
- const pd = join34(pkgsDir, d);
12822
- if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
13697
+ const pd = join35(pkgsDir, d);
13698
+ if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
12823
13699
  }
12824
13700
  } catch {
12825
13701
  }
@@ -12828,7 +13704,7 @@ function gatherManifestVersions(repoRoot2) {
12828
13704
  for (const dir of dirs) {
12829
13705
  let pkg;
12830
13706
  try {
12831
- pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
13707
+ pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
12832
13708
  } catch {
12833
13709
  continue;
12834
13710
  }
@@ -12848,28 +13724,28 @@ function findJelly(repoRoot2) {
12848
13724
  try {
12849
13725
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
12850
13726
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
12851
- const pkg = JSON.parse(readFileSync33(pkgJson, "utf8"));
13727
+ const pkg = JSON.parse(readFileSync34(pkgJson, "utf8"));
12852
13728
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
12853
13729
  if (bin) {
12854
- const p = join34(dir, bin);
13730
+ const p = join35(dir, bin);
12855
13731
  if (existsSync34(p)) return p;
12856
13732
  }
12857
13733
  } catch {
12858
13734
  }
12859
13735
  for (const base of [repoRoot2, process.cwd()]) {
12860
- const b = join34(base, "node_modules", ".bin", "jelly");
13736
+ const b = join35(base, "node_modules", ".bin", "jelly");
12861
13737
  if (existsSync34(b)) return b;
12862
13738
  }
12863
13739
  return null;
12864
13740
  }
12865
13741
  function findEntries(repoRoot2) {
12866
13742
  const dirs = [repoRoot2];
12867
- const pkgsDir = join34(repoRoot2, "packages");
13743
+ const pkgsDir = join35(repoRoot2, "packages");
12868
13744
  if (existsSync34(pkgsDir)) {
12869
13745
  try {
12870
13746
  for (const d of readdirSync9(pkgsDir)) {
12871
- const pd = join34(pkgsDir, d);
12872
- if (existsSync34(join34(pd, "package.json"))) dirs.push(pd);
13747
+ const pd = join35(pkgsDir, d);
13748
+ if (existsSync34(join35(pd, "package.json"))) dirs.push(pd);
12873
13749
  }
12874
13750
  } catch {
12875
13751
  }
@@ -12877,11 +13753,11 @@ function findEntries(repoRoot2) {
12877
13753
  const entries = [];
12878
13754
  for (const dir of dirs) {
12879
13755
  try {
12880
- const pkg = JSON.parse(readFileSync33(join34(dir, "package.json"), "utf8"));
13756
+ const pkg = JSON.parse(readFileSync34(join35(dir, "package.json"), "utf8"));
12881
13757
  const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
12882
13758
  for (const c of cands) {
12883
13759
  if (typeof c !== "string") continue;
12884
- const f = join34(dir, c);
13760
+ const f = join35(dir, c);
12885
13761
  if (existsSync34(f)) {
12886
13762
  entries.push(f);
12887
13763
  break;
@@ -12917,7 +13793,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
12917
13793
  const commit = currentCommit(repoRoot2);
12918
13794
  if (!opts.force && commit && existsSync34(REACHABILITY_PATH)) {
12919
13795
  try {
12920
- const prev = JSON.parse(readFileSync33(REACHABILITY_PATH, "utf8"));
13796
+ const prev = JSON.parse(readFileSync34(REACHABILITY_PATH, "utf8"));
12921
13797
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
12922
13798
  } catch {
12923
13799
  }
@@ -13006,7 +13882,7 @@ function runReachabilityScan(repoRoot2, opts = {}) {
13006
13882
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
13007
13883
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot2) };
13008
13884
  try {
13009
- writeFileSync24(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13885
+ writeFileSync25(REACHABILITY_PATH, JSON.stringify(file, null, 2));
13010
13886
  } catch (e) {
13011
13887
  return { ok: false, reason: "write failed: " + String(e.message || e) };
13012
13888
  }
@@ -13018,7 +13894,7 @@ var init_reachabilityScan = __esm({
13018
13894
  "use strict";
13019
13895
  init_cveReachability();
13020
13896
  require2 = createRequire(import.meta.url);
13021
- REACHABILITY_PATH = join34(homedir34(), ".synkro", "reachability.json");
13897
+ REACHABILITY_PATH = join35(homedir35(), ".synkro", "reachability.json");
13022
13898
  }
13023
13899
  });
13024
13900
 
@@ -13027,15 +13903,15 @@ var reachabilityScan_exports = {};
13027
13903
  __export(reachabilityScan_exports, {
13028
13904
  reachabilityScanCommand: () => reachabilityScanCommand
13029
13905
  });
13030
- import { readFileSync as readFileSync34, existsSync as existsSync35 } from "fs";
13031
- import { join as join35 } from "path";
13032
- import { homedir as homedir35 } from "os";
13906
+ import { readFileSync as readFileSync35, existsSync as existsSync35 } from "fs";
13907
+ import { join as join36 } from "path";
13908
+ import { homedir as homedir36 } from "os";
13033
13909
  import { execFileSync as execFileSync6 } from "child_process";
13034
13910
  function readConfigEnv4() {
13035
- const p = join35(SYNKRO_DIR17, "config.env");
13911
+ const p = join36(SYNKRO_DIR17, "config.env");
13036
13912
  if (!existsSync35(p)) return {};
13037
13913
  const out = {};
13038
- for (const line of readFileSync34(p, "utf-8").split("\n")) {
13914
+ for (const line of readFileSync35(p, "utf-8").split("\n")) {
13039
13915
  const t = line.trim();
13040
13916
  if (!t || t.startsWith("#")) continue;
13041
13917
  const eq = t.indexOf("=");
@@ -13067,11 +13943,11 @@ async function pushToCloud(cfg, repo) {
13067
13943
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
13068
13944
  let jwt2 = "";
13069
13945
  try {
13070
- jwt2 = readFileSync34(join35(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
13946
+ jwt2 = readFileSync35(join36(SYNKRO_DIR17, ".mcp-jwt"), "utf-8").trim();
13071
13947
  } catch {
13072
13948
  }
13073
13949
  if (!jwt2 || !existsSync35(REACHABILITY_PATH)) return;
13074
- const body = readFileSync34(REACHABILITY_PATH, "utf-8");
13950
+ const body = readFileSync35(REACHABILITY_PATH, "utf-8");
13075
13951
  try {
13076
13952
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
13077
13953
  method: "POST",
@@ -13103,7 +13979,7 @@ var init_reachabilityScan2 = __esm({
13103
13979
  "cli/commands/reachabilityScan.ts"() {
13104
13980
  "use strict";
13105
13981
  init_reachabilityScan();
13106
- SYNKRO_DIR17 = join35(homedir35(), ".synkro");
13982
+ SYNKRO_DIR17 = join36(homedir36(), ".synkro");
13107
13983
  }
13108
13984
  });
13109
13985
 
@@ -13134,10 +14010,10 @@ async function startCommand(rest = []) {
13134
14010
  if (cfg.explicit) {
13135
14011
  console.log(`Synkro: starting server (${cfg.claudeWorkers} claude + ${cfg.cursorWorkers} cursor + ${cfg.codexWorkers} codex)
13136
14012
  `);
13137
- await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, connectedRepo: resolveConnectedRepo() });
13138
- const ready = await waitForContainerReady(6e4);
14013
+ await dockerUpdate({ claudeWorkers: cfg.claudeWorkers, cursorWorkers: cfg.cursorWorkers, codexWorkers: cfg.codexWorkers, conductorProvider: cfg.conductorProvider, connectedRepo: resolveConnectedRepo() });
14014
+ const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
13139
14015
  if (!ready) {
13140
- console.error("\n\u26A0 container did not pass /healthz within 60s");
14016
+ console.error("\n\u26A0 container did not pass /healthz within 10m");
13141
14017
  process.exit(1);
13142
14018
  }
13143
14019
  console.log("\nServer is running.");
@@ -13162,13 +14038,14 @@ async function updateCommand() {
13162
14038
  const claudeWorkers = cfg.claudeWorkers ?? 8;
13163
14039
  const cursorWorkers = cfg.cursorWorkers ?? 0;
13164
14040
  const codexWorkers = cfg.codexWorkers ?? 0;
14041
+ const conductorProvider = cfg.conductorProvider;
13165
14042
  console.log("Synkro: updating to the latest container image");
13166
14043
  console.log(` preserving pool: ${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex worker(s)
13167
14044
  `);
13168
- await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
13169
- const ready = await waitForContainerReady(9e4);
14045
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
14046
+ const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
13170
14047
  if (!ready) {
13171
- console.error("\n\u26A0 container did not pass its health check within 90s \u2014 check: docker logs synkro-server");
14048
+ console.error("\n\u26A0 container did not pass its health check within 10m \u2014 check: docker logs synkro-server");
13172
14049
  process.exit(1);
13173
14050
  }
13174
14051
  try {
@@ -13190,20 +14067,22 @@ async function restartCommand(rest = []) {
13190
14067
  let claudeWorkers = cfg.claudeWorkers;
13191
14068
  let cursorWorkers = cfg.cursorWorkers;
13192
14069
  let codexWorkers = cfg.codexWorkers;
14070
+ let conductorProvider = cfg.conductorProvider;
13193
14071
  if (!cfg.explicit) {
13194
14072
  const reconciled = reconcileHarness();
13195
14073
  if (reconciled) {
13196
14074
  claudeWorkers = reconciled.claudeWorkers;
13197
14075
  cursorWorkers = reconciled.cursorWorkers;
13198
14076
  codexWorkers = reconciled.codexWorkers;
14077
+ conductorProvider = reconciled.conductorProvider;
13199
14078
  }
13200
14079
  }
13201
14080
  console.log(`Synkro: restarting server (${claudeWorkers} claude + ${cursorWorkers} cursor + ${codexWorkers} codex)
13202
14081
  `);
13203
- await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, connectedRepo: resolveConnectedRepo() });
13204
- const ready = await waitForContainerReady(6e4);
14082
+ await dockerUpdate({ claudeWorkers, cursorWorkers, codexWorkers, conductorProvider, connectedRepo: resolveConnectedRepo() });
14083
+ const ready = await waitForContainerReady(LOCAL_CONTAINER_READY_TIMEOUT_MS);
13205
14084
  if (!ready) {
13206
- console.error("\n\u26A0 container did not pass /healthz within 60s");
14085
+ console.error("\n\u26A0 container did not pass /healthz within 10m");
13207
14086
  process.exit(1);
13208
14087
  }
13209
14088
  console.log("\nServer restarted successfully.");
@@ -13215,11 +14094,13 @@ async function restartCommand(rest = []) {
13215
14094
  console.warn("\u26A0 workers did not register within 30s \u2014 skill sync skipped");
13216
14095
  }
13217
14096
  }
14097
+ var LOCAL_CONTAINER_READY_TIMEOUT_MS;
13218
14098
  var init_lifecycle = __esm({
13219
14099
  "cli/commands/lifecycle.ts"() {
13220
14100
  "use strict";
13221
14101
  init_dockerInstall();
13222
14102
  init_install();
14103
+ LOCAL_CONTAINER_READY_TIMEOUT_MS = 6e5;
13223
14104
  }
13224
14105
  });
13225
14106
 
@@ -13228,13 +14109,13 @@ var config_exports = {};
13228
14109
  __export(config_exports, {
13229
14110
  configCommand: () => configCommand
13230
14111
  });
13231
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync25, existsSync as existsSync36 } from "fs";
13232
- import { join as join36 } from "path";
13233
- import { homedir as homedir36 } from "os";
14112
+ import { readFileSync as readFileSync36, writeFileSync as writeFileSync26, existsSync as existsSync36 } from "fs";
14113
+ import { join as join37 } from "path";
14114
+ import { homedir as homedir37 } from "os";
13234
14115
  function readConfigEnv5() {
13235
14116
  if (!existsSync36(CONFIG_PATH9)) return {};
13236
14117
  const out = {};
13237
- for (const line of readFileSync35(CONFIG_PATH9, "utf-8").split("\n")) {
14118
+ for (const line of readFileSync36(CONFIG_PATH9, "utf-8").split("\n")) {
13238
14119
  const t = line.trim();
13239
14120
  if (!t || t.startsWith("#")) continue;
13240
14121
  const eq = t.indexOf("=");
@@ -13247,7 +14128,7 @@ function updateConfigValue(key, value) {
13247
14128
  console.error("No config found. Run `synkro install` first.");
13248
14129
  process.exit(1);
13249
14130
  }
13250
- const lines = readFileSync35(CONFIG_PATH9, "utf-8").split("\n");
14131
+ const lines = readFileSync36(CONFIG_PATH9, "utf-8").split("\n");
13251
14132
  const pattern = new RegExp(`^${key}=`);
13252
14133
  let found = false;
13253
14134
  const updated = lines.map((line) => {
@@ -13258,7 +14139,7 @@ function updateConfigValue(key, value) {
13258
14139
  return line;
13259
14140
  });
13260
14141
  if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
13261
- writeFileSync25(CONFIG_PATH9, updated.join("\n"), "utf-8");
14142
+ writeFileSync26(CONFIG_PATH9, updated.join("\n"), "utf-8");
13262
14143
  }
13263
14144
  function resolveInferenceMode(cfg) {
13264
14145
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -13416,8 +14297,8 @@ var init_config = __esm({
13416
14297
  "use strict";
13417
14298
  init_stub();
13418
14299
  init_optout();
13419
- SYNKRO_DIR18 = join36(homedir36(), ".synkro");
13420
- CONFIG_PATH9 = join36(SYNKRO_DIR18, "config.env");
14300
+ SYNKRO_DIR18 = join37(homedir37(), ".synkro");
14301
+ CONFIG_PATH9 = join37(SYNKRO_DIR18, "config.env");
13421
14302
  }
13422
14303
  });
13423
14304
 
@@ -13606,14 +14487,14 @@ Usage:
13606
14487
  });
13607
14488
 
13608
14489
  // cli/bootstrap.js
13609
- import { readFileSync as readFileSync36, existsSync as existsSync37 } from "fs";
14490
+ import { readFileSync as readFileSync37, existsSync as existsSync37 } from "fs";
13610
14491
  import { resolve as resolve5 } from "path";
13611
14492
  var envCandidates = [
13612
14493
  resolve5(process.env.HOME ?? "", ".synkro", "config.env")
13613
14494
  ];
13614
14495
  for (const envPath of envCandidates) {
13615
14496
  if (!existsSync37(envPath)) continue;
13616
- const envContent = readFileSync36(envPath, "utf-8");
14497
+ const envContent = readFileSync37(envPath, "utf-8");
13617
14498
  for (const line of envContent.split("\n")) {
13618
14499
  const trimmed = line.trim();
13619
14500
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -13630,7 +14511,7 @@ var subArgs = args.slice(1);
13630
14511
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
13631
14512
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "version", "--version", "-v", "help", "--help", "-h", ""]);
13632
14513
  function printVersion() {
13633
- console.log("1.7.87");
14514
+ console.log("1.7.89");
13634
14515
  }
13635
14516
  function printHelp2() {
13636
14517
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents