@synkro-sh/cli 1.10.9 → 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
@@ -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.9";
150
+ cliVersion2 = "1.10.10";
151
151
  } catch {
152
152
  }
153
153
  const creds = loadCredentialsIdentity();
@@ -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 ? [{
@@ -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, {
@@ -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", [
@@ -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.9";
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;
@@ -8994,7 +8993,7 @@ 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.9")}`
8996
+ `SYNKRO_VERSION=${shellQuoteSingle2("1.10.10")}`
8998
8997
  ];
8999
8998
  if (safeSynkroBin) lines2.push(`SYNKRO_CLI_BIN=${shellQuoteSingle2(safeSynkroBin)}`);
9000
8999
  if (safeUserId) lines2.push(`SYNKRO_USER_ID=${shellQuoteSingle2(safeUserId)}`);
@@ -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.9",
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,
@@ -15166,7 +15165,6 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
15166
15165
  const tabPopup = "display-popup -E -w 60 -h 18 -S fg=colour111 " + shellQuote3(tabLauncher);
15167
15166
  const dispatch = 'if-shell -F "#{==:#{mouse_status_range},tabmenu}" "' + tabPopup + '" "select-window -t="';
15168
15167
  const style = [
15169
- ["set-option", "-t", UI_SESSION, "-q", "@synkro_build", buildStamp(bootPath)],
15170
15168
  // Mouse: drag the pane border to resize the sidebar, click a pane to
15171
15169
  // focus it, click rows/chips. Without this the fixed split reads as
15172
15170
  // "blocked in".
@@ -15197,21 +15195,86 @@ async function styleOuterSession(bootPath, repoCwd, sidebarWidth) {
15197
15195
  // Alt+s always returns focus to the sidebar (the leftmost pane).
15198
15196
  ["bind-key", "-n", "M-s", "select-pane", "-L"]
15199
15197
  ];
15200
- for (const argv of style) await run(HOST, ["tmux", ...argv]);
15201
- 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;
15202
15205
  }
15203
15206
  function parseUiPanes(output) {
15204
- return String(output || "").split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 4).map(([window, pane, width, ...command]) => ({
15207
+ return String(output || "").split("\n").map((line) => line.split("|")).filter((cols) => cols.length >= 7).map(([window, pane, width, dead, remain, cwd, ...command]) => ({
15205
15208
  window,
15206
15209
  pane,
15207
15210
  width: Number(width) || 0,
15211
+ dead: dead === "1",
15212
+ remain: remain === "on",
15213
+ cwd,
15208
15214
  command: command.join("|")
15209
15215
  })).filter((row2) => Boolean(row2.window && row2.pane));
15210
15216
  }
15211
- async function refreshUiShell(bootPath, repoCwd) {
15212
- const expected = buildStamp(bootPath);
15213
- const current = await run(HOST, ["tmux", "show-options", "-t", UI_SESSION, "-v", "@synkro_build"]);
15214
- if (current.ok && current.stdout.trim() === expected) return;
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 = {}) {
15215
15278
  const listed = await run(HOST, [
15216
15279
  "tmux",
15217
15280
  "list-panes",
@@ -15219,18 +15282,64 @@ async function refreshUiShell(bootPath, repoCwd) {
15219
15282
  "-t",
15220
15283
  UI_SESSION,
15221
15284
  "-F",
15222
- "#{window_id}|#{pane_id}|#{pane_width}|#{pane_start_command}"
15285
+ "#{window_id}|#{pane_id}|#{pane_width}|#{pane_dead}|#{remain-on-exit}|#{@synkro_cwd}|#{pane_start_command}"
15223
15286
  ]);
15224
- if (!listed.ok) return;
15287
+ if (!listed.ok) return { ok: false, changed: false };
15225
15288
  const panes = parseUiPanes(listed.stdout);
15226
15289
  const sidebars = panes.filter((row2) => row2.command.includes("--sidebar"));
15227
- if (sidebars.length === 0) return;
15228
- await styleOuterSession(bootPath, repoCwd, sidebars[0].width || 30);
15290
+ if (sidebars.length === 0) return { ok: false, changed: false };
15291
+ let changed = false;
15229
15292
  for (const sidebar of sidebars) {
15230
- const center = panes.find((row2) => row2.window === sidebar.window && row2.pane !== sidebar.pane);
15231
- if (!center) continue;
15232
- await run(HOST, ["tmux", "respawn-pane", "-k", "-t", sidebar.pane, sidebarCommand(bootPath, center.pane, repoCwd)]);
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;
15233
15341
  }
15342
+ return true;
15234
15343
  }
15235
15344
  async function buildTab(bootPath, repoCwd, spec) {
15236
15345
  await run(HOST, ["tmux", "set-option", "-g", "history-limit", "50000"]);
@@ -15238,8 +15347,20 @@ async function buildTab(bootPath, repoCwd, spec) {
15238
15347
  if (!await uiSessionExists()) {
15239
15348
  const cols = String(Number(process.stdout.columns || 0) || 220);
15240
15349
  const rows = String(Number(process.stdout.rows || 0) || 55);
15241
- const create = ["tmux", "new-session", "-d", "-s", UI_SESSION, "-x", cols, "-y", rows, "-c", spec.cwd];
15242
- 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
+ ];
15243
15364
  const made = await run(HOST, create);
15244
15365
  if (!made.ok) {
15245
15366
  throw new Error("tmux new-session failed: " + made.stderr.trim());
@@ -15249,14 +15370,26 @@ async function buildTab(bootPath, repoCwd, spec) {
15249
15370
  const first = await run(HOST, ["tmux", "list-windows", "-t", UI_SESSION, "-F", "#{window_id}"]);
15250
15371
  windowTarget = first.stdout.split("\n")[0]?.trim() || UI_SESSION + ":1";
15251
15372
  } else {
15252
- const create = ["tmux", "new-window", "-t", UI_SESSION, "-P", "-F", "#{window_id}", "-c", spec.cwd];
15253
- 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
+ ];
15254
15385
  const created = await run(HOST, create);
15255
15386
  windowTarget = created.stdout.trim();
15256
15387
  if (!created.ok || !windowTarget) return;
15257
15388
  }
15258
15389
  const sidebarCols = sidebarColumns(await currentWindowColumns(windowTarget));
15259
- await styleOuterSession(bootPath, repoCwd, Number(sidebarCols));
15390
+ if (!await styleOuterSession(bootPath, repoCwd, Number(sidebarCols))) {
15391
+ throw new Error("tmux outer session styling failed");
15392
+ }
15260
15393
  const split = await run(HOST, [
15261
15394
  "tmux",
15262
15395
  "split-window",
@@ -15271,18 +15404,35 @@ async function buildTab(bootPath, repoCwd, spec) {
15271
15404
  "tail -f /dev/null"
15272
15405
  ]);
15273
15406
  const sidebarPane = split.stdout.trim();
15407
+ if (!split.ok || !sidebarPane) throw new Error("tmux sidebar split failed: " + split.stderr.trim());
15274
15408
  const panes = await run(HOST, ["tmux", "list-panes", "-t", windowTarget, "-F", "#{pane_id}"]);
15275
15409
  const centerPane = panes.stdout.split("\n").map((line) => line.trim()).filter(Boolean).find((id) => id !== sidebarPane) || "";
15276
- 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());
15277
15413
  if (spec.agentSession) {
15278
15414
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_agent", spec.agentSession]);
15279
15415
  }
15280
15416
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_cwd", spec.cwd]);
15281
15417
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "-q", "@synkro_kind", spec.kind || "terminal"]);
15282
- 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());
15283
15431
  const title = spec.title || tabTitle("terminal", spec.cwd.split("/").filter(Boolean).pop() || "space");
15284
15432
  await run(HOST, ["tmux", "set-option", "-t", windowTarget, "-w", "automatic-rename", "off"]);
15285
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());
15286
15436
  await snapshotTabs();
15287
15437
  await run(HOST, ["tmux", "select-pane", "-t", spec.focus === "sidebar" ? sidebarPane : centerPane]);
15288
15438
  }
@@ -15325,7 +15475,9 @@ async function launchUi(bootPath, repoCwd) {
15325
15475
  if (!await uiSessionExists()) {
15326
15476
  await buildTab(bootPath, repoCwd, { cwd: repoCwd, center: makeTerminalCommand(bootPath), focus: "sidebar" });
15327
15477
  } else {
15328
- await refreshUiShell(bootPath, repoCwd);
15478
+ if (!await refreshUiShell(bootPath, repoCwd)) {
15479
+ throw new Error("Synkro UI could not repair its tmux layout");
15480
+ }
15329
15481
  }
15330
15482
  await pruneCollapsedClients();
15331
15483
  return runInherit(process.env.TMUX ? ["tmux", "switch-client", "-t", UI_SESSION] : ["tmux", "attach-session", "-t", UI_SESSION]);
@@ -15798,8 +15950,11 @@ var init_awake = __esm({
15798
15950
  function runnerFor(backend) {
15799
15951
  return backend === "container" ? { kind: "container", container: CONTAINER_NAME2 } : { kind: "host" };
15800
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
+ }
15801
15956
  async function runSidebar() {
15802
- const centerPane = process.env.SYNKRO_UI_CENTER || "";
15957
+ let centerPane = process.env.SYNKRO_UI_CENTER || "";
15803
15958
  const outerSession = process.env.SYNKRO_UI_OUTER || "synkro-ui";
15804
15959
  const bootPath = process.env.SYNKRO_UI_BOOT || process.argv[1];
15805
15960
  const repoCwd = process.env.SYNKRO_UI_REPO || process.cwd();
@@ -15967,53 +16122,114 @@ async function runSidebar() {
15967
16122
  const agent = state.agents[state.agentIndex];
15968
16123
  if (agent) await showAgent(agent);
15969
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
+ }
15970
16141
  async function showWorkspaceTerminal(cwd) {
15971
- if (!centerPane || !cwd) return;
16142
+ const pane = await resolveCenterPane(cwd);
16143
+ if (!pane) return false;
15972
16144
  const command = makeTerminalCommand(bootPath);
15973
- const argv = ["tmux", "respawn-pane", "-k", "-t", centerPane, "-c", cwd];
16145
+ const argv = ["tmux", "respawn-pane", "-k", "-t", pane, "-c", cwd];
15974
16146
  if (command) argv.push(command);
15975
16147
  const respawned = await run(host, argv);
15976
16148
  if (!respawned.ok) {
15977
16149
  state.message = "could not restore workspace terminal";
15978
- return;
16150
+ return false;
15979
16151
  }
15980
16152
  const spaceName = cwd.split("/").filter(Boolean).pop() || "space";
15981
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-u", "@synkro_agent"]);
15982
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_cwd", cwd]);
15983
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_kind", "terminal"]);
15984
- 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
+ }
15985
16165
  state.viewing = "";
15986
16166
  await snapshotTabs();
16167
+ return true;
15987
16168
  }
15988
- async function showAgent(agent) {
15989
- if (!centerPane) return;
15990
- if (agent.status === "offline") {
15991
- await restoreSelected();
15992
- return;
15993
- }
16169
+ async function markAgentViewed(agent) {
15994
16170
  const current = memory.get(agent.session);
15995
16171
  memory.set(agent.session, { hash: current?.hash || "", wasWorking: false, seen: true });
15996
16172
  if (agent.space) rememberLastAgent(await repoOf(agent.space), agent.session);
15997
16173
  state.viewing = agent.session;
16174
+ }
16175
+ async function showAgent(agent) {
16176
+ if (agent.status === "offline") {
16177
+ await restoreSelected();
16178
+ return false;
16179
+ }
15998
16180
  const windows = await run(host, ["tmux", "list-windows", "-t", outerSession, "-F", "#{window_id}|#{@synkro_agent}"]);
15999
16181
  const holder = windows.stdout.split("\n").map((line) => line.split("|")).find((cols) => cols[1] === agent.session);
16000
16182
  if (holder) {
16001
- await run(host, ["tmux", "select-window", "-t", holder[0]]);
16002
- state.message = agent.name;
16003
- 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", ""]);
16004
16201
  }
16202
+ const pane = await resolveCenterPane(agent.space || selectedSpace()?.path || repoCwd);
16203
+ if (!pane) return false;
16005
16204
  await run(runnerFor(agent.backend), buildEnableMouse(agent.session));
16006
16205
  for (const argv of buildClipboardBindings(agent.session)) {
16007
16206
  await run(runnerFor(agent.backend), argv);
16008
16207
  }
16009
16208
  const command = buildCenterAttachCommand(runnerFor(agent.backend), agent.session);
16010
- 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
+ }
16011
16215
  const where = agent.space ? agent.space.split("/").filter(Boolean).pop() || "" : "";
16012
- await run(host, ["tmux", "rename-window", "-t", centerPane, tabTitle(agent.harness, where)]);
16013
- await run(host, ["tmux", "set-option", "-t", centerPane, "-w", "-q", "@synkro_agent", agent.session]);
16014
- 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);
16015
16230
  await snapshotTabs();
16016
16231
  state.message = "attached " + agent.name;
16232
+ return true;
16017
16233
  }
16018
16234
  async function confirmKillAgent() {
16019
16235
  const agent = state.agents[state.agentIndex];
@@ -16024,7 +16240,8 @@ async function runSidebar() {
16024
16240
  if (allAgents.some((candidate) => candidate.session === agent.session && candidate.status !== "offline")) return;
16025
16241
  state.message = "closed " + agent.name;
16026
16242
  if (!wasViewing) return;
16027
- await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16243
+ const restored = await showWorkspaceTerminal(agent.space || selectedSpace()?.path || repoCwd);
16244
+ if (!restored) return;
16028
16245
  const successor = nextLiveAgent(allAgents, agent.repo || "");
16029
16246
  if (successor) await showAgent(successor);
16030
16247
  }
@@ -16940,6 +17157,42 @@ var init_dialog = __esm({
16940
17157
  }
16941
17158
  });
16942
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
+
16943
17196
  // cli/harness/render.ts
16944
17197
  function spinnerFrame(tick) {
16945
17198
  return SPINNER[Math.abs(tick) % SPINNER.length];
@@ -17001,10 +17254,11 @@ function renderEvent(event, width = 100) {
17001
17254
  }
17002
17255
  case "tool-end": {
17003
17256
  if (event.blocked) {
17257
+ const reason = cleanGuardText(event.reason);
17004
17258
  return [
17005
17259
  S2.panel + S2.blocked + " \u29C9 Blocked" + S2.reset + S2.panel + S2.secondary + " " + clip3(terminalText(event.target), body - 21) + S2.reset,
17006
- ...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),
17007
- ...wrap(blockNextStep(event.reason), body - 6).map((line) => S2.secondary + " " + line + 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)
17008
17262
  ];
17009
17263
  }
17010
17264
  const timing = event.durationMs == null ? "" : " \xB7 " + seconds(event.durationMs);
@@ -17053,6 +17307,7 @@ var ESC2, color, S2, CLEAR_LINE, BULLET, ELBOW, SPINNER, TOOL_LABEL;
17053
17307
  var init_render2 = __esm({
17054
17308
  "cli/harness/render.ts"() {
17055
17309
  "use strict";
17310
+ init_events();
17056
17311
  ESC2 = "\x1B[";
17057
17312
  color = (hex, plane = 38) => {
17058
17313
  const [r, g, b] = hex.match(/../g).map((part) => Number.parseInt(part, 16));
@@ -17107,35 +17362,6 @@ var init_composer = __esm({
17107
17362
  }
17108
17363
  });
17109
17364
 
17110
- // cli/harness/events.ts
17111
- function fixPollId(raw) {
17112
- const text = String(raw || "");
17113
- return FIX_POLL_MARKER.exec(text)?.[1] || LEGACY_FIX_POLL_MARKER.exec(text)?.[1] || "";
17114
- }
17115
- function cleanGuardText(raw) {
17116
- 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();
17117
- }
17118
- function blockReason(raw) {
17119
- const text = cleanGuardText(raw);
17120
- if (!text) return "blocked by policy";
17121
- const guardAt = text.lastIndexOf("Guard:");
17122
- if (guardAt >= 0) return text.slice(guardAt + "Guard:".length).trim();
17123
- const afterTag = text.match(/\[synkro:[^\]]*\]\s*(.+)/is);
17124
- if (afterTag) return afterTag[1].trim();
17125
- const afterHook = text.match(/blocked by a hook:\s*(.+)/is);
17126
- if (afterHook) return afterHook[1].trim();
17127
- return text;
17128
- }
17129
- var BLOCK_MARKER, FIX_POLL_MARKER, LEGACY_FIX_POLL_MARKER;
17130
- var init_events = __esm({
17131
- "cli/harness/events.ts"() {
17132
- "use strict";
17133
- BLOCK_MARKER = /blocked by a hook|\[synkro:/i;
17134
- FIX_POLL_MARKER = /\[synkro:fix-poll\s+item_id=\\?["']?([A-Za-z0-9_-]+)\\?["']?\]/i;
17135
- LEGACY_FIX_POLL_MARKER = /SYNKRO FIX POLL\s*\(item_id=([A-Za-z0-9_-]+)\)/i;
17136
- }
17137
- });
17138
-
17139
17365
  // cli/harness/changes.ts
17140
17366
  import { lstatSync as lstatSync2, readFileSync as readFileSync33, readlinkSync } from "fs";
17141
17367
  import { resolve as resolve6, relative } from "path";
@@ -17419,6 +17645,7 @@ function parseCursorLine(line, streamPartial = false) {
17419
17645
  blocked,
17420
17646
  reason: rejected ? blocked ? blockReason(rawReason) : rawReason || "rejected" : "",
17421
17647
  pollId: blocked ? fixPollId(rawReason) : "",
17648
+ pollIds: blocked ? fixPollIds(rawReason) : [],
17422
17649
  exitCode: success ? Number(success.exitCode ?? 0) : null,
17423
17650
  output: String(success?.stdout || success?.stderr || ""),
17424
17651
  fileChanges: completedFileChanges(body)
@@ -17761,6 +17988,11 @@ var init_run = __esm({
17761
17988
  // cli/harness/codex.ts
17762
17989
  import { spawn as spawn10 } from "child_process";
17763
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) : "";
17995
+ }
17764
17996
  function stringify(value) {
17765
17997
  if (typeof value === "string") return value;
17766
17998
  try {
@@ -17843,6 +18075,7 @@ function codexEvents(message) {
17843
18075
  blocked,
17844
18076
  reason: blocked ? blockReason(raw || status) : "",
17845
18077
  pollId: blocked ? fixPollId(raw) : "",
18078
+ pollIds: blocked ? fixPollIds(raw) : [],
17846
18079
  exitCode: typeof item.exitCode === "number" ? item.exitCode : null,
17847
18080
  output: raw,
17848
18081
  durationMs: typeof item.durationMs === "number" ? item.durationMs : null,
@@ -17864,6 +18097,7 @@ function codexEvents(message) {
17864
18097
  exitCode: null,
17865
18098
  output: reason,
17866
18099
  pollId: fixPollId(reason),
18100
+ pollIds: fixPollIds(reason),
17867
18101
  durationMs: typeof run2.durationMs === "number" ? run2.durationMs : null
17868
18102
  }];
17869
18103
  }
@@ -17898,6 +18132,7 @@ var init_codex = __esm({
17898
18132
  cwd;
17899
18133
  resumeId;
17900
18134
  child = null;
18135
+ initialized = false;
17901
18136
  nextId = 1;
17902
18137
  pending = /* @__PURE__ */ new Map();
17903
18138
  onEvent = null;
@@ -17974,7 +18209,7 @@ var init_codex = __esm({
17974
18209
  }
17975
18210
  });
17976
18211
  this.child.stderr.on("data", (chunk) => {
17977
- const message = String(chunk || "").trim();
18212
+ const message = codexStderrNotice(String(chunk || ""), this.initialized);
17978
18213
  if (message) this.onEvent?.({ type: "notice", text: message });
17979
18214
  });
17980
18215
  this.child.once("exit", (code) => {
@@ -17985,6 +18220,7 @@ var init_codex = __esm({
17985
18220
  });
17986
18221
  await this.request("initialize", { clientInfo: { name: "synkro-terminal", title: "Synkro", version: "1" }, capabilities: { experimentalApi: true } });
17987
18222
  this.notify("initialized");
18223
+ this.initialized = true;
17988
18224
  try {
17989
18225
  const event = codexAccountUsageEvent(await this.request("account/rateLimits/read", null, 4e3));
17990
18226
  if (event) this.accountUsage = event;
@@ -18321,11 +18557,13 @@ async function loadFixPoll(itemId, fetchImpl = fetch) {
18321
18557
  const body = await response.json();
18322
18558
  const candidates = Array.isArray(body.candidates) ? body.candidates.filter((candidate) => typeof candidate === "string").slice(0, 8) : [];
18323
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;
18324
18561
  return {
18325
18562
  itemId,
18326
18563
  filePath: String(body.file_path || ""),
18327
18564
  ruleId: String(body.rule_id || ""),
18328
- candidates
18565
+ candidates,
18566
+ recommendedIdx
18329
18567
  };
18330
18568
  }
18331
18569
  async function recordFixPoll(itemId, chosenIdx, fetchImpl = fetch) {
@@ -18348,7 +18586,8 @@ function fixPollLines(poll, selected, width = 100) {
18348
18586
  ];
18349
18587
  choices.forEach((choice, index) => {
18350
18588
  const prefix = index === selected ? S2.user + " \u276F " : S2.dim + " ";
18351
- const text = wrap(index + 1 + ". " + neutralizeTerminalControls(choice), max);
18589
+ const recommendation = index === poll.recommendedIdx ? "Recommended: " : "";
18590
+ const text = wrap(index + 1 + ". " + recommendation + neutralizeTerminalControls(choice), max);
18352
18591
  lines2.push(prefix + (text[0] || "") + S2.reset);
18353
18592
  for (const continuation of text.slice(1)) lines2.push(" " + continuation);
18354
18593
  });
@@ -18359,7 +18598,7 @@ async function pickFixPoll(poll, io = {}) {
18359
18598
  const input = io.input || process.stdin;
18360
18599
  const output = io.output || process.stdout;
18361
18600
  if (!input.isTTY || !output.isTTY) return -1;
18362
- let selected = 0;
18601
+ let selected = poll.recommendedIdx >= 0 && poll.recommendedIdx < poll.candidates.length ? poll.recommendedIdx : 0;
18363
18602
  let rendered = 0;
18364
18603
  const draw = () => {
18365
18604
  if (rendered) output.write("\x1B[" + rendered + "A\x1B[J");
@@ -18396,7 +18635,7 @@ async function pickFixPoll(poll, io = {}) {
18396
18635
  return choice;
18397
18636
  }
18398
18637
  async function resolveFixPolls(events, io = {}) {
18399
- 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)));
18400
18639
  const recorded = [];
18401
18640
  for (const itemId of ids) {
18402
18641
  const poll = await loadFixPoll(itemId, io.fetchImpl).catch(() => null);
@@ -19930,7 +20169,7 @@ function pseudonymousHostnameHash(installationId, host) {
19930
20169
  }
19931
20170
  function cliVersion() {
19932
20171
  try {
19933
- return "1.10.9";
20172
+ return "1.10.10";
19934
20173
  } catch {
19935
20174
  return "0.0.0";
19936
20175
  }
@@ -20700,7 +20939,7 @@ var subArgs = args.slice(1);
20700
20939
  var isDetachedChild = process.env.SYNKRO_TELEMETRY_DETACHED === "1";
20701
20940
  var FLUSH_SKIP = /* @__PURE__ */ new Set(["grade", "inventory-sync", "version", "--version", "-v", "help", "--help", "-h", ""]);
20702
20941
  function printVersion() {
20703
- console.log("1.10.9");
20942
+ console.log("1.10.10");
20704
20943
  }
20705
20944
  function printHelp2() {
20706
20945
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents