@synkro-sh/cli 1.10.8 → 1.10.10

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
@@ -81,10 +81,10 @@ import { createHash } from "crypto";
81
81
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
82
82
  import { homedir as homedir2, hostname, platform } from "os";
83
83
  import { join as join2 } from "path";
84
- function readConfigValue(key) {
85
- if (process.env[key]) return String(process.env[key]).toLowerCase();
84
+ function readConfigValue(key2) {
85
+ if (process.env[key2]) return String(process.env[key2]).toLowerCase();
86
86
  try {
87
- const re = key === "SYNKRO_GRADING_MODE" ? /^SYNKRO_GRADING_MODE=['"]?([^'"\n]*)/m : /^SYNKRO_STORAGE_MODE=['"]?([^'"\n]*)/m;
87
+ const re = key2 === "SYNKRO_GRADING_MODE" ? /^SYNKRO_GRADING_MODE=['"]?([^'"\n]*)/m : /^SYNKRO_STORAGE_MODE=['"]?([^'"\n]*)/m;
88
88
  const m = readFileSync2(CONFIG_PATH, "utf-8").match(re);
89
89
  return m ? m[1].toLowerCase() : "";
90
90
  } catch {
@@ -147,7 +147,7 @@ function getIdentity() {
147
147
  if (cached2) return cached2;
148
148
  let cliVersion2 = "0.0.0";
149
149
  try {
150
- cliVersion2 = "1.10.8";
150
+ cliVersion2 = "1.10.10";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -789,9 +789,9 @@ function sanitize(raw, maxLen = 256) {
789
789
  function shellQuoteSingle(value) {
790
790
  return `'${value.replace(/'/g, "'\\''")}'`;
791
791
  }
792
- function writeConfigEnvFlag(key, value) {
792
+ function writeConfigEnvFlag(key2, value) {
793
793
  const safe = sanitize(value, 8);
794
- const line = `${key}=${shellQuoteSingle(safe)}`;
794
+ const line = `${key2}=${shellQuoteSingle(safe)}`;
795
795
  let content = "";
796
796
  if (existsSync5(CONFIG_PATH2)) {
797
797
  try {
@@ -800,7 +800,7 @@ function writeConfigEnvFlag(key, value) {
800
800
  content = "";
801
801
  }
802
802
  }
803
- const re = new RegExp(`^${key}=.*$`, "m");
803
+ const re = new RegExp(`^${key2}=.*$`, "m");
804
804
  if (re.test(content)) {
805
805
  content = content.replace(re, line);
806
806
  } else {
@@ -1231,15 +1231,15 @@ async function exportEvents(path) {
1231
1231
  writeFileSync4(path, "", "utf-8");
1232
1232
  return;
1233
1233
  }
1234
- const lines = [];
1234
+ const lines2 = [];
1235
1235
  try {
1236
1236
  const rows = await sql`SELECT * FROM telemetry_events ORDER BY occurred_at ASC`;
1237
1237
  for (const row2 of rows) {
1238
- lines.push(JSON.stringify(row2));
1238
+ lines2.push(JSON.stringify(row2));
1239
1239
  }
1240
1240
  } catch {
1241
1241
  }
1242
- writeFileSync4(path, lines.join("\n") + (lines.length > 0 ? "\n" : ""), "utf-8");
1242
+ writeFileSync4(path, lines2.join("\n") + (lines2.length > 0 ? "\n" : ""), "utf-8");
1243
1243
  }
1244
1244
  function pendingFileSize() {
1245
1245
  try {
@@ -1645,10 +1645,9 @@ function installCCHooks(settingsPath, config) {
1645
1645
  {
1646
1646
  type: "command",
1647
1647
  command: config.cvePrecheckScriptPath,
1648
- // 15s: the on-findings VEX grade fans out across the warm pool; single-package
1649
- // edits finish in ~5s, but a multi-package edit (many CVEs) can approach ~10s,
1650
- // so 15 gives comfortable headroom. Only runs when a real vuln is present.
1651
- timeout: 15
1648
+ // Only a confirmed vulnerable runtime dependency reaches VEX triage. Codex can
1649
+ // take 13-17s for a valid answer, so match the bounded 25s gate plus its margin.
1650
+ timeout: 30
1652
1651
  },
1653
1652
  // skill-judge — its OWN system-message block, parallel with edit-precheck.
1654
1653
  ...config.skillJudgeScriptPath ? [{
@@ -1856,21 +1855,21 @@ function validateHooksPath(path) {
1856
1855
  return resolved;
1857
1856
  }
1858
1857
  function readHooksFile(rawPath) {
1859
- const safePath = validateHooksPath(rawPath);
1858
+ const safePath2 = validateHooksPath(rawPath);
1860
1859
  try {
1861
- const raw = readFileSync10(safePath, "utf-8");
1860
+ const raw = readFileSync10(safePath2, "utf-8");
1862
1861
  return JSON.parse(raw);
1863
1862
  } catch (err) {
1864
1863
  if (err?.code === "ENOENT") return { version: 1, hooks: {} };
1865
- throw new Error(`Failed to parse ${safePath}: ${err.message}`);
1864
+ throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
1866
1865
  }
1867
1866
  }
1868
1867
  function writeHooksFileAtomic(rawPath, data) {
1869
- const safePath = validateHooksPath(rawPath);
1870
- mkdirSync5(dirname2(safePath), { recursive: true });
1871
- const tmpPath = `${safePath}.synkro.tmp`;
1868
+ const safePath2 = validateHooksPath(rawPath);
1869
+ mkdirSync5(dirname2(safePath2), { recursive: true });
1870
+ const tmpPath = `${safePath2}.synkro.tmp`;
1872
1871
  writeFileSync7(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
1873
- renameSync4(tmpPath, safePath);
1872
+ renameSync4(tmpPath, safePath2);
1874
1873
  }
1875
1874
  function isSynkroEntry2(entry) {
1876
1875
  if (entry?.[SYNKRO_MARKER2]) return true;
@@ -1972,7 +1971,7 @@ function installCursorHooks(hooksJsonPath, config) {
1972
1971
  matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
1973
1972
  });
1974
1973
  pushCcHook(h, "preToolUse", config.cvePrecheckScriptPath, {
1975
- timeout: 10,
1974
+ timeout: 30,
1976
1975
  matcher: "Write|Edit|StrReplace|MultiEdit|NotebookEdit|edit_file|reapply|edit_notebook|ApplyPatch|apply_patch"
1977
1976
  });
1978
1977
  pushCcHook(h, "preToolUse", config.agentJudgeScriptPath, {
@@ -2158,20 +2157,20 @@ function validateHooksPath2(path) {
2158
2157
  return resolved;
2159
2158
  }
2160
2159
  function readHooksFile2(rawPath) {
2161
- const safePath = validateHooksPath2(rawPath);
2162
- if (!existsSync12(safePath)) return { hooks: {} };
2160
+ const safePath2 = validateHooksPath2(rawPath);
2161
+ if (!existsSync12(safePath2)) return { hooks: {} };
2163
2162
  try {
2164
- return JSON.parse(readFileSync11(safePath, "utf-8"));
2163
+ return JSON.parse(readFileSync11(safePath2, "utf-8"));
2165
2164
  } catch (err) {
2166
- throw new Error(`Failed to parse ${safePath}: ${err.message}`);
2165
+ throw new Error(`Failed to parse ${safePath2}: ${err.message}`);
2167
2166
  }
2168
2167
  }
2169
2168
  function writeHooksFileAtomic2(rawPath, data) {
2170
- const safePath = validateHooksPath2(rawPath);
2171
- mkdirSync6(dirname3(safePath), { recursive: true });
2172
- const tmpPath = `${safePath}.synkro.tmp`;
2169
+ const safePath2 = validateHooksPath2(rawPath);
2170
+ mkdirSync6(dirname3(safePath2), { recursive: true });
2171
+ const tmpPath = `${safePath2}.synkro.tmp`;
2173
2172
  writeFileSync8(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
2174
- renameSync5(tmpPath, safePath);
2173
+ renameSync5(tmpPath, safePath2);
2175
2174
  }
2176
2175
  function isSynkroEntry3(entry) {
2177
2176
  if (entry?.[SYNKRO_MARKER3]) return true;
@@ -2204,7 +2203,7 @@ function installCodexHooks(hooksJsonPath, config) {
2204
2203
  push(h, "PreToolUse", [
2205
2204
  cmd(config.editPrecheckScriptPath, 50, "Reviewing edit"),
2206
2205
  cmd(config.cwePrecheckScriptPath, 50),
2207
- cmd(config.cvePrecheckScriptPath, 15),
2206
+ cmd(config.cvePrecheckScriptPath, 30),
2208
2207
  ...config.skillJudgeScriptPath ? [cmd(config.skillJudgeScriptPath, 50)] : []
2209
2208
  ], M_EDIT);
2210
2209
  push(h, "PostToolUse", [
@@ -2351,7 +2350,7 @@ function buildCodexHookTrustEdits(hooks) {
2351
2350
  return [...edits.values()];
2352
2351
  }
2353
2352
  function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTrust = false) {
2354
- return new Promise((resolve8) => {
2353
+ return new Promise((resolve9) => {
2355
2354
  let settled = false;
2356
2355
  let stdout = "";
2357
2356
  let pending = "";
@@ -2369,7 +2368,7 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
2369
2368
  child.kill();
2370
2369
  } catch {
2371
2370
  }
2372
- resolve8(summary);
2371
+ resolve9(summary);
2373
2372
  };
2374
2373
  const timer = setTimeout(() => finish(null), 1e4);
2375
2374
  try {
@@ -2384,9 +2383,9 @@ function queryCodexHookTrust(codexBinary = "codex", cwd = process.cwd(), autoTru
2384
2383
  child.stdout.on("data", (chunk) => {
2385
2384
  stdout += chunk;
2386
2385
  pending += chunk;
2387
- const lines = pending.split("\n");
2388
- pending = lines.pop() || "";
2389
- for (const line of lines) {
2386
+ const lines2 = pending.split("\n");
2387
+ pending = lines2.pop() || "";
2388
+ for (const line of lines2) {
2390
2389
  let message;
2391
2390
  try {
2392
2391
  message = JSON.parse(line);
@@ -2620,11 +2619,11 @@ function writeCodexTomlAtomic(path, content) {
2620
2619
  renameSync6(tmpPath, path);
2621
2620
  }
2622
2621
  function removeCodexManagedBlock(content, path) {
2623
- const lines = content.split("\n");
2622
+ const lines2 = content.split("\n");
2624
2623
  const out = [];
2625
2624
  let removed = false;
2626
2625
  let inside = false;
2627
- for (const line of lines) {
2626
+ for (const line of lines2) {
2628
2627
  if (!inside && line.trim() === CODEX_MCP_BEGIN) {
2629
2628
  inside = true;
2630
2629
  removed = true;
@@ -2652,8 +2651,8 @@ function findCodexMcpSection(content) {
2652
2651
  body: content.slice(match.index, next?.index ?? content.length)
2653
2652
  };
2654
2653
  }
2655
- function readTomlString(block, key) {
2656
- const match = block.match(new RegExp(`^\\s*${key}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
2654
+ function readTomlString(block, key2) {
2655
+ const match = block.match(new RegExp(`^\\s*${key2}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")\\s*$`, "m"));
2657
2656
  if (!match) return null;
2658
2657
  try {
2659
2658
  return JSON.parse(match[1]);
@@ -5181,7 +5180,7 @@ function createCallbackServer() {
5181
5180
  "Access-Control-Allow-Headers": "Content-Type",
5182
5181
  "Vary": "Origin"
5183
5182
  };
5184
- return new Promise((resolve8, reject) => {
5183
+ return new Promise((resolve9, reject) => {
5185
5184
  const server = createServer((req, res) => {
5186
5185
  if (req.method === "OPTIONS") {
5187
5186
  const origin = req.headers.origin;
@@ -5270,7 +5269,7 @@ function createCallbackServer() {
5270
5269
  res.end(JSON.stringify({ ok: true }));
5271
5270
  setTimeout(() => {
5272
5271
  server.close();
5273
- resolve8(authData);
5272
+ resolve9(authData);
5274
5273
  }, 200);
5275
5274
  });
5276
5275
  req.on("error", (e) => {
@@ -5606,7 +5605,7 @@ function detectSubdirRepos() {
5606
5605
  }
5607
5606
  }
5608
5607
  function ask(rl, question) {
5609
- return new Promise((resolve8) => rl.question(question, resolve8));
5608
+ return new Promise((resolve9) => rl.question(question, resolve9));
5610
5609
  }
5611
5610
  async function linkRepo(repo, linkedNames) {
5612
5611
  try {
@@ -5845,7 +5844,7 @@ async function runClaudeDesktopTap(opts = {}) {
5845
5844
  writeFileSync12(join13(sessionDir, "mcp_patch.py"), MCP_PATCH_PY, "utf-8");
5846
5845
  const runnerPath = join13(sessionDir, "run.sh");
5847
5846
  writeFileSync12(runnerPath, buildRunner(sessionDir), { mode: 493 });
5848
- await new Promise((resolve8) => {
5847
+ await new Promise((resolve9) => {
5849
5848
  const child = spawn3("bash", [runnerPath], {
5850
5849
  stdio: "inherit",
5851
5850
  env: { ...process.env, SYNKRO_CAPTURE_URL: CAPTURE_URL, SYNKRO_SCAN_URL: SCAN_URL, SYNKRO_SCAN_TURN_URL: SCAN_TURN_URL, SYNKRO_DLP_POLICY_URL: DLP_POLICY_URL, SYNKRO_TURN_VERDICTS_URL: TURN_VERDICTS_URL, SYNKRO_TURN_VERDICT_URL: TURN_VERDICT_URL, SYNKRO_MCP_EVENT_URL: MCP_EVENT_URL, SYNKRO_TAP_TOKEN: token, SYNKRO_TAP_TOKEN_FILE: JWT_PATH, SYNKRO_CD_BACKFILL: opts.backfill ? "1" : "" }
@@ -5861,7 +5860,7 @@ async function runClaudeDesktopTap(opts = {}) {
5861
5860
  child.on("exit", () => {
5862
5861
  process.off("SIGINT", forward);
5863
5862
  process.off("SIGTERM", forward);
5864
- resolve8();
5863
+ resolve9();
5865
5864
  });
5866
5865
  });
5867
5866
  }
@@ -7034,8 +7033,8 @@ function cursorApiKeyConfigured() {
7034
7033
  return false;
7035
7034
  }
7036
7035
  }
7037
- function writeCursorApiKey(key) {
7038
- const trimmed = key.trim();
7036
+ function writeCursorApiKey(key2) {
7037
+ const trimmed = key2.trim();
7039
7038
  if (!trimmed) return;
7040
7039
  mkdirSync11(CURSOR_CREDS_DIR, { recursive: true });
7041
7040
  chmodSync2(CURSOR_CREDS_DIR, 448);
@@ -7043,15 +7042,15 @@ function writeCursorApiKey(key) {
7043
7042
  chmodSync2(CURSOR_API_KEY_FILE, 384);
7044
7043
  }
7045
7044
  async function validateCursorApiKey() {
7046
- let key;
7045
+ let key2;
7047
7046
  try {
7048
- key = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
7047
+ key2 = readFileSync16(CURSOR_API_KEY_FILE, "utf-8").trim();
7049
7048
  } catch {
7050
7049
  return null;
7051
7050
  }
7052
- if (!key) return null;
7051
+ if (!key2) return null;
7053
7052
  try {
7054
- const auth = Buffer.from(`${key}:`).toString("base64");
7053
+ const auth = Buffer.from(`${key2}:`).toString("base64");
7055
7054
  const r = await fetch("https://api.cursor.com/v1/me", {
7056
7055
  headers: { Authorization: `Basic ${auth}` },
7057
7056
  signal: AbortSignal.timeout(8e3)
@@ -7391,15 +7390,15 @@ function parseSynkroToml(raw) {
7391
7390
  }
7392
7391
  const eq = line.indexOf("=");
7393
7392
  if (eq === -1) continue;
7394
- const key = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
7393
+ const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
7395
7394
  let valRaw = line.slice(eq + 1).trim();
7396
7395
  if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
7397
7396
  const h = valRaw.indexOf("#");
7398
7397
  if (h !== -1) valRaw = valRaw.slice(0, h).trim();
7399
7398
  }
7400
7399
  const value = parseTomlValue(valRaw);
7401
- if (section) result[section][key] = value;
7402
- else result[key] = value;
7400
+ if (section) result[section][key2] = value;
7401
+ else result[key2] = value;
7403
7402
  }
7404
7403
  return result;
7405
7404
  }
@@ -7676,7 +7675,7 @@ async function dockerInstall(opts = {}) {
7676
7675
  "SYNKRO_CODEX_MODEL",
7677
7676
  "SYNKRO_CONDUCTOR_MODEL",
7678
7677
  "SYNKRO_ROUTE_MODEL"
7679
- ].flatMap((key) => process.env[key] ? ["-e", `${key}=${process.env[key]}`] : []),
7678
+ ].flatMap((key2) => process.env[key2] ? ["-e", `${key2}=${process.env[key2]}`] : []),
7680
7679
  // Fix-poll kill switch. Default ON in the image; a benchmark/headless run
7681
7680
  // (e.g. sec-code-bench) sets SYNKRO_FIX_POLL=0 so ask-mode violations skip the
7682
7681
  // interactive AskUserQuestion poll and fall through to generate-the-fix. Only
@@ -7934,7 +7933,7 @@ var init_dockerInstall = __esm({
7934
7933
  HOST_PGLITE_PORT = parseInt(process.env.SYNKRO_HOST_PGLITE_PORT || "15433", 10);
7935
7934
  CONTAINER_NAME = resolveContainerName();
7936
7935
  defaultImageVersion = () => {
7937
- if (true) return "1.10.8";
7936
+ if (true) return "1.10.10";
7938
7937
  try {
7939
7938
  const pkg = JSON.parse(readFileSync17(new URL("../../package.json", import.meta.url), "utf8"));
7940
7939
  if (pkg.version) return pkg.version;
@@ -7966,7 +7965,7 @@ function captureClaudeSetupToken() {
7966
7965
  const bin = "script";
7967
7966
  const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
7968
7967
  const OAUTH_HINT = 'The browser approval did not return a token. This usually means claude.ai rejected the OAuth request (its "Authorization failed \u2014 Unsupported media type" page), most often because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped the request headers, or you approved on the wrong Claude account. Fix: retry in a clean incognito window with extensions disabled, and approve on the Claude account you want the cloud workers to use.';
7969
- return new Promise((resolve8, reject) => {
7968
+ return new Promise((resolve9, reject) => {
7970
7969
  const proc = nodeSpawn(bin, args2, {
7971
7970
  stdio: "inherit",
7972
7971
  env: { ...process.env, FORCE_COLOR: "3", COLORTERM: "truecolor", TERM: "xterm-256color" }
@@ -8033,7 +8032,7 @@ function captureClaudeSetupToken() {
8033
8032
  reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));
8034
8033
  return;
8035
8034
  }
8036
- resolve8(token);
8035
+ resolve9(token);
8037
8036
  });
8038
8037
  });
8039
8038
  }
@@ -8059,13 +8058,13 @@ function findCodexBinary() {
8059
8058
  function runCodexLogin(codexBin, codexHome) {
8060
8059
  mkdirSync13(codexHome, { recursive: true, mode: 448 });
8061
8060
  writeFileSync15(join17(codexHome, "config.toml"), 'cli_auth_credentials_store = "file"\n', { mode: 384 });
8062
- return new Promise((resolve8, reject) => {
8061
+ return new Promise((resolve9, reject) => {
8063
8062
  const proc = nodeSpawn2(codexBin, ["login"], {
8064
8063
  stdio: "inherit",
8065
8064
  env: { ...process.env, CODEX_HOME: codexHome }
8066
8065
  });
8067
8066
  proc.on("error", (err) => reject(new Error(`failed to spawn codex login: ${err.message}`)));
8068
- proc.on("close", (code) => code === 0 ? resolve8() : reject(new Error(`codex login exited with code ${code}`)));
8067
+ proc.on("close", (code) => code === 0 ? resolve9() : reject(new Error(`codex login exited with code ${code}`)));
8069
8068
  });
8070
8069
  }
8071
8070
  async function setupCodexCloud(gatewayUrl, bearerToken, onStatus) {
@@ -8477,8 +8476,8 @@ function addUsage(a, b) {
8477
8476
  }
8478
8477
  function parseCodexTranscriptUsage(transcript, options = {}) {
8479
8478
  if (!transcript) return null;
8480
- const lines = transcript.split("\n");
8481
- const hasCanonicalAssistant = lines.some((line) => {
8479
+ const lines2 = transcript.split("\n");
8480
+ const hasCanonicalAssistant = lines2.some((line) => {
8482
8481
  try {
8483
8482
  const entry = JSON.parse(line);
8484
8483
  return entry?.type === "event_msg" && entry?.payload?.type === "agent_message";
@@ -8492,8 +8491,8 @@ function parseCodexTranscriptUsage(transcript, options = {}) {
8492
8491
  let previous = { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 };
8493
8492
  let latest = null;
8494
8493
  let sawSnapshot = false;
8495
- for (let i = 0; i < lines.length; i++) {
8496
- const line = lines[i].trim();
8494
+ for (let i = 0; i < lines2.length; i++) {
8495
+ const line = lines2[i].trim();
8497
8496
  if (!line) continue;
8498
8497
  let entry;
8499
8498
  try {
@@ -8675,8 +8674,8 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
8675
8674
  const entryModel = typeof message.model === "string" && message.model ? message.model : "unknown";
8676
8675
  if (entryModel !== "<synthetic>") model = entryModel;
8677
8676
  const day = isoDay(entry.timestamp, fallbackDay);
8678
- const key = `${day}\0${entryModel}`;
8679
- const row2 = rollups.get(key) ?? {
8677
+ const key2 = `${day}\0${entryModel}`;
8678
+ const row2 = rollups.get(key2) ?? {
8680
8679
  day,
8681
8680
  model: entryModel,
8682
8681
  turns: 0,
@@ -8690,7 +8689,7 @@ function parseClaudeTranscriptUsage(transcript, fallbackDay = (/* @__PURE__ */ n
8690
8689
  row2.output_tokens += counts.output_tokens;
8691
8690
  row2.cache_creation_input_tokens += counts.cache_creation_input_tokens;
8692
8691
  row2.cache_read_input_tokens += counts.cache_read_input_tokens;
8693
- rollups.set(key, row2);
8692
+ rollups.set(key2, row2);
8694
8693
  turns += 1;
8695
8694
  usage2.input_tokens += counts.input_tokens;
8696
8695
  usage2.output_tokens += counts.output_tokens;
@@ -8767,20 +8766,20 @@ async function promptAgentSelection(detected) {
8767
8766
  detected.forEach((a, i) => console.log(` ${i + 1}. ${a.name}`));
8768
8767
  console.log(` ${detected.length + 1}. Both / all (default)`);
8769
8768
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8770
- const ask3 = () => new Promise((resolve8) => {
8769
+ const ask3 = () => new Promise((resolve9) => {
8771
8770
  rl.question(`Pick [1-${detected.length + 1}] (default: all): `, (answer) => {
8772
8771
  const t = answer.trim().toLowerCase();
8773
8772
  if (t === "" || t === String(detected.length + 1) || t === "both" || t === "all") {
8774
8773
  rl.close();
8775
- return resolve8(detected);
8774
+ return resolve9(detected);
8776
8775
  }
8777
8776
  const n = parseInt(t, 10);
8778
8777
  if (Number.isInteger(n) && n >= 1 && n <= detected.length) {
8779
8778
  rl.close();
8780
- return resolve8([detected[n - 1]]);
8779
+ return resolve9([detected[n - 1]]);
8781
8780
  }
8782
8781
  console.log("Invalid choice. Try again.");
8783
- resolve8(ask3());
8782
+ resolve9(ask3());
8784
8783
  });
8785
8784
  });
8786
8785
  return ask3();
@@ -8803,17 +8802,17 @@ async function promptCursorApiKey(opts) {
8803
8802
  return;
8804
8803
  }
8805
8804
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8806
- const key = await new Promise((resolve8) => {
8805
+ const key2 = await new Promise((resolve9) => {
8807
8806
  rl.question(
8808
8807
  "Cursor grading needs a Cursor API key (cursor.com \u2192 Settings \u2192 API Keys).\nPaste it now, or press Enter to skip (Cursor workers stay idle until set): ",
8809
8808
  (answer) => {
8810
8809
  rl.close();
8811
- resolve8(answer.trim());
8810
+ resolve9(answer.trim());
8812
8811
  }
8813
8812
  );
8814
8813
  });
8815
- if (key) {
8816
- writeCursorApiKey2(key);
8814
+ if (key2) {
8815
+ writeCursorApiKey2(key2);
8817
8816
  console.log(" \u2713 Cursor API key saved.");
8818
8817
  } else {
8819
8818
  console.log(" \u26A0 Skipped \u2014 Cursor workers will be idle. Re-run install or pass --cursor-api-key=\u2026 later.");
@@ -8823,7 +8822,7 @@ async function promptDeployLocation(current = "local") {
8823
8822
  if (!process.stdin.isTTY) return current;
8824
8823
  const other = current === "cloud" ? "local" : "cloud";
8825
8824
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8826
- return new Promise((resolve8) => {
8825
+ return new Promise((resolve9) => {
8827
8826
  rl.question(
8828
8827
  `Where should Synkro run?
8829
8828
  local \u2014 a grading container on this machine (Docker)
@@ -8832,7 +8831,7 @@ Each worker uses the account credentials you authorize. Choose [${current}] / ${
8832
8831
  (answer) => {
8833
8832
  rl.close();
8834
8833
  const a = answer.trim().toLowerCase();
8835
- resolve8(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
8834
+ resolve9(a === "cloud" ? "cloud" : a === "local" ? "local" : current);
8836
8835
  }
8837
8836
  );
8838
8837
  });
@@ -8986,7 +8985,7 @@ function writeConfigEnv(opts) {
8986
8985
  const safeTier = sanitizeConfigValue(opts.tier ?? "pro", 32);
8987
8986
  const safeInference = sanitizeConfigValue(opts.inference ?? "fast", 16);
8988
8987
  const safeSynkroBin = sanitizeConfigValue(opts.synkroBin ?? "", 1024);
8989
- const lines = [
8988
+ const lines2 = [
8990
8989
  "# Synkro CLI config (managed by synkro install)",
8991
8990
  "# JWT auth \u2014 the hook scripts read SYNKRO_CREDENTIALS_PATH at runtime",
8992
8991
  "# and send Authorization: Bearer <access_token> on every gateway call.",
@@ -8994,27 +8993,27 @@ function writeConfigEnv(opts) {
8994
8993
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle2(credsPath)}`,
8995
8994
  `SYNKRO_TIER=${shellQuoteSingle2(safeTier)}`,
8996
8995
  `SYNKRO_INFERENCE=${shellQuoteSingle2(safeInference)}`,
8997
- `SYNKRO_VERSION=${shellQuoteSingle2("1.10.8")}`
8996
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.10.10")}`
8998
8997
  ];
8999
- if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
9000
- if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
9001
- if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle2(safeOrgId)}`);
9002
- if (safeEmail) lines.push(`SYNKRO_EMAIL=${shellQuoteSingle2(safeEmail)}`);
8998
+ if (safeSynkroBin) lines2.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
8999
+ if (safeUserId) lines2.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
9000
+ if (safeOrgId) lines2.push(`SYNKRO_ORG_ID=${shellQuoteSingle2(safeOrgId)}`);
9001
+ if (safeEmail) lines2.push(`SYNKRO_EMAIL=${shellQuoteSingle2(safeEmail)}`);
9003
9002
  if (opts.transcriptConsent !== void 0) {
9004
- lines.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle2(opts.transcriptConsent ? "yes" : "no")}`);
9003
+ lines2.push(`SYNKRO_TRANSCRIPT_CONSENT=${shellQuoteSingle2(opts.transcriptConsent ? "yes" : "no")}`);
9005
9004
  }
9006
- if (opts.transcriptConsentCC !== void 0) lines.push(`SYNKRO_TRANSCRIPT_CONSENT_CC=${shellQuoteSingle2(opts.transcriptConsentCC ? "yes" : "no")}`);
9007
- if (opts.transcriptConsentCursor !== void 0) lines.push(`SYNKRO_TRANSCRIPT_CONSENT_CURSOR=${shellQuoteSingle2(opts.transcriptConsentCursor ? "yes" : "no")}`);
9008
- if (opts.transcriptConsentCodex !== void 0) lines.push(`SYNKRO_TRANSCRIPT_CONSENT_CODEX=${shellQuoteSingle2(opts.transcriptConsentCodex ? "yes" : "no")}`);
9009
- lines.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle2(opts.localInference ? "yes" : "no")}`);
9005
+ if (opts.transcriptConsentCC !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CC=${shellQuoteSingle2(opts.transcriptConsentCC ? "yes" : "no")}`);
9006
+ if (opts.transcriptConsentCursor !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CURSOR=${shellQuoteSingle2(opts.transcriptConsentCursor ? "yes" : "no")}`);
9007
+ if (opts.transcriptConsentCodex !== void 0) lines2.push(`SYNKRO_TRANSCRIPT_CONSENT_CODEX=${shellQuoteSingle2(opts.transcriptConsentCodex ? "yes" : "no")}`);
9008
+ lines2.push(`SYNKRO_LOCAL_INFERENCE=${shellQuoteSingle2(opts.localInference ? "yes" : "no")}`);
9010
9009
  const safeMode = sanitizeConfigValue(opts.deploymentMode ?? "docker", 16);
9011
- lines.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle2(safeMode)}`);
9012
- lines.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
9013
- lines.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
9014
- lines.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
9015
- lines.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
9016
- lines.push("");
9017
- writeFileSync17(CONFIG_PATH4, lines.join("\n"), "utf-8");
9010
+ lines2.push(`SYNKRO_DEPLOYMENT_MODE=${shellQuoteSingle2(safeMode)}`);
9011
+ lines2.push(`SYNKRO_GRADING_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.gradingMode ?? "local", 16))}`);
9012
+ lines2.push(`SYNKRO_STORAGE_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.storageMode ?? "local", 16))}`);
9013
+ lines2.push(`SYNKRO_DEPLOY_LOCATION=${shellQuoteSingle2(sanitizeConfigValue(opts.deployLocation ?? "local", 16))}`);
9014
+ lines2.push(`SYNKRO_HOOK_MODE=${shellQuoteSingle2(sanitizeConfigValue(opts.hookMode ?? "stub", 8))}`);
9015
+ lines2.push("");
9016
+ writeFileSync17(CONFIG_PATH4, lines2.join("\n"), "utf-8");
9018
9017
  chmodSync5(CONFIG_PATH4, 384);
9019
9018
  }
9020
9019
  function persistedTranscriptConsent(source) {
@@ -9741,7 +9740,7 @@ async function installCommand(opts = {}) {
9741
9740
  await setTelemetryState({ enabled: true, remoteFlushEnabled: telemetryConsent });
9742
9741
  emit("install", {
9743
9742
  phase: "started",
9744
- cli_version_to: "1.10.8",
9743
+ cli_version_to: "1.10.10",
9745
9744
  agents_detected: agents.map((a) => a.kind),
9746
9745
  with_github: false,
9747
9746
  with_local_cc: false,
@@ -10303,15 +10302,15 @@ function parseSynkroToml2(raw) {
10303
10302
  }
10304
10303
  const eq = line.indexOf("=");
10305
10304
  if (eq === -1) continue;
10306
- const key = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
10305
+ const key2 = line.slice(0, eq).trim().replace(/^["']|["']$/g, "");
10307
10306
  let valRaw = line.slice(eq + 1).trim();
10308
10307
  if (!valRaw.startsWith('"') && !valRaw.startsWith("'") && !valRaw.startsWith("[")) {
10309
10308
  const h = valRaw.indexOf("#");
10310
10309
  if (h !== -1) valRaw = valRaw.slice(0, h).trim();
10311
10310
  }
10312
10311
  const value = parseTomlValue2(valRaw);
10313
- if (section) result[section][key] = value;
10314
- else result[key] = value;
10312
+ if (section) result[section][key2] = value;
10313
+ else result[key2] = value;
10315
10314
  }
10316
10315
  return result;
10317
10316
  }
@@ -10592,8 +10591,8 @@ async function syncSkillFiles() {
10592
10591
  console.log(` \u2298 skill ${source}: empty file, skipped`);
10593
10592
  return null;
10594
10593
  }
10595
- const lines = content.split("\n").length;
10596
- console.log(` \u2192 read ${source} (${lines} lines, ${(content.length / 1024).toFixed(1)} KB)`);
10594
+ const lines2 = content.split("\n").length;
10595
+ console.log(` \u2192 read ${source} (${lines2} lines, ${(content.length / 1024).toFixed(1)} KB)`);
10597
10596
  return { source, content };
10598
10597
  }).filter(Boolean);
10599
10598
  if (tasks.length === 0) return;
@@ -10872,10 +10871,10 @@ function extractSessionInsights(projectsDir) {
10872
10871
  const filePath = join19(projectsDir, file);
10873
10872
  try {
10874
10873
  const content = readFileSync21(filePath, "utf-8");
10875
- const lines = content.split("\n").filter(Boolean);
10876
- for (let i = 0; i < lines.length; i++) {
10874
+ const lines2 = content.split("\n").filter(Boolean);
10875
+ for (let i = 0; i < lines2.length; i++) {
10877
10876
  try {
10878
- const entry = JSON.parse(lines[i]);
10877
+ const entry = JSON.parse(lines2[i]);
10879
10878
  if (entry.type === "user" && typeof entry.message?.content === "string" && entry.message.content.startsWith("This session is being continued")) {
10880
10879
  insights.push({
10881
10880
  session_id: sessionId,
@@ -10888,9 +10887,9 @@ function extractSessionInsights(projectsDir) {
10888
10887
  }
10889
10888
  }
10890
10889
  const userMessages = [];
10891
- for (let i = lines.length - 1; i >= 0 && userMessages.length < 20; i--) {
10890
+ for (let i = lines2.length - 1; i >= 0 && userMessages.length < 20; i--) {
10892
10891
  try {
10893
- const entry = JSON.parse(lines[i]);
10892
+ const entry = JSON.parse(lines2[i]);
10894
10893
  if (entry.type === "user") {
10895
10894
  const text = typeof entry.message?.content === "string" ? entry.message.content : Array.isArray(entry.message?.content) ? entry.message.content.map((b) => b.text ?? b).filter((t) => typeof t === "string").join(" ") : null;
10896
10895
  if (text && text.length > 10 && text.length < 2e3 && !text.startsWith("This session is being continued")) {
@@ -10949,13 +10948,13 @@ function extractTextContent(content) {
10949
10948
  function getCodexTranscriptFiles(repo) {
10950
10949
  const sessionsDir = join19(process.env.CODEX_HOME || join19(homedir21(), ".codex"), "sessions");
10951
10950
  if (!existsSync22(sessionsDir)) return [];
10952
- let relative2 = [];
10951
+ let relative3 = [];
10953
10952
  try {
10954
- relative2 = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
10953
+ relative3 = readdirSync4(sessionsDir, { recursive: true, encoding: "utf-8" });
10955
10954
  } catch {
10956
10955
  return [];
10957
10956
  }
10958
- return relative2.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
10957
+ return relative3.filter((p) => p.endsWith(".jsonl")).map((p) => join19(sessionsDir, p)).filter((filePath) => {
10959
10958
  try {
10960
10959
  const first = readFileSync21(filePath, "utf-8").split("\n", 1)[0];
10961
10960
  const meta = JSON.parse(first);
@@ -10972,11 +10971,11 @@ function isJsonSyntaxError(error) {
10972
10971
  }
10973
10972
  function parseCodexTranscriptFile(filePath) {
10974
10973
  const transcript = readFileSync21(filePath, "utf-8");
10975
- const lines = transcript.split("\n");
10974
+ const lines2 = transcript.split("\n");
10976
10975
  const transcriptUsage = parseCodexTranscriptUsage(transcript);
10977
10976
  let sessionId = "";
10978
10977
  let model = "";
10979
- for (const line of lines) {
10978
+ for (const line of lines2) {
10980
10979
  try {
10981
10980
  const entry = JSON.parse(line);
10982
10981
  if (entry.type === "session_meta") {
@@ -11060,11 +11059,11 @@ function isSafeConvId(id) {
11060
11059
  }
11061
11060
  function parseCursorTranscriptFile(filePath) {
11062
11061
  const content = readFileSync21(filePath, "utf-8");
11063
- const lines = content.split("\n").filter(Boolean);
11062
+ const lines2 = content.split("\n").filter(Boolean);
11064
11063
  const messages = [];
11065
- for (let i = 0; i < lines.length; i++) {
11064
+ for (let i = 0; i < lines2.length; i++) {
11066
11065
  try {
11067
- const entry = JSON.parse(lines[i]);
11066
+ const entry = JSON.parse(lines2[i]);
11068
11067
  const role = entry.role || entry.message?.role;
11069
11068
  if (role !== "user" && role !== "assistant") continue;
11070
11069
  const text = extractTextContent(entry.message?.content ?? entry.content);
@@ -11124,11 +11123,11 @@ async function syncCursorTranscriptsLocal(mcpPort, mcpToken, repo) {
11124
11123
  }
11125
11124
  function parseTranscriptFile(filePath) {
11126
11125
  const content = readFileSync21(filePath, "utf-8");
11127
- const lines = content.split("\n").filter(Boolean);
11126
+ const lines2 = content.split("\n").filter(Boolean);
11128
11127
  const messages = [];
11129
- for (let i = 0; i < lines.length; i++) {
11128
+ for (let i = 0; i < lines2.length; i++) {
11130
11129
  try {
11131
- const entry = JSON.parse(lines[i]);
11130
+ const entry = JSON.parse(lines2[i]);
11132
11131
  if (entry.type !== "user" && entry.type !== "assistant") continue;
11133
11132
  const msg = {
11134
11133
  message_index: i,
@@ -12030,10 +12029,10 @@ function confirmPurge() {
12030
12029
  return Promise.resolve(false);
12031
12030
  }
12032
12031
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
12033
- return new Promise((resolve8) => {
12032
+ return new Promise((resolve9) => {
12034
12033
  rl.question(" Type 'yes' to wipe everything (anything else cancels): ", (answer) => {
12035
12034
  rl.close();
12036
- resolve8(answer.trim().toLowerCase() === "yes");
12035
+ resolve9(answer.trim().toLowerCase() === "yes");
12037
12036
  });
12038
12037
  });
12039
12038
  }
@@ -12194,8 +12193,8 @@ function readRecentTurns(n = 20) {
12194
12193
  const size = statSync3(TURN_LOG_PATH).size;
12195
12194
  if (size === 0) return [];
12196
12195
  const text = readFileSync23(TURN_LOG_PATH, "utf-8");
12197
- const lines = text.split("\n").filter(Boolean);
12198
- const lastN = lines.slice(-n).reverse();
12196
+ const lines2 = text.split("\n").filter(Boolean);
12197
+ const lastN = lines2.slice(-n).reverse();
12199
12198
  return lastN.map((line) => {
12200
12199
  try {
12201
12200
  return JSON.parse(line);
@@ -12286,7 +12285,7 @@ async function submitToChannel(role, payload, opts = {}) {
12286
12285
  const port = opts.port ?? CHANNEL_PORT;
12287
12286
  const startedAt = Date.now();
12288
12287
  try {
12289
- const result = await new Promise((resolve8, reject) => {
12288
+ const result = await new Promise((resolve9, reject) => {
12290
12289
  const req = httpRequest({
12291
12290
  host: CHANNEL_HOST,
12292
12291
  port,
@@ -12312,7 +12311,7 @@ async function submitToChannel(role, payload, opts = {}) {
12312
12311
  reject(new LocalCCError(parsed.error));
12313
12312
  return;
12314
12313
  }
12315
- resolve8(String(parsed.result ?? ""));
12314
+ resolve9(String(parsed.result ?? ""));
12316
12315
  } catch (err) {
12317
12316
  reject(new LocalCCError(`malformed channel response: ${text.slice(0, 200)}`, err));
12318
12317
  }
@@ -12338,14 +12337,14 @@ async function submitToChannel(role, payload, opts = {}) {
12338
12337
  }
12339
12338
  }
12340
12339
  function isChannelAvailable(port = CHANNEL_PORT, timeoutMs = 500) {
12341
- return new Promise((resolve8) => {
12340
+ return new Promise((resolve9) => {
12342
12341
  const sock = connect(port, CHANNEL_HOST);
12343
12342
  const done = (ok) => {
12344
12343
  try {
12345
12344
  sock.destroy();
12346
12345
  } catch {
12347
12346
  }
12348
- resolve8(ok);
12347
+ resolve9(ok);
12349
12348
  };
12350
12349
  sock.once("connect", () => done(true));
12351
12350
  sock.once("error", () => done(false));
@@ -12377,10 +12376,10 @@ __export(grade_exports, {
12377
12376
  gradeCommand: () => gradeCommand
12378
12377
  });
12379
12378
  async function readStdin() {
12380
- return new Promise((resolve8, reject) => {
12379
+ return new Promise((resolve9, reject) => {
12381
12380
  const chunks = [];
12382
12381
  process.stdin.on("data", (c) => chunks.push(c));
12383
- process.stdin.on("end", () => resolve8(Buffer.concat(chunks).toString("utf-8")));
12382
+ process.stdin.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
12384
12383
  process.stdin.on("error", reject);
12385
12384
  });
12386
12385
  }
@@ -12467,9 +12466,9 @@ async function fetchOrgRules(gatewayUrl, apiKey) {
12467
12466
  function applyLiteralMatchNegative(rules, file) {
12468
12467
  if (!file.patch) return [];
12469
12468
  const findings = [];
12470
- const lines = file.patch.split("\n");
12469
+ const lines2 = file.patch.split("\n");
12471
12470
  let currentNewLine = 0;
12472
- for (const line of lines) {
12471
+ for (const line of lines2) {
12473
12472
  if (line.startsWith("@@")) {
12474
12473
  const m = line.match(/\+(\d+)(?:,\d+)?/);
12475
12474
  if (m) currentNewLine = parseInt(m[1], 10);
@@ -12597,12 +12596,12 @@ async function fetchScanContext(gatewayUrl, apiKey, repo, prNumber, sha) {
12597
12596
  }
12598
12597
  function getFileDiffWithLines(file) {
12599
12598
  if (!file.patch) return { hunks: "", newFileLineMap: /* @__PURE__ */ new Map() };
12600
- const lines = file.patch.split("\n");
12599
+ const lines2 = file.patch.split("\n");
12601
12600
  const annotated = [];
12602
12601
  const lineMap = /* @__PURE__ */ new Map();
12603
12602
  let currentNewLine = 0;
12604
12603
  let patchIndex = 0;
12605
- for (const line of lines) {
12604
+ for (const line of lines2) {
12606
12605
  patchIndex++;
12607
12606
  if (line.startsWith("@@")) {
12608
12607
  const match = line.match(/\+(\d+)(?:,\d+)?/);
@@ -12634,7 +12633,7 @@ function spawnClaudeJudge(file, claudeToken, promptHeader) {
12634
12633
  Diff:
12635
12634
  ${hunks}`;
12636
12635
  const fullPrompt = promptHeader + userMessage;
12637
- return new Promise((resolve8) => {
12636
+ return new Promise((resolve9) => {
12638
12637
  const t0 = Date.now();
12639
12638
  const proc = spawn6(
12640
12639
  "claude",
@@ -12662,7 +12661,7 @@ ${hunks}`;
12662
12661
  const latencyMs = Date.now() - t0;
12663
12662
  if (code !== 0) {
12664
12663
  console.warn(` claude exited ${code}: ${(stderr || stdout).slice(0, 500)}`);
12665
- resolve8({ findings: [], latencyMs });
12664
+ resolve9({ findings: [], latencyMs });
12666
12665
  return;
12667
12666
  }
12668
12667
  try {
@@ -12681,10 +12680,10 @@ ${hunks}`;
12681
12680
  description: f.description,
12682
12681
  fix: f.fix
12683
12682
  }));
12684
- resolve8({ findings, latencyMs });
12683
+ resolve9({ findings, latencyMs });
12685
12684
  } catch (parseErr) {
12686
12685
  console.warn(` failed to parse claude response: ${stdout.slice(0, 300)}`);
12687
- resolve8({ findings: [], latencyMs });
12686
+ resolve9({ findings: [], latencyMs });
12688
12687
  }
12689
12688
  });
12690
12689
  });
@@ -12733,7 +12732,7 @@ ${JSON.stringify(findings, null, 2)}
12733
12732
  `;
12734
12733
  }
12735
12734
  function spawnOpusConsolidator(findings, claudeToken) {
12736
- return new Promise((resolve8) => {
12735
+ return new Promise((resolve9) => {
12737
12736
  const prompt = buildConsolidationPrompt(findings);
12738
12737
  const proc = spawn6(
12739
12738
  "claude",
@@ -12760,7 +12759,7 @@ function spawnOpusConsolidator(findings, claudeToken) {
12760
12759
  proc.on("close", (code) => {
12761
12760
  if (code !== 0) {
12762
12761
  console.warn(` opus consolidation exited ${code}: ${(stderr || stdout).slice(0, 300)}`);
12763
- resolve8(fallbackReview(findings));
12762
+ resolve9(fallbackReview(findings));
12764
12763
  return;
12765
12764
  }
12766
12765
  try {
@@ -12781,10 +12780,10 @@ function spawnOpusConsolidator(findings, claudeToken) {
12781
12780
  const order = ["low", "medium", "high", "critical"];
12782
12781
  return order.indexOf(f.severity) > order.indexOf(max) ? f.severity : max;
12783
12782
  }, "low");
12784
- resolve8({ summary: review.summary || "", comments, severity: maxSeverity });
12783
+ resolve9({ summary: review.summary || "", comments, severity: maxSeverity });
12785
12784
  } catch {
12786
12785
  console.warn(` failed to parse opus response, using fallback`);
12787
- resolve8(fallbackReview(findings));
12786
+ resolve9(fallbackReview(findings));
12788
12787
  }
12789
12788
  });
12790
12789
  });
@@ -12792,15 +12791,15 @@ function spawnOpusConsolidator(findings, claudeToken) {
12792
12791
  function fallbackReview(findings) {
12793
12792
  const grouped = /* @__PURE__ */ new Map();
12794
12793
  for (const f of findings) {
12795
- const key = `${f.file}::${f.category}`;
12796
- if (!grouped.has(key)) grouped.set(key, []);
12797
- grouped.get(key).push(f);
12794
+ const key2 = `${f.file}::${f.category}`;
12795
+ if (!grouped.has(key2)) grouped.set(key2, []);
12796
+ grouped.get(key2).push(f);
12798
12797
  }
12799
12798
  const comments = [];
12800
12799
  for (const [, group] of grouped) {
12801
12800
  const first = group[0];
12802
- const lines = group.map((f) => f.line);
12803
- const linesStr = lines.length > 1 ? `Lines ${lines.join(", ")}` : `Line ${lines[0]}`;
12801
+ const lines2 = group.map((f) => f.line);
12802
+ const linesStr = lines2.length > 1 ? `Lines ${lines2.join(", ")}` : `Line ${lines2[0]}`;
12804
12803
  const severityEmoji = first.severity === "critical" ? "\u{1F534}" : first.severity === "high" ? "\u{1F7E0}" : first.severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
12805
12804
  comments.push({
12806
12805
  path: first.file,
@@ -13254,10 +13253,10 @@ function stopTask(channel = CHANNEL_PRIMARY) {
13254
13253
  t = findTask(channel);
13255
13254
  }
13256
13255
  }
13257
- function tailLogs(lines = 80, channel = CHANNEL_PRIMARY) {
13256
+ function tailLogs(lines2 = 80, channel = CHANNEL_PRIMARY) {
13258
13257
  const t = findTask(channel);
13259
13258
  if (!t) return `(no ${channel.taskLabel} task)`;
13260
- const r = spawnSync9("pueue", ["log", "--lines", String(lines), String(t.id)], { encoding: "utf-8" });
13259
+ const r = spawnSync9("pueue", ["log", "--lines", String(lines2), String(t.id)], { encoding: "utf-8" });
13261
13260
  return r.stdout || r.stderr || "(no output)";
13262
13261
  }
13263
13262
  function ensureRunning(opts = {}) {
@@ -13267,14 +13266,14 @@ function ensureRunning(opts = {}) {
13267
13266
  return startTask(opts);
13268
13267
  }
13269
13268
  function probePort(host, port, timeoutMs = 500) {
13270
- return new Promise((resolve8) => {
13269
+ return new Promise((resolve9) => {
13271
13270
  const sock = connect2(port, host);
13272
13271
  const done = (ok) => {
13273
13272
  try {
13274
13273
  sock.destroy();
13275
13274
  } catch {
13276
13275
  }
13277
- resolve8(ok);
13276
+ resolve9(ok);
13278
13277
  };
13279
13278
  sock.once("connect", () => done(true));
13280
13279
  sock.once("error", () => done(false));
@@ -13824,7 +13823,7 @@ function cmdLogs(rest) {
13824
13823
  if (!raw) console.log(" " + colorize("(use --raw / -r to see full payloads, --live / -f to follow)", 90));
13825
13824
  return;
13826
13825
  }
13827
- return new Promise((resolve8) => {
13826
+ return new Promise((resolve9) => {
13828
13827
  console.log(" " + colorize("\u2014 following new turns (Ctrl-C to exit) \u2014", 90));
13829
13828
  const stop = followTurns((t) => {
13830
13829
  console.log(" " + formatTurn(t, raw));
@@ -13832,7 +13831,7 @@ function cmdLogs(rest) {
13832
13831
  const onSigint = () => {
13833
13832
  stop();
13834
13833
  process.removeListener("SIGINT", onSigint);
13835
- resolve8();
13834
+ resolve9();
13836
13835
  };
13837
13836
  process.on("SIGINT", onSigint);
13838
13837
  });
@@ -14050,7 +14049,7 @@ function extractToolResultText(content, e) {
14050
14049
  function parseSession(file, seenStableIds) {
14051
14050
  const { filePath, sessionId, parentSessionId } = file;
14052
14051
  const transcript = readFileSync27(filePath, "utf-8");
14053
- const lines = transcript.split("\n").filter(Boolean);
14052
+ const lines2 = transcript.split("\n").filter(Boolean);
14054
14053
  const transcriptUsage = parseClaudeTranscriptUsage(
14055
14054
  transcript,
14056
14055
  statSync4(filePath).mtime.toISOString().slice(0, 10),
@@ -14062,10 +14061,10 @@ function parseSession(file, seenStableIds) {
14062
14061
  const messages = [];
14063
14062
  const actions = [];
14064
14063
  let step = 0;
14065
- for (let i = 0; i < lines.length; i++) {
14064
+ for (let i = 0; i < lines2.length; i++) {
14066
14065
  let e;
14067
14066
  try {
14068
- e = JSON.parse(lines[i]);
14067
+ e = JSON.parse(lines2[i]);
14069
14068
  } catch {
14070
14069
  continue;
14071
14070
  }
@@ -14121,9 +14120,9 @@ function parseSession(file, seenStableIds) {
14121
14120
  }
14122
14121
  function ask2(q) {
14123
14122
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
14124
- return new Promise((resolve8) => rl.question(q, (a) => {
14123
+ return new Promise((resolve9) => rl.question(q, (a) => {
14125
14124
  rl.close();
14126
- resolve8(/^y(es)?$/i.test(a.trim()));
14125
+ resolve9(/^y(es)?$/i.test(a.trim()));
14127
14126
  }));
14128
14127
  }
14129
14128
  async function importCommand() {
@@ -14261,8 +14260,8 @@ function computeDigest(canonical2) {
14261
14260
  }
14262
14261
  function verifySignature(digest, signatureB64, publicKeyPem) {
14263
14262
  try {
14264
- const key = crypto.createPublicKey(publicKeyPem);
14265
- return crypto.verify(null, Buffer.from(digest, "utf8"), key, Buffer.from(signatureB64, "base64"));
14263
+ const key2 = crypto.createPublicKey(publicKeyPem);
14264
+ return crypto.verify(null, Buffer.from(digest, "utf8"), key2, Buffer.from(signatureB64, "base64"));
14266
14265
  } catch {
14267
14266
  return false;
14268
14267
  }
@@ -14654,6 +14653,7 @@ function buildSpawnAgent(opts) {
14654
14653
  ["tmux", "set-option", "-t", session, "-q", "@synkro_harness", opts.harness],
14655
14654
  ["tmux", "set-option", "-t", session, "-q", "@synkro_space", opts.space],
14656
14655
  ["tmux", "set-option", "-t", session, "-q", "@synkro_backend", opts.backend],
14656
+ ["tmux", "set-option", "-t", session, "-q", "@synkro_mode", opts.mode || "native"],
14657
14657
  // Keep the pane visible after exit so the sidebar can render 'done'
14658
14658
  // instead of the agent silently vanishing.
14659
14659
  ["tmux", "set-option", "-t", session, "remain-on-exit", "on"]
@@ -14675,7 +14675,7 @@ function buildAgentSnapshot() {
14675
14675
  return ["sh", "-c", script];
14676
14676
  }
14677
14677
  function parseAgentSnapshot(output) {
14678
- const lines = String(output || "").split("\n");
14678
+ const lines2 = String(output || "").split("\n");
14679
14679
  const captures = /* @__PURE__ */ new Map();
14680
14680
  const listLines = [];
14681
14681
  let current = null;
@@ -14683,7 +14683,7 @@ function parseAgentSnapshot(output) {
14683
14683
  const flush2 = () => {
14684
14684
  if (current) captures.set(current, chunk.join("\n"));
14685
14685
  };
14686
- for (const line of lines) {
14686
+ for (const line of lines2) {
14687
14687
  if (line.startsWith("===")) {
14688
14688
  flush2();
14689
14689
  current = line.slice(3).trim();
@@ -14962,12 +14962,12 @@ async function discoverAgents(runner, backend, memory) {
14962
14962
  }
14963
14963
  function offlineAgents(live, records) {
14964
14964
  const alive = new Set(live.map((agent) => agent.session));
14965
- return records.filter((record) => !alive.has(record.session)).map((record) => ({
14966
- name: record.name,
14967
- session: record.session,
14968
- harness: record.harness,
14969
- space: record.space,
14970
- backend: record.backend,
14965
+ return records.filter((record2) => !alive.has(record2.session)).map((record2) => ({
14966
+ name: record2.name,
14967
+ session: record2.session,
14968
+ harness: record2.harness,
14969
+ space: record2.space,
14970
+ backend: record2.backend,
14971
14971
  status: "offline"
14972
14972
  }));
14973
14973
  }
@@ -15004,8 +15004,8 @@ function saveRecords(records) {
15004
15004
  } catch {
15005
15005
  }
15006
15006
  }
15007
- function recordSession(record) {
15008
- saveRecords([...loadRecords().filter((row2) => row2.session !== record.session), record]);
15007
+ function recordSession(record2) {
15008
+ saveRecords([...loadRecords().filter((row2) => row2.session !== record2.session), record2]);
15009
15009
  }
15010
15010
  function forgetSession(session) {
15011
15011
  saveRecords(loadRecords().filter((row2) => row2.session !== session));
@@ -15040,11 +15040,11 @@ function lastAgentFor(space) {
15040
15040
  return loadLastAgents()[String(space || "").replace(/\/+$/, "")] || "";
15041
15041
  }
15042
15042
  function rememberLastAgent(space, session) {
15043
- const key = String(space || "").replace(/\/+$/, "");
15044
- if (!key || !session) return;
15043
+ const key2 = String(space || "").replace(/\/+$/, "");
15044
+ if (!key2 || !session) return;
15045
15045
  try {
15046
15046
  mkdirSync21(dirname10(LAST_AGENT_FILE), { recursive: true });
15047
- writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [key]: session }, null, 2));
15047
+ writeFileSync24(LAST_AGENT_FILE, JSON.stringify({ ...loadLastAgents(), [key2]: session }, null, 2));
15048
15048
  } catch {
15049
15049
  }
15050
15050
  }
@@ -15083,9 +15083,17 @@ var init_manifest = __esm({
15083
15083
  });
15084
15084
 
15085
15085
  // cli/ui/launch.ts
15086
- import { mkdirSync as mkdirSync22, writeFileSync as writeFileSync25 } from "fs";
15086
+ import { mkdirSync as mkdirSync22, statSync as statSync5, writeFileSync as writeFileSync25 } from "fs";
15087
15087
  import { homedir as homedir34 } from "os";
15088
15088
  import { join as join34 } from "path";
15089
+ function buildStamp(bootPath) {
15090
+ try {
15091
+ const stat = statSync5(bootPath);
15092
+ return String(stat.size) + ":" + String(Math.floor(stat.mtimeMs));
15093
+ } catch {
15094
+ return bootPath;
15095
+ }
15096
+ }
15089
15097
  function sidebarColumns(totalColumns) {
15090
15098
  return String(Math.max(20, Math.min(34, Math.round(totalColumns * 0.24))));
15091
15099
  }
@@ -15187,8 +15195,151 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
15187
15195
  // Alt+s always returns focus to the sidebar (the leftmost pane).
15188
15196
  ["bind-key", "-n", "M-s", "select-pane", "-L"]
15189
15197
  ];
15190
- for (const argv of style) await run(HOST, ["tmux", ...argv]);
15191
- for (const argv of buildClipboardBindings(UI_SESSION)) await run(HOST, argv);
15198
+ for (const argv of style) {
15199
+ if (!(await run(HOST, ["tmux", ...argv])).ok) return false;
15200
+ }
15201
+ for (const argv of buildClipboardBindings(UI_SESSION)) {
15202
+ if (!(await run(HOST, argv)).ok) return false;
15203
+ }
15204
+ return true;
15205
+ }
15206
+ function parseUiPanes(output) {
15207
+ return String(output || "").split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 7).map(([window, pane, width, dead, remain, cwd, ...command]) => ({
15208
+ window,
15209
+ pane,
15210
+ width: Number(width) || 0,
15211
+ dead: dead === "1",
15212
+ remain: remain === "on",
15213
+ cwd,
15214
+ command: command.join("|")
15215
+ })).filter((row2) => Boolean(row2.window && row2.pane));
15216
+ }
15217
+ async function ensureUiCenterPane(options) {
15218
+ const listed = await run(HOST, [
15219
+ "tmux",
15220
+ "list-panes",
15221
+ "-t",
15222
+ options.sidebarPane,
15223
+ "-F",
15224
+ "#{pane_id}|#{pane_dead}|#{remain-on-exit}|#{pane_start_command}"
15225
+ ]);
15226
+ if (!listed.ok) return { ok: false, pane: "", created: false, recovered: false };
15227
+ const rows = listed.stdout.split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 4).map(([pane, dead, remain, ...command]) => ({
15228
+ pane,
15229
+ dead: dead === "1",
15230
+ remain: remain === "on",
15231
+ command: command.join("|")
15232
+ }));
15233
+ let center = rows.find((row2) => row2.pane !== options.sidebarPane);
15234
+ let created = false;
15235
+ if (!center) {
15236
+ const split = await run(HOST, [
15237
+ "tmux",
15238
+ "split-window",
15239
+ "-h",
15240
+ "-t",
15241
+ options.sidebarPane,
15242
+ "-c",
15243
+ options.cwd,
15244
+ "-P",
15245
+ "-F",
15246
+ "#{pane_id}",
15247
+ "tail -f /dev/null"
15248
+ ]);
15249
+ const pane = split.stdout.trim();
15250
+ if (!split.ok || !pane) return { ok: false, pane: "", created: false, recovered: false };
15251
+ center = { pane, dead: false, remain: false, command: "tail -f /dev/null" };
15252
+ created = true;
15253
+ }
15254
+ const kept = await run(HOST, buildKeepPaneAfterExit(center.pane));
15255
+ if (!kept.ok) return { ok: false, pane: center.pane, created, recovered: false };
15256
+ const recovered = created || center.dead;
15257
+ if (recovered) {
15258
+ const command = options.terminalCommand === void 0 ? makeTerminalCommand(options.bootPath) : options.terminalCommand;
15259
+ const respawn = ["tmux", "respawn-pane", "-k", "-t", center.pane, "-c", options.cwd];
15260
+ if (command) respawn.push(command);
15261
+ const restored = await run(HOST, respawn);
15262
+ if (!restored.ok) return { ok: false, pane: center.pane, created, recovered: false };
15263
+ const spaceName = options.cwd.split("/").filter(Boolean).pop() || "space";
15264
+ const metadata = [
15265
+ ["tmux", "set-option", "-t", center.pane, "-w", "-q", "@synkro_agent", ""],
15266
+ ["tmux", "set-option", "-t", center.pane, "-w", "-q", "@synkro_cwd", options.cwd],
15267
+ ["tmux", "set-option", "-t", center.pane, "-w", "-q", "@synkro_kind", "terminal"],
15268
+ ["tmux", "set-option", "-t", center.pane, "-w", "automatic-rename", "off"],
15269
+ ["tmux", "rename-window", "-t", center.pane, tabTitle("terminal", spaceName)]
15270
+ ];
15271
+ for (const argv of metadata) {
15272
+ if (!(await run(HOST, argv)).ok) return { ok: false, pane: center.pane, created, recovered: false };
15273
+ }
15274
+ }
15275
+ return { ok: true, pane: center.pane, created, recovered };
15276
+ }
15277
+ async function reconcileUiShell(bootPath, repoCwd, options = {}) {
15278
+ const listed = await run(HOST, [
15279
+ "tmux",
15280
+ "list-panes",
15281
+ "-s",
15282
+ "-t",
15283
+ UI_SESSION,
15284
+ "-F",
15285
+ "#{window_id}|#{pane_id}|#{pane_width}|#{pane_dead}|#{remain-on-exit}|#{@synkro_cwd}|#{pane_start_command}"
15286
+ ]);
15287
+ if (!listed.ok) return { ok: false, changed: false };
15288
+ const panes = parseUiPanes(listed.stdout);
15289
+ const sidebars = panes.filter((row2) => row2.command.includes("--sidebar"));
15290
+ if (sidebars.length === 0) return { ok: false, changed: false };
15291
+ let changed = false;
15292
+ for (const sidebar of sidebars) {
15293
+ const previous = panes.find((row2) => row2.window === sidebar.window && row2.pane !== sidebar.pane);
15294
+ const cwd = sidebar.cwd ? sidebar.cwd.startsWith("/") ? sidebar.cwd : join34(repoCwd, sidebar.cwd) : repoCwd;
15295
+ const center = await ensureUiCenterPane({
15296
+ sidebarPane: sidebar.pane,
15297
+ bootPath,
15298
+ cwd,
15299
+ terminalCommand: options.terminalCommand
15300
+ });
15301
+ if (!center.ok) return { ok: false, changed };
15302
+ if (sidebar.cwd !== cwd) {
15303
+ const migrated2 = await run(HOST, ["tmux", "set-option", "-t", sidebar.window, "-w", "-q", "@synkro_cwd", cwd]);
15304
+ if (!migrated2.ok) return { ok: false, changed };
15305
+ changed = true;
15306
+ }
15307
+ const staleBinding = !sidebar.command.includes("SYNKRO_UI_CENTER=" + center.pane + " ");
15308
+ if (options.refreshSidebars || center.recovered || staleBinding) {
15309
+ const rebound = await run(HOST, [
15310
+ "tmux",
15311
+ "respawn-pane",
15312
+ "-k",
15313
+ "-t",
15314
+ sidebar.pane,
15315
+ sidebarCommand(bootPath, center.pane, repoCwd)
15316
+ ]);
15317
+ if (!rebound.ok) return { ok: false, changed };
15318
+ changed = true;
15319
+ }
15320
+ if (!previous?.remain || center.created || center.recovered) changed = true;
15321
+ }
15322
+ return { ok: true, changed };
15323
+ }
15324
+ async function refreshUiShell(bootPath, repoCwd, options = {}) {
15325
+ const expected = buildStamp(bootPath);
15326
+ const current = await run(HOST, ["tmux", "show-options", "-t", UI_SESSION, "-v", "@synkro_build"]);
15327
+ const buildChanged = !current.ok || current.stdout.trim() !== expected;
15328
+ if (buildChanged) {
15329
+ const width = await currentWindowColumns(UI_SESSION);
15330
+ if (!await styleOuterSession(bootPath, repoCwd, Number(sidebarColumns(width)))) return false;
15331
+ }
15332
+ const reconciled = await reconcileUiShell(bootPath, repoCwd, {
15333
+ terminalCommand: options.terminalCommand,
15334
+ refreshSidebars: buildChanged
15335
+ });
15336
+ if (!reconciled.ok) return false;
15337
+ if (reconciled.changed && options.persistLayout !== false) await snapshotTabs();
15338
+ if (buildChanged) {
15339
+ const stamped = await run(HOST, ["tmux", "set-option", "-t", UI_SESSION, "-q", "@synkro_build", expected]);
15340
+ if (!stamped.ok) return false;
15341
+ }
15342
+ return true;
15192
15343
  }
15193
15344
  async function buildTab(bootPath, repoCwd, spec) {
15194
15345
  await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
@@ -15196,8 +15347,20 @@ async function buildTab(bootPath, repoCwd, spec) {
15196
15347
  if (!await uiSessionExists()) {
15197
15348
  const cols = String(Number(process.stdout.columns || 0) || 220);
15198
15349
  const rows = String(Number(process.stdout.rows || 0) || 55);
15199
- const create = ["tmux", "new-session", "-d", "-s", UI_SESSION, "-x", cols, "-y", rows, "-c", spec.cwd];
15200
- if (spec.center) create.push(spec.center);
15350
+ const create = [
15351
+ "tmux",
15352
+ "new-session",
15353
+ "-d",
15354
+ "-s",
15355
+ UI_SESSION,
15356
+ "-x",
15357
+ cols,
15358
+ "-y",
15359
+ rows,
15360
+ "-c",
15361
+ spec.cwd,
15362
+ "tail -f /dev/null"
15363
+ ];
15201
15364
  const made = await run(HOST, create);
15202
15365
  if (!made.ok) {
15203
15366
  throw new Error("tmux new-session failed: " + made.stderr.trim());
@@ -15207,14 +15370,26 @@ async function buildTab(bootPath, repoCwd, spec) {
15207
15370
  const first = await run(HOST, ["tmux", "list-windows", "-t", UI_SESSION, "-F", "#{window_id}"]);
15208
15371
  windowTarget = first.stdout.split("\n")[0]?.trim() || UI_SESSION + ":1";
15209
15372
  } else {
15210
- const create = ["tmux", "new-window", "-t", UI_SESSION, "-P", "-F", "#{window_id}", "-c", spec.cwd];
15211
- if (spec.center) create.push(spec.center);
15373
+ const create = [
15374
+ "tmux",
15375
+ "new-window",
15376
+ "-t",
15377
+ UI_SESSION,
15378
+ "-P",
15379
+ "-F",
15380
+ "#{window_id}",
15381
+ "-c",
15382
+ spec.cwd,
15383
+ "tail -f /dev/null"
15384
+ ];
15212
15385
  const created = await run(HOST, create);
15213
15386
  windowTarget = created.stdout.trim();
15214
15387
  if (!created.ok || !windowTarget) return;
15215
15388
  }
15216
15389
  const sidebarCols = sidebarColumns(await currentWindowColumns(windowTarget));
15217
- await styleOuterSession(bootPath, repoCwd, Number(sidebarCols));
15390
+ if (!await styleOuterSession(bootPath, repoCwd, Number(sidebarCols))) {
15391
+ throw new Error("tmux outer session styling failed");
15392
+ }
15218
15393
  const split = await run(HOST, [
15219
15394
  "tmux",
15220
15395
  "split-window",
@@ -15229,18 +15404,35 @@ async function buildTab(bootPath, repoCwd, spec) {
15229
15404
  "tail -f /dev/null"
15230
15405
  ]);
15231
15406
  const sidebarPane = split.stdout.trim();
15407
+ if (!split.ok || !sidebarPane) throw new Error("tmux sidebar split failed: " + split.stderr.trim());
15232
15408
  const panes = await run(HOST, ["tmux", "list-panes", "-t", windowTarget, "-F", "#{pane_id}"]);
15233
15409
  const centerPane = panes.stdout.split("\n").map((line) => line.trim()).filter(Boolean).find((id) => id !== sidebarPane) || "";
15234
- await run(HOST, buildKeepPaneAfterExit(centerPane));
15410
+ if (!centerPane) throw new Error("tmux center pane was not created");
15411
+ const kept = await run(HOST, buildKeepPaneAfterExit(centerPane));
15412
+ if (!kept.ok) throw new Error("tmux center durability failed: " + kept.stderr.trim());
15235
15413
  if (spec.agentSession) {
15236
15414
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_agent", spec.agentSession]);
15237
15415
  }
15238
15416
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_cwd", spec.cwd]);
15239
15417
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_kind", spec.kind || "terminal"]);
15240
- await run(HOST, ["tmux", "respawn-pane", "-k", "-t", sidebarPane, sidebarCommand(bootPath, centerPane, repoCwd)]);
15418
+ const sidebarStarted = await run(HOST, [
15419
+ "tmux",
15420
+ "respawn-pane",
15421
+ "-k",
15422
+ "-t",
15423
+ sidebarPane,
15424
+ sidebarCommand(bootPath, centerPane, repoCwd)
15425
+ ]);
15426
+ if (!sidebarStarted.ok) throw new Error("tmux sidebar start failed: " + sidebarStarted.stderr.trim());
15427
+ const centerStart = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", spec.cwd];
15428
+ if (spec.center) centerStart.push(spec.center);
15429
+ const centerStarted = await run(HOST, centerStart);
15430
+ if (!centerStarted.ok) throw new Error("tmux center start failed: " + centerStarted.stderr.trim());
15241
15431
  const title = spec.title || tabTitle("terminal", spec.cwd.split("/").filter(Boolean).pop() || "space");
15242
15432
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "automatic-rename", "off"]);
15243
15433
  await run(HOST, ["tmux", "rename-window", "-t", windowTarget, title]);
15434
+ const stamped = await run(HOST, ["tmux", "set-option", "-t", UI_SESSION, "-q", "@synkro_build", buildStamp(bootPath)]);
15435
+ if (!stamped.ok) throw new Error("tmux build stamp failed: " + stamped.stderr.trim());
15244
15436
  await snapshotTabs();
15245
15437
  await run(HOST, ["tmux", "select-pane", "-t", spec.focus === "sidebar" ? sidebarPane : centerPane]);
15246
15438
  }
@@ -15282,6 +15474,10 @@ async function launchUi(bootPath, repoCwd) {
15282
15474
  rememberSpace(repoCwd);
15283
15475
  if (!await uiSessionExists()) {
15284
15476
  await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
15477
+ } else {
15478
+ if (!await refreshUiShell(bootPath, repoCwd)) {
15479
+ throw new Error("Synkro UI could not repair its tmux layout");
15480
+ }
15285
15481
  }
15286
15482
  await pruneCollapsedClients();
15287
15483
  return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
@@ -15338,10 +15534,10 @@ function row(selected, width, content) {
15338
15534
  return STYLE.select + body.split(STYLE.reset).join(STYLE.reset + STYLE.select) + STYLE.reset;
15339
15535
  }
15340
15536
  function stripForPad(text, width) {
15341
- let visible = 0;
15537
+ let visible2 = 0;
15342
15538
  let out = "";
15343
15539
  let index = 0;
15344
- while (index < text.length && visible < width) {
15540
+ while (index < text.length && visible2 < width) {
15345
15541
  if (text.startsWith(ESC, index)) {
15346
15542
  const end = text.indexOf("m", index);
15347
15543
  if (end === -1) break;
@@ -15350,13 +15546,13 @@ function stripForPad(text, width) {
15350
15546
  } else {
15351
15547
  out += text[index];
15352
15548
  index += 1;
15353
- visible += 1;
15549
+ visible2 += 1;
15354
15550
  }
15355
15551
  }
15356
- return out + " ".repeat(Math.max(0, width - visible));
15552
+ return out + " ".repeat(Math.max(0, width - visible2));
15357
15553
  }
15358
15554
  function visibleLength(text) {
15359
- let visible = 0;
15555
+ let visible2 = 0;
15360
15556
  let index = 0;
15361
15557
  while (index < text.length) {
15362
15558
  if (text.startsWith(ESC, index)) {
@@ -15364,11 +15560,11 @@ function visibleLength(text) {
15364
15560
  if (end === -1) break;
15365
15561
  index = end + 1;
15366
15562
  } else {
15367
- visible += 1;
15563
+ visible2 += 1;
15368
15564
  index += 1;
15369
15565
  }
15370
15566
  }
15371
- return visible;
15567
+ return visible2;
15372
15568
  }
15373
15569
  function splitRow(width, left, right) {
15374
15570
  const gap = Math.max(1, width - 2 - visibleLength(left) - visibleLength(right));
@@ -15380,10 +15576,10 @@ function windowAround(count, selected, capacity) {
15380
15576
  return { start, end: start + capacity };
15381
15577
  }
15382
15578
  function renderCollapsed(state, width, height) {
15383
- const lines = [];
15579
+ const lines2 = [];
15384
15580
  const targets = [];
15385
15581
  const push2 = (text, target = null) => {
15386
- lines.push(stripForPad(" " + text, width));
15582
+ lines2.push(stripForPad(" " + text, width));
15387
15583
  targets.push(target);
15388
15584
  };
15389
15585
  push2("");
@@ -15400,16 +15596,16 @@ function renderCollapsed(state, width, height) {
15400
15596
  });
15401
15597
  push2("");
15402
15598
  push2(STYLE.dim + "+" + STYLE.reset, { kind: "new" });
15403
- while (lines.length < height - 1) push2("");
15599
+ while (lines2.length < height - 1) push2("");
15404
15600
  push2(STYLE.dim + "\u203A\u203A" + STYLE.reset, { kind: "collapse" });
15405
- return { lines: lines.slice(0, height), targets: targets.slice(0, height) };
15601
+ return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
15406
15602
  }
15407
15603
  function renderLayout(state, width = 30, height = 40) {
15408
15604
  if (state.collapsed) return renderCollapsed(state, width, height);
15409
- const lines = [];
15605
+ const lines2 = [];
15410
15606
  const targets = [];
15411
15607
  const push2 = (line, target = null) => {
15412
- lines.push(line);
15608
+ lines2.push(line);
15413
15609
  targets.push(target);
15414
15610
  };
15415
15611
  const chrome = 8;
@@ -15431,7 +15627,7 @@ function renderLayout(state, width = 30, height = 40) {
15431
15627
  push2(row(selected, width, " " + STYLE.branch + clip(space.branch, width - 6 - (space.track ? space.track.length + 1 : 0)) + STYLE.reset + drift), target);
15432
15628
  });
15433
15629
  if (state.spaces.length === 0) push2(row(false, width, STYLE.dim + "no spaces found" + STYLE.reset));
15434
- while (lines.length < 3 + spacesCapacity * 2) push2(pad("", width));
15630
+ while (lines2.length < 3 + spacesCapacity * 2) push2(pad("", width));
15435
15631
  push2("");
15436
15632
  push2(splitRow(width, "new", "menu"), { kind: "new" });
15437
15633
  push2(STYLE.dim + "\u2500".repeat(Math.max(0, width)) + STYLE.reset);
@@ -15476,13 +15672,13 @@ function renderLayout(state, width = 30, height = 40) {
15476
15672
  push2("");
15477
15673
  push2(row(false, width, STYLE.blocked + "\u26D4 needs consent" + STYLE.reset));
15478
15674
  for (const action of actionsForAsk(selectedAgent.ask)) {
15479
- const key = action === "track" ? "g" : action === "skip" ? "s" : "y";
15480
- push2(row(false, width, STYLE.dim + " " + key + " \u2014 " + action + STYLE.reset));
15675
+ const key2 = action === "track" ? "g" : action === "skip" ? "s" : "y";
15676
+ push2(row(false, width, STYLE.dim + " " + key2 + " \u2014 " + action + STYLE.reset));
15481
15677
  }
15482
15678
  }
15483
- while (lines.length < height - 1) push2(pad("", width));
15679
+ while (lines2.length < height - 1) push2(pad("", width));
15484
15680
  push2(stripForPad(pad("", width - 3) + STYLE.dim + "\u2039\u2039 " + STYLE.reset, width), { kind: "collapse" });
15485
- return { lines: lines.slice(0, height), targets: targets.slice(0, height) };
15681
+ return { lines: lines2.slice(0, height), targets: targets.slice(0, height) };
15486
15682
  }
15487
15683
  function nextSelection(state, spaces, agents, delta) {
15488
15684
  const flat = state.section === "spaces" ? state.spaceIndex : spaces + state.agentIndex;
@@ -15631,8 +15827,8 @@ async function spawnAgent(info, request) {
15631
15827
  if (request.backend === "container") {
15632
15828
  cwd = request.cwd.startsWith(CONTAINER_WORK) ? request.cwd : await provisionContainerWorkspace(runner, slug);
15633
15829
  }
15634
- const command = harnessCommand(request);
15635
- for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend })) {
15830
+ const command = request.command || harnessCommand(request);
15831
+ for (const argv of buildSpawnAgent({ name: slug, cwd, command, harness: request.harness, space: cwd, backend: request.backend, mode: request.mode })) {
15636
15832
  const result = await run(runner, argv);
15637
15833
  if (!result.ok && argv[1] === "new-session") {
15638
15834
  return { ok: false, session, error: result.stderr.trim() || "tmux new-session failed" };
@@ -15646,7 +15842,8 @@ async function spawnAgent(info, request) {
15646
15842
  harness: request.harness,
15647
15843
  space: cwd,
15648
15844
  spaceName: request.spaceName,
15649
- backend: request.backend
15845
+ backend: request.backend,
15846
+ mode: request.mode || "native"
15650
15847
  });
15651
15848
  return { ok: true, session };
15652
15849
  }
@@ -15753,8 +15950,11 @@ var init_awake = __esm({
15753
15950
  function runnerFor(backend) {
15754
15951
  return backend === "container" ? { kind: "container", container: CONTAINER_NAME2 } : { kind: "host" };
15755
15952
  }
15953
+ function hasLiveAgentCenter(paneOutput, session) {
15954
+ return String(paneOutput || "").split("\n").map((line) => line.split("|")).some(([dead, ...command]) => dead === "0" && !command.join("|").includes("--sidebar") && command.join("|").includes(session));
15955
+ }
15756
15956
  async function runSidebar() {
15757
- const centerPane = process.env.SYNKRO_UI_CENTER || "";
15957
+ let centerPane = process.env.SYNKRO_UI_CENTER || "";
15758
15958
  const outerSession = process.env.SYNKRO_UI_OUTER || "synkro-ui";
15759
15959
  const bootPath = process.env.SYNKRO_UI_BOOT || process.argv[1];
15760
15960
  const repoCwd = process.env.SYNKRO_UI_REPO || process.cwd();
@@ -15922,53 +16122,114 @@ async function runSidebar() {
15922
16122
  const agent = state.agents[state.agentIndex];
15923
16123
  if (agent) await showAgent(agent);
15924
16124
  }
16125
+ async function resolveCenterPane(cwd) {
16126
+ if (!ownPane || !cwd) return "";
16127
+ const resolved = await ensureUiCenterPane({ sidebarPane: ownPane, bootPath, cwd });
16128
+ if (!resolved.ok) {
16129
+ state.message = "could not restore this tab";
16130
+ return "";
16131
+ }
16132
+ centerPane = resolved.pane;
16133
+ if (resolved.recovered) {
16134
+ state.viewing = "";
16135
+ await snapshotTabs();
16136
+ stripWidth = -1;
16137
+ await syncChipStrip();
16138
+ }
16139
+ return centerPane;
16140
+ }
15925
16141
  async function showWorkspaceTerminal(cwd) {
15926
- if (!centerPane || !cwd) return;
16142
+ const pane = await resolveCenterPane(cwd);
16143
+ if (!pane) return false;
15927
16144
  const command = makeTerminalCommand(bootPath);
15928
- const argv = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", cwd];
16145
+ const argv = ["tmux", "respawn-pane", "-k", "-t", pane, "-c", cwd];
15929
16146
  if (command) argv.push(command);
15930
16147
  const respawned = await run(host, argv);
15931
16148
  if (!respawned.ok) {
15932
16149
  state.message = "could not restore workspace terminal";
15933
- return;
16150
+ return false;
15934
16151
  }
15935
16152
  const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
15936
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-u", "@synkro_agent"]);
15937
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_cwd", cwd]);
15938
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_kind", "terminal"]);
15939
- await run(host, ["tmux", "rename-window", "-t", centerPane, tabTitle("terminal", spaceName)]);
16153
+ const metadata = [
16154
+ ["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_agent", ""],
16155
+ ["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_cwd", cwd],
16156
+ ["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_kind", "terminal"],
16157
+ ["tmux", "rename-window", "-t", pane, tabTitle("terminal", spaceName)]
16158
+ ];
16159
+ for (const commandArgv of metadata) {
16160
+ if (!(await run(host, commandArgv)).ok) {
16161
+ state.message = "could not update this tab";
16162
+ return false;
16163
+ }
16164
+ }
15940
16165
  state.viewing = "";
15941
16166
  await snapshotTabs();
16167
+ return true;
15942
16168
  }
15943
- async function showAgent(agent) {
15944
- if (!centerPane) return;
15945
- if (agent.status === "offline") {
15946
- await restoreSelected();
15947
- return;
15948
- }
16169
+ async function markAgentViewed(agent) {
15949
16170
  const current = memory.get(agent.session);
15950
16171
  memory.set(agent.session, { hash: current?.hash || "", wasWorking: false, seen: true });
15951
16172
  if (agent.space) rememberLastAgent(await repoOf(agent.space), agent.session);
15952
16173
  state.viewing = agent.session;
16174
+ }
16175
+ async function showAgent(agent) {
16176
+ if (agent.status === "offline") {
16177
+ await restoreSelected();
16178
+ return false;
16179
+ }
15953
16180
  const windows = await run(host, ["tmux", "list-windows", "-t", outerSession, "-F", "#{window_id}|#{@synkro_agent}"]);
15954
16181
  const holder = windows.stdout.split("\n").map((line) => line.split("|")).find((cols) => cols[1] === agent.session);
15955
16182
  if (holder) {
15956
- await run(host, ["tmux", "select-window", "-t", holder[0]]);
15957
- state.message = agent.name;
15958
- return;
16183
+ const holderPanes = await run(host, [
16184
+ "tmux",
16185
+ "list-panes",
16186
+ "-t",
16187
+ holder[0],
16188
+ "-F",
16189
+ "#{pane_dead}|#{pane_start_command}"
16190
+ ]);
16191
+ const liveCenter = holderPanes.ok && hasLiveAgentCenter(holderPanes.stdout, agent.session);
16192
+ if (liveCenter) {
16193
+ const selected = await run(host, ["tmux", "select-window", "-t", holder[0]]);
16194
+ if (selected.ok) {
16195
+ await markAgentViewed(agent);
16196
+ state.message = agent.name;
16197
+ return true;
16198
+ }
16199
+ }
16200
+ await run(host, ["tmux", "set-option", "-t", holder[0], "-w", "-q", "@synkro_agent", ""]);
15959
16201
  }
16202
+ const pane = await resolveCenterPane(agent.space || selectedSpace()?.path || repoCwd);
16203
+ if (!pane) return false;
15960
16204
  await run(runnerFor(agent.backend), buildEnableMouse(agent.session));
15961
16205
  for (const argv of buildClipboardBindings(agent.session)) {
15962
16206
  await run(runnerFor(agent.backend), argv);
15963
16207
  }
15964
16208
  const command = buildCenterAttachCommand(runnerFor(agent.backend), agent.session);
15965
- await run(host, ["tmux", "respawn-pane", "-k", "-t", centerPane, command]);
16209
+ const attached = await run(host, ["tmux", "respawn-pane", "-k", "-t", pane, command]);
16210
+ if (!attached.ok) {
16211
+ state.message = "could not open " + agent.name;
16212
+ await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16213
+ return false;
16214
+ }
15966
16215
  const where = agent.space ? agent.space.split("/").filter(Boolean).pop() || "" : "";
15967
- await run(host, ["tmux", "rename-window", "-t", centerPane, tabTitle(agent.harness, where)]);
15968
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_agent", agent.session]);
15969
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_kind", agent.harness]);
16216
+ const metadata = [
16217
+ ["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_agent", agent.session],
16218
+ ["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_cwd", agent.space || repoCwd],
16219
+ ["tmux", "set-option", "-t", pane, "-w", "-q", "@synkro_kind", agent.harness],
16220
+ ["tmux", "rename-window", "-t", pane, tabTitle(agent.harness, where)]
16221
+ ];
16222
+ for (const commandArgv of metadata) {
16223
+ if (!(await run(host, commandArgv)).ok) {
16224
+ state.message = "could not update " + agent.name + " tab";
16225
+ await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16226
+ return false;
16227
+ }
16228
+ }
16229
+ await markAgentViewed(agent);
15970
16230
  await snapshotTabs();
15971
16231
  state.message = "attached " + agent.name;
16232
+ return true;
15972
16233
  }
15973
16234
  async function confirmKillAgent() {
15974
16235
  const agent = state.agents[state.agentIndex];
@@ -15979,7 +16240,8 @@ async function runSidebar() {
15979
16240
  if (allAgents.some((candidate) => candidate.session === agent.session && candidate.status !== "offline")) return;
15980
16241
  state.message = "closed " + agent.name;
15981
16242
  if (!wasViewing) return;
15982
- await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16243
+ const restored = await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16244
+ if (!restored) return;
15983
16245
  const successor = nextLiveAgent(allAgents, agent.repo || "");
15984
16246
  if (successor) await showAgent(successor);
15985
16247
  }
@@ -16046,45 +16308,45 @@ async function runSidebar() {
16046
16308
  }
16047
16309
  await activate(target);
16048
16310
  }
16049
- async function handleKey(key) {
16050
- if (key === " ") state.section = state.section === "spaces" ? "agents" : "spaces";
16051
- else if (key === "j" || key === CSI + "B") moveSelection(1);
16052
- else if (key === "k" || key === CSI + "A") moveSelection(-1);
16053
- else if (key === "\r") {
16311
+ async function handleKey(key2) {
16312
+ if (key2 === " ") state.section = state.section === "spaces" ? "agents" : "spaces";
16313
+ else if (key2 === "j" || key2 === CSI + "B") moveSelection(1);
16314
+ else if (key2 === "k" || key2 === CSI + "A") moveSelection(-1);
16315
+ else if (key2 === "\r") {
16054
16316
  if (state.section === "agents") await attachSelected();
16055
16317
  else await openDialog("new-tab");
16056
- } else if (key === "n" || key === "T") {
16318
+ } else if (key2 === "n" || key2 === "T") {
16057
16319
  worldChanged = true;
16058
16320
  await openDialog("new-tab");
16059
- } else if (key === "m") await mainMenu();
16060
- else if (key === "O") void openDialog("new-workspace");
16061
- else if (key === "C") {
16321
+ } else if (key2 === "m") await mainMenu();
16322
+ else if (key2 === "O") void openDialog("new-workspace");
16323
+ else if (key2 === "C") {
16062
16324
  worldChanged = true;
16063
16325
  closeSelectedWorkspace();
16064
- } else if (key === "r") {
16326
+ } else if (key2 === "r") {
16065
16327
  worldChanged = true;
16066
16328
  await restoreSelected();
16067
- } else if (key === "d") {
16329
+ } else if (key2 === "d") {
16068
16330
  const client = await attachedClient();
16069
16331
  await run(host, client ? ["tmux", "detach-client", "-t", client] : ["tmux", "detach-client"]);
16070
- } else if (key === "K") await keybindsMenu();
16071
- else if (key === "G") {
16332
+ } else if (key2 === "K") await keybindsMenu();
16333
+ else if (key2 === "G") {
16072
16334
  state.grouped = !state.grouped;
16073
16335
  applyFilter();
16074
- } else if (key === "a") {
16336
+ } else if (key2 === "a") {
16075
16337
  state.filter = state.filter === "space" ? "all" : "space";
16076
16338
  applyFilter();
16077
- } else if (key === "<" || key === ">" || key === "," || key === ".") await toggleCollapsed();
16078
- else if (key === "x") {
16339
+ } else if (key2 === "<" || key2 === ">" || key2 === "," || key2 === ".") await toggleCollapsed();
16340
+ else if (key2 === "x") {
16079
16341
  worldChanged = true;
16080
16342
  await confirmKillAgent();
16081
- } else if (key === "i") {
16343
+ } else if (key2 === "i") {
16082
16344
  const agent = state.agents[state.agentIndex];
16083
16345
  if (agent) await run(runnerFor(agent.backend), buildInterrupt(agent.session));
16084
- } else if (key === "g") await consent("track");
16085
- else if (key === "s") await consent("skip");
16086
- else if (key === "y") await consent("stay");
16087
- else if (key === "q" || key === KEY_CTRL_C) {
16346
+ } else if (key2 === "g") await consent("track");
16347
+ else if (key2 === "s") await consent("skip");
16348
+ else if (key2 === "y") await consent("stay");
16349
+ else if (key2 === "q" || key2 === KEY_CTRL_C) {
16088
16350
  await snapshotTabs();
16089
16351
  releaseAwake();
16090
16352
  await run(host, ["tmux", "kill-session", "-t", outerSession]);
@@ -16206,8 +16468,8 @@ var init_repos = __esm({
16206
16468
 
16207
16469
  // cli/ui/tabs.ts
16208
16470
  import { execSync as execSync7 } from "child_process";
16209
- function isLegacyCodexRenderer(command) {
16210
- return /(?:^|\s)ui\s+--run\s+codex(?:\s|$)/.test(String(command || "").trim());
16471
+ function embeddedSessionCommand(bootPath, harness, cwd) {
16472
+ return ["node", bootPath, "ui", "--run", harness, cwd].map(shellQuote3).join(" ");
16211
16473
  }
16212
16474
  function repoRoot() {
16213
16475
  try {
@@ -16229,6 +16491,29 @@ async function createTab(bootPath, kind, spacePath) {
16229
16491
  });
16230
16492
  return;
16231
16493
  }
16494
+ if (kind === "cursor-synkro" || kind === "codex-synkro") {
16495
+ const harness2 = kind === "codex-synkro" ? "codex" : "cursor";
16496
+ const command = embeddedSessionCommand(bootPath, harness2, spacePath);
16497
+ const spawned2 = await spawnAgent({ runner: HOST2, backend: "host", note: "runtime: host" }, {
16498
+ name: space + "-" + harness2 + "-synkro-" + String(process.pid % 1e4),
16499
+ harness: harness2,
16500
+ spaceName: space,
16501
+ cwd: spacePath,
16502
+ backend: "host",
16503
+ command,
16504
+ mode: "embedded"
16505
+ });
16506
+ if (!spawned2.ok) return;
16507
+ await buildTab(bootPath, repo, {
16508
+ cwd: spacePath,
16509
+ center: buildCenterAttachCommand(HOST2, spawned2.session),
16510
+ title: tabTitle(harness2, space),
16511
+ kind,
16512
+ agentSession: spawned2.session,
16513
+ focus: "center"
16514
+ });
16515
+ return;
16516
+ }
16232
16517
  if (kind === "settings") {
16233
16518
  await buildTab(bootPath, repo, {
16234
16519
  cwd: spacePath,
@@ -16272,43 +16557,26 @@ async function openAgentTab(bootPath, session) {
16272
16557
  "-p",
16273
16558
  "-t",
16274
16559
  session,
16275
- ["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}"].join("|")
16560
+ ["#{@synkro_harness}", "#{@synkro_space}", "#{@synkro_backend}", "#{@synkro_mode}"].join("|")
16276
16561
  ]);
16277
- const [harness, space, backend] = (meta.stdout.trim() || "||").split("|");
16562
+ const [harness, space, backend, mode] = (meta.stdout.trim() || "|||").split("|");
16278
16563
  const containerHosted = backend === "container";
16279
- let runner = containerHosted ? info.runner : HOST2;
16280
- let activeSession = session;
16564
+ const runner = mode === "embedded" ? HOST2 : containerHosted ? info.runner : HOST2;
16281
16565
  const spaceName = (space || "").split("/").filter(Boolean).pop() || "space";
16282
- if (harness === "codex") {
16283
- const command = await run(HOST2, ["tmux", "display-message", "-p", "-t", session, "#{pane_start_command}"]);
16284
- if (command.ok && isLegacyCodexRenderer(command.stdout)) {
16285
- await run(HOST2, buildKillSession(session));
16286
- const restored = await spawnAgent(info, {
16287
- name: session.replace(/^synkro-agent-/, ""),
16288
- harness: "codex",
16289
- spaceName,
16290
- cwd: space || repoRoot(),
16291
- backend: "host",
16292
- resume: true
16293
- });
16294
- if (!restored.ok) return;
16295
- activeSession = restored.session;
16296
- runner = HOST2;
16297
- }
16298
- }
16299
- const alive = (await run(runner, ["tmux", "has-session", "-t", activeSession])).ok;
16566
+ const alive = (await run(runner, ["tmux", "has-session", "-t", session])).ok;
16300
16567
  if (!alive) return;
16301
16568
  await buildTab(bootPath, repoRoot(), {
16302
16569
  cwd: space || repoRoot(),
16303
- center: buildCenterAttachCommand(runner, activeSession),
16570
+ center: buildCenterAttachCommand(runner, session),
16304
16571
  title: tabTitle(harness || "claude", spaceName),
16305
- kind: harness || "claude",
16306
- agentSession: activeSession,
16572
+ kind: mode === "embedded" ? (harness || "cursor") + "-synkro" : harness || "claude",
16573
+ agentSession: session,
16307
16574
  focus: "center"
16308
16575
  });
16309
16576
  }
16310
16577
  async function restoreTabs(bootPath, repoCwd) {
16311
16578
  const tabs = loadTabs();
16579
+ const records = loadRecords();
16312
16580
  if (tabs.length === 0) return false;
16313
16581
  const info = await detectContainerBackend();
16314
16582
  const hostSessions = (await run(HOST2, ["tmux", "list-sessions", "-F", "#{session_name}"])).stdout;
@@ -16318,16 +16586,11 @@ async function restoreTabs(bootPath, repoCwd) {
16318
16586
  for (const tab of tabs) {
16319
16587
  const space = tab.cwd.split("/").filter(Boolean).pop() || "space";
16320
16588
  const containerHosted = info.backend === "container" && (await run(info.runner, ["test", "-d", tab.cwd])).ok;
16321
- const harness = ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
16322
- const sessionRunner = harness === "codex" ? HOST2 : containerHosted ? info.runner : HOST2;
16323
- let sessionAlive = Boolean(tab.agentSession && alive.has(tab.agentSession));
16324
- if (sessionAlive && harness === "codex") {
16325
- const command = await run(HOST2, ["tmux", "display-message", "-p", "-t", tab.agentSession, "#{pane_start_command}"]);
16326
- if (command.ok && isLegacyCodexRenderer(command.stdout)) {
16327
- await run(HOST2, buildKillSession(tab.agentSession));
16328
- sessionAlive = false;
16329
- }
16330
- }
16589
+ const embedded = tab.kind === "cursor-synkro" || tab.kind === "codex-synkro";
16590
+ const harness = embedded ? tab.kind.replace("-synkro", "") : ["claude", "codex", "cursor"].includes(tab.kind) ? tab.kind : "";
16591
+ const sessionRunner = embedded || harness === "codex" ? HOST2 : containerHosted ? info.runner : HOST2;
16592
+ const sessionAlive = Boolean(tab.agentSession && alive.has(tab.agentSession));
16593
+ const record2 = records.find((row2) => row2.session === tab.agentSession);
16331
16594
  if (tab.agentSession && sessionAlive) {
16332
16595
  await run(sessionRunner, buildEnableMouse(tab.agentSession));
16333
16596
  for (const argv of buildClipboardBindings(tab.agentSession)) {
@@ -16342,20 +16605,23 @@ async function restoreTabs(bootPath, repoCwd) {
16342
16605
  focus: "center"
16343
16606
  });
16344
16607
  } else if (harness) {
16608
+ const command = embedded ? embeddedSessionCommand(bootPath, harness, tab.cwd) : void 0;
16345
16609
  const spawned = await spawnAgent(info, {
16346
- name: space + "-" + harness + "-" + String(process.pid % 1e4),
16610
+ name: record2?.name || space + "-" + harness + "-" + String(process.pid % 1e4),
16347
16611
  harness,
16348
16612
  spaceName: space,
16349
16613
  cwd: tab.cwd,
16350
- backend: harness === "codex" ? "host" : containerHosted ? "container" : "host",
16351
- resume: true
16614
+ backend: embedded || harness === "codex" ? "host" : containerHosted ? "container" : "host",
16615
+ resume: !embedded,
16616
+ command,
16617
+ mode: embedded ? "embedded" : "native"
16352
16618
  });
16353
- const restoredInContainer = harness !== "codex" && containerHosted;
16619
+ const restoredInContainer = !embedded && harness !== "codex" && containerHosted;
16354
16620
  await buildTab(bootPath, repoCwd, spawned.ok ? {
16355
16621
  cwd: tab.cwd,
16356
16622
  center: buildCenterAttachCommand(restoredInContainer ? info.runner : HOST2, spawned.session),
16357
16623
  title: tabTitle(harness, space),
16358
- kind: harness,
16624
+ kind: embedded ? tab.kind : harness,
16359
16625
  agentSession: spawned.session,
16360
16626
  focus: "center"
16361
16627
  } : { cwd: tab.cwd, center: makeTerminalCommand(bootPath), title: tabTitle("terminal", space), kind: "terminal", focus: "center" });
@@ -16387,11 +16653,20 @@ var init_tabs = __esm({
16387
16653
  // cli/ui/dialog.ts
16388
16654
  import { existsSync as existsSync37 } from "fs";
16389
16655
  import { homedir as homedir37 } from "os";
16656
+ function providerChoices(harnesses) {
16657
+ return [
16658
+ { value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
16659
+ ...harnesses.map((harness) => ({
16660
+ value: harness === "cursor" || harness === "codex" ? harness + "-synkro" : harness,
16661
+ label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
16662
+ }))
16663
+ ];
16664
+ }
16390
16665
  function write(text) {
16391
16666
  process.stdout.write(text);
16392
16667
  }
16393
16668
  function visibleLength2(text) {
16394
- let visible = 0;
16669
+ let visible2 = 0;
16395
16670
  let index = 0;
16396
16671
  while (index < text.length) {
16397
16672
  if (text.startsWith(CSI2, index)) {
@@ -16399,11 +16674,11 @@ function visibleLength2(text) {
16399
16674
  if (end === -1) break;
16400
16675
  index = end + 1;
16401
16676
  } else {
16402
- visible += 1;
16677
+ visible2 += 1;
16403
16678
  index += 1;
16404
16679
  }
16405
16680
  }
16406
- return visible;
16681
+ return visible2;
16407
16682
  }
16408
16683
  function clipPath(text, max) {
16409
16684
  const value = String(text || "");
@@ -16446,34 +16721,34 @@ async function pick(opts) {
16446
16721
  const perItem = opts.choices.some((choice) => choice.detail) ? 2 : 1;
16447
16722
  const visibleItems = Math.max(1, Math.floor(Math.max(2, height - 5) / perItem));
16448
16723
  const firstRow = 4;
16449
- return new Promise((resolve8) => {
16724
+ return new Promise((resolve9) => {
16450
16725
  const view = () => opts.choices.filter((choice) => matches(choice, filter));
16451
16726
  const draw = () => {
16452
16727
  const shown = view();
16453
16728
  if (selected >= shown.length) selected = Math.max(0, shown.length - 1);
16454
16729
  if (selected < top) top = selected;
16455
16730
  if (selected >= top + visibleItems) top = selected - visibleItems + 1;
16456
- const lines = [];
16457
- lines.push(" " + S.title + opts.title + S.reset);
16731
+ const lines2 = [];
16732
+ lines2.push(" " + S.title + opts.title + S.reset);
16458
16733
  const left = filter && !opts.menu ? " " + S.accent + "/ " + S.reset + filter + "\u258C" : " " + S.dim + opts.hint + S.reset;
16459
16734
  const right = opts.menu ? "" : S.dim + String(shown.length) + (shown.length === 1 ? " match" : " matches") + S.reset;
16460
- lines.push(padRow(left, Math.max(0, width - visibleLength2(right) - 1)) + right);
16461
- lines.push("");
16462
- if (shown.length === 0) lines.push(" " + S.dim + (opts.emptyNote || "nothing matches") + S.reset);
16735
+ lines2.push(padRow(left, Math.max(0, width - visibleLength2(right) - 1)) + right);
16736
+ lines2.push("");
16737
+ if (shown.length === 0) lines2.push(" " + S.dim + (opts.emptyNote || "nothing matches") + S.reset);
16463
16738
  shown.slice(top, top + visibleItems).forEach((choice, offset) => {
16464
16739
  const index = top + offset;
16465
16740
  const isSelected = index === selected;
16466
16741
  const marker = isSelected ? S.accent + "\u203A" + S.reset + " " : " ";
16467
16742
  const note = choice.note ? S.dim + choice.note + S.reset : "";
16468
16743
  const head = " " + marker + S.bold + clip2(choice.label, width - 6 - visibleLength2(note)) + S.reset;
16469
- lines.push(selectable(padRow(head, Math.max(0, width - visibleLength2(note) - 1)) + note, width, isSelected));
16744
+ lines2.push(selectable(padRow(head, Math.max(0, width - visibleLength2(note) - 1)) + note, width, isSelected));
16470
16745
  if (perItem === 2) {
16471
- lines.push(selectable(" " + S.muted + clipPath(choice.detail || "", width - 6) + S.reset, width, isSelected));
16746
+ lines2.push(selectable(" " + S.muted + clipPath(choice.detail || "", width - 6) + S.reset, width, isSelected));
16472
16747
  }
16473
16748
  });
16474
- while (lines.length < height - 1) lines.push("");
16475
- lines.push(" " + S.dim + opts.footer + S.reset);
16476
- write(CSI2 + "H" + lines.slice(0, height).map((line) => padRow(line, width)).join("\n"));
16749
+ while (lines2.length < height - 1) lines2.push("");
16750
+ lines2.push(" " + S.dim + opts.footer + S.reset);
16751
+ write(CSI2 + "H" + lines2.slice(0, height).map((line) => padRow(line, width)).join("\n"));
16477
16752
  };
16478
16753
  const rowOfItem = (index) => firstRow + (index - top) * perItem;
16479
16754
  const MOUSE = /\[<(\d+);(\d+);(\d+)([Mm])/g;
@@ -16494,7 +16769,7 @@ async function pick(opts) {
16494
16769
  const hit = shown.findIndex((_, index) => y >= rowOfItem(index) && y < rowOfItem(index) + perItem);
16495
16770
  if (hit >= 0) {
16496
16771
  process.stdin.off("data", onData);
16497
- resolve8(shown[hit].value);
16772
+ resolve9(shown[hit].value);
16498
16773
  return;
16499
16774
  }
16500
16775
  }
@@ -16502,13 +16777,13 @@ async function pick(opts) {
16502
16777
  if (!sawMouse) {
16503
16778
  if (input === KEY_ESC && !input.includes("[<") || input === KEY_CTRL_C2) {
16504
16779
  process.stdin.off("data", onData);
16505
- resolve8(null);
16780
+ resolve9(null);
16506
16781
  return;
16507
16782
  }
16508
16783
  if (input === "\r") {
16509
16784
  if (shown.length === 0) return;
16510
16785
  process.stdin.off("data", onData);
16511
- resolve8(shown[selected].value);
16786
+ resolve9(shown[selected].value);
16512
16787
  return;
16513
16788
  }
16514
16789
  const down = input === CSI2 + "B" || opts.menu && (input === "j" || input === CSI2 + "C");
@@ -16532,9 +16807,9 @@ async function readLine(opts) {
16532
16807
  let value = "";
16533
16808
  let error = "";
16534
16809
  const width = Math.max(20, Number(process.stdout.columns || 80));
16535
- return new Promise((resolve8) => {
16810
+ return new Promise((resolve9) => {
16536
16811
  const draw = () => {
16537
- const lines = [
16812
+ const lines2 = [
16538
16813
  " " + S.title + opts.title + S.reset,
16539
16814
  "",
16540
16815
  " " + S.dim + opts.label + S.reset,
@@ -16545,13 +16820,13 @@ async function readLine(opts) {
16545
16820
  "",
16546
16821
  " " + S.dim + opts.footer + S.reset
16547
16822
  ];
16548
- write(CSI2 + "2J" + CSI2 + "H" + lines.map((line) => padRow(line, width)).join("\n"));
16823
+ write(CSI2 + "2J" + CSI2 + "H" + lines2.map((line) => padRow(line, width)).join("\n"));
16549
16824
  };
16550
16825
  const onData = (chunk) => {
16551
16826
  const input = chunk.toString("utf8");
16552
16827
  if (input === KEY_ESC || input === KEY_CTRL_C2) {
16553
16828
  process.stdin.off("data", onData);
16554
- resolve8(null);
16829
+ resolve9(null);
16555
16830
  return;
16556
16831
  }
16557
16832
  if (input === "\r") {
@@ -16563,7 +16838,7 @@ async function readLine(opts) {
16563
16838
  return;
16564
16839
  }
16565
16840
  process.stdin.off("data", onData);
16566
- resolve8(value.trim());
16841
+ resolve9(value.trim());
16567
16842
  })();
16568
16843
  return;
16569
16844
  }
@@ -16823,13 +17098,7 @@ async function runDialog(kind, repoCwd, argA = "", argB = "") {
16823
17098
  process.exit(0);
16824
17099
  }
16825
17100
  const harnesses = await detectHarnesses();
16826
- const sessions = [
16827
- { value: "terminal", label: TAB_GLYPHS.terminal + " Terminal" },
16828
- ...harnesses.map((harness) => ({
16829
- value: harness,
16830
- label: (TAB_GLYPHS[harness] || "") + " " + (HARNESS_LABELS[harness] || harness)
16831
- }))
16832
- ];
17101
+ const sessions = providerChoices(harnesses);
16833
17102
  for (; ; ) {
16834
17103
  const session = await pick({
16835
17104
  menu: true,
@@ -16888,6 +17157,42 @@ var init_dialog = __esm({
16888
17157
  }
16889
17158
  });
16890
17159
 
17160
+ // cli/harness/events.ts
17161
+ function fixPollIds(raw) {
17162
+ const text = String(raw || "");
17163
+ const ids = [
17164
+ ...Array.from(text.matchAll(FIX_POLL_MARKERS), (match) => match[1]),
17165
+ ...Array.from(text.matchAll(LEGACY_FIX_POLL_MARKERS), (match) => match[1])
17166
+ ];
17167
+ return Array.from(new Set(ids.filter(Boolean)));
17168
+ }
17169
+ function fixPollId(raw) {
17170
+ return fixPollIds(raw)[0] || "";
17171
+ }
17172
+ function cleanGuardText(raw) {
17173
+ return String(raw || "").replace(FIX_POLL_MARKERS, "").replace(/\n*\s*SYNKRO FIX POLL[\s\S]*$/i, "").replace(/\n?\d{4}-\d\d-\d\dT[^\n]*\sERROR\s+codex_core::tools::router:[^\n]*/gi, "").replace(/\s*Checking command\s*$/i, "").trim();
17174
+ }
17175
+ function blockReason(raw) {
17176
+ const text = cleanGuardText(raw);
17177
+ if (!text) return "blocked by policy";
17178
+ const guardAt = text.lastIndexOf("Guard:");
17179
+ if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
17180
+ const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
17181
+ if (afterTag) return afterTag[1].trim();
17182
+ const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
17183
+ if (afterHook) return afterHook[1].trim();
17184
+ return text;
17185
+ }
17186
+ var BLOCK_MARKER, FIX_POLL_MARKERS, LEGACY_FIX_POLL_MARKERS;
17187
+ var init_events = __esm({
17188
+ "cli/harness/events.ts"() {
17189
+ "use strict";
17190
+ BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
17191
+ FIX_POLL_MARKERS = /\[synkro:fix-poll\s+item_id=\\*["']?([A-Za-z0-9_-]+)\\*["']?\s*\]/gi;
17192
+ LEGACY_FIX_POLL_MARKERS = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/gi;
17193
+ }
17194
+ });
17195
+
16891
17196
  // cli/harness/render.ts
16892
17197
  function spinnerFrame(tick) {
16893
17198
  return SPINNER[Math.abs(tick) % SPINNER.length];
@@ -16896,15 +17201,27 @@ function terminalText(text) {
16896
17201
  return String(text).replace(/[\u0000-\u001F\u007F-\u009F]/g, "");
16897
17202
  }
16898
17203
  function clip3(text, max) {
16899
- const value = String(text || "").replace(/\s+/g, " ").trim();
17204
+ const value = cleanOutput(text).replace(/\s+/g, " ").trim();
16900
17205
  if (max <= 1) return value;
16901
17206
  return value.length <= max ? value : value.slice(0, max - 1) + "\u2026";
16902
17207
  }
17208
+ function cleanOutput(text) {
17209
+ return String(text || "").replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
17210
+ }
16903
17211
  function seconds(ms) {
16904
17212
  const total = Math.max(0, Math.round(ms / 1e3));
16905
17213
  if (total < 60) return total + "s";
16906
17214
  return Math.floor(total / 60) + "m" + String(total % 60).padStart(2, "0") + "s";
16907
17215
  }
17216
+ function blockNextStep(reason) {
17217
+ if (/tracking (?:decision|is required)|task tracking|skip tracking|\bconductor\b/i.test(reason)) {
17218
+ return "Next: reply \u201Ctrack it\u201D to create a task, or \u201Cskip tracking\u201D to continue untracked.";
17219
+ }
17220
+ if (/\bCWE-\d+\b|\bCVE-\d{4}-\d+\b/i.test(reason)) {
17221
+ return "Next: revise the flagged code, then retry. Do not bypass the rule.";
17222
+ }
17223
+ return "Next: resolve the rule above, then retry.";
17224
+ }
16908
17225
  function statusLine(opts) {
16909
17226
  const hint = opts.hint ? " \xB7 " + opts.hint : "";
16910
17227
  const body = opts.text + " (" + seconds(opts.elapsedMs) + hint + ")";
@@ -16914,86 +17231,105 @@ function statusLine(opts) {
16914
17231
  function renderEvent(event, width = 100) {
16915
17232
  const body = Math.max(30, width - 4);
16916
17233
  switch (event.type) {
16917
- // The workspace and both accounts are already in the session header, so
16918
- // this line carries only what the header could not know before the harness
16919
- // started: which model answered, and whether a subscription or a key paid.
16920
17234
  case "session-start":
16921
- return [
16922
- "",
16923
- S2.dim + " " + clip3(event.model, body - 20) + (event.authSource === "login" ? " \xB7 subscription" : " \xB7 api key") + S2.reset,
16924
- ""
16925
- ];
17235
+ return [];
16926
17236
  case "user-message":
16927
- return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset, ""];
17237
+ return ["", S2.user + " \u276F " + S2.reset + S2.bold + clip3(event.text, body) + S2.reset];
16928
17238
  // Live state, not transcript. The runner promotes these to the status line.
16929
17239
  case "thinking":
16930
17240
  return [];
16931
17241
  case "assistant-message": {
16932
- const lines = wrap(event.text, body - 2);
17242
+ const lines2 = wrap(event.text, body - 2);
16933
17243
  return [
16934
17244
  "",
16935
- ...lines.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset),
16936
- ""
17245
+ ...lines2.map((line, index) => index === 0 ? S2.agent + " " + BULLET + " " + line + S2.reset : S2.agent + " " + line + S2.reset)
16937
17246
  ];
16938
17247
  }
16939
17248
  case "tool-start": {
16940
17249
  const label = TOOL_LABEL[event.kind] || TOOL_LABEL.other;
16941
17250
  return [
16942
- S2.tool + " " + BULLET + " " + label + S2.reset + S2.dim + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
17251
+ "",
17252
+ S2.panel + S2.tool + " " + BULLET + " " + label + S2.reset + S2.panel + S2.secondary + "(" + clip3(event.target, body - label.length - 8) + ")" + S2.reset
16943
17253
  ];
16944
17254
  }
16945
17255
  case "tool-end": {
16946
17256
  if (event.blocked) {
17257
+ const reason = cleanGuardText(event.reason);
16947
17258
  return [
16948
- S2.blocked + " " + BULLET + " Blocked" + S2.reset + S2.dim + " " + clip3(terminalText(event.target), body - 14) + S2.reset,
16949
- ...wrap(terminalText(event.reason), body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset)
17259
+ S2.panel + S2.blocked + " \u29C9 Blocked" + S2.reset + S2.panel + S2.secondary + " " + clip3(terminalText(event.target), body - 21) + S2.reset,
17260
+ ...wrap(terminalText(reason), body - 6).map((line, index) => index === 0 ? S2.blocked + " " + ELBOW + " " + S2.reset + S2.rule + line + S2.reset : S2.rule + " " + line + S2.reset),
17261
+ ...wrap(blockNextStep(reason), body - 6).map((line) => S2.secondary + " " + line + S2.reset)
16950
17262
  ];
16951
17263
  }
17264
+ const timing = event.durationMs == null ? "" : " \xB7 " + seconds(event.durationMs);
16952
17265
  const detail = event.ok ? event.output.trim() ? clip3(terminalText(event.output), body - 10) : "done" : "failed" + (event.exitCode === null ? "" : " (exit " + event.exitCode + ")");
16953
17266
  const tint = event.ok ? S2.ok : S2.blocked;
16954
- return [tint + " " + ELBOW + " " + S2.reset + S2.dim + detail + S2.reset];
17267
+ const changes = (event.fileChanges || []).flatMap((change) => wrap(change.path, body - 20).map((line, index) => S2.panel + S2.secondary + (index === 0 ? " " : " ") + line + (index === 0 ? " " + S2.ok + "+" + change.additions + S2.secondary + " " + S2.blocked + "-" + change.deletions : "") + S2.reset));
17268
+ return [
17269
+ S2.panel + tint + " " + ELBOW + " " + S2.reset + S2.panel + S2.secondary + detail + timing + S2.reset,
17270
+ ...changes
17271
+ ];
16955
17272
  }
16956
17273
  // A clean finish needs no announcement: the prompt returning IS the signal.
16957
17274
  case "turn-end":
16958
17275
  return event.ok ? [] : ["", S2.blocked + " " + BULLET + " turn ended with an error" + S2.reset, ""];
16959
17276
  case "notice":
16960
- return [S2.dim + " " + clip3(event.text, body) + S2.reset];
17277
+ return [S2.secondary + " " + clip3(event.text, body) + S2.reset];
17278
+ case "plan":
17279
+ return [
17280
+ S2.panel + S2.tool + " Plan" + S2.reset,
17281
+ ...event.steps.map((row2) => S2.panel + (row2.status === "completed" ? S2.ok + " \u2713 " : S2.secondary + " \xB7 ") + clip3(row2.step, body - 4) + S2.reset)
17282
+ ];
17283
+ case "assistant-delta":
17284
+ case "usage":
17285
+ return [];
16961
17286
  default:
16962
17287
  return [];
16963
17288
  }
16964
17289
  }
16965
17290
  function wrap(text, width) {
16966
- const words = String(text || "").replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
17291
+ const words = cleanOutput(text).replace(/\s+/g, " ").trim().split(" ").filter(Boolean);
16967
17292
  if (words.length === 0) return [];
16968
- const lines = [];
17293
+ const lines2 = [];
16969
17294
  let line = "";
16970
17295
  for (const word of words) {
16971
17296
  if (!line) line = word;
16972
17297
  else if ((line + " " + word).length <= width) line += " " + word;
16973
17298
  else {
16974
- lines.push(line);
17299
+ lines2.push(line);
16975
17300
  line = word;
16976
17301
  }
16977
17302
  }
16978
- if (line) lines.push(line);
16979
- return lines;
17303
+ if (line) lines2.push(line);
17304
+ return lines2;
16980
17305
  }
16981
- var ESC2, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
17306
+ var ESC2, color, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
16982
17307
  var init_render2 = __esm({
16983
17308
  "cli/harness/render.ts"() {
16984
17309
  "use strict";
17310
+ init_events();
16985
17311
  ESC2 = "\x1B[";
17312
+ color = (hex, plane = 38) => {
17313
+ const [r, g, b] = hex.match(/../g).map((part) => Number.parseInt(part, 16));
17314
+ return ESC2 + plane + ";2;" + r + ";" + g + ";" + b + "m";
17315
+ };
16986
17316
  S2 = {
16987
17317
  reset: ESC2 + "0m",
16988
17318
  dim: ESC2 + "2m",
16989
17319
  bold: ESC2 + "1m",
16990
- user: ESC2 + "38;5;111m",
16991
- agent: ESC2 + "38;5;252m",
16992
- think: ESC2 + "38;5;244m",
16993
- tool: ESC2 + "38;5;180m",
16994
- ok: ESC2 + "38;5;114m",
16995
- blocked: ESC2 + "38;5;203m",
16996
- rule: ESC2 + "38;5;211m"
17320
+ canvas: color("101416", 48),
17321
+ panel: color("171D1F", 48),
17322
+ composer: color("13191B", 48),
17323
+ text: color("C4C8C4"),
17324
+ secondary: color("7F8A86"),
17325
+ border: color("2A3334"),
17326
+ user: color("87A68E"),
17327
+ agent: color("C4C8C4"),
17328
+ think: color("B29A6A"),
17329
+ tool: color("7F9CA5"),
17330
+ ok: color("789B82"),
17331
+ blocked: color("B87474"),
17332
+ rule: color("B87474")
16997
17333
  };
16998
17334
  CLEAR_LINE = "\r" + ESC2 + "2K";
16999
17335
  BULLET = "\u23FA";
@@ -17015,297 +17351,207 @@ var init_render2 = __esm({
17015
17351
 
17016
17352
  // cli/harness/composer.ts
17017
17353
  import { execFileSync as execFileSync5, spawnSync as spawnSync12 } from "child_process";
17018
- import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as statSync5, writeFileSync as writeFileSync27 } from "fs";
17354
+ import { existsSync as existsSync38, mkdtempSync as mkdtempSync2, rmSync as rmSync7, statSync as statSync6, writeFileSync as writeFileSync27 } from "fs";
17019
17355
  import { homedir as homedir38, tmpdir } from "os";
17020
17356
  import { basename as basename3, dirname as dirname12, extname, isAbsolute as isAbsolute2, join as join37, resolve as resolve5, sep as sep3 } from "path";
17021
17357
  import { createInterface as createInterface5 } from "readline";
17022
- function markerCarryLength(input, marker) {
17023
- for (let length = Math.min(input.length, marker.length - 1); length > 0; length -= 1) {
17024
- if (marker.startsWith(input.slice(-length))) return length;
17025
- }
17026
- return 0;
17027
- }
17028
- function normalizePathToken(raw) {
17029
- let value = raw.trim();
17030
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
17031
- value = value.slice(1, -1);
17358
+ var init_composer = __esm({
17359
+ "cli/harness/composer.ts"() {
17360
+ "use strict";
17361
+ init_render2();
17032
17362
  }
17033
- value = value.replace(/\\([\\ "'()])/g, "$1");
17034
- if (value === "~") return homedir38();
17035
- if (value.startsWith("~/")) return join37(homedir38(), value.slice(2));
17036
- return value;
17363
+ });
17364
+
17365
+ // cli/harness/changes.ts
17366
+ import { lstatSync as lstatSync2, readFileSync as readFileSync33, readlinkSync } from "fs";
17367
+ import { resolve as resolve6, relative } from "path";
17368
+ function lines(text) {
17369
+ if (!text) return [];
17370
+ const rows = text.replace(/\r\n/g, "\n").split("\n");
17371
+ if (rows[rows.length - 1] === "") rows.pop();
17372
+ return rows;
17037
17373
  }
17038
- function imagePathsInText(text, cwd) {
17039
- const candidates = String(text || "").match(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|(?:\\.|[^\s])+/g) || [];
17040
- const images = [];
17041
- for (const candidate of candidates) {
17042
- const token = normalizePathToken(candidate);
17043
- if (!IMAGE_EXTENSIONS.has(extname(token).toLowerCase())) continue;
17044
- const path = isAbsolute2(token) ? token : resolve5(cwd, token);
17045
- try {
17046
- if (statSync5(path).isFile() && !images.includes(path)) images.push(path);
17047
- } catch {
17374
+ function lineChangeCounts(before, after) {
17375
+ const a = lines(before);
17376
+ const b = lines(after);
17377
+ if (a.length === 0) return { additions: b.length, deletions: 0 };
17378
+ if (b.length === 0) return { additions: 0, deletions: a.length };
17379
+ const max = a.length + b.length;
17380
+ let frontier = /* @__PURE__ */ new Map([[1, 0]]);
17381
+ for (let distance = 0; distance <= Math.min(max, MAX_DIFF_DISTANCE); distance++) {
17382
+ const next = /* @__PURE__ */ new Map();
17383
+ for (let diagonal = -distance; diagonal <= distance; diagonal += 2) {
17384
+ const down = frontier.get(diagonal + 1) ?? -1;
17385
+ const right = (frontier.get(diagonal - 1) ?? -1) + 1;
17386
+ let x = diagonal === -distance || diagonal !== distance && right < down ? down : right;
17387
+ if (x < 0) x = 0;
17388
+ let y = x - diagonal;
17389
+ while (x < a.length && y < b.length && a[x] === b[y]) {
17390
+ x++;
17391
+ y++;
17392
+ }
17393
+ next.set(diagonal, x);
17394
+ if (x >= a.length && y >= b.length) {
17395
+ return {
17396
+ additions: (distance + b.length - a.length) / 2,
17397
+ deletions: (distance + a.length - b.length) / 2
17398
+ };
17399
+ }
17048
17400
  }
17401
+ frontier = next;
17049
17402
  }
17050
- return images;
17051
- }
17052
- function codexUserInput(draft) {
17053
- const input = [];
17054
- if (draft.text.trim()) input.push({ type: "text", text: draft.text, text_elements: [] });
17055
- for (const path of draft.images) input.push({ type: "localImage", path });
17056
- return input;
17057
- }
17058
- function clipboardText() {
17059
- try {
17060
- if (process.platform === "darwin") return execFileSync5("pbpaste", [], { encoding: "utf8", timeout: 1500 });
17061
- const wayland = spawnSync12("wl-paste", ["--no-newline"], { encoding: "utf8", timeout: 1500 });
17062
- if (wayland.status === 0) return String(wayland.stdout || "");
17063
- const x11 = spawnSync12("xclip", ["-selection", "clipboard", "-o"], { encoding: "utf8", timeout: 1500 });
17064
- return x11.status === 0 ? String(x11.stdout || "") : "";
17065
- } catch {
17066
- return "";
17403
+ let start = 0;
17404
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
17405
+ let aEnd = a.length;
17406
+ let bEnd = b.length;
17407
+ while (aEnd > start && bEnd > start && a[aEnd - 1] === b[bEnd - 1]) {
17408
+ aEnd--;
17409
+ bEnd--;
17067
17410
  }
17411
+ return { additions: bEnd - start, deletions: aEnd - start };
17068
17412
  }
17069
- function macClipboardPng(target) {
17070
- const script = [
17071
- "on run argv",
17072
- "set outputPath to item 1 of argv",
17073
- "set imageData to the clipboard as \xABclass PNGf\xBB",
17074
- "set outputFile to open for access POSIX file outputPath with write permission",
17075
- "set eof outputFile to 0",
17076
- "write imageData to outputFile",
17077
- "close access outputFile",
17078
- "end run"
17079
- ].join("\n");
17080
- const result = spawnSync12("osascript", ["-e", script, target], { encoding: "utf8", timeout: 3e3 });
17081
- return result.status === 0 && existsSync38(target);
17082
- }
17083
- function linuxClipboardPng(target) {
17084
- const wayland = spawnSync12("wl-paste", ["--type", "image/png"], { encoding: null, timeout: 3e3 });
17085
- if (wayland.status === 0 && Buffer.isBuffer(wayland.stdout) && wayland.stdout.length > 0) {
17086
- writeFileSync27(target, wayland.stdout);
17087
- return true;
17088
- }
17089
- const x11 = spawnSync12("xclip", ["-selection", "clipboard", "-t", "image/png", "-o"], { encoding: null, timeout: 3e3 });
17090
- if (x11.status === 0 && Buffer.isBuffer(x11.stdout) && x11.stdout.length > 0) {
17091
- writeFileSync27(target, x11.stdout);
17092
- return true;
17413
+ function unifiedDiffCounts(diff) {
17414
+ let additions = 0;
17415
+ let deletions = 0;
17416
+ for (const line of String(diff || "").split("\n")) {
17417
+ if (line.startsWith("+++") || line.startsWith("---")) continue;
17418
+ if (line.startsWith("+")) additions++;
17419
+ else if (line.startsWith("-")) deletions++;
17093
17420
  }
17094
- return false;
17421
+ return { additions, deletions };
17422
+ }
17423
+ function safePath(cwd, file) {
17424
+ const absolute = resolve6(cwd, file);
17425
+ const rel = relative(cwd, absolute);
17426
+ return rel && rel !== ".." && !rel.startsWith("../") ? rel : null;
17095
17427
  }
17096
- function clipboardImage() {
17097
- const directory = mkdtempSync2(join37(tmpdir(), "synkro-paste-"));
17098
- const target = join37(directory, "clipboard.png");
17428
+ function worktreeContent(cwd, file) {
17429
+ const rel = safePath(cwd, file);
17430
+ if (!rel) return null;
17099
17431
  try {
17100
- const copied = process.platform === "darwin" ? macClipboardPng(target) : process.platform === "linux" && linuxClipboardPng(target);
17101
- if (copied && statSync5(target).size > 0) return target;
17432
+ const absolute = resolve6(cwd, rel);
17433
+ const stat = lstatSync2(absolute);
17434
+ if (stat.isSymbolicLink()) return "symlink:" + readlinkSync(absolute);
17435
+ if (!stat.isFile() || stat.size > MAX_TEXT_BYTES) return null;
17436
+ const content = readFileSync33(absolute);
17437
+ return content.includes(0) ? null : content.toString("utf8");
17102
17438
  } catch {
17439
+ return null;
17103
17440
  }
17104
- rmSync7(directory, { recursive: true, force: true });
17105
- return "";
17106
17441
  }
17107
- function cleanupPromptDraft(draft) {
17108
- for (const path of draft.temporaryImages) {
17109
- const directory = dirname12(resolve5(path));
17110
- const temporaryRoot = resolve5(tmpdir()) + sep3;
17111
- if (!directory.startsWith(temporaryRoot) || !basename3(directory).startsWith("synkro-paste-")) continue;
17112
- try {
17113
- rmSync7(directory, { recursive: true, force: true });
17114
- } catch {
17115
- }
17116
- }
17442
+ function fileOperation(event) {
17443
+ return (event.type === "tool-start" || event.type === "tool-end") && /^(edit|write|delete)$/.test(event.kind);
17117
17444
  }
17118
- function fallbackPrompt(cwd) {
17119
- return new Promise((resolveDraft) => {
17120
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
17121
- let settled = false;
17122
- const finish = (value) => {
17123
- if (settled) return;
17124
- settled = true;
17125
- rl.close();
17126
- resolveDraft(value);
17127
- };
17128
- rl.once("close", () => finish(null));
17129
- rl.once("SIGINT", () => finish(null));
17130
- rl.question(S2.user + " \u276F " + S2.reset, (text) => {
17131
- finish({ text, images: imagePathsInText(text, cwd), temporaryImages: [] });
17132
- });
17133
- });
17445
+ function eventPaths(cwd, event) {
17446
+ const provided = event.type === "tool-end" ? (event.fileChanges || []).map((change) => change.path) : [];
17447
+ const targets = String(event.target || "").split(/,\s*/);
17448
+ return [...new Set([...provided, ...targets].map((file) => safePath(cwd, file)).filter((file) => Boolean(file)))];
17134
17449
  }
17135
- function readPrompt(cwd) {
17136
- const input = process.stdin;
17137
- const output = process.stdout;
17138
- if (!input.isTTY || !output.isTTY || !input.setRawMode) return fallbackPrompt(cwd);
17139
- return new Promise((resolveDraft) => {
17140
- let text = "";
17141
- let cursor = 0;
17142
- let stream = "";
17143
- let pasting = false;
17144
- let settled = false;
17145
- const images = [];
17146
- const temporaryImages = [];
17147
- const wasRaw = Boolean(input.isRaw);
17148
- const prompt = S2.user + " \u276F " + S2.reset;
17149
- const redraw = () => {
17150
- const safe = text.replace(/[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g, "").replace(/[\r\n]+/g, " / ");
17151
- const attachmentLabel = images.length ? " [" + images.length + " image" + (images.length === 1 ? "" : "s") + "]" : "";
17152
- const attachments = attachmentLabel ? S2.dim + attachmentLabel + S2.reset : "";
17153
- output.write("\r\x1B[2K" + prompt + safe + attachments);
17154
- const after = text.slice(cursor).replace(/[\r\n]+/g, " / ").length + attachmentLabel.length;
17155
- if (after > 0) output.write("\x1B[" + after + "D");
17156
- };
17157
- const finish = (draft) => {
17158
- if (settled) return;
17159
- settled = true;
17160
- input.off("data", onData);
17161
- input.setRawMode?.(wasRaw);
17162
- output.write("\x1B[?2004l\n");
17163
- if (!draft) cleanupPromptDraft({ temporaryImages });
17164
- resolveDraft(draft);
17165
- };
17166
- const insert = (value) => {
17167
- text = text.slice(0, cursor) + value + text.slice(cursor);
17168
- cursor += value.length;
17169
- };
17170
- const attachClipboard = () => {
17171
- const image = clipboardImage();
17172
- if (image) {
17173
- images.push(image);
17174
- temporaryImages.push(image);
17175
- return;
17450
+ function snapshotOperation(cwd, event) {
17451
+ return new Map(eventPaths(cwd, event).map((file) => [file, worktreeContent(cwd, file)]));
17452
+ }
17453
+ var MAX_TEXT_BYTES, MAX_DIFF_DISTANCE, OperationChangeTracker;
17454
+ var init_changes = __esm({
17455
+ "cli/harness/changes.ts"() {
17456
+ "use strict";
17457
+ MAX_TEXT_BYTES = 2 * 1024 * 1024;
17458
+ MAX_DIFF_DISTANCE = 4e3;
17459
+ OperationChangeTracker = class {
17460
+ constructor(cwd, capture) {
17461
+ this.cwd = cwd;
17462
+ this.capture = capture;
17176
17463
  }
17177
- insert(clipboardText());
17178
- };
17179
- const processStream = () => {
17180
- while (stream && !settled) {
17181
- if (pasting) {
17182
- const end = stream.indexOf(PASTE_END);
17183
- if (end < 0) {
17184
- const keep = markerCarryLength(stream, PASTE_END);
17185
- insert(stream.slice(0, stream.length - keep));
17186
- stream = stream.slice(stream.length - keep);
17187
- break;
17188
- }
17189
- insert(stream.slice(0, end));
17190
- stream = stream.slice(end + PASTE_END.length);
17191
- pasting = false;
17192
- continue;
17193
- }
17194
- if (stream.startsWith(PASTE_START)) {
17195
- stream = stream.slice(PASTE_START.length);
17196
- pasting = true;
17197
- continue;
17198
- }
17199
- if (PASTE_START.startsWith(stream)) break;
17200
- if (stream.startsWith("\x1B[D")) {
17201
- cursor = Math.max(0, cursor - 1);
17202
- stream = stream.slice(3);
17203
- continue;
17204
- }
17205
- if (stream.startsWith("\x1B[C")) {
17206
- cursor = Math.min(text.length, cursor + 1);
17207
- stream = stream.slice(3);
17208
- continue;
17209
- }
17210
- if (stream.startsWith("\x1B[H")) {
17211
- cursor = 0;
17212
- stream = stream.slice(3);
17213
- continue;
17214
- }
17215
- if (stream.startsWith("\x1B[F")) {
17216
- cursor = text.length;
17217
- stream = stream.slice(3);
17218
- continue;
17219
- }
17220
- if (stream.startsWith("\x1B[A") || stream.startsWith("\x1B[B")) {
17221
- stream = stream.slice(3);
17222
- continue;
17464
+ cwd;
17465
+ capture;
17466
+ starts = /* @__PURE__ */ new Map();
17467
+ snapshot(event) {
17468
+ return this.capture ? this.capture() : snapshotOperation(this.cwd, event);
17469
+ }
17470
+ observe(event) {
17471
+ if (event.type === "tool-start") {
17472
+ if (fileOperation(event)) this.starts.set(event.id, this.snapshot(event));
17473
+ return event;
17223
17474
  }
17224
- if (stream.startsWith("\x1B") && stream.length < 3) break;
17225
- const char = stream[0];
17226
- stream = stream.slice(1);
17227
- if (char === "\r" || char === "\n") {
17228
- const found = imagePathsInText(text, cwd);
17229
- for (const path of found) if (!images.includes(path)) images.push(path);
17230
- finish({ text, images, temporaryImages });
17231
- } else if (char === "" || char === "" && !text && images.length === 0) {
17232
- finish(null);
17233
- } else if (char === "\x7F" || char === "\b") {
17234
- if (cursor > 0) {
17235
- text = text.slice(0, cursor - 1) + text.slice(cursor);
17236
- cursor -= 1;
17237
- }
17238
- } else if (char === "") {
17239
- text = text.slice(cursor);
17240
- cursor = 0;
17241
- } else if (char === "") {
17242
- const before = text.slice(0, cursor).replace(/\s*\S+\s*$/, "");
17243
- text = before + text.slice(cursor);
17244
- cursor = before.length;
17245
- } else if (char === "") {
17246
- attachClipboard();
17247
- } else if (char >= " " || char === " ") {
17248
- insert(char);
17475
+ if (event.type !== "tool-end") return event;
17476
+ const before = this.starts.get(event.id);
17477
+ this.starts.delete(event.id);
17478
+ if (!before) return event;
17479
+ const after = this.snapshot(event);
17480
+ const files = [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].sort();
17481
+ const fileChanges2 = [];
17482
+ for (const file of files) {
17483
+ const oldText = before.get(file);
17484
+ const newText = after.has(file) ? after.get(file) : worktreeContent(this.cwd, file);
17485
+ if (oldText === newText || oldText === null && newText === null) continue;
17486
+ const counts = lineChangeCounts(oldText || "", newText || "");
17487
+ fileChanges2.push({ path: file, ...counts });
17249
17488
  }
17489
+ const provided = event.fileChanges || [];
17490
+ const measured = new Map(fileChanges2.map((change) => [change.path, change]));
17491
+ const merged = provided.map((change) => {
17492
+ const actual = measured.get(change.path) || measured.get(safePath(this.cwd, change.path) || "");
17493
+ if (actual && change.additions === 0 && change.deletions === 0) return actual;
17494
+ return change;
17495
+ });
17496
+ const providedPaths = new Set(provided.flatMap((change) => [change.path, safePath(this.cwd, change.path) || ""]));
17497
+ for (const change of fileChanges2) if (!providedPaths.has(change.path)) merged.push(change);
17498
+ return { ...event, fileChanges: merged.length ? merged : fileChanges2 };
17250
17499
  }
17251
- if (!settled) redraw();
17252
- };
17253
- let queue = Promise.resolve();
17254
- const onData = (chunk) => {
17255
- queue = queue.then(() => {
17256
- stream += chunk.toString("utf8");
17257
- processStream();
17258
- });
17259
17500
  };
17260
- input.setRawMode(true);
17261
- input.resume();
17262
- input.on("data", onData);
17263
- output.write("\x1B[?2004h" + prompt);
17264
- });
17501
+ }
17502
+ });
17503
+
17504
+ // cli/codexUsage.ts
17505
+ function record(value) {
17506
+ return value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
17265
17507
  }
17266
- var PASTE_START, PASTE_END, IMAGE_EXTENSIONS;
17267
- var init_composer = __esm({
17268
- "cli/harness/composer.ts"() {
17508
+ function finiteNumber(value) {
17509
+ const n = Number(value);
17510
+ return Number.isFinite(n) ? n : null;
17511
+ }
17512
+ function rateWindow(value) {
17513
+ const row2 = record(value);
17514
+ if (!row2) return null;
17515
+ const usedPercent = finiteNumber(row2.usedPercent);
17516
+ if (usedPercent == null) return null;
17517
+ return {
17518
+ durationMins: finiteNumber(row2.windowDurationMins),
17519
+ usedPercent,
17520
+ resetsAt: finiteNumber(row2.resetsAt)
17521
+ };
17522
+ }
17523
+ function codexUsageFromRateLimitsResponse(value) {
17524
+ const response = record(value);
17525
+ if (!response) return null;
17526
+ const byLimitId = record(response.rateLimitsByLimitId);
17527
+ const snapshot = record(byLimitId?.codex) || record(response.rateLimits);
17528
+ if (!snapshot) return null;
17529
+ const primary = rateWindow(snapshot.primary);
17530
+ const secondary = rateWindow(snapshot.secondary);
17531
+ const windows = [primary, secondary].filter((w) => w != null);
17532
+ if (windows.length === 0) return null;
17533
+ const fiveHour = windows.find((w) => w.durationMins === 300) || windows.find((w) => w.durationMins != null && w.durationMins <= 360) || (primary?.durationMins == null ? primary : null);
17534
+ const sevenDay = windows.find((w) => w.durationMins === 10080) || windows.find((w) => w.durationMins != null && w.durationMins >= 1440) || (secondary !== fiveHour && secondary?.durationMins == null ? secondary : null);
17535
+ const status = typeof snapshot.rateLimitReachedType === "string" ? snapshot.rateLimitReachedType : snapshot.spendControlReached === true ? "spend_control_reached" : null;
17536
+ return {
17537
+ util5h: fiveHour ? fiveHour.usedPercent / 100 : null,
17538
+ util7d: sevenDay ? sevenDay.usedPercent / 100 : null,
17539
+ reset5h: fiveHour?.resetsAt ?? null,
17540
+ reset7d: sevenDay?.resetsAt ?? null,
17541
+ status,
17542
+ planType: typeof snapshot.planType === "string" ? snapshot.planType : null,
17543
+ limitId: typeof snapshot.limitId === "string" ? snapshot.limitId : null
17544
+ };
17545
+ }
17546
+ var init_codexUsage = __esm({
17547
+ "cli/codexUsage.ts"() {
17269
17548
  "use strict";
17270
- init_render2();
17271
- PASTE_START = "\x1B[200~";
17272
- PASTE_END = "\x1B[201~";
17273
- IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
17274
17549
  }
17275
17550
  });
17276
17551
 
17277
- // cli/harness/events.ts
17278
- function fixPollId(raw) {
17279
- const text = String(raw || "");
17280
- return FIX_POLL_MARKER.exec(text)?.[1] || LEGACY_FIX_POLL_MARKER.exec(text)?.[1] || "";
17281
- }
17282
- function cleanGuardText(raw) {
17283
- return String(raw || "").replace(FIX_POLL_MARKER, "").replace(/\n*\s*SYNKRO FIX POLL[\s\S]*$/i, "").replace(/\n?\d{4}-\d\d-\d\dT[^\n]*\sERROR\s+codex_core::tools::router:[^\n]*/gi, "").replace(/\s*Checking command\s*$/i, "").trim();
17284
- }
17285
- function blockReason(raw) {
17286
- const text = cleanGuardText(raw);
17287
- if (!text) return "blocked by policy";
17288
- const guardAt = text.lastIndexOf("Guard:");
17289
- if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
17290
- const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
17291
- if (afterTag) return afterTag[1].trim();
17292
- const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
17293
- if (afterHook) return afterHook[1].trim();
17294
- return text;
17295
- }
17296
- var BLOCK_MARKER, FIX_POLL_MARKER, LEGACY_FIX_POLL_MARKER;
17297
- var init_events = __esm({
17298
- "cli/harness/events.ts"() {
17299
- "use strict";
17300
- BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
17301
- FIX_POLL_MARKER = /\[synkro:fix-poll\s+item_id=\\?["']?([A-Za-z0-9_-]+)\\?["']?\]/i;
17302
- LEGACY_FIX_POLL_MARKER = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/i;
17303
- }
17304
- });
17305
-
17306
- // cli/harness/cursor.ts
17307
- function unwrapReplay(text) {
17308
- return text.replace(/<\/?user_query>/gi, "").trim();
17552
+ // cli/harness/cursor.ts
17553
+ function unwrapReplay(text) {
17554
+ return text.replace(/<\/?user_query>/gi, "").trim();
17309
17555
  }
17310
17556
  function textOf(message) {
17311
17557
  const content = message?.content;
@@ -17315,10 +17561,10 @@ function textOf(message) {
17315
17561
  }
17316
17562
  function toolPayload(toolCall) {
17317
17563
  if (!toolCall) return { kind: "other", body: {} };
17318
- for (const [key, kind] of Object.entries(TOOL_KINDS)) {
17319
- if (toolCall[key]) return { kind, body: toolCall[key] };
17564
+ for (const [key2, kind] of Object.entries(TOOL_KINDS)) {
17565
+ if (toolCall[key2]) return { kind, body: toolCall[key2] };
17320
17566
  }
17321
- const fallback = Object.keys(toolCall).find((key) => key.endsWith("ToolCall"));
17567
+ const fallback = Object.keys(toolCall).find((key2) => key2.endsWith("ToolCall"));
17322
17568
  return fallback ? { kind: "other", body: toolCall[fallback] } : { kind: "other", body: {} };
17323
17569
  }
17324
17570
  function targetOf(kind, body) {
@@ -17327,7 +17573,25 @@ function targetOf(kind, body) {
17327
17573
  const text = typeof candidate === "string" ? candidate : JSON.stringify(candidate ?? "");
17328
17574
  return text || body?.description || kind;
17329
17575
  }
17330
- function parseCursorLine(line) {
17576
+ function completedFileChanges(body) {
17577
+ const success = body?.result?.success || {};
17578
+ const rows = Array.isArray(success.changes) ? success.changes : Array.isArray(success.files) ? success.files : [];
17579
+ return rows.flatMap((change) => {
17580
+ const path = String(change?.path || change?.filePath || change?.file_path || "");
17581
+ if (!path) return [];
17582
+ const diff = String(change?.diff || change?.patch || "");
17583
+ const hasAdditions = change?.additions != null || change?.linesAdded != null;
17584
+ const hasDeletions = change?.deletions != null || change?.linesRemoved != null;
17585
+ if (!diff && !hasAdditions && !hasDeletions) return [];
17586
+ const counted = unifiedDiffCounts(diff);
17587
+ return [{
17588
+ path,
17589
+ additions: hasAdditions ? Number(change?.additions ?? change?.linesAdded) : counted.additions,
17590
+ deletions: hasDeletions ? Number(change?.deletions ?? change?.linesRemoved) : counted.deletions
17591
+ }];
17592
+ });
17593
+ }
17594
+ function parseCursorLine(line, streamPartial = false) {
17331
17595
  const trimmed = String(line || "").trim();
17332
17596
  if (!trimmed) return [];
17333
17597
  let frame;
@@ -17353,7 +17617,7 @@ function parseCursorLine(line) {
17353
17617
  }
17354
17618
  if (type === "assistant") {
17355
17619
  const text = textOf(frame.message);
17356
- return text ? [{ type: "assistant-message", text }] : [];
17620
+ return text ? [streamPartial ? { type: "assistant-delta", id: String(frame.model_call_id || ""), text } : { type: "assistant-message", text }] : [];
17357
17621
  }
17358
17622
  if (type === "thinking" && subtype === "delta" && frame.text) {
17359
17623
  return [{ type: "thinking", text: String(frame.text) }];
@@ -17381,8 +17645,10 @@ function parseCursorLine(line) {
17381
17645
  blocked,
17382
17646
  reason: rejected ? blocked ? blockReason(rawReason) : rawReason || "rejected" : "",
17383
17647
  pollId: blocked ? fixPollId(rawReason) : "",
17648
+ pollIds: blocked ? fixPollIds(rawReason) : [],
17384
17649
  exitCode: success ? Number(success.exitCode ?? 0) : null,
17385
- output: String(success?.stdout || success?.stderr || "")
17650
+ output: String(success?.stdout || success?.stderr || ""),
17651
+ fileChanges: completedFileChanges(body)
17386
17652
  }];
17387
17653
  }
17388
17654
  return [];
@@ -17397,11 +17663,15 @@ function parseCursorLine(line) {
17397
17663
  }];
17398
17664
  }
17399
17665
  if (type === "result") {
17400
- return [{
17401
- type: "turn-end",
17402
- ok: !frame.is_error,
17403
- text: String(frame.result || "")
17404
- }];
17666
+ const result = String(frame.result || "");
17667
+ return [
17668
+ ...streamPartial && result ? [{ type: "assistant-message", text: result }] : [],
17669
+ {
17670
+ type: "turn-end",
17671
+ ok: !frame.is_error,
17672
+ text: result
17673
+ }
17674
+ ];
17405
17675
  }
17406
17676
  return [];
17407
17677
  }
@@ -17420,12 +17690,14 @@ function feed(buffer, chunk) {
17420
17690
  const rest = parts.pop() ?? "";
17421
17691
  return { lines: parts, rest };
17422
17692
  }
17423
- function cursorArgs(prompt) {
17693
+ function cursorArgs(prompt, resumeId = "") {
17424
17694
  return [
17425
17695
  "-p",
17426
17696
  prompt,
17427
17697
  "--output-format",
17428
17698
  "stream-json",
17699
+ "--stream-partial-output",
17700
+ ...resumeId ? ["--resume", resumeId] : [],
17429
17701
  // --force auto-runs tools but does NOT bypass hooks (verified live), so
17430
17702
  // Synkro's guards still gate every call; --trust loads workspace hooks.
17431
17703
  "--force",
@@ -17437,6 +17709,7 @@ var init_cursor = __esm({
17437
17709
  "cli/harness/cursor.ts"() {
17438
17710
  "use strict";
17439
17711
  init_events();
17712
+ init_changes();
17440
17713
  TOOL_KINDS = {
17441
17714
  shellToolCall: "shell",
17442
17715
  readToolCall: "read",
@@ -17473,6 +17746,7 @@ function createTurnSink(opts) {
17473
17746
  const now = opts.now || (() => Date.now());
17474
17747
  const showPrompt = opts.showPrompt !== false;
17475
17748
  let blocked = 0;
17749
+ let sessionId = "";
17476
17750
  let replaying = false;
17477
17751
  const seen = /* @__PURE__ */ new Set();
17478
17752
  let sawTurnEnd = false;
@@ -17553,6 +17827,7 @@ function createTurnSink(opts) {
17553
17827
  };
17554
17828
  const emit2 = (event) => {
17555
17829
  events.push(event);
17830
+ if (event.type === "session-start" && event.sessionId) sessionId = event.sessionId;
17556
17831
  if (event.type === "tool-end" && event.blocked) blocked += 1;
17557
17832
  opts.onEvent?.(event);
17558
17833
  if (event.type === "notice" && event.kind === "retry") {
@@ -17603,25 +17878,25 @@ function createTurnSink(opts) {
17603
17878
  }
17604
17879
  replaying = true;
17605
17880
  }
17606
- const key = replayKey(event);
17607
- if (key) {
17608
- if (replaying && seen.has(key)) {
17881
+ const key2 = replayKey(event);
17882
+ if (key2) {
17883
+ if (replaying && seen.has(key2)) {
17609
17884
  lastWasAnswer = event.type === "assistant-message";
17610
17885
  settle();
17611
17886
  return;
17612
17887
  }
17613
- seen.add(key);
17888
+ seen.add(key2);
17614
17889
  }
17615
- if (key || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
17890
+ if (key2 || event.type === "thinking") lastWasAnswer = event.type === "assistant-message";
17616
17891
  if (event.type === "turn-end") sawTurnEnd = true;
17617
17892
  if (event.type === "assistant-message") sawAnswer = true;
17618
17893
  emit2(event);
17619
17894
  };
17620
17895
  return {
17621
17896
  chunk(text) {
17622
- const { lines, rest } = feed(buffer, text);
17897
+ const { lines: lines2, rest } = feed(buffer, text);
17623
17898
  buffer = rest;
17624
- for (const line of lines) for (const event of parseCursorLine(line)) take(event);
17899
+ for (const line of lines2) for (const event of parseCursorLine(line, opts.streamPartial)) take(event);
17625
17900
  },
17626
17901
  notice(text) {
17627
17902
  emit2({ type: "notice", text });
@@ -17644,7 +17919,7 @@ function createTurnSink(opts) {
17644
17919
  }
17645
17920
  }
17646
17921
  stopStatus();
17647
- return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit };
17922
+ return { events, blocked, exitCode: interrupted ? 130 : sawAnswer ? 0 : exit, sessionId };
17648
17923
  }
17649
17924
  };
17650
17925
  }
@@ -17660,10 +17935,11 @@ async function runCursorTurn(opts) {
17660
17935
  animate: Boolean(process.stdout.isTTY),
17661
17936
  showPrompt: opts.showPrompt,
17662
17937
  showHeader: opts.showHeader,
17663
- onGiveUp: () => stopHarness()
17938
+ onGiveUp: () => stopHarness(),
17939
+ streamPartial: true
17664
17940
  });
17665
- return new Promise((resolve8) => {
17666
- const child = spawn9("cursor-agent", cursorArgs(opts.prompt), {
17941
+ return new Promise((resolve9) => {
17942
+ const child = spawn9("cursor-agent", cursorArgs(opts.prompt, opts.resumeId), {
17667
17943
  cwd: opts.cwd,
17668
17944
  stdio: ["ignore", "pipe", "pipe"]
17669
17945
  });
@@ -17691,11 +17967,11 @@ async function runCursorTurn(opts) {
17691
17967
  });
17692
17968
  child.on("close", (code) => {
17693
17969
  opts.signal?.removeEventListener("abort", onAbort);
17694
- resolve8(sink.finish(code ?? 0, interrupted));
17970
+ resolve9(sink.finish(code ?? 0, interrupted));
17695
17971
  });
17696
17972
  child.on("error", (error) => {
17697
17973
  sink.notice("failed to start cursor-agent: " + String(error));
17698
- resolve8(sink.finish(1, interrupted));
17974
+ resolve9(sink.finish(1, interrupted));
17699
17975
  });
17700
17976
  });
17701
17977
  }
@@ -17711,360 +17987,562 @@ var init_run = __esm({
17711
17987
 
17712
17988
  // cli/harness/codex.ts
17713
17989
  import { spawn as spawn10 } from "child_process";
17714
- function itemTarget(item) {
17715
- if (item?.type === "commandExecution") {
17716
- return { kind: "shell", target: String(item.command || ""), description: "" };
17717
- }
17718
- if (item?.type === "fileChange") {
17719
- const paths = Array.isArray(item.changes) ? item.changes.map((change) => String(change?.path || "")).filter(Boolean) : [];
17720
- return { kind: "edit", target: paths.join(", ") || "files", description: "" };
17721
- }
17722
- if (item?.type === "mcpToolCall") {
17723
- return { kind: "other", target: String(item.server || "") + "::" + String(item.tool || ""), description: "MCP tool" };
17724
- }
17725
- if (item?.type === "dynamicToolCall") {
17726
- return { kind: "other", target: [item.namespace, item.tool].filter(Boolean).join("::"), description: "tool" };
17727
- }
17728
- return { kind: "other", target: String(item?.type || "tool"), description: "" };
17990
+ import { createInterface as createInterface6 } from "readline";
17991
+ function codexStderrNotice(raw, initialized) {
17992
+ const text = String(raw || "").trim();
17993
+ if (!text || initialized) return "";
17994
+ return /fatal|panic|authentication failed|not logged in/i.test(text) ? text.split("\n")[0].slice(0, 300) : "";
17729
17995
  }
17730
- function itemOutput(item) {
17731
- if (item?.type === "commandExecution") return String(item.aggregatedOutput || "");
17732
- if (item?.type === "mcpToolCall") return item.error ? String(item.error?.message || JSON.stringify(item.error)) : item.result ? JSON.stringify(item.result) : "";
17733
- if (item?.type === "dynamicToolCall") return item.contentItems ? JSON.stringify(item.contentItems) : "";
17734
- return "";
17996
+ function stringify(value) {
17997
+ if (typeof value === "string") return value;
17998
+ try {
17999
+ return JSON.stringify(value);
18000
+ } catch {
18001
+ return String(value ?? "");
18002
+ }
18003
+ }
18004
+ function toolKind(item) {
18005
+ if (item.type === "commandExecution") return "shell";
18006
+ if (item.type === "fileChange") return "edit";
18007
+ if (item.type === "webSearch") return "search";
18008
+ return "other";
18009
+ }
18010
+ function toolTarget(item) {
18011
+ if (item.type === "commandExecution") return item.command || "command";
18012
+ if (item.type === "fileChange") return (item.changes || []).map((change) => change.path || change.filePath || "").filter(Boolean).join(", ") || "workspace files";
18013
+ if (item.type === "mcpToolCall") return [item.server, item.tool].filter(Boolean).join(".") || "MCP tool";
18014
+ if (item.type === "dynamicToolCall") return [item.namespace, item.tool].filter(Boolean).join(".") || "tool";
18015
+ return item.query || item.type || "tool";
18016
+ }
18017
+ function fileChanges(item) {
18018
+ if (item.type !== "fileChange" || !Array.isArray(item.changes)) return [];
18019
+ return item.changes.flatMap((change) => {
18020
+ const path = String(change.path || change.filePath || "");
18021
+ if (!path) return [];
18022
+ const diff = String(change.diff || change.patch || "");
18023
+ const hasAdditions = change.additions != null || change.linesAdded != null;
18024
+ const hasDeletions = change.deletions != null || change.linesRemoved != null;
18025
+ if (!diff && !hasAdditions && !hasDeletions) return [];
18026
+ const counted = unifiedDiffCounts(diff);
18027
+ const additions = hasAdditions ? Number(change.additions ?? change.linesAdded) : counted.additions;
18028
+ const deletions = hasDeletions ? Number(change.deletions ?? change.linesRemoved) : counted.deletions;
18029
+ return [{ path, additions, deletions }];
18030
+ });
17735
18031
  }
17736
- function parseCodexNotification(method, params, startedAt = /* @__PURE__ */ new Map(), approvalReasons = /* @__PURE__ */ new Map()) {
17737
- if (method === "hook/started" || method === "hook/completed") {
17738
- const run2 = params?.run || {};
17739
- if (run2.eventName !== "preToolUse") return [];
17740
- const asMs = (value) => {
17741
- const number = Number(value || 0);
17742
- return number > 0 && number < 1e11 ? number * 1e3 : number;
17743
- };
17744
- const started = asMs(run2.startedAt);
17745
- const priorStart = Number(approvalReasons.get("__pretool_started__") || 0);
17746
- if (started && (!priorStart || started < priorStart)) approvalReasons.set("__pretool_started__", String(started));
17747
- if (method === "hook/completed") {
17748
- const completed = asMs(run2.completedAt) || started + Number(run2.durationMs || 0);
17749
- const priorCompleted = Number(approvalReasons.get("__pretool_completed__") || 0);
17750
- if (completed > priorCompleted) approvalReasons.set("__pretool_completed__", String(completed));
17751
- const entries = Array.isArray(run2.entries) ? run2.entries : [];
17752
- const pollEntry = entries.find((entry) => fixPollId(String(entry?.text || "")));
17753
- const blockingEntry = entries.find((entry) => entry?.kind === "stop" || entry?.kind === "error");
17754
- const text = String(pollEntry?.text || blockingEntry?.text || run2.statusMessage || "");
17755
- if (text && (run2.status === "blocked" || run2.status === "failed" || BLOCK_MARKER.test(text))) {
17756
- approvalReasons.set("__latest_hook__", text);
17757
- approvalReasons.set("__latest_hook_id__", String(run2.id || "synkro-policy-block"));
17758
- approvalReasons.set("__latest_hook_duration__", String(Number(run2.durationMs || Math.max(0, completed - started))));
17759
- }
17760
- }
17761
- return [];
17762
- }
17763
- if (method === "item/started") {
17764
- const item = params?.item;
17765
- if (!item?.id) return [];
17766
- startedAt.set(String(item.id), Number(params.startedAtMs || Date.now()));
17767
- if (!["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall"].includes(item.type)) return [];
17768
- return [{ type: "tool-start", id: String(item.id), ...itemTarget(item) }];
17769
- }
17770
- if (method === "item/completed") {
17771
- const item = params?.item;
17772
- if (!item?.id) return [];
17773
- if (item.type === "hookPrompt") {
17774
- const text = Array.isArray(item.fragments) ? item.fragments.map((fragment) => String(fragment?.text || "")).filter(Boolean).join("\n") : "";
17775
- if (text) {
17776
- const start2 = startedAt.get(String(item.id));
17777
- const completed2 = Number(params.completedAtMs || Date.now());
17778
- approvalReasons.set("__latest_hook__", text);
17779
- approvalReasons.set("__latest_hook_id__", String(item.id));
17780
- approvalReasons.set("__latest_hook_duration__", String(start2 === void 0 ? 0 : Math.max(0, completed2 - start2)));
17781
- }
17782
- startedAt.delete(String(item.id));
17783
- return [];
17784
- }
17785
- if (item.type === "agentMessage") {
17786
- return item.text ? [{ type: "assistant-message", text: String(item.text) }] : [];
17787
- }
17788
- if (item.type === "reasoning") {
17789
- const text = [...item.summary || [], ...item.content || []].join(" ").trim();
17790
- return text ? [{ type: "thinking", text }] : [];
17791
- }
17792
- if (!["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall"].includes(item.type)) return [];
17793
- const output = itemOutput(item);
17794
- const approvalReason = approvalReasons.get(String(item.id)) || "";
17795
- const hookReason = approvalReasons.get("__latest_hook__") || "";
17796
- const rawReason = [approvalReason, hookReason, output].filter(Boolean).join("\n");
18032
+ function codexAccountUsageEvent(value) {
18033
+ const usage2 = codexUsageFromRateLimitsResponse(value);
18034
+ if (usage2?.util7d == null) return null;
18035
+ return {
18036
+ type: "account-usage",
18037
+ period: "weekly",
18038
+ remainingPercent: Math.max(0, Math.min(100, Math.round((1 - usage2.util7d) * 100))),
18039
+ resetsAt: usage2.reset7d
18040
+ };
18041
+ }
18042
+ function codexMessageTurnId(message) {
18043
+ return String(message.params?.turnId || message.params?.turn?.id || "");
18044
+ }
18045
+ function codexMessageIsTurnScoped(message) {
18046
+ const method = String(message.method || "");
18047
+ return method.startsWith("item/") || method === "hook/completed" || method === "turn/plan/updated";
18048
+ }
18049
+ function codexEvents(message) {
18050
+ const method = String(message.method || "");
18051
+ const params = message.params || {};
18052
+ if (method === "account/rateLimits/updated") {
18053
+ const event = codexAccountUsageEvent(params);
18054
+ return event ? [event] : [];
18055
+ }
18056
+ const item = params.item || {};
18057
+ if (method === "item/agentMessage/delta") return [{ type: "assistant-delta", id: params.itemId || "", text: params.delta || "" }];
18058
+ if (method === "item/reasoning/summaryTextDelta") return [{ type: "thinking", text: params.delta || "Thinking" }];
18059
+ const tools = ["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"];
18060
+ if (method === "item/started" && tools.includes(item.type)) {
18061
+ return [{ type: "tool-start", id: item.id || "", kind: toolKind(item), target: toolTarget(item), description: "" }];
18062
+ }
18063
+ if (method === "item/completed" && item.type === "agentMessage") return [{ type: "assistant-message", text: item.text || "" }];
18064
+ if (method === "item/completed" && tools.includes(item.type)) {
18065
+ const raw = stringify(item.aggregatedOutput || item.error?.message || item.result?.content || item.contentItems || "");
17797
18066
  const status = String(item.status || "");
17798
- const blocked = BLOCK_MARKER.test(rawReason) || /blocked by PreToolUse hook/i.test(rawReason) || Boolean(hookReason) && (status === "failed" || status === "declined");
17799
- const itemStart = startedAt.get(String(item.id));
17800
- const hookStart = Number(approvalReasons.get("__pretool_started__") || 0);
17801
- const start = itemStart === void 0 ? hookStart || void 0 : hookStart ? Math.min(itemStart, hookStart) : itemStart;
17802
- const completed = Number(params.completedAtMs || Date.now());
17803
- const durationMs = start === void 0 ? item.durationMs ?? null : Math.max(0, completed - start);
17804
- startedAt.delete(String(item.id));
17805
- approvalReasons.delete(String(item.id));
17806
- approvalReasons.delete("__latest_hook__");
17807
- approvalReasons.delete("__latest_hook_id__");
17808
- approvalReasons.delete("__latest_hook_duration__");
17809
- approvalReasons.delete("__pretool_started__");
17810
- approvalReasons.delete("__pretool_completed__");
18067
+ const blocked = BLOCK_MARKER.test(raw) || /declined|blocked|denied/.test(status);
18068
+ const ok = !blocked && !/failed|error/.test(status) && (item.exitCode === null || item.exitCode === void 0 || item.exitCode === 0);
17811
18069
  return [{
17812
18070
  type: "tool-end",
17813
- id: String(item.id),
17814
- ...itemTarget(item),
17815
- ok: status === "completed" && Number(item.exitCode ?? 0) === 0,
18071
+ id: item.id || "",
18072
+ kind: toolKind(item),
18073
+ target: toolTarget(item),
18074
+ ok,
17816
18075
  blocked,
17817
- reason: blocked ? blockReason(rawReason) : "",
17818
- pollId: blocked ? fixPollId(rawReason) : "",
17819
- exitCode: item.exitCode === null || item.exitCode === void 0 ? null : Number(item.exitCode),
17820
- output: blocked ? "" : output,
17821
- durationMs
18076
+ reason: blocked ? blockReason(raw || status) : "",
18077
+ pollId: blocked ? fixPollId(raw) : "",
18078
+ pollIds: blocked ? fixPollIds(raw) : [],
18079
+ exitCode: typeof item.exitCode === "number" ? item.exitCode : null,
18080
+ output: raw,
18081
+ durationMs: typeof item.durationMs === "number" ? item.durationMs : null,
18082
+ fileChanges: fileChanges(item)
17822
18083
  }];
17823
18084
  }
17824
- if (method === "turn/completed") {
17825
- const status = String(params?.turn?.status || "failed");
17826
- const hookReason = approvalReasons.get("__latest_hook__") || "";
17827
- const events = [];
17828
- if (hookReason) {
17829
- events.push({
17830
- type: "tool-end",
17831
- id: approvalReasons.get("__latest_hook_id__") || "synkro-policy-block",
17832
- kind: "other",
17833
- target: "policy check",
17834
- ok: false,
17835
- blocked: true,
17836
- reason: blockReason(hookReason),
17837
- pollId: fixPollId(hookReason),
17838
- exitCode: null,
17839
- output: "",
17840
- durationMs: Number(approvalReasons.get("__latest_hook_duration__") || 0)
17841
- });
17842
- approvalReasons.delete("__latest_hook__");
17843
- approvalReasons.delete("__latest_hook_id__");
17844
- approvalReasons.delete("__latest_hook_duration__");
17845
- }
17846
- approvalReasons.delete("__pretool_started__");
17847
- approvalReasons.delete("__pretool_completed__");
17848
- events.push({ type: "turn-end", ok: status === "completed", text: String(params?.turn?.error?.message || "") });
17849
- return events;
18085
+ if (method === "hook/completed") {
18086
+ const run2 = params.run || {};
18087
+ if (run2.status !== "blocked") return [];
18088
+ const reason = (run2.entries || []).map((entry) => entry.text || "").filter(Boolean).join("\n") || run2.statusMessage || "blocked by policy";
18089
+ return [{
18090
+ type: "tool-end",
18091
+ id: "hook:" + (run2.id || ""),
18092
+ kind: "other",
18093
+ target: run2.eventName || "Guard",
18094
+ ok: false,
18095
+ blocked: true,
18096
+ reason: blockReason(reason),
18097
+ exitCode: null,
18098
+ output: reason,
18099
+ pollId: fixPollId(reason),
18100
+ pollIds: fixPollIds(reason),
18101
+ durationMs: typeof run2.durationMs === "number" ? run2.durationMs : null
18102
+ }];
17850
18103
  }
17851
- return [];
17852
- }
17853
- function codexStderrNotice(raw, initialized) {
17854
- const text = String(raw || "").trim();
17855
- if (!text || initialized) return "";
17856
- return /fatal|panic|authentication failed|not logged in/i.test(text) ? text.split("\n")[0].slice(0, 300) : "";
17857
- }
17858
- async function askApproval(text) {
17859
- const input = process.stdin;
17860
- const output = process.stdout;
17861
- if (!input.isTTY || !output.isTTY) return false;
17862
- output.write("\n" + S2.rule + " Synkro approval required" + S2.reset + "\n " + text.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ").slice(0, 600) + "\n");
17863
- output.write(S2.dim + " Press y to allow once; any other key denies." + S2.reset + "\n");
17864
- return new Promise((resolve8) => {
17865
- const wasRaw = Boolean(input.isRaw);
17866
- const onData = (chunk) => {
17867
- input.off("data", onData);
17868
- if (input.setRawMode) input.setRawMode(wasRaw);
17869
- resolve8(chunk.toString("utf8").toLowerCase() === "y");
17870
- };
17871
- if (input.setRawMode) input.setRawMode(true);
17872
- input.resume();
17873
- input.on("data", onData);
17874
- });
17875
- }
17876
- async function runCodexTurn(opts) {
17877
- const session = new CodexAppSession();
17878
- try {
17879
- return await session.turn(opts);
17880
- } finally {
17881
- session.close();
18104
+ if (method === "turn/plan/updated") {
18105
+ return [{ type: "plan", steps: (params.plan || []).map((row2) => ({ step: row2.step || "", status: row2.status || "pending" })) }];
18106
+ }
18107
+ if (method === "thread/tokenUsage/updated") {
18108
+ const usage2 = params.tokenUsage || {};
18109
+ const total = usage2.total || usage2.last || {};
18110
+ const used = Number(total.totalTokens ?? total.total_tokens ?? usage2.totalTokens ?? 0);
18111
+ const context = Number(usage2.modelContextWindow ?? usage2.model_context_window ?? 0);
18112
+ return [{ type: "usage", used, contextWindow: context || null }];
17882
18113
  }
18114
+ if (method === "warning" || method === "error") return [{ type: "notice", text: params.message || params.error?.message || "Codex transport warning" }];
18115
+ return [];
17883
18116
  }
17884
- var CodexAppSession;
18117
+ var CodexSession;
17885
18118
  var init_codex = __esm({
17886
18119
  "cli/harness/codex.ts"() {
17887
18120
  "use strict";
17888
18121
  init_composer();
17889
18122
  init_events();
18123
+ init_changes();
18124
+ init_codexUsage();
17890
18125
  init_run();
17891
18126
  init_render2();
17892
- CodexAppSession = class {
18127
+ CodexSession = class {
18128
+ constructor(cwd, resumeId = "") {
18129
+ this.cwd = cwd;
18130
+ this.resumeId = resumeId;
18131
+ }
18132
+ cwd;
18133
+ resumeId;
17893
18134
  child = null;
17894
- buffer = "";
18135
+ initialized = false;
17895
18136
  nextId = 1;
17896
18137
  pending = /* @__PURE__ */ new Map();
17897
- initialized = false;
18138
+ onEvent = null;
18139
+ activeTurn = "";
18140
+ turnDone = null;
18141
+ completedTurns = /* @__PURE__ */ new Map();
18142
+ accountUsage = null;
17898
18143
  threadId = "";
17899
- model = "Codex";
17900
- cwd = "";
17901
- active = null;
17902
- send(message) {
17903
- this.child?.stdin.write(JSON.stringify({ jsonrpc: "2.0", ...message }) + "\n");
17904
- }
17905
- request(method, params) {
18144
+ model = "";
18145
+ request(method, params, timeoutMs = 0) {
18146
+ if (!this.child) return Promise.reject(new Error("Codex app-server is not running"));
17906
18147
  const id = this.nextId++;
17907
- return new Promise((resolve8, reject) => {
17908
- this.pending.set(id, { resolve: resolve8, reject });
17909
- this.send({ id, method, params });
18148
+ this.child.stdin.write(JSON.stringify({ method, id, params }) + "\n");
18149
+ return new Promise((resolve9, reject) => {
18150
+ const timer = timeoutMs > 0 ? setTimeout(() => {
18151
+ this.pending.delete(id);
18152
+ reject(new Error(method + " timed out"));
18153
+ }, timeoutMs) : null;
18154
+ this.pending.set(id, {
18155
+ resolve: (value) => {
18156
+ if (timer) clearTimeout(timer);
18157
+ resolve9(value);
18158
+ },
18159
+ reject: (error) => {
18160
+ if (timer) clearTimeout(timer);
18161
+ reject(error);
18162
+ }
18163
+ });
17910
18164
  });
17911
18165
  }
17912
- respond(id, result) {
17913
- this.send({ id, result });
18166
+ notify(method, params = {}) {
18167
+ this.child?.stdin.write(JSON.stringify({ method, params }) + "\n");
17914
18168
  }
17915
- async handleServerRequest(message) {
17916
- const method = String(message.method || "");
17917
- const params = message.params || {};
17918
- if (params.itemId && params.reason) this.active?.approvalReasons.set(String(params.itemId), String(params.reason));
17919
- if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval") {
17920
- const summary = String(params.reason || params.command || params.grantRoot || "Allow this action?");
17921
- const accepted = await askApproval(summary);
17922
- this.respond(message.id, { decision: accepted ? "accept" : "decline" });
17923
- return;
17924
- }
17925
- if (method === "item/tool/requestUserInput") {
17926
- const answers = {};
17927
- for (const question of params.questions || []) answers[String(question.id)] = { answers: [] };
17928
- this.respond(message.id, { answers });
17929
- return;
17930
- }
17931
- if (method === "mcpServer/elicitation/request") {
17932
- this.respond(message.id, { action: "decline" });
17933
- return;
17934
- }
17935
- this.respond(message.id, {});
17936
- }
17937
- handleLine(line) {
17938
- let message;
17939
- try {
17940
- message = JSON.parse(line);
17941
- } catch {
18169
+ receive(message) {
18170
+ if (message.id !== void 0 && !message.method) {
18171
+ const pending = this.pending.get(message.id);
18172
+ if (!pending) return;
18173
+ this.pending.delete(message.id);
18174
+ if (message.error) pending.reject(new Error(message.error.message || stringify(message.error)));
18175
+ else pending.resolve(message.result);
17942
18176
  return;
17943
18177
  }
17944
- if (message.method && message.id !== void 0) {
17945
- void this.handleServerRequest(message);
18178
+ if (message.id !== void 0 && message.method) {
18179
+ this.child?.stdin.write(JSON.stringify({ id: message.id, result: { decision: "decline" } }) + "\n");
18180
+ this.onEvent?.({ type: "notice", text: "Codex requested an approval the embedded policy did not permit." });
17946
18181
  return;
17947
18182
  }
17948
- if (message.method) {
17949
- const active = this.active;
17950
- if (!active) return;
17951
- const events = parseCodexNotification(message.method, message.params, active.startedAt, active.approvalReasons);
17952
- for (const event of events) active.sink.event(event);
17953
- if (message.method === "turn/started") active.turnId = String(message.params?.turn?.id || "");
17954
- if (message.method === "turn/completed") {
17955
- const result = active.sink.finish(message.params?.turn?.status === "completed" ? 0 : 1, active.interrupted);
17956
- this.active = null;
17957
- active.resolve(result);
17958
- }
17959
- return;
18183
+ const messageTurnId = codexMessageTurnId(message);
18184
+ const staleTurnEvent = codexMessageIsTurnScoped(message) && (!this.activeTurn || messageTurnId && messageTurnId !== this.activeTurn);
18185
+ for (const event of codexEvents(message)) {
18186
+ if (event.type === "account-usage") this.accountUsage = event;
18187
+ if (!staleTurnEvent) this.onEvent?.(event);
17960
18188
  }
17961
- if (message.id !== void 0) {
17962
- const pending = this.pending.get(Number(message.id));
17963
- if (!pending) return;
17964
- this.pending.delete(Number(message.id));
17965
- if (message.error) pending.reject(new Error(String(message.error?.message || JSON.stringify(message.error))));
17966
- else pending.resolve(message.result);
18189
+ if (message.method === "turn/completed" && message.params?.turn?.id === this.activeTurn) {
18190
+ const status = String(message.params.turn.status || "");
18191
+ this.completedTurns.set(message.params.turn.id, status);
18192
+ this.turnDone?.(status);
18193
+ } else if (message.method === "turn/completed" && message.params?.turn?.id) {
18194
+ this.completedTurns.set(message.params.turn.id, String(message.params.turn.status || ""));
17967
18195
  }
17968
18196
  }
17969
- async start(cwd) {
17970
- if (this.child) return;
17971
- this.cwd = cwd;
17972
- this.child = spawn10("codex", ["app-server"], { cwd, stdio: ["pipe", "pipe", "pipe"] });
17973
- this.child.stdout.on("data", (chunk) => {
17974
- this.buffer += chunk.toString("utf8");
17975
- let newline = this.buffer.indexOf("\n");
17976
- while (newline !== -1) {
17977
- const line = this.buffer.slice(0, newline).trim();
17978
- this.buffer = this.buffer.slice(newline + 1);
17979
- if (line) this.handleLine(line);
17980
- newline = this.buffer.indexOf("\n");
18197
+ async start() {
18198
+ this.child = spawn10("codex", ["app-server", "--stdio"], { cwd: this.cwd, stdio: ["pipe", "pipe", "pipe"] });
18199
+ this.child.on("error", (cause) => {
18200
+ const error = cause instanceof Error ? cause : new Error(String(cause));
18201
+ for (const pending of this.pending.values()) pending.reject(error);
18202
+ this.pending.clear();
18203
+ this.turnDone?.("failed");
18204
+ });
18205
+ createInterface6({ input: this.child.stdout }).on("line", (line) => {
18206
+ try {
18207
+ this.receive(JSON.parse(line));
18208
+ } catch {
17981
18209
  }
17982
18210
  });
17983
18211
  this.child.stderr.on("data", (chunk) => {
17984
- const note = codexStderrNotice(chunk.toString("utf8"), this.initialized);
17985
- if (note) this.active?.sink.notice(note);
18212
+ const message = codexStderrNotice(String(chunk || ""), this.initialized);
18213
+ if (message) this.onEvent?.({ type: "notice", text: message });
17986
18214
  });
17987
- this.child.on("close", (code) => {
17988
- const active = this.active;
17989
- if (active) {
17990
- this.active = null;
17991
- active.resolve(active.sink.finish(code ?? 1, active.interrupted));
17992
- }
17993
- for (const pending of this.pending.values()) pending.reject(new Error("codex app-server exited"));
18215
+ this.child.once("exit", (code) => {
18216
+ const error = new Error("Codex app-server exited" + (code === null ? "" : " (" + code + ")"));
18217
+ for (const pending of this.pending.values()) pending.reject(error);
17994
18218
  this.pending.clear();
17995
- this.child = null;
18219
+ this.turnDone?.("failed");
17996
18220
  });
17997
- this.child.on("error", (error) => {
17998
- const failure = new Error("failed to start codex app-server: " + String(error));
17999
- const active = this.active;
18000
- if (active) {
18001
- active.sink.notice(failure.message);
18002
- this.active = null;
18003
- active.resolve(active.sink.finish(1, active.interrupted));
18004
- }
18005
- for (const pending of this.pending.values()) pending.reject(failure);
18006
- this.pending.clear();
18007
- });
18008
- const initialized = await this.request("initialize", { clientInfo: { name: "synkro-governed", version: "1.0.0" } });
18221
+ await this.request("initialize", { clientInfo: { name: "synkro-terminal", title: "Synkro", version: "1" }, capabilities: { experimentalApi: true } });
18222
+ this.notify("initialized");
18009
18223
  this.initialized = true;
18010
- this.send({ method: "initialized", params: {} });
18011
- const started = await this.request("thread/start", {
18012
- cwd,
18013
- approvalPolicy: "on-request",
18014
- approvalsReviewer: "user",
18015
- sandbox: "workspace-write",
18016
- ephemeral: false
18017
- });
18018
- this.threadId = String(started?.thread?.id || "");
18019
- this.model = String(started?.model || initialized?.userAgent || "Codex");
18020
- if (!this.threadId) throw new Error("codex app-server did not return a thread id");
18224
+ try {
18225
+ const event = codexAccountUsageEvent(await this.request("account/rateLimits/read", null, 4e3));
18226
+ if (event) this.accountUsage = event;
18227
+ } catch {
18228
+ }
18229
+ let result;
18230
+ try {
18231
+ result = this.resumeId ? await this.request("thread/resume", { threadId: this.resumeId, cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write" }) : await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
18232
+ } catch (error) {
18233
+ if (!this.resumeId) throw error;
18234
+ result = await this.request("thread/start", { cwd: this.cwd, approvalPolicy: "never", sandbox: "workspace-write", serviceName: "Synkro" });
18235
+ }
18236
+ this.threadId = result.thread?.id || "";
18237
+ this.model = result.model || "";
18021
18238
  }
18022
- async turn(opts) {
18023
- await this.start(opts.cwd);
18024
- if (this.active) throw new Error("a Codex turn is already running");
18025
- const sink = createTurnSink({
18026
- write: opts.write || ((text) => process.stdout.write(text)),
18027
- onEvent: opts.onEvent,
18028
- width: opts.width || Number(process.stdout.columns || 100),
18029
- animate: false,
18030
- showPrompt: opts.showPrompt,
18031
- showHeader: opts.showHeader
18032
- });
18033
- sink.event({ type: "session-start", sessionId: this.threadId, model: this.model, cwd: this.cwd, authSource: "login" });
18034
- sink.event({
18035
- type: "user-message",
18036
- text: opts.prompt || (opts.images?.length ? "[" + opts.images.length + " image attached]" : "")
18037
- });
18038
- const result = new Promise((resolve8) => {
18039
- this.active = { sink, resolve: resolve8, startedAt: /* @__PURE__ */ new Map(), approvalReasons: /* @__PURE__ */ new Map(), turnId: "", interrupted: false };
18040
- });
18041
- const onAbort = () => {
18042
- if (!this.active) return;
18043
- this.active.interrupted = true;
18044
- if (this.active.turnId) void this.request("turn/interrupt", { threadId: this.threadId, turnId: this.active.turnId }).catch(() => {
18045
- });
18239
+ async runTurn(prompt, onEvent, signal) {
18240
+ let blocked = 0;
18241
+ let turnId = "";
18242
+ let interrupt = null;
18243
+ this.onEvent = (event) => {
18244
+ if (event.type === "tool-end" && event.blocked) blocked++;
18245
+ onEvent(event);
18046
18246
  };
18047
- if (opts.signal?.aborted) onAbort();
18048
- else opts.signal?.addEventListener("abort", onAbort, { once: true });
18049
18247
  try {
18050
- await this.request("turn/start", {
18051
- threadId: this.threadId,
18052
- cwd: opts.cwd,
18053
- input: codexUserInput({ text: opts.prompt, images: opts.images || [] })
18248
+ onEvent({ type: "session-start", sessionId: this.threadId, model: this.model || "Codex", cwd: this.cwd, authSource: "login" });
18249
+ if (this.accountUsage) onEvent(this.accountUsage);
18250
+ onEvent({ type: "user-message", text: prompt });
18251
+ const response = await this.request("turn/start", { threadId: this.threadId, input: [{ type: "text", text: prompt }] });
18252
+ turnId = response.turn?.id || "";
18253
+ this.activeTurn = turnId;
18254
+ let aborted = false;
18255
+ let interruptSent = false;
18256
+ interrupt = () => {
18257
+ aborted = true;
18258
+ if (interruptSent || !this.activeTurn) return;
18259
+ interruptSent = true;
18260
+ void this.request("turn/interrupt", { threadId: this.threadId, turnId: this.activeTurn }).catch(() => {
18261
+ });
18262
+ };
18263
+ if (signal?.aborted) interrupt();
18264
+ else signal?.addEventListener("abort", interrupt, { once: true });
18265
+ const alreadyDone = this.completedTurns.get(turnId);
18266
+ const status = alreadyDone ?? await new Promise((resolve9) => {
18267
+ this.turnDone = resolve9;
18054
18268
  });
18055
- return await result;
18269
+ this.completedTurns.delete(turnId);
18270
+ signal?.removeEventListener("abort", interrupt);
18271
+ this.activeTurn = "";
18272
+ this.turnDone = null;
18273
+ this.onEvent = null;
18274
+ onEvent({ type: "turn-end", ok: aborted || /completed/.test(status), text: aborted ? "interrupted" : status });
18275
+ return { exitCode: aborted ? 130 : /completed/.test(status) ? 0 : 1, blocked, sessionId: this.threadId };
18056
18276
  } finally {
18057
- opts.signal?.removeEventListener("abort", onAbort);
18277
+ if (interrupt) signal?.removeEventListener("abort", interrupt);
18278
+ if (turnId) this.completedTurns.delete(turnId);
18279
+ this.activeTurn = "";
18280
+ this.turnDone = null;
18281
+ this.onEvent = null;
18058
18282
  }
18059
18283
  }
18060
18284
  close() {
18061
- this.child?.kill();
18285
+ this.child?.kill("SIGTERM");
18062
18286
  this.child = null;
18063
18287
  }
18064
18288
  };
18065
18289
  }
18066
18290
  });
18067
18291
 
18292
+ // cli/harness/screen.ts
18293
+ var ANSI, REVERSE, STREAM_DRAW_MS, STATUS_TICK_MS, resetDateFormat, visible, pathLabel, SynkroScreen;
18294
+ var init_screen = __esm({
18295
+ "cli/harness/screen.ts"() {
18296
+ "use strict";
18297
+ init_render2();
18298
+ init_changes();
18299
+ ANSI = /\x1b\[[0-?]*[ -\/]*[@-~]/g;
18300
+ REVERSE = "\x1B[7m";
18301
+ STREAM_DRAW_MS = 80;
18302
+ STATUS_TICK_MS = 240;
18303
+ resetDateFormat = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" });
18304
+ visible = (value) => value.replace(ANSI, "").length;
18305
+ pathLabel = (cwd) => {
18306
+ const parts = cwd.split("/").filter(Boolean);
18307
+ return parts.length > 2 ? "\u2026/" + parts.slice(-2).join("/") : cwd;
18308
+ };
18309
+ SynkroScreen = class {
18310
+ constructor(harness, cwd, write2 = (text) => process.stdout.write(text)) {
18311
+ this.harness = harness;
18312
+ this.cwd = cwd;
18313
+ this.write = write2;
18314
+ this.changes = new OperationChangeTracker(cwd);
18315
+ }
18316
+ harness;
18317
+ cwd;
18318
+ write;
18319
+ transcript = [];
18320
+ draft = "";
18321
+ input = "";
18322
+ selectionAnchor = null;
18323
+ selectionFocus = null;
18324
+ status = "Ready";
18325
+ statusSince = Date.now();
18326
+ tick = 0;
18327
+ used = 0;
18328
+ context = null;
18329
+ weeklyRemaining = null;
18330
+ weeklyResetsAt = null;
18331
+ model = "";
18332
+ timer = null;
18333
+ drawTimer = null;
18334
+ lastDrawAt = 0;
18335
+ scrollOffset = 0;
18336
+ changes;
18337
+ resize = () => this.draw();
18338
+ enter() {
18339
+ this.write("\x1B[?1049h\x1B[?25l\x1B[?1000h\x1B[?1002h\x1B[?1006h");
18340
+ process.stdout.on("resize", this.resize);
18341
+ this.timer = setInterval(() => {
18342
+ if (this.status === "Ready") return;
18343
+ this.tick++;
18344
+ this.queueDraw();
18345
+ }, STATUS_TICK_MS);
18346
+ this.timer.unref();
18347
+ this.draw();
18348
+ }
18349
+ leave() {
18350
+ if (this.timer) clearInterval(this.timer);
18351
+ if (this.drawTimer) clearTimeout(this.drawTimer);
18352
+ process.stdout.removeListener("resize", this.resize);
18353
+ this.write(S2.reset + "\x1B[?1006l\x1B[?1002l\x1B[?1000l\x1B[?25h\x1B[?1049l");
18354
+ }
18355
+ setInput(value) {
18356
+ this.input = cleanOutput(value);
18357
+ this.selectionAnchor = null;
18358
+ this.selectionFocus = null;
18359
+ this.draw();
18360
+ }
18361
+ selectionRange() {
18362
+ if (this.selectionAnchor === null || this.selectionFocus === null || this.selectionAnchor === this.selectionFocus) return null;
18363
+ return [Math.min(this.selectionAnchor, this.selectionFocus), Math.max(this.selectionAnchor, this.selectionFocus)];
18364
+ }
18365
+ clearSelection() {
18366
+ this.selectionAnchor = null;
18367
+ this.selectionFocus = null;
18368
+ this.draw();
18369
+ }
18370
+ scrollBy(lines2) {
18371
+ this.scrollOffset = Math.max(0, this.scrollOffset + lines2);
18372
+ this.draw();
18373
+ }
18374
+ scrollToBottom() {
18375
+ this.scrollOffset = 0;
18376
+ this.draw();
18377
+ }
18378
+ /** Select text using one-based SGR mouse coordinates from the terminal. */
18379
+ selectInputAt(x, y, extend, width = this.width(), height = this.height()) {
18380
+ const rows = this.inputRows(width);
18381
+ const composerLength = rows.length + 2;
18382
+ const viewport = Math.max(3, height - composerLength - 2);
18383
+ const lineIndex = y - (viewport + 2);
18384
+ const row2 = rows[lineIndex];
18385
+ if (!row2) return;
18386
+ const index = Math.min(row2.end, row2.start + Math.max(0, x - 5));
18387
+ if (!extend || this.selectionAnchor === null) this.selectionAnchor = index;
18388
+ this.selectionFocus = index;
18389
+ this.draw();
18390
+ }
18391
+ updateStatus(value) {
18392
+ if (this.status === value) return false;
18393
+ this.status = value;
18394
+ this.statusSince = Date.now();
18395
+ return true;
18396
+ }
18397
+ setStatus(value) {
18398
+ if (this.updateStatus(value)) this.draw();
18399
+ }
18400
+ add(event) {
18401
+ event = this.changes.observe(event);
18402
+ if (event.type === "session-start") {
18403
+ this.model = event.model;
18404
+ this.draw();
18405
+ return;
18406
+ }
18407
+ if (event.type === "assistant-delta") {
18408
+ this.draft += event.text;
18409
+ this.updateStatus("Writing\u2026");
18410
+ this.queueDraw();
18411
+ return;
18412
+ }
18413
+ if (event.type === "assistant-message") this.draft = "";
18414
+ if (event.type === "usage") {
18415
+ this.used = event.used;
18416
+ this.context = event.contextWindow;
18417
+ if (event.model) this.model = event.model;
18418
+ this.draw();
18419
+ return;
18420
+ }
18421
+ if (event.type === "account-usage") {
18422
+ this.weeklyRemaining = event.remainingPercent;
18423
+ this.weeklyResetsAt = event.resetsAt;
18424
+ this.draw();
18425
+ return;
18426
+ }
18427
+ if (event.type === "thinking") {
18428
+ if (this.updateStatus(event.text || "Thinking\u2026")) this.draw();
18429
+ return;
18430
+ }
18431
+ if (event.type === "tool-start") this.updateStatus("Working\u2026");
18432
+ if (event.type === "tool-end") this.updateStatus(event.blocked ? "Guard enforced" : "Working\u2026");
18433
+ if (event.type === "turn-end") this.updateStatus(event.ok ? "Ready" : "Turn ended");
18434
+ const rendered = renderEvent(event, this.width());
18435
+ if (this.scrollOffset > 0) this.scrollOffset += rendered.length;
18436
+ this.transcript.push(...rendered);
18437
+ this.draw();
18438
+ }
18439
+ width() {
18440
+ return Math.max(50, Number(process.stdout.columns || 100));
18441
+ }
18442
+ height() {
18443
+ return Math.max(16, Number(process.stdout.rows || 30));
18444
+ }
18445
+ inputRows(width) {
18446
+ const lineWidth = Math.max(1, Math.max(30, width - 4) - 4);
18447
+ if (!this.input) return [{ text: "", start: 0, end: 0 }];
18448
+ const rows = [];
18449
+ for (let start = 0; start < this.input.length; start += lineWidth) {
18450
+ const text = this.input.slice(start, start + lineWidth);
18451
+ rows.push({ text, start, end: start + text.length });
18452
+ }
18453
+ return rows.slice(-3);
18454
+ }
18455
+ selectedText(row2) {
18456
+ const range = this.selectionRange();
18457
+ if (!range || range[1] <= row2.start || range[0] >= row2.end) return S2.text + row2.text;
18458
+ const from = Math.max(range[0], row2.start) - row2.start;
18459
+ const to = Math.min(range[1], row2.end) - row2.start;
18460
+ return S2.text + row2.text.slice(0, from) + REVERSE + row2.text.slice(from, to) + S2.reset + S2.composer + S2.text + row2.text.slice(to);
18461
+ }
18462
+ frame(width = this.width(), height = this.height()) {
18463
+ const inner = Math.max(30, width - 4);
18464
+ const provider = this.harness === "codex" ? "Codex" : "Cursor";
18465
+ const draft = this.draft ? ["", ...wrap(this.draft, inner - 2).map((line, i) => S2.agent + " " + (i ? " " : "\u23FA ") + line + S2.reset)] : [];
18466
+ const status = this.status === "Ready" ? S2.secondary + " \xB7 Ready" + S2.reset : statusLine({ tick: this.tick, text: this.status, elapsedMs: Date.now() - this.statusSince, width });
18467
+ const rule = S2.border + " " + "\u2500".repeat(inner) + S2.reset;
18468
+ const inputRows = this.inputRows(width);
18469
+ const composer = [rule, ...inputRows.map((row2, i) => S2.composer + (i ? " " : S2.user + " \u276F ") + this.selectedText(row2) + (i === inputRows.length - 1 ? S2.user + " \u258C" : "") + S2.reset), rule];
18470
+ const usedPercent = this.context ? Math.min(100, Math.round(this.used / this.context * 100)) : null;
18471
+ const contextUsage = usedPercent === null ? "\u2014 context" : usedPercent + "% context";
18472
+ const resetDate = this.weeklyResetsAt == null ? "" : " \xB7 " + resetDateFormat.format(new Date(this.weeklyResetsAt * 1e3));
18473
+ const accountUsage = this.weeklyRemaining === null ? "Weekly \u2014" : "Weekly " + this.weeklyRemaining + "% left" + resetDate;
18474
+ const usage2 = contextUsage + " \xB7 " + accountUsage;
18475
+ const left = S2.secondary + " " + pathLabel(this.cwd) + S2.reset + S2.canvas;
18476
+ const badge = S2.panel + S2.secondary + " " + usage2 + " " + S2.reset + S2.canvas;
18477
+ const right = badge + S2.secondary + " \xB7 " + provider + (this.model ? " " + cleanOutput(this.model) : "") + " \xB7 \u29C9 powered by synkro " + S2.reset + S2.canvas;
18478
+ const footer = left + " ".repeat(Math.max(1, width - visible(left) - visible(right))) + right;
18479
+ const viewport = Math.max(3, height - composer.length - 2);
18480
+ const fullContent = [...this.transcript, ...draft, status];
18481
+ const maxScroll = Math.max(0, fullContent.length - viewport);
18482
+ const effectiveOffset = Math.min(this.scrollOffset, maxScroll);
18483
+ const end = fullContent.length - effectiveOffset;
18484
+ const content = fullContent.slice(Math.max(0, end - viewport), end);
18485
+ while (content.length < viewport) content.unshift("");
18486
+ const rows = [...content, ...composer, footer].slice(0, height);
18487
+ while (rows.length < height) rows.push("");
18488
+ return rows.map((line) => {
18489
+ const fill = line.startsWith(S2.panel) ? S2.panel : line.includes(S2.composer) ? S2.composer : S2.canvas;
18490
+ return S2.canvas + line + fill + " ".repeat(Math.max(0, width - visible(line))) + S2.reset;
18491
+ }).join("\n");
18492
+ }
18493
+ queueDraw() {
18494
+ if (this.drawTimer) return;
18495
+ const delay = Math.max(0, STREAM_DRAW_MS - (Date.now() - this.lastDrawAt));
18496
+ this.drawTimer = setTimeout(() => {
18497
+ this.drawTimer = null;
18498
+ this.draw();
18499
+ }, delay);
18500
+ this.drawTimer.unref?.();
18501
+ }
18502
+ draw() {
18503
+ if (this.drawTimer) {
18504
+ clearTimeout(this.drawTimer);
18505
+ this.drawTimer = null;
18506
+ }
18507
+ this.lastDrawAt = Date.now();
18508
+ this.write("\x1B[H" + this.frame());
18509
+ }
18510
+ };
18511
+ }
18512
+ });
18513
+
18514
+ // cli/harness/state.ts
18515
+ import { mkdirSync as mkdirSync24, readFileSync as readFileSync34, writeFileSync as writeFileSync28 } from "fs";
18516
+ import { homedir as homedir39 } from "os";
18517
+ import { dirname as dirname13, join as join38 } from "path";
18518
+ function load(file = STATE_FILE) {
18519
+ try {
18520
+ const value = JSON.parse(readFileSync34(file, "utf8"));
18521
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
18522
+ } catch {
18523
+ return {};
18524
+ }
18525
+ }
18526
+ function loadSessionId(harness, cwd, file = STATE_FILE) {
18527
+ return load(file)[key(harness, cwd)] || "";
18528
+ }
18529
+ function saveSessionId(harness, cwd, sessionId, file = STATE_FILE) {
18530
+ if (!sessionId) return;
18531
+ try {
18532
+ mkdirSync24(dirname13(file), { recursive: true });
18533
+ writeFileSync28(file, JSON.stringify({ ...load(file), [key(harness, cwd)]: sessionId }, null, 2));
18534
+ } catch {
18535
+ }
18536
+ }
18537
+ var STATE_FILE, key;
18538
+ var init_state = __esm({
18539
+ "cli/harness/state.ts"() {
18540
+ "use strict";
18541
+ STATE_FILE = join38(homedir39(), ".synkro", "terminal-sessions.json");
18542
+ key = (harness, cwd) => harness + ":" + cwd.replace(/\/+$/, "");
18543
+ }
18544
+ });
18545
+
18068
18546
  // cli/harness/fixPoll.ts
18069
18547
  function pollBaseUrl() {
18070
18548
  const port = String(process.env.SYNKRO_MCP_PORT || "18931");
@@ -18079,11 +18557,13 @@ async function loadFixPoll(itemId, fetchImpl = fetch) {
18079
18557
  const body = await response.json();
18080
18558
  const candidates = Array.isArray(body.candidates) ? body.candidates.filter((candidate) => typeof candidate === "string").slice(0, 8) : [];
18081
18559
  if (!candidates.length || body.status !== "pending") return null;
18560
+ const recommendedIdx = Number.isInteger(body.recommended_idx) && body.recommended_idx >= 0 && body.recommended_idx < candidates.length ? body.recommended_idx : -1;
18082
18561
  return {
18083
18562
  itemId,
18084
18563
  filePath: String(body.file_path || ""),
18085
18564
  ruleId: String(body.rule_id || ""),
18086
- candidates
18565
+ candidates,
18566
+ recommendedIdx
18087
18567
  };
18088
18568
  }
18089
18569
  async function recordFixPoll(itemId, chosenIdx, fetchImpl = fetch) {
@@ -18098,7 +18578,7 @@ function fixPollLines(poll, selected, width = 100) {
18098
18578
  const max = Math.max(32, width - 10);
18099
18579
  const choices = [...poll.candidates, "None of the above"];
18100
18580
  const scope = [poll.ruleId, poll.filePath].filter(Boolean).join(" \xB7 ");
18101
- const lines = [
18581
+ const lines2 = [
18102
18582
  "",
18103
18583
  S2.bold + S2.rule + " Synkro needs your decision" + S2.reset,
18104
18584
  ...scope ? [S2.dim + " " + neutralizeTerminalControls(scope) + S2.reset] : [],
@@ -18106,42 +18586,43 @@ function fixPollLines(poll, selected, width = 100) {
18106
18586
  ];
18107
18587
  choices.forEach((choice, index) => {
18108
18588
  const prefix = index === selected ? S2.user + " \u276F " : S2.dim + " ";
18109
- const text = wrap(index + 1 + ". " + neutralizeTerminalControls(choice), max);
18110
- lines.push(prefix + (text[0] || "") + S2.reset);
18111
- for (const continuation of text.slice(1)) lines.push(" " + continuation);
18589
+ const recommendation = index === poll.recommendedIdx ? "Recommended: " : "";
18590
+ const text = wrap(index + 1 + ". " + recommendation + neutralizeTerminalControls(choice), max);
18591
+ lines2.push(prefix + (text[0] || "") + S2.reset);
18592
+ for (const continuation of text.slice(1)) lines2.push(" " + continuation);
18112
18593
  });
18113
- lines.push("", S2.dim + " \u2191/\u2193 select \xB7 Enter confirm \xB7 1-" + choices.length + " choose" + S2.reset);
18114
- return lines;
18594
+ lines2.push("", S2.dim + " \u2191/\u2193 select \xB7 Enter confirm \xB7 1-" + choices.length + " choose" + S2.reset);
18595
+ return lines2;
18115
18596
  }
18116
18597
  async function pickFixPoll(poll, io = {}) {
18117
18598
  const input = io.input || process.stdin;
18118
18599
  const output = io.output || process.stdout;
18119
18600
  if (!input.isTTY || !output.isTTY) return -1;
18120
- let selected = 0;
18601
+ let selected = poll.recommendedIdx >= 0 && poll.recommendedIdx < poll.candidates.length ? poll.recommendedIdx : 0;
18121
18602
  let rendered = 0;
18122
18603
  const draw = () => {
18123
18604
  if (rendered) output.write("\x1B[" + rendered + "A\x1B[J");
18124
- const lines = fixPollLines(poll, selected, Number(output.columns || 100));
18125
- output.write(lines.join("\n") + "\n");
18126
- rendered = lines.length;
18605
+ const lines2 = fixPollLines(poll, selected, Number(output.columns || 100));
18606
+ output.write(lines2.join("\n") + "\n");
18607
+ rendered = lines2.length;
18127
18608
  };
18128
- const choice = await new Promise((resolve8) => {
18609
+ const choice = await new Promise((resolve9) => {
18129
18610
  const choices = poll.candidates.length + 1;
18130
18611
  const wasRaw = Boolean(input.isRaw);
18131
18612
  const done = (index) => {
18132
18613
  input.off("data", onData);
18133
18614
  if (input.setRawMode) input.setRawMode(wasRaw);
18134
- resolve8(index === poll.candidates.length ? -1 : index);
18615
+ resolve9(index === poll.candidates.length ? -1 : index);
18135
18616
  };
18136
18617
  const onData = (chunk) => {
18137
- const key = chunk.toString("utf8");
18138
- if (key === "") return done(poll.candidates.length);
18139
- if (key === "\x1B" || key.toLowerCase() === "n") return done(poll.candidates.length);
18140
- if (key === "\r" || key === "\n") return done(selected);
18141
- if (key === "\x1B[A" || key === "k") selected = (selected - 1 + choices) % choices;
18142
- else if (key === "\x1B[B" || key === "j") selected = (selected + 1) % choices;
18143
- else if (/^[1-9]$/.test(key)) {
18144
- const index = Number(key) - 1;
18618
+ const key2 = chunk.toString("utf8");
18619
+ if (key2 === "") return done(poll.candidates.length);
18620
+ if (key2 === "\x1B" || key2.toLowerCase() === "n") return done(poll.candidates.length);
18621
+ if (key2 === "\r" || key2 === "\n") return done(selected);
18622
+ if (key2 === "\x1B[A" || key2 === "k") selected = (selected - 1 + choices) % choices;
18623
+ else if (key2 === "\x1B[B" || key2 === "j") selected = (selected + 1) % choices;
18624
+ else if (/^[1-9]$/.test(key2)) {
18625
+ const index = Number(key2) - 1;
18145
18626
  if (index < choices) return done(index);
18146
18627
  } else return;
18147
18628
  draw();
@@ -18154,7 +18635,7 @@ async function pickFixPoll(poll, io = {}) {
18154
18635
  return choice;
18155
18636
  }
18156
18637
  async function resolveFixPolls(events, io = {}) {
18157
- const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").map((event) => event.pollId || "").filter(Boolean)));
18638
+ const ids = Array.from(new Set(events.filter((event) => event.type === "tool-end").flatMap((event) => event.pollIds?.length ? event.pollIds : [event.pollId || ""]).filter(Boolean)));
18158
18639
  const recorded = [];
18159
18640
  for (const itemId of ids) {
18160
18641
  const poll = await loadFixPoll(itemId, io.fetchImpl).catch(() => null);
@@ -18173,134 +18654,227 @@ var init_fixPoll = __esm({
18173
18654
  }
18174
18655
  });
18175
18656
 
18176
- // cli/harness/identity.ts
18177
- import { execFileSync as execFileSync6 } from "child_process";
18178
- import { homedir as homedir39 } from "os";
18179
- function shortenPath(path, home = homedir39()) {
18180
- const value = String(path || "");
18181
- if (home && value === home) return "~";
18182
- if (home && value.startsWith(home + "/")) return "~" + value.slice(home.length);
18183
- return value;
18184
- }
18185
- function parseCursorAccount(output) {
18186
- const match = String(output || "").match(/logged in as\s+(\S+)/i);
18187
- return match ? match[1].trim() : "";
18657
+ // cli/harness/session.ts
18658
+ function deleteSelectedText(value, range) {
18659
+ if (!range) return value.slice(0, -1);
18660
+ return value.slice(0, range[0]) + value.slice(range[1]);
18188
18661
  }
18189
- function cursorAccount() {
18190
- try {
18191
- const out = execFileSync6("cursor-agent", ["status"], {
18192
- encoding: "utf8",
18193
- timeout: 5e3,
18194
- stdio: ["ignore", "pipe", "pipe"]
18195
- });
18196
- return parseCursorAccount(out);
18197
- } catch {
18198
- return "";
18662
+ function handleScrollInput(screen, data) {
18663
+ let handled = false;
18664
+ const mousePattern = /\x1b\[<(\d+);\d+;\d+[Mm]/g;
18665
+ let mouse;
18666
+ while ((mouse = mousePattern.exec(data)) !== null) {
18667
+ const button = Number(mouse[1]);
18668
+ if (button === 64 || button === 65) {
18669
+ screen.scrollBy(button === 64 ? 3 : -3);
18670
+ handled = true;
18671
+ }
18199
18672
  }
18200
- }
18201
- function synkroAccount() {
18202
- let email = "";
18203
- try {
18204
- email = String(getUserInfo().email || "");
18205
- } catch {
18206
- return { email: "", needsLogin: false };
18673
+ if (data.includes("\x1B[5~")) {
18674
+ screen.scrollBy(10);
18675
+ handled = true;
18207
18676
  }
18208
- let needsLogin = false;
18209
- try {
18210
- needsLogin = isTokenExpired() && !loadCredentials()?.refresh_token;
18211
- } catch {
18677
+ if (data.includes("\x1B[6~")) {
18678
+ screen.scrollBy(-10);
18679
+ handled = true;
18212
18680
  }
18213
- return { email, needsLogin };
18214
- }
18215
- function readIdentity(harness) {
18216
- const synkro = synkroAccount();
18217
- return {
18218
- harness: harness === "cursor" ? cursorAccount() : "",
18219
- synkro: synkro.email,
18220
- needsLogin: synkro.needsLogin
18221
- };
18681
+ return handled;
18222
18682
  }
18223
- function identityHeader(opts) {
18224
- const lines = ["", S2.bold + " " + shortenPath(opts.cwd, opts.home) + S2.reset];
18225
- const label = (name, value, note = "") => S2.dim + " " + name.padEnd(7) + S2.reset + S2.think + value + S2.reset + note;
18226
- if (opts.identity.harness) lines.push(label(opts.harness, opts.identity.harness));
18227
- if (opts.identity.synkro) {
18228
- lines.push(label("synkro", opts.identity.synkro, opts.identity.needsLogin ? S2.blocked + " \xB7 session ended, run synkro login" + S2.reset : ""));
18229
- }
18230
- lines.push("");
18231
- return lines;
18683
+ function printEvent(event) {
18684
+ const lines2 = renderEvent(event, Number(process.stdout.columns || 100));
18685
+ if (lines2.length) process.stdout.write(lines2.join("\n") + "\n");
18232
18686
  }
18233
- var init_identity2 = __esm({
18234
- "cli/harness/identity.ts"() {
18235
- "use strict";
18236
- init_auth();
18237
- init_render2();
18238
- }
18239
- });
18240
-
18241
- // cli/harness/session.ts
18242
18687
  async function runOnce(harness, cwd, prompt, echoPrompt = true, showHeader = true, signal) {
18243
- if (harness !== "cursor" && harness !== "codex") {
18688
+ if (!supported(harness)) {
18244
18689
  process.stdout.write(S2.dim + " " + harness + " sessions are not embedded yet\n" + S2.reset);
18245
18690
  return 1;
18246
18691
  }
18247
- const result = harness === "codex" ? await runCodexTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal }) : await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal });
18248
- await resolveFixPolls(result.events);
18249
- if (result.blocked > 0) {
18250
- process.stdout.write(
18251
- S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
18252
- );
18692
+ const resumeId = loadSessionId(harness, cwd);
18693
+ if (harness === "cursor") {
18694
+ const result = await runCursorTurn({ prompt, cwd, showPrompt: echoPrompt, showHeader, signal, resumeId });
18695
+ saveSessionId(harness, cwd, result.sessionId || "");
18696
+ await resolveFixPolls(result.events);
18697
+ return result.exitCode;
18698
+ }
18699
+ const session = new CodexSession(cwd, resumeId);
18700
+ const events = [];
18701
+ try {
18702
+ await session.start();
18703
+ const result = await session.runTurn(prompt, (event) => {
18704
+ events.push(event);
18705
+ printEvent(event);
18706
+ }, signal);
18707
+ saveSessionId(harness, cwd, result.sessionId);
18708
+ await resolveFixPolls(events);
18709
+ return result.exitCode;
18710
+ } finally {
18711
+ session.close();
18253
18712
  }
18254
- return result.exitCode;
18255
18713
  }
18256
- async function runGovernedSession(harness, cwd, prompt) {
18257
- if (prompt) return runOnce(harness, cwd, prompt);
18258
- process.stdout.write(identityHeader({ cwd, harness, identity: readIdentity(harness) }).join("\n") + "\n");
18259
- const codex = harness === "codex" ? new CodexAppSession() : null;
18260
- let turn = null;
18261
- const interruptTurn = () => {
18262
- turn?.abort();
18714
+ function readPrompt(screen) {
18715
+ return new Promise((resolve9) => {
18716
+ let value = "";
18717
+ const finish = (answer) => {
18718
+ process.stdin.removeListener("data", onData);
18719
+ screen.setInput("");
18720
+ resolve9(answer);
18721
+ };
18722
+ const onData = (chunk) => {
18723
+ const data = chunk.toString("utf8");
18724
+ const scrolled = handleScrollInput(screen, data);
18725
+ const mousePattern = /\x1b\[<(\d+);(\d+);(\d+)([Mm])/g;
18726
+ let mouse;
18727
+ let sawMouse = false;
18728
+ while ((mouse = mousePattern.exec(data)) !== null) {
18729
+ sawMouse = true;
18730
+ const button = Number(mouse[1]);
18731
+ if (button === 0 || button === 32) {
18732
+ screen.selectInputAt(Number(mouse[2]), Number(mouse[3]), button === 32 || mouse[4] === "m");
18733
+ }
18734
+ }
18735
+ if (sawMouse || scrolled) return;
18736
+ const selection = screen.selectionRange();
18737
+ if (data === "\x1B[3~") {
18738
+ value = deleteSelectedText(value, selection);
18739
+ screen.setInput(value);
18740
+ return;
18741
+ }
18742
+ if (data === "\x1B") {
18743
+ if (selection) {
18744
+ value = deleteSelectedText(value, selection);
18745
+ screen.setInput(value);
18746
+ } else finish(null);
18747
+ return;
18748
+ }
18749
+ if (data.startsWith("\x1B")) return;
18750
+ for (const char of data) {
18751
+ if (char === "" || char === "") {
18752
+ finish(null);
18753
+ return;
18754
+ }
18755
+ if (char === "\r" || char === "\n") {
18756
+ finish(value);
18757
+ return;
18758
+ }
18759
+ if (char === "\x7F" || char === "\b") value = deleteSelectedText(value, screen.selectionRange());
18760
+ else if (char === "") value = "";
18761
+ else if (char === "") value = value.replace(/\s*\S+\s*$/, "");
18762
+ else if (char >= " ") {
18763
+ const range = screen.selectionRange();
18764
+ value = range ? value.slice(0, range[0]) + char + value.slice(range[1]) : value + char;
18765
+ }
18766
+ screen.setInput(value);
18767
+ }
18768
+ };
18769
+ process.stdin.on("data", onData);
18770
+ screen.setInput(value);
18771
+ });
18772
+ }
18773
+ async function cancellable(screen, run2) {
18774
+ const controller = new AbortController();
18775
+ const onData = (chunk) => {
18776
+ const text = chunk.toString("utf8");
18777
+ if (handleScrollInput(screen, text)) return;
18778
+ if (text.includes("") || text === "\x1B") {
18779
+ screen.setStatus("Interrupting\u2026");
18780
+ controller.abort();
18781
+ }
18263
18782
  };
18264
- process.on("SIGINT", interruptTurn);
18265
- let first = true;
18783
+ process.stdin.on("data", onData);
18784
+ try {
18785
+ return await run2(controller.signal);
18786
+ } finally {
18787
+ process.stdin.removeListener("data", onData);
18788
+ }
18789
+ }
18790
+ async function resolveScreenFixPolls(screen, events) {
18791
+ if (!events.some((event) => event.type === "tool-end" && Boolean(event.pollId))) return;
18792
+ screen.leave();
18793
+ try {
18794
+ await resolveFixPolls(events);
18795
+ } finally {
18796
+ screen.enter();
18797
+ }
18798
+ }
18799
+ async function runSynkroSession(harness, cwd, prompt) {
18800
+ if (prompt) return runOnce(harness, cwd, prompt);
18801
+ if (!supported(harness)) return runOnce(harness, cwd, "");
18802
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
18803
+ process.stderr.write("synkro terminal requires a TTY\n");
18804
+ return 1;
18805
+ }
18806
+ const screen = new SynkroScreen(harness, cwd);
18807
+ const resumeId = loadSessionId(harness, cwd);
18808
+ let codex = null;
18809
+ process.stdin.setRawMode(true);
18810
+ process.stdin.resume();
18811
+ screen.enter();
18266
18812
  try {
18813
+ if (harness === "codex") {
18814
+ screen.setStatus("Connecting to Codex\u2026");
18815
+ codex = new CodexSession(cwd, resumeId);
18816
+ await codex.start();
18817
+ saveSessionId(harness, cwd, codex.threadId);
18818
+ screen.add({ type: "session-start", sessionId: codex.threadId, model: codex.model || "Codex", cwd, authSource: "login" });
18819
+ screen.setStatus("Ready");
18820
+ }
18267
18821
  for (; ; ) {
18268
- const draft = await readPrompt(cwd);
18269
- if (draft === null) break;
18270
- const line = draft.text.trim();
18271
- if (!line && draft.images.length === 0) continue;
18272
- if (draft.images.length === 0 && (line === "exit" || line === "quit")) break;
18822
+ const answer = await readPrompt(screen);
18823
+ if (answer === null) break;
18824
+ const line = answer.trim();
18825
+ if (!line) continue;
18826
+ if (line === "/exit" || line === "/quit" || line === "exit" || line === "quit") break;
18827
+ screen.setStatus("Working\u2026");
18828
+ const events = [];
18273
18829
  try {
18274
- turn = new AbortController();
18275
- const cursorPrompt = draft.images.length ? [draft.text, "", "Attached image files:", ...draft.images].join("\n") : draft.text;
18276
- const result = codex ? await codex.turn({ prompt: draft.text, images: draft.images, cwd, showPrompt: false, showHeader: first, signal: turn.signal }) : await runCursorTurn({ prompt: cursorPrompt, cwd, showPrompt: false, showHeader: first, signal: turn.signal });
18277
- await resolveFixPolls(result.events);
18278
- if (result.blocked > 0) {
18279
- process.stdout.write(
18280
- S2.blocked + " " + result.blocked + " action" + (result.blocked === 1 ? "" : "s") + " blocked by policy" + S2.reset + "\n"
18281
- );
18830
+ if (harness === "cursor") {
18831
+ const result = await cancellable(screen, (signal) => runCursorTurn({
18832
+ prompt: line,
18833
+ cwd,
18834
+ signal,
18835
+ resumeId: loadSessionId(harness, cwd),
18836
+ write: () => {
18837
+ },
18838
+ onEvent: (event) => {
18839
+ events.push(event);
18840
+ screen.add(event);
18841
+ },
18842
+ showHeader: false
18843
+ }));
18844
+ saveSessionId(harness, cwd, result.sessionId || "");
18845
+ } else {
18846
+ const result = await cancellable(screen, (signal) => codex.runTurn(line, (event) => {
18847
+ events.push(event);
18848
+ screen.add(event);
18849
+ }, signal));
18850
+ saveSessionId(harness, cwd, result.sessionId);
18282
18851
  }
18283
- first = false;
18284
- } finally {
18285
- cleanupPromptDraft(draft);
18286
- turn = null;
18852
+ await resolveScreenFixPolls(screen, events);
18853
+ } catch (error) {
18854
+ screen.add({ type: "notice", text: error instanceof Error ? error.message : String(error) });
18855
+ screen.add({ type: "turn-end", ok: false, text: "" });
18287
18856
  }
18857
+ screen.setStatus("Ready");
18288
18858
  }
18859
+ return 0;
18289
18860
  } finally {
18290
- process.off("SIGINT", interruptTurn);
18291
18861
  codex?.close();
18862
+ screen.leave();
18863
+ process.stdin.setRawMode(false);
18864
+ process.stdin.pause();
18292
18865
  }
18293
- return 0;
18294
18866
  }
18867
+ var supported;
18295
18868
  var init_session = __esm({
18296
18869
  "cli/harness/session.ts"() {
18297
18870
  "use strict";
18298
- init_composer();
18299
- init_run();
18300
18871
  init_codex();
18301
- init_fixPoll();
18302
- init_identity2();
18872
+ init_run();
18303
18873
  init_render2();
18874
+ init_screen();
18875
+ init_state();
18876
+ init_fixPoll();
18877
+ supported = (harness) => harness === "cursor" || harness === "codex";
18304
18878
  }
18305
18879
  });
18306
18880
 
@@ -18341,17 +18915,19 @@ async function takeover(kind, cwd) {
18341
18915
  const runner = backend === "container" ? info.runner : HOST3;
18342
18916
  return runInherit(["env", "TMUX=", ...runnerInteractiveArgs(runner, ["tmux", "attach-session", "-t", spawned.session])]);
18343
18917
  }
18344
- async function restoreSession(session) {
18345
- const record = loadRecords().find((row2) => row2.session === session);
18346
- if (!record) return;
18918
+ async function restoreSession(session, bootPath) {
18919
+ const record2 = loadRecords().find((row2) => row2.session === session);
18920
+ if (!record2) return;
18347
18921
  const info = await detectContainerBackend();
18348
18922
  await spawnAgent(info, {
18349
- name: record.name,
18350
- harness: record.harness,
18351
- spaceName: record.spaceName,
18352
- cwd: record.space,
18353
- backend: record.harness === "codex" ? "host" : record.backend,
18354
- resume: true
18923
+ name: record2.name,
18924
+ harness: record2.harness,
18925
+ spaceName: record2.spaceName,
18926
+ cwd: record2.space,
18927
+ backend: record2.mode === "embedded" ? "host" : record2.backend,
18928
+ resume: record2.mode !== "embedded",
18929
+ mode: record2.mode,
18930
+ command: record2.mode === "embedded" ? embeddedSessionCommand(bootPath, record2.harness, record2.space) : void 0
18355
18931
  });
18356
18932
  }
18357
18933
  async function printStatus() {
@@ -18404,7 +18980,7 @@ async function uiCommand(args2) {
18404
18980
  const harness = args2[runAt + 1] || "cursor";
18405
18981
  const cwd = args2[runAt + 2] || repoRoot();
18406
18982
  const prompt = args2.slice(runAt + 3).join(" ").trim();
18407
- process.exitCode = await runGovernedSession(harness, cwd, prompt);
18983
+ process.exitCode = await runSynkroSession(harness, cwd, prompt);
18408
18984
  return;
18409
18985
  }
18410
18986
  const takeoverAt = args2.indexOf("--takeover");
@@ -18414,7 +18990,7 @@ async function uiCommand(args2) {
18414
18990
  }
18415
18991
  const restoreAt = args2.indexOf("--restore");
18416
18992
  if (restoreAt !== -1) {
18417
- await restoreSession(args2[restoreAt + 1] || "");
18993
+ await restoreSession(args2[restoreAt + 1] || "", bootPath);
18418
18994
  return;
18419
18995
  }
18420
18996
  if (args2.includes("--status")) {
@@ -18492,12 +19068,12 @@ __export(linear_exports, {
18492
19068
  formatLinks: () => formatLinks,
18493
19069
  linearCommand: () => linearCommand
18494
19070
  });
18495
- import { readFileSync as readFileSync33 } from "fs";
19071
+ import { readFileSync as readFileSync35 } from "fs";
18496
19072
  import { homedir as homedir40 } from "os";
18497
- import { join as join38 } from "path";
19073
+ import { join as join39 } from "path";
18498
19074
  function mcpJwt() {
18499
19075
  try {
18500
- return readFileSync33(join38(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
19076
+ return readFileSync35(join39(SYNKRO_DIR14, ".mcp-jwt"), "utf-8").trim();
18501
19077
  } catch {
18502
19078
  return "";
18503
19079
  }
@@ -18536,7 +19112,7 @@ var SYNKRO_DIR14, PORT2, BASE;
18536
19112
  var init_linear = __esm({
18537
19113
  "cli/commands/linear.ts"() {
18538
19114
  "use strict";
18539
- SYNKRO_DIR14 = join38(homedir40(), ".synkro");
19115
+ SYNKRO_DIR14 = join39(homedir40(), ".synkro");
18540
19116
  PORT2 = process.env.SYNKRO_MCP_PORT || "18931";
18541
19117
  BASE = `http://127.0.0.1:${PORT2}`;
18542
19118
  }
@@ -18544,13 +19120,13 @@ var init_linear = __esm({
18544
19120
 
18545
19121
  // cli/scanning/cveReachability.ts
18546
19122
  import { parse } from "@babel/parser";
18547
- import { readFileSync as readFileSync34 } from "fs";
19123
+ import { readFileSync as readFileSync36 } from "fs";
18548
19124
  function walk(node, visit) {
18549
19125
  if (!node || typeof node.type !== "string") return;
18550
19126
  visit(node);
18551
- for (const key of Object.keys(node)) {
18552
- if (key === "loc" || key === "start" || key === "end" || key === "range" || key === "leadingComments" || key === "trailingComments") continue;
18553
- const child = node[key];
19127
+ for (const key2 of Object.keys(node)) {
19128
+ if (key2 === "loc" || key2 === "start" || key2 === "end" || key2 === "range" || key2 === "leadingComments" || key2 === "trailingComments") continue;
19129
+ const child = node[key2];
18554
19130
  if (Array.isArray(child)) {
18555
19131
  for (const c of child) if (c && typeof c.type === "string") walk(c, visit);
18556
19132
  } else if (child && typeof child.type === "string") walk(child, visit);
@@ -18685,9 +19261,9 @@ var init_cveReachability = __esm({
18685
19261
  });
18686
19262
 
18687
19263
  // cli/reachability/reachabilityScan.ts
18688
- import { spawnSync as spawnSync13, execFileSync as execFileSync7 } from "child_process";
18689
- import { readFileSync as readFileSync35, writeFileSync as writeFileSync28, existsSync as existsSync39, readdirSync as readdirSync10 } from "fs";
18690
- import { join as join39 } from "path";
19264
+ import { spawnSync as spawnSync13, execFileSync as execFileSync6 } from "child_process";
19265
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync29, existsSync as existsSync39, readdirSync as readdirSync10 } from "fs";
19266
+ import { join as join40 } from "path";
18691
19267
  import { homedir as homedir41 } from "os";
18692
19268
  import { createRequire } from "module";
18693
19269
  function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
@@ -18705,7 +19281,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
18705
19281
  }
18706
19282
  for (const e of ents) {
18707
19283
  if (files.length >= maxFiles) break;
18708
- const full = join39(dir, e.name);
19284
+ const full = join40(dir, e.name);
18709
19285
  if (e.isDirectory()) {
18710
19286
  if (!SKIP2.has(e.name) && !e.name.startsWith(".")) stack.push(full);
18711
19287
  continue;
@@ -18713,7 +19289,7 @@ function walkSourceFiles(repoRoot3, maxFiles = 4e3, maxBytes = 5e5) {
18713
19289
  if (!EXT.test(e.name) || e.name.endsWith(".d.ts")) continue;
18714
19290
  const rel = full.startsWith(repoRoot3 + "/") ? full.slice(repoRoot3.length + 1) : full;
18715
19291
  try {
18716
- const content = readFileSync35(full, "utf8");
19292
+ const content = readFileSync37(full, "utf8");
18717
19293
  if (content.length <= maxBytes) files.push({ path: rel, content });
18718
19294
  } catch {
18719
19295
  }
@@ -18732,12 +19308,12 @@ function cleanVersion(spec) {
18732
19308
  function gatherManifestVersions(repoRoot3) {
18733
19309
  const out = {};
18734
19310
  const dirs = [repoRoot3];
18735
- const pkgsDir = join39(repoRoot3, "packages");
19311
+ const pkgsDir = join40(repoRoot3, "packages");
18736
19312
  if (existsSync39(pkgsDir)) {
18737
19313
  try {
18738
19314
  for (const d of readdirSync10(pkgsDir)) {
18739
- const pd = join39(pkgsDir, d);
18740
- if (existsSync39(join39(pd, "package.json"))) dirs.push(pd);
19315
+ const pd = join40(pkgsDir, d);
19316
+ if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
18741
19317
  }
18742
19318
  } catch {
18743
19319
  }
@@ -18746,7 +19322,7 @@ function gatherManifestVersions(repoRoot3) {
18746
19322
  for (const dir of dirs) {
18747
19323
  let pkg;
18748
19324
  try {
18749
- pkg = JSON.parse(readFileSync35(join39(dir, "package.json"), "utf8"));
19325
+ pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
18750
19326
  } catch {
18751
19327
  continue;
18752
19328
  }
@@ -18766,28 +19342,28 @@ function findJelly(repoRoot3) {
18766
19342
  try {
18767
19343
  const pkgJson = require2.resolve("@cs-au-dk/jelly/package.json");
18768
19344
  const dir = pkgJson.slice(0, pkgJson.length - "package.json".length);
18769
- const pkg = JSON.parse(readFileSync35(pkgJson, "utf8"));
19345
+ const pkg = JSON.parse(readFileSync37(pkgJson, "utf8"));
18770
19346
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && (pkg.bin.jelly || pkg.bin[Object.keys(pkg.bin)[0]]);
18771
19347
  if (bin) {
18772
- const p = join39(dir, bin);
19348
+ const p = join40(dir, bin);
18773
19349
  if (existsSync39(p)) return p;
18774
19350
  }
18775
19351
  } catch {
18776
19352
  }
18777
19353
  for (const base of [repoRoot3, process.cwd()]) {
18778
- const b = join39(base, "node_modules", ".bin", "jelly");
19354
+ const b = join40(base, "node_modules", ".bin", "jelly");
18779
19355
  if (existsSync39(b)) return b;
18780
19356
  }
18781
19357
  return null;
18782
19358
  }
18783
19359
  function findEntries(repoRoot3) {
18784
19360
  const dirs = [repoRoot3];
18785
- const pkgsDir = join39(repoRoot3, "packages");
19361
+ const pkgsDir = join40(repoRoot3, "packages");
18786
19362
  if (existsSync39(pkgsDir)) {
18787
19363
  try {
18788
19364
  for (const d of readdirSync10(pkgsDir)) {
18789
- const pd = join39(pkgsDir, d);
18790
- if (existsSync39(join39(pd, "package.json"))) dirs.push(pd);
19365
+ const pd = join40(pkgsDir, d);
19366
+ if (existsSync39(join40(pd, "package.json"))) dirs.push(pd);
18791
19367
  }
18792
19368
  } catch {
18793
19369
  }
@@ -18795,11 +19371,11 @@ function findEntries(repoRoot3) {
18795
19371
  const entries = [];
18796
19372
  for (const dir of dirs) {
18797
19373
  try {
18798
- const pkg = JSON.parse(readFileSync35(join39(dir, "package.json"), "utf8"));
19374
+ const pkg = JSON.parse(readFileSync37(join40(dir, "package.json"), "utf8"));
18799
19375
  const cands = [pkg.source, pkg.module, pkg.main, "src/index.ts", "src/index.js", "src/main.ts", "src/server.ts", "index.ts", "index.js"];
18800
19376
  for (const c of cands) {
18801
19377
  if (typeof c !== "string") continue;
18802
- const f = join39(dir, c);
19378
+ const f = join40(dir, c);
18803
19379
  if (existsSync39(f)) {
18804
19380
  entries.push(f);
18805
19381
  break;
@@ -18812,7 +19388,7 @@ function findEntries(repoRoot3) {
18812
19388
  }
18813
19389
  function currentCommit(repoRoot3) {
18814
19390
  try {
18815
- return execFileSync7("git", ["rev-parse", "HEAD"], { cwd: repoRoot3, encoding: "utf8" }).trim();
19391
+ return execFileSync6("git", ["rev-parse", "HEAD"], { cwd: repoRoot3, encoding: "utf8" }).trim();
18816
19392
  } catch {
18817
19393
  return "";
18818
19394
  }
@@ -18835,7 +19411,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
18835
19411
  const commit = currentCommit(repoRoot3);
18836
19412
  if (!opts.force && commit && existsSync39(REACHABILITY_PATH)) {
18837
19413
  try {
18838
- const prev = JSON.parse(readFileSync35(REACHABILITY_PATH, "utf8"));
19414
+ const prev = JSON.parse(readFileSync37(REACHABILITY_PATH, "utf8"));
18839
19415
  if (prev.commit === commit) return { ok: true, cached: true, packages: Object.keys(prev.packages || {}).length };
18840
19416
  } catch {
18841
19417
  }
@@ -18924,7 +19500,7 @@ function runReachabilityScan(repoRoot3, opts = {}) {
18924
19500
  if (Object.keys(packages).length === 0) return { ok: false, reason: "no package usage found (no jelly output, no AST imports)" };
18925
19501
  const file = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), commit, tool, packages, versions: gatherManifestVersions(repoRoot3) };
18926
19502
  try {
18927
- writeFileSync28(REACHABILITY_PATH, JSON.stringify(file, null, 2));
19503
+ writeFileSync29(REACHABILITY_PATH, JSON.stringify(file, null, 2));
18928
19504
  } catch (e) {
18929
19505
  return { ok: false, reason: "write failed: " + String(e.message || e) };
18930
19506
  }
@@ -18936,7 +19512,7 @@ var init_reachabilityScan = __esm({
18936
19512
  "use strict";
18937
19513
  init_cveReachability();
18938
19514
  require2 = createRequire(import.meta.url);
18939
- REACHABILITY_PATH = join39(homedir41(), ".synkro", "reachability.json");
19515
+ REACHABILITY_PATH = join40(homedir41(), ".synkro", "reachability.json");
18940
19516
  }
18941
19517
  });
18942
19518
 
@@ -18945,15 +19521,15 @@ var reachabilityScan_exports = {};
18945
19521
  __export(reachabilityScan_exports, {
18946
19522
  reachabilityScanCommand: () => reachabilityScanCommand
18947
19523
  });
18948
- import { readFileSync as readFileSync36, existsSync as existsSync40 } from "fs";
18949
- import { join as join40 } from "path";
19524
+ import { readFileSync as readFileSync38, existsSync as existsSync40 } from "fs";
19525
+ import { join as join41 } from "path";
18950
19526
  import { homedir as homedir42 } from "os";
18951
- import { execFileSync as execFileSync8 } from "child_process";
19527
+ import { execFileSync as execFileSync7 } from "child_process";
18952
19528
  function readConfigEnv4() {
18953
- const p = join40(SYNKRO_DIR15, "config.env");
19529
+ const p = join41(SYNKRO_DIR15, "config.env");
18954
19530
  if (!existsSync40(p)) return {};
18955
19531
  const out = {};
18956
- for (const line of readFileSync36(p, "utf-8").split("\n")) {
19532
+ for (const line of readFileSync38(p, "utf-8").split("\n")) {
18957
19533
  const t = line.trim();
18958
19534
  if (!t || t.startsWith("#")) continue;
18959
19535
  const eq = t.indexOf("=");
@@ -18963,7 +19539,7 @@ function readConfigEnv4() {
18963
19539
  }
18964
19540
  function repoRoot2() {
18965
19541
  try {
18966
- return execFileSync8("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
19542
+ return execFileSync7("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8" }).trim();
18967
19543
  } catch {
18968
19544
  return process.cwd();
18969
19545
  }
@@ -18971,7 +19547,7 @@ function repoRoot2() {
18971
19547
  function repoSlug(root) {
18972
19548
  const run2 = (a) => {
18973
19549
  try {
18974
- return execFileSync8("git", a, { encoding: "utf-8" }).trim();
19550
+ return execFileSync7("git", a, { encoding: "utf-8" }).trim();
18975
19551
  } catch {
18976
19552
  return "";
18977
19553
  }
@@ -18985,11 +19561,11 @@ async function pushToCloud(cfg, repo) {
18985
19561
  while (gwBase.endsWith("/")) gwBase = gwBase.slice(0, -1);
18986
19562
  let jwt2 = "";
18987
19563
  try {
18988
- jwt2 = readFileSync36(join40(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
19564
+ jwt2 = readFileSync38(join41(SYNKRO_DIR15, ".mcp-jwt"), "utf-8").trim();
18989
19565
  } catch {
18990
19566
  }
18991
19567
  if (!jwt2 || !existsSync40(REACHABILITY_PATH)) return;
18992
- const body = readFileSync36(REACHABILITY_PATH, "utf-8");
19568
+ const body = readFileSync38(REACHABILITY_PATH, "utf-8");
18993
19569
  try {
18994
19570
  const resp = await fetch(gwBase + "/api/v1/reachability?repo=" + encodeURIComponent(repo), {
18995
19571
  method: "POST",
@@ -19021,7 +19597,7 @@ var init_reachabilityScan2 = __esm({
19021
19597
  "cli/commands/reachabilityScan.ts"() {
19022
19598
  "use strict";
19023
19599
  init_reachabilityScan();
19024
- SYNKRO_DIR15 = join40(homedir42(), ".synkro");
19600
+ SYNKRO_DIR15 = join41(homedir42(), ".synkro");
19025
19601
  }
19026
19602
  });
19027
19603
 
@@ -19151,13 +19727,13 @@ var config_exports = {};
19151
19727
  __export(config_exports, {
19152
19728
  configCommand: () => configCommand
19153
19729
  });
19154
- import { readFileSync as readFileSync37, writeFileSync as writeFileSync29, existsSync as existsSync41 } from "fs";
19155
- import { join as join41 } from "path";
19730
+ import { readFileSync as readFileSync39, writeFileSync as writeFileSync30, existsSync as existsSync41 } from "fs";
19731
+ import { join as join42 } from "path";
19156
19732
  import { homedir as homedir43 } from "os";
19157
19733
  function readConfigEnv5() {
19158
19734
  if (!existsSync41(CONFIG_PATH9)) return {};
19159
19735
  const out = {};
19160
- for (const line of readFileSync37(CONFIG_PATH9, "utf-8").split("\n")) {
19736
+ for (const line of readFileSync39(CONFIG_PATH9, "utf-8").split("\n")) {
19161
19737
  const t = line.trim();
19162
19738
  if (!t || t.startsWith("#")) continue;
19163
19739
  const eq = t.indexOf("=");
@@ -19165,23 +19741,23 @@ function readConfigEnv5() {
19165
19741
  }
19166
19742
  return out;
19167
19743
  }
19168
- function updateConfigValue(key, value) {
19744
+ function updateConfigValue(key2, value) {
19169
19745
  if (!existsSync41(CONFIG_PATH9)) {
19170
19746
  console.error("No config found. Run `synkro install` first.");
19171
19747
  process.exit(1);
19172
19748
  }
19173
- const lines = readFileSync37(CONFIG_PATH9, "utf-8").split("\n");
19174
- const pattern = new RegExp(`^${key}=`);
19749
+ const lines2 = readFileSync39(CONFIG_PATH9, "utf-8").split("\n");
19750
+ const pattern = new RegExp(`^${key2}=`);
19175
19751
  let found = false;
19176
- const updated = lines.map((line) => {
19752
+ const updated = lines2.map((line) => {
19177
19753
  if (pattern.test(line.trim())) {
19178
19754
  found = true;
19179
- return `${key}='${value}'`;
19755
+ return `${key2}='${value}'`;
19180
19756
  }
19181
19757
  return line;
19182
19758
  });
19183
- if (!found) updated.splice(updated.length - 1, 0, `${key}='${value}'`);
19184
- writeFileSync29(CONFIG_PATH9, updated.join("\n"), "utf-8");
19759
+ if (!found) updated.splice(updated.length - 1, 0, `${key2}='${value}'`);
19760
+ writeFileSync30(CONFIG_PATH9, updated.join("\n"), "utf-8");
19185
19761
  }
19186
19762
  function resolveInferenceMode(cfg) {
19187
19763
  if ((cfg.SYNKRO_GRADING_MODE || "local") === "byok") return "byok";
@@ -19339,8 +19915,8 @@ var init_config = __esm({
19339
19915
  "use strict";
19340
19916
  init_stub();
19341
19917
  init_optout();
19342
- SYNKRO_DIR16 = join41(homedir43(), ".synkro");
19343
- CONFIG_PATH9 = join41(SYNKRO_DIR16, "config.env");
19918
+ SYNKRO_DIR16 = join42(homedir43(), ".synkro");
19919
+ CONFIG_PATH9 = join42(SYNKRO_DIR16, "config.env");
19344
19920
  }
19345
19921
  });
19346
19922
 
@@ -19349,7 +19925,7 @@ var telemetry_exports2 = {};
19349
19925
  __export(telemetry_exports2, {
19350
19926
  telemetryCommand: () => telemetryCommand
19351
19927
  });
19352
- import { createInterface as createInterface6 } from "readline";
19928
+ import { createInterface as createInterface7 } from "readline";
19353
19929
  function parseFlag(args2, name) {
19354
19930
  const prefix = `--${name}=`;
19355
19931
  for (const a of args2) if (a.startsWith(prefix)) return a.slice(prefix.length);
@@ -19430,12 +20006,12 @@ async function runExport(args2) {
19430
20006
  }
19431
20007
  function confirmYesNo(question) {
19432
20008
  if (!process.stdin.isTTY) return Promise.resolve(false);
19433
- return new Promise((resolve8) => {
19434
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
20009
+ return new Promise((resolve9) => {
20010
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
19435
20011
  rl.question(`${question} (y/N): `, (answer) => {
19436
20012
  rl.close();
19437
20013
  const t = answer.trim().toLowerCase();
19438
- resolve8(t === "y" || t === "yes");
20014
+ resolve9(t === "y" || t === "yes");
19439
20015
  });
19440
20016
  });
19441
20017
  }
@@ -19530,11 +20106,11 @@ Usage:
19530
20106
 
19531
20107
  // cli/inventory/identity.ts
19532
20108
  import { randomUUID as randomUUID5 } from "crypto";
19533
- import { existsSync as existsSync42, mkdirSync as mkdirSync24, readFileSync as readFileSync38, renameSync as renameSync9, writeFileSync as writeFileSync30 } from "fs";
20109
+ import { existsSync as existsSync42, mkdirSync as mkdirSync25, readFileSync as readFileSync40, renameSync as renameSync9, writeFileSync as writeFileSync31 } from "fs";
19534
20110
  import { homedir as homedir44 } from "os";
19535
- import { dirname as dirname13, join as join42 } from "path";
20111
+ import { dirname as dirname14, join as join43 } from "path";
19536
20112
  function operationalIdentityPath() {
19537
- return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join42(homedir44(), ".synkro", "installation.json");
20113
+ return process.env.SYNKRO_OPERATIONAL_IDENTITY_PATH || join43(homedir44(), ".synkro", "installation.json");
19538
20114
  }
19539
20115
  function validIdentity(value) {
19540
20116
  if (!value || typeof value !== "object") return false;
@@ -19542,9 +20118,9 @@ function validIdentity(value) {
19542
20118
  return typeof row2.installation_id === "string" && UUID_RE.test(row2.installation_id) && typeof row2.created_at === "string" && Number.isFinite(Date.parse(row2.created_at));
19543
20119
  }
19544
20120
  function writeIdentity(path, identity) {
19545
- mkdirSync24(dirname13(path), { recursive: true, mode: 448 });
20121
+ mkdirSync25(dirname14(path), { recursive: true, mode: 448 });
19546
20122
  const temp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
19547
- writeFileSync30(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
20123
+ writeFileSync31(temp, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf8", mode: 384 });
19548
20124
  renameSync9(temp, path);
19549
20125
  }
19550
20126
  function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
@@ -19552,7 +20128,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
19552
20128
  if (prior) return prior;
19553
20129
  if (existsSync42(path)) {
19554
20130
  try {
19555
- const parsed = JSON.parse(readFileSync38(path, "utf8"));
20131
+ const parsed = JSON.parse(readFileSync40(path, "utf8"));
19556
20132
  if (validIdentity(parsed)) {
19557
20133
  cached4.set(path, parsed);
19558
20134
  return parsed;
@@ -19566,7 +20142,7 @@ function getOperationalInstallationIdentity(path = operationalIdentityPath()) {
19566
20142
  return identity;
19567
20143
  }
19568
20144
  var UUID_RE, cached4;
19569
- var init_identity3 = __esm({
20145
+ var init_identity2 = __esm({
19570
20146
  "cli/inventory/identity.ts"() {
19571
20147
  "use strict";
19572
20148
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -19578,12 +20154,12 @@ var init_identity3 = __esm({
19578
20154
  import { createHash as createHash5 } from "crypto";
19579
20155
  import {
19580
20156
  existsSync as existsSync43,
19581
- readFileSync as readFileSync39,
20157
+ readFileSync as readFileSync41,
19582
20158
  readdirSync as readdirSync11,
19583
- statSync as statSync6
20159
+ statSync as statSync7
19584
20160
  } from "fs";
19585
20161
  import { arch, homedir as homedir45, hostname as hostname2, platform as platform6, release as release2 } from "os";
19586
- import { basename as basename4, join as join43, relative, resolve as resolve6 } from "path";
20162
+ import { basename as basename4, join as join44, relative as relative2, resolve as resolve7 } from "path";
19587
20163
  import { fileURLToPath } from "url";
19588
20164
  function sha256(value) {
19589
20165
  return createHash5("sha256").update(value).digest("hex");
@@ -19593,7 +20169,7 @@ function pseudonymousHostnameHash(installationId, host) {
19593
20169
  }
19594
20170
  function cliVersion() {
19595
20171
  try {
19596
- return "1.10.8";
20172
+ return "1.10.10";
19597
20173
  } catch {
19598
20174
  return "0.0.0";
19599
20175
  }
@@ -19601,7 +20177,7 @@ function cliVersion() {
19601
20177
  function readJson(path) {
19602
20178
  try {
19603
20179
  if (!existsSync43(path)) return null;
19604
- const parsed = JSON.parse(readFileSync39(path, "utf8"));
20180
+ const parsed = JSON.parse(readFileSync41(path, "utf8"));
19605
20181
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
19606
20182
  } catch {
19607
20183
  return null;
@@ -19610,7 +20186,7 @@ function readJson(path) {
19610
20186
  function readText(path) {
19611
20187
  try {
19612
20188
  if (!existsSync43(path)) return "";
19613
- return readFileSync39(path, "utf8");
20189
+ return readFileSync41(path, "utf8");
19614
20190
  } catch {
19615
20191
  return "";
19616
20192
  }
@@ -19696,16 +20272,16 @@ function mcpArtifactsFromJson(harness, config, configScope = "user") {
19696
20272
  }
19697
20273
  function claudeDesktopConfigCandidates(home, targetPlatform) {
19698
20274
  if (targetPlatform === "darwin") {
19699
- return [join43(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
20275
+ return [join44(home, "Library", "Application Support", "Claude", "claude_desktop_config.json")];
19700
20276
  }
19701
20277
  if (targetPlatform === "linux") {
19702
20278
  return [
19703
- join43(home, ".config", "Claude", "claude_desktop_config.json"),
19704
- join43(home, ".config", "claude", "claude_desktop_config.json")
20279
+ join44(home, ".config", "Claude", "claude_desktop_config.json"),
20280
+ join44(home, ".config", "claude", "claude_desktop_config.json")
19705
20281
  ];
19706
20282
  }
19707
20283
  if (targetPlatform === "win32" && process.env.APPDATA) {
19708
- return [join43(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
20284
+ return [join44(process.env.APPDATA, "Claude", "claude_desktop_config.json")];
19709
20285
  }
19710
20286
  return [];
19711
20287
  }
@@ -19713,7 +20289,7 @@ function claudeManagedMcpConfigCandidates(targetPlatform) {
19713
20289
  if (targetPlatform === "darwin") return ["/Library/Application Support/ClaudeCode/managed-mcp.json"];
19714
20290
  if (targetPlatform === "linux") return ["/etc/claude-code/managed-mcp.json"];
19715
20291
  if (targetPlatform === "win32" && process.env.ProgramFiles) {
19716
- return [join43(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
20292
+ return [join44(process.env.ProgramFiles, "ClaudeCode", "managed-mcp.json")];
19717
20293
  }
19718
20294
  return [];
19719
20295
  }
@@ -19721,7 +20297,7 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
19721
20297
  const roots = /* @__PURE__ */ new Set();
19722
20298
  const add = (value) => {
19723
20299
  if (typeof value !== "string" || !value.trim()) return;
19724
- const path = resolve6(value);
20300
+ const path = resolve7(value);
19725
20301
  if (existsSync43(path)) roots.add(path);
19726
20302
  };
19727
20303
  add(currentDirectory);
@@ -19733,10 +20309,10 @@ function discoveredProjectRoots(claudeState, currentDirectory, explicit = [], cu
19733
20309
  return [...roots];
19734
20310
  }
19735
20311
  function cursorWorkspaceStorageCandidates(home, targetPlatform) {
19736
- if (targetPlatform === "darwin") return [join43(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
19737
- if (targetPlatform === "linux") return [join43(home, ".config", "Cursor", "User", "workspaceStorage")];
20312
+ if (targetPlatform === "darwin") return [join44(home, "Library", "Application Support", "Cursor", "User", "workspaceStorage")];
20313
+ if (targetPlatform === "linux") return [join44(home, ".config", "Cursor", "User", "workspaceStorage")];
19738
20314
  if (targetPlatform === "win32" && process.env.APPDATA) {
19739
- return [join43(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
20315
+ return [join44(process.env.APPDATA, "Cursor", "User", "workspaceStorage")];
19740
20316
  }
19741
20317
  return [];
19742
20318
  }
@@ -19752,12 +20328,12 @@ function cursorWorkspaceRoots(home, targetPlatform) {
19752
20328
  }
19753
20329
  for (const entry of entries) {
19754
20330
  if (!entry.isDirectory() || entry.isSymbolicLink?.()) continue;
19755
- const state = readJson(join43(storage, entry.name, "workspace.json"));
20331
+ const state = readJson(join44(storage, entry.name, "workspace.json"));
19756
20332
  const raw = state?.folder;
19757
20333
  if (typeof raw !== "string" || !raw.trim()) continue;
19758
20334
  try {
19759
20335
  const path = raw.startsWith("file:") ? fileURLToPath(raw) : raw;
19760
- if (existsSync43(path)) roots.add(resolve6(path));
20336
+ if (existsSync43(path)) roots.add(resolve7(path));
19761
20337
  } catch {
19762
20338
  }
19763
20339
  }
@@ -19769,8 +20345,8 @@ function codexMcpArtifacts(content) {
19769
20345
  const sections = [...content.matchAll(/^\s*\[\s*([^\]]+)\s*\]\s*$/gm)];
19770
20346
  for (let index = 0; index < sections.length && artifacts.length < 1e3; index++) {
19771
20347
  const section = sections[index];
19772
- const key = section[1].trim();
19773
- const root = key.match(/^mcp_servers\s*\.\s*(?:"((?:[^"\\]|\\.)+)"|'([^']+)'|([A-Za-z0-9_-]+))$/);
20348
+ const key2 = section[1].trim();
20349
+ const root = key2.match(/^mcp_servers\s*\.\s*(?:"((?:[^"\\]|\\.)+)"|'([^']+)'|([A-Za-z0-9_-]+))$/);
19774
20350
  if (!root) continue;
19775
20351
  let name = root[1] || root[2] || root[3];
19776
20352
  if (root[1]) {
@@ -19783,8 +20359,8 @@ function codexMcpArtifacts(content) {
19783
20359
  const start = (section.index ?? 0) + section[0].length;
19784
20360
  const end = sections[index + 1]?.index ?? content.length;
19785
20361
  const block = content.slice(start, end);
19786
- const stringValue = (key2) => {
19787
- const found = block.match(new RegExp(`^\\s*${key2}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, "m"));
20362
+ const stringValue = (key3) => {
20363
+ const found = block.match(new RegExp(`^\\s*${key3}\\s*=\\s*("(?:[^"\\\\]|\\\\.)*")`, "m"));
19788
20364
  if (!found) return void 0;
19789
20365
  try {
19790
20366
  return JSON.parse(found[1]);
@@ -19816,9 +20392,9 @@ function flattenHookEntries(value) {
19816
20392
  const out = [];
19817
20393
  for (const entry of value) {
19818
20394
  if (!entry || typeof entry !== "object") continue;
19819
- const record = entry;
19820
- if (typeof record.command === "string") out.push(record);
19821
- if (Array.isArray(record.hooks)) out.push(...flattenHookEntries(record.hooks));
20395
+ const record2 = entry;
20396
+ if (typeof record2.command === "string") out.push(record2);
20397
+ if (Array.isArray(record2.hooks)) out.push(...flattenHookEntries(record2.hooks));
19822
20398
  }
19823
20399
  return out;
19824
20400
  }
@@ -19847,7 +20423,7 @@ function hookArtifacts(harness, config) {
19847
20423
  function parseFrontmatter(content) {
19848
20424
  const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
19849
20425
  if (!match) return {};
19850
- const value = (key) => match[1].match(new RegExp(`^${key}:\\s*["']?([^"'\\n]+)`, "m"))?.[1]?.trim();
20426
+ const value = (key2) => match[1].match(new RegExp(`^${key2}:\\s*["']?([^"'\\n]+)`, "m"))?.[1]?.trim();
19851
20427
  return { name: value("name"), version: value("version") };
19852
20428
  }
19853
20429
  function skillArtifacts(harness, root) {
@@ -19862,7 +20438,7 @@ function skillArtifacts(harness, root) {
19862
20438
  }
19863
20439
  for (const entry of entries) {
19864
20440
  if (entry.isSymbolicLink?.()) continue;
19865
- const path = join43(dir, entry.name);
20441
+ const path = join44(dir, entry.name);
19866
20442
  if (entry.isFile() && entry.name === "SKILL.md") manifests.push(path);
19867
20443
  else if (entry.isDirectory()) visit(path);
19868
20444
  }
@@ -19871,8 +20447,8 @@ function skillArtifacts(harness, root) {
19871
20447
  return manifests.map((path) => {
19872
20448
  const content = readText(path);
19873
20449
  const frontmatter = parseFrontmatter(content);
19874
- const rel = relative(root, path).replaceAll("\\", "/");
19875
- const name = frontmatter.name || basename4(join43(path, "..")) || "skill";
20450
+ const rel = relative2(root, path).replaceAll("\\", "/");
20451
+ const name = frontmatter.name || basename4(join44(path, "..")) || "skill";
19876
20452
  return {
19877
20453
  harness,
19878
20454
  type: "skill",
@@ -19896,7 +20472,7 @@ function cursorExtensionArtifacts(root) {
19896
20472
  }
19897
20473
  const artifacts = [];
19898
20474
  for (const dir of dirs) {
19899
- const pkg = readJson(join43(root, dir.name, "package.json"));
20475
+ const pkg = readJson(join44(root, dir.name, "package.json"));
19900
20476
  if (!pkg) continue;
19901
20477
  const publisher = typeof pkg.publisher === "string" ? pkg.publisher : void 0;
19902
20478
  const name = typeof pkg.name === "string" ? pkg.name : dir.name;
@@ -19916,21 +20492,21 @@ function cursorExtensionArtifacts(root) {
19916
20492
  return artifacts;
19917
20493
  }
19918
20494
  function deploymentMode2(home) {
19919
- const raw = readText(join43(home, ".synkro", "config.env"));
19920
- const value = (key) => raw.match(new RegExp(`^${key}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
20495
+ const raw = readText(join44(home, ".synkro", "config.env"));
20496
+ const value = (key2) => raw.match(new RegExp(`^${key2}=['"]?([^'"\\n]*)`, "m"))?.[1]?.toLowerCase();
19921
20497
  if (value("SYNKRO_GRADING_MODE") === "byok") return "byok";
19922
20498
  if (value("SYNKRO_STORAGE_MODE") === "cloud") return "cloud";
19923
20499
  return "local";
19924
20500
  }
19925
20501
  function telemetryHealth(home) {
19926
- const meta = readJson(join43(home, ".synkro", "telemetry-meta.json"));
20502
+ const meta = readJson(join44(home, ".synkro", "telemetry-meta.json"));
19927
20503
  const health = {};
19928
20504
  if (meta?.last_flush_ok_at && Number.isFinite(Date.parse(meta.last_flush_ok_at))) health.telemetry_last_flush_at = meta.last_flush_ok_at;
19929
20505
  if (meta?.last_flush_error) health.telemetry_last_error = "flush_failed";
19930
- const queue = join43(home, ".synkro", "telemetry-pending.jsonl");
20506
+ const queue = join44(home, ".synkro", "telemetry-pending.jsonl");
19931
20507
  try {
19932
- const size = statSync6(queue).size;
19933
- health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync39(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
20508
+ const size = statSync7(queue).size;
20509
+ health.telemetry_backlog = size <= 5 * 1024 * 1024 ? readFileSync41(queue, "utf8").split("\n").filter(Boolean).length : Math.ceil(size / 1024);
19934
20510
  } catch {
19935
20511
  }
19936
20512
  return health;
@@ -19971,7 +20547,7 @@ function harnessSnapshot(agent) {
19971
20547
  }
19972
20548
  const config = readJson(agent.settingsPath);
19973
20549
  const coverage = inspectCodexHooks(agent.settingsPath);
19974
- const toml = readText(join43(agent.configDir, "config.toml"));
20550
+ const toml = readText(join44(agent.configDir, "config.toml"));
19975
20551
  const permission = toml.match(/^\s*approval_policy\s*=\s*["']([^"']+)/m)?.[1];
19976
20552
  return {
19977
20553
  row: {
@@ -19992,7 +20568,7 @@ function collectOperationalInventory(options = {}) {
19992
20568
  const detected = options.detectedAgents ?? detectAgents();
19993
20569
  const identity = getOperationalInstallationIdentity(options.identityPath);
19994
20570
  const targetPlatform = options.platformName ?? platform6();
19995
- const codexHome = options.homeDir ? join43(home, ".codex") : process.env.CODEX_HOME || join43(home, ".codex");
20571
+ const codexHome = options.homeDir ? join44(home, ".codex") : process.env.CODEX_HOME || join44(home, ".codex");
19996
20572
  const harnesses = [];
19997
20573
  const artifacts = [];
19998
20574
  for (const agent of detected) {
@@ -20000,7 +20576,7 @@ function collectOperationalInventory(options = {}) {
20000
20576
  harnesses.push(row2);
20001
20577
  artifacts.push(...hookArtifacts(row2.harness, config));
20002
20578
  }
20003
- const claudeJson = readJson(join43(home, ".claude.json"));
20579
+ const claudeJson = readJson(join44(home, ".claude.json"));
20004
20580
  artifacts.push(...mcpArtifactsFromJson("claude_code", claudeJson));
20005
20581
  if (claudeJson?.projects && typeof claudeJson.projects === "object") {
20006
20582
  for (const [projectPath, project] of Object.entries(claudeJson.projects)) {
@@ -20008,8 +20584,8 @@ function collectOperationalInventory(options = {}) {
20008
20584
  artifacts.push(...mcpArtifactsFromJson("claude_code", project, `local:${sha256(projectPath).slice(0, 16)}`));
20009
20585
  }
20010
20586
  }
20011
- artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join43(home, ".cursor", "mcp.json"))));
20012
- artifacts.push(...codexMcpArtifacts(readText(join43(codexHome, "config.toml"))));
20587
+ artifacts.push(...mcpArtifactsFromJson("cursor", readJson(join44(home, ".cursor", "mcp.json"))));
20588
+ artifacts.push(...codexMcpArtifacts(readText(join44(codexHome, "config.toml"))));
20013
20589
  const projectRoots = discoveredProjectRoots(
20014
20590
  claudeJson,
20015
20591
  options.currentDirectory ?? process.cwd(),
@@ -20020,11 +20596,11 @@ function collectOperationalInventory(options = {}) {
20020
20596
  const scopeHash = sha256(projectRoot).slice(0, 16);
20021
20597
  artifacts.push(...mcpArtifactsFromJson(
20022
20598
  "claude_code",
20023
- readJson(join43(projectRoot, ".mcp.json")),
20599
+ readJson(join44(projectRoot, ".mcp.json")),
20024
20600
  `project:${scopeHash}`
20025
20601
  ));
20026
- const cursorProjectConfig = join43(projectRoot, ".cursor", "mcp.json");
20027
- if (resolve6(cursorProjectConfig) !== resolve6(join43(home, ".cursor", "mcp.json"))) {
20602
+ const cursorProjectConfig = join44(projectRoot, ".cursor", "mcp.json");
20603
+ if (resolve7(cursorProjectConfig) !== resolve7(join44(home, ".cursor", "mcp.json"))) {
20028
20604
  artifacts.push(...mcpArtifactsFromJson(
20029
20605
  "cursor",
20030
20606
  readJson(cursorProjectConfig),
@@ -20046,7 +20622,7 @@ function collectOperationalInventory(options = {}) {
20046
20622
  });
20047
20623
  artifacts.push(...mcpArtifactsFromJson("claude_desktop", desktopConfig));
20048
20624
  }
20049
- const claudeSettings = readJson(join43(home, ".claude", "settings.json"));
20625
+ const claudeSettings = readJson(join44(home, ".claude", "settings.json"));
20050
20626
  if (claudeSettings?.enabledPlugins && typeof claudeSettings.enabledPlugins === "object") {
20051
20627
  for (const [name, enabled] of Object.entries(claudeSettings.enabledPlugins)) {
20052
20628
  artifacts.push({
@@ -20060,14 +20636,14 @@ function collectOperationalInventory(options = {}) {
20060
20636
  });
20061
20637
  }
20062
20638
  }
20063
- artifacts.push(...skillArtifacts("claude_code", join43(home, ".claude", "skills")));
20064
- artifacts.push(...skillArtifacts("cursor", join43(home, ".cursor", "skills")));
20065
- artifacts.push(...skillArtifacts("codex", join43(codexHome, "skills")));
20066
- artifacts.push(...cursorExtensionArtifacts(join43(home, ".cursor", "extensions")));
20639
+ artifacts.push(...skillArtifacts("claude_code", join44(home, ".claude", "skills")));
20640
+ artifacts.push(...skillArtifacts("cursor", join44(home, ".cursor", "skills")));
20641
+ artifacts.push(...skillArtifacts("codex", join44(codexHome, "skills")));
20642
+ artifacts.push(...cursorExtensionArtifacts(join44(home, ".cursor", "extensions")));
20067
20643
  const uniqueArtifacts = /* @__PURE__ */ new Map();
20068
20644
  for (const artifact of artifacts) {
20069
- const key = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
20070
- uniqueArtifacts.set(key, artifact);
20645
+ const key2 = `${artifact.harness || "global"}:${artifact.type}:${artifact.canonical_id}`;
20646
+ uniqueArtifacts.set(key2, artifact);
20071
20647
  }
20072
20648
  const codingHarnesses = harnesses.filter((row2) => row2.harness === "claude_code" || row2.harness === "cursor" || row2.harness === "codex");
20073
20649
  const health = telemetryHealth(home) ?? {};
@@ -20100,7 +20676,7 @@ var init_collector = __esm({
20100
20676
  init_ccHookConfig();
20101
20677
  init_cursorHookConfig();
20102
20678
  init_codexHookConfig();
20103
- init_identity3();
20679
+ init_identity2();
20104
20680
  }
20105
20681
  });
20106
20682
 
@@ -20119,19 +20695,19 @@ import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
20119
20695
  import { spawn as spawn11 } from "child_process";
20120
20696
  import {
20121
20697
  existsSync as existsSync44,
20122
- mkdirSync as mkdirSync25,
20123
- readFileSync as readFileSync40,
20698
+ mkdirSync as mkdirSync26,
20699
+ readFileSync as readFileSync42,
20124
20700
  renameSync as renameSync10,
20125
- writeFileSync as writeFileSync31
20701
+ writeFileSync as writeFileSync32
20126
20702
  } from "fs";
20127
20703
  import { homedir as homedir46 } from "os";
20128
- import { dirname as dirname14, join as join44 } from "path";
20704
+ import { dirname as dirname15, join as join45 } from "path";
20129
20705
  function syncStatePath() {
20130
- return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join44(homedir46(), ".synkro", "inventory-sync.json");
20706
+ return process.env.SYNKRO_INVENTORY_SYNC_STATE_PATH || join45(homedir46(), ".synkro", "inventory-sync.json");
20131
20707
  }
20132
20708
  function readState(path = syncStatePath()) {
20133
20709
  try {
20134
- const parsed = JSON.parse(readFileSync40(path, "utf8"));
20710
+ const parsed = JSON.parse(readFileSync42(path, "utf8"));
20135
20711
  return parsed && typeof parsed === "object" ? parsed : {};
20136
20712
  } catch {
20137
20713
  return {};
@@ -20139,9 +20715,9 @@ function readState(path = syncStatePath()) {
20139
20715
  }
20140
20716
  function writeState(state, path = syncStatePath()) {
20141
20717
  try {
20142
- mkdirSync25(dirname14(path), { recursive: true, mode: 448 });
20718
+ mkdirSync26(dirname15(path), { recursive: true, mode: 448 });
20143
20719
  const temp = `${path}.${process.pid}.tmp`;
20144
- writeFileSync31(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
20720
+ writeFileSync32(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf8", mode: 384 });
20145
20721
  renameSync10(temp, path);
20146
20722
  } catch {
20147
20723
  }
@@ -20154,18 +20730,18 @@ function shouldSyncInventory(state, now = Date.now(), target) {
20154
20730
  return !Number.isFinite(lastAttempt) || lastAttempt <= 0 || now - lastAttempt >= FAILURE_RETRY_MS;
20155
20731
  }
20156
20732
  function readConfig() {
20157
- const path = join44(homedir46(), ".synkro", "config.env");
20733
+ const path = join45(homedir46(), ".synkro", "config.env");
20158
20734
  const out = {};
20159
20735
  try {
20160
- for (const rawLine of readFileSync40(path, "utf8").split("\n")) {
20736
+ for (const rawLine of readFileSync42(path, "utf8").split("\n")) {
20161
20737
  const line = rawLine.trim();
20162
20738
  if (!line || line.startsWith("#")) continue;
20163
20739
  const index = line.indexOf("=");
20164
20740
  if (index <= 0) continue;
20165
- const key = line.slice(0, index).trim();
20741
+ const key2 = line.slice(0, index).trim();
20166
20742
  let value = line.slice(index + 1).trim();
20167
20743
  if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
20168
- out[key] = value;
20744
+ out[key2] = value;
20169
20745
  }
20170
20746
  } catch {
20171
20747
  }
@@ -20196,7 +20772,7 @@ function resolveInventoryGateway(raw) {
20196
20772
  }
20197
20773
  async function loadToken() {
20198
20774
  try {
20199
- const durable = readFileSync40(join44(homedir46(), ".synkro", ".mcp-jwt"), "utf8").trim();
20775
+ const durable = readFileSync42(join45(homedir46(), ".synkro", ".mcp-jwt"), "utf8").trim();
20200
20776
  if (durable) return durable;
20201
20777
  } catch {
20202
20778
  }
@@ -20212,7 +20788,7 @@ async function loadToken() {
20212
20788
  function stable(value) {
20213
20789
  if (Array.isArray(value)) return value.map(stable);
20214
20790
  if (!value || typeof value !== "object") return value;
20215
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => [key, stable(child)]));
20791
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key2, child]) => [key2, stable(child)]));
20216
20792
  }
20217
20793
  function inventorySnapshotChunks(snapshot, maxBytes = INVENTORY_CHUNK_BYTES) {
20218
20794
  const { collected_at: _heartbeat, ...material } = snapshot;
@@ -20338,23 +20914,23 @@ var init_sync2 = __esm({
20338
20914
  });
20339
20915
 
20340
20916
  // cli/bootstrap.js
20341
- import { readFileSync as readFileSync41, existsSync as existsSync45 } from "fs";
20342
- import { resolve as resolve7 } from "path";
20917
+ import { readFileSync as readFileSync43, existsSync as existsSync45 } from "fs";
20918
+ import { resolve as resolve8 } from "path";
20343
20919
  process.title = "synkro";
20344
20920
  var envCandidates = [
20345
- resolve7(process.env.HOME ?? "", ".synkro", "config.env")
20921
+ resolve8(process.env.HOME ?? "", ".synkro", "config.env")
20346
20922
  ];
20347
20923
  for (const envPath of envCandidates) {
20348
20924
  if (!existsSync45(envPath)) continue;
20349
- const envContent = readFileSync41(envPath, "utf-8");
20925
+ const envContent = readFileSync43(envPath, "utf-8");
20350
20926
  for (const line of envContent.split("\n")) {
20351
20927
  const trimmed = line.trim();
20352
20928
  if (!trimmed || trimmed.startsWith("#")) continue;
20353
20929
  const eqIndex = trimmed.indexOf("=");
20354
20930
  if (eqIndex <= 0) continue;
20355
- const key = trimmed.slice(0, eqIndex).trim();
20931
+ const key2 = trimmed.slice(0, eqIndex).trim();
20356
20932
  const value = trimmed.slice(eqIndex + 1).trim().replace(/^['"]|['"]$/g, "");
20357
- if (!process.env[key] && !value.startsWith("op://")) process.env[key] = value;
20933
+ if (!process.env[key2] && !value.startsWith("op://")) process.env[key2] = value;
20358
20934
  }
20359
20935
  }
20360
20936
  var args = process.argv.slice(2);
@@ -20363,7 +20939,7 @@ var subArgs = args.slice(1);
20363
20939
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
20364
20940
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
20365
20941
  function printVersion() {
20366
- console.log("1.10.8");
20942
+ console.log("1.10.10");
20367
20943
  }
20368
20944
  function printHelp2() {
20369
20945
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents