@rehpic/vcli 0.1.0-beta.96.1 → 0.1.0-beta.99.4

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/index.js CHANGED
@@ -1462,22 +1462,142 @@ function discoverAttachableSessions() {
1462
1462
  ...discoverClaudeSessions()
1463
1463
  ]);
1464
1464
  }
1465
- async function resumeProviderSession(provider, sessionKey, cwd, prompt2) {
1465
+ async function launchProviderSession(provider, cwd, prompt2, onEvent) {
1466
1466
  if (provider === "codex") {
1467
1467
  return runCodexAppServerTurn({
1468
+ cwd,
1469
+ prompt: prompt2,
1470
+ launchCommand: "codex app-server",
1471
+ onEvent
1472
+ });
1473
+ }
1474
+ if (provider === "claude_code") {
1475
+ return runClaudeSdkTurn({
1476
+ cwd,
1477
+ prompt: prompt2,
1478
+ launchCommand: "@anthropic-ai/claude-agent-sdk query()",
1479
+ onEvent
1480
+ });
1481
+ }
1482
+ return runGenericCliAgentTurn({
1483
+ provider,
1484
+ cwd,
1485
+ prompt: prompt2,
1486
+ launchCommand: genericProviderLaunchCommand(provider),
1487
+ onEvent
1488
+ });
1489
+ }
1490
+ async function resumeProviderSession(provider, sessionKey, cwd, prompt2, onEvent) {
1491
+ if (provider === "codex") {
1492
+ return runCodexAppServerTurn({
1493
+ cwd,
1494
+ prompt: prompt2,
1495
+ sessionKey,
1496
+ launchCommand: "codex app-server (thread/resume)",
1497
+ onEvent
1498
+ });
1499
+ }
1500
+ if (provider === "claude_code") {
1501
+ return runClaudeSdkTurn({
1468
1502
  cwd,
1469
1503
  prompt: prompt2,
1470
1504
  sessionKey,
1471
- launchCommand: "codex app-server (thread/resume)"
1505
+ launchCommand: "@anthropic-ai/claude-agent-sdk query(resume)",
1506
+ onEvent
1472
1507
  });
1473
1508
  }
1474
- return runClaudeSdkTurn({
1509
+ return runGenericCliAgentTurn({
1510
+ provider,
1475
1511
  cwd,
1476
1512
  prompt: prompt2,
1477
1513
  sessionKey,
1478
- launchCommand: "@anthropic-ai/claude-agent-sdk query(resume)"
1514
+ launchCommand: genericProviderLaunchCommand(provider),
1515
+ onEvent
1479
1516
  });
1480
1517
  }
1518
+ async function runGenericCliAgentTurn(args) {
1519
+ const command = genericProviderCommand(args.provider);
1520
+ const child = spawn2(command.bin, [...command.args, args.prompt], {
1521
+ cwd: args.cwd,
1522
+ env: { ...process.env },
1523
+ stdio: ["ignore", "pipe", "pipe"]
1524
+ });
1525
+ let stdout = "";
1526
+ let stderr = "";
1527
+ await Promise.resolve(
1528
+ args.onEvent?.({
1529
+ provider: args.provider,
1530
+ role: "status",
1531
+ text: `Starting ${providerLabel(args.provider)} CLI session`,
1532
+ status: "in_progress"
1533
+ })
1534
+ );
1535
+ child.stdout.on("data", (chunk) => {
1536
+ stdout += chunk.toString();
1537
+ });
1538
+ child.stderr.on("data", (chunk) => {
1539
+ stderr += chunk.toString();
1540
+ });
1541
+ const exit = await new Promise((resolve, reject) => {
1542
+ child.on("error", reject);
1543
+ child.on("close", (code, signal) => resolve({ code, signal }));
1544
+ });
1545
+ if (exit.code && exit.code !== 0) {
1546
+ const message = stderr.trim() || `${providerLabel(args.provider)} exited with code ${exit.code}`;
1547
+ await Promise.resolve(
1548
+ args.onEvent?.({
1549
+ provider: args.provider,
1550
+ role: "error",
1551
+ text: message,
1552
+ title: `${providerLabel(args.provider)} failed`,
1553
+ status: "failed"
1554
+ })
1555
+ );
1556
+ throw new Error(message);
1557
+ }
1558
+ const responseText = stdout.trim();
1559
+ if (responseText) {
1560
+ await Promise.resolve(
1561
+ args.onEvent?.({
1562
+ provider: args.provider,
1563
+ role: "assistant",
1564
+ text: responseText,
1565
+ status: "completed"
1566
+ })
1567
+ );
1568
+ }
1569
+ return {
1570
+ provider: args.provider,
1571
+ providerLabel: providerLabel(args.provider),
1572
+ sessionKey: args.sessionKey ?? `${args.provider}:${Date.now()}`,
1573
+ cwd: args.cwd,
1574
+ ...getGitInfo(args.cwd),
1575
+ title: summarizeTitle(responseText, args.cwd) ?? providerLabel(args.provider),
1576
+ mode: "managed",
1577
+ status: "waiting",
1578
+ supportsInboundMessages: true,
1579
+ responseText,
1580
+ launchCommand: args.launchCommand
1581
+ };
1582
+ }
1583
+ function genericProviderCommand(provider) {
1584
+ if (provider === "cursor") return { bin: "cursor-agent", args: ["--print"] };
1585
+ if (provider === "copilot") return { bin: "copilot", args: [] };
1586
+ if (provider === "opencode") return { bin: "opencode", args: ["run"] };
1587
+ return { bin: "pi", args: [] };
1588
+ }
1589
+ function genericProviderLaunchCommand(provider) {
1590
+ const command = genericProviderCommand(provider);
1591
+ return [command.bin, ...command.args].join(" ");
1592
+ }
1593
+ function providerLabel(provider) {
1594
+ if (provider === "codex") return "Codex";
1595
+ if (provider === "claude_code") return "Claude";
1596
+ if (provider === "cursor") return "Cursor";
1597
+ if (provider === "copilot") return "GitHub Copilot";
1598
+ if (provider === "opencode") return "OpenCode";
1599
+ return "Pi";
1600
+ }
1481
1601
  async function runCodexAppServerTurn(args) {
1482
1602
  const child = spawn2("codex", ["app-server"], {
1483
1603
  cwd: args.cwd,
@@ -1488,6 +1608,8 @@ async function runCodexAppServerTurn(args) {
1488
1608
  let stdoutBuffer = "";
1489
1609
  let sessionKey = args.sessionKey;
1490
1610
  let finalAssistantText = "";
1611
+ let activeAssistantText = "";
1612
+ const eventWrites = [];
1491
1613
  let completed = false;
1492
1614
  let nextRequestId = 1;
1493
1615
  const pending = /* @__PURE__ */ new Map();
@@ -1503,6 +1625,9 @@ async function runCodexAppServerTurn(args) {
1503
1625
  reject(error);
1504
1626
  };
1505
1627
  });
1628
+ const emitEvent = (event) => {
1629
+ eventWrites.push(Promise.resolve(args.onEvent?.(event)));
1630
+ };
1506
1631
  child.stdout.on("data", (chunk) => {
1507
1632
  stdoutBuffer += chunk.toString();
1508
1633
  while (true) {
@@ -1519,11 +1644,11 @@ async function runCodexAppServerTurn(args) {
1519
1644
  if (!payload || typeof payload !== "object") {
1520
1645
  continue;
1521
1646
  }
1522
- const responseId = payload.id;
1647
+ const responseId = readProperty(payload, "id");
1523
1648
  if (typeof responseId === "number" && pending.has(responseId)) {
1524
1649
  const entry = pending.get(responseId);
1525
1650
  pending.delete(responseId);
1526
- const errorRecord = asObject(payload.error);
1651
+ const errorRecord = asObject(readProperty(payload, "error"));
1527
1652
  if (errorRecord) {
1528
1653
  entry.reject(
1529
1654
  new Error(
@@ -1532,11 +1657,11 @@ async function runCodexAppServerTurn(args) {
1532
1657
  );
1533
1658
  continue;
1534
1659
  }
1535
- entry.resolve(payload.result);
1660
+ entry.resolve(readProperty(payload, "result"));
1536
1661
  continue;
1537
1662
  }
1538
- const method = asString(payload.method);
1539
- const params = asObject(payload.params);
1663
+ const method = asString(readProperty(payload, "method"));
1664
+ const params = asObject(readProperty(payload, "params"));
1540
1665
  if (!method || !params) {
1541
1666
  continue;
1542
1667
  }
@@ -1545,13 +1670,46 @@ async function runCodexAppServerTurn(args) {
1545
1670
  continue;
1546
1671
  }
1547
1672
  if (method === "item/agentMessage/delta") {
1548
- finalAssistantText += asString(params.delta) ?? "";
1673
+ const delta = asString(params.delta) ?? "";
1674
+ finalAssistantText += delta;
1675
+ activeAssistantText += delta;
1549
1676
  continue;
1550
1677
  }
1551
1678
  if (method === "item/completed") {
1552
1679
  const item = asObject(params.item);
1553
- if (asString(item?.type) === "agentMessage") {
1680
+ const itemType = asString(item?.type);
1681
+ if (itemType === "agentMessage") {
1554
1682
  finalAssistantText = asString(item?.text) ?? finalAssistantText;
1683
+ const text2 = finalAssistantText || activeAssistantText;
1684
+ if (text2.trim()) {
1685
+ emitEvent({
1686
+ provider: "codex",
1687
+ role: "assistant",
1688
+ text: text2.trim(),
1689
+ status: "completed"
1690
+ });
1691
+ }
1692
+ activeAssistantText = "";
1693
+ } else if (itemType === "reasoning") {
1694
+ const text2 = asString(item?.text) ?? asString(item?.summary);
1695
+ if (text2?.trim()) {
1696
+ emitEvent({
1697
+ provider: "codex",
1698
+ role: "reasoning",
1699
+ text: text2.trim(),
1700
+ status: "completed"
1701
+ });
1702
+ }
1703
+ } else if (itemType === "toolCall") {
1704
+ const title = asString(item?.title) ?? asString(item?.name) ?? asString(item?.command) ?? "Tool";
1705
+ const text2 = asString(item?.text) ?? asString(item?.output) ?? asString(item?.command) ?? title;
1706
+ emitEvent({
1707
+ provider: "codex",
1708
+ role: "tool",
1709
+ title,
1710
+ text: text2,
1711
+ status: asString(item?.status) === "failed" ? "failed" : "completed"
1712
+ });
1555
1713
  }
1556
1714
  continue;
1557
1715
  }
@@ -1560,12 +1718,25 @@ async function runCodexAppServerTurn(args) {
1560
1718
  const status = asString(turn?.status);
1561
1719
  if (status === "failed") {
1562
1720
  const turnError = asObject(turn?.error);
1721
+ emitEvent({
1722
+ provider: "codex",
1723
+ role: "error",
1724
+ title: "Codex turn failed",
1725
+ text: asString(turnError?.message) ?? "Codex turn failed without an error message",
1726
+ status: "failed"
1727
+ });
1563
1728
  failTurn?.(
1564
1729
  new Error(
1565
1730
  asString(turnError?.message) ?? "Codex turn failed without an error message"
1566
1731
  )
1567
1732
  );
1568
1733
  } else if (status === "interrupted") {
1734
+ emitEvent({
1735
+ provider: "codex",
1736
+ role: "status",
1737
+ text: "Codex turn was interrupted",
1738
+ status: "failed"
1739
+ });
1569
1740
  failTurn?.(new Error("Codex turn was interrupted"));
1570
1741
  } else {
1571
1742
  completeTurn?.();
@@ -1607,21 +1778,23 @@ async function runCodexAppServerTurn(args) {
1607
1778
  waitForExit
1608
1779
  ]);
1609
1780
  notify("initialized", {});
1610
- const threadResult = await Promise.race([
1611
- args.sessionKey ? request("thread/resume", {
1612
- threadId: args.sessionKey,
1613
- cwd: args.cwd,
1614
- approvalPolicy: "never",
1615
- personality: "pragmatic"
1616
- }) : request("thread/start", {
1617
- cwd: args.cwd,
1618
- approvalPolicy: "never",
1619
- personality: "pragmatic",
1620
- serviceName: "vector_bridge"
1621
- }),
1622
- waitForExit
1623
- ]);
1624
- sessionKey = asString(asObject(threadResult.thread)?.id) ?? asString(asObject(threadResult.thread)?.threadId) ?? sessionKey;
1781
+ const threadResult = asObject(
1782
+ await Promise.race([
1783
+ args.sessionKey ? request("thread/resume", {
1784
+ threadId: args.sessionKey,
1785
+ cwd: args.cwd,
1786
+ approvalPolicy: "never",
1787
+ personality: "pragmatic"
1788
+ }) : request("thread/start", {
1789
+ cwd: args.cwd,
1790
+ approvalPolicy: "never",
1791
+ personality: "pragmatic",
1792
+ serviceName: "vector_bridge"
1793
+ }),
1794
+ waitForExit
1795
+ ])
1796
+ );
1797
+ sessionKey = asString(asObject(readProperty(threadResult, "thread"))?.id) ?? asString(asObject(readProperty(threadResult, "thread"))?.threadId) ?? sessionKey;
1625
1798
  if (!sessionKey) {
1626
1799
  throw new Error("Codex app-server did not return a thread id");
1627
1800
  }
@@ -1636,6 +1809,7 @@ async function runCodexAppServerTurn(args) {
1636
1809
  waitForExit
1637
1810
  ]);
1638
1811
  await Promise.race([turnCompleted, waitForExit]);
1812
+ await Promise.allSettled(eventWrites);
1639
1813
  const gitInfo = getGitInfo(args.cwd);
1640
1814
  return {
1641
1815
  provider: "codex",
@@ -1684,30 +1858,40 @@ async function runClaudeSdkTurn(args) {
1684
1858
  if (!message || typeof message !== "object") {
1685
1859
  continue;
1686
1860
  }
1687
- sessionKey = asString(message.session_id) ?? sessionKey;
1688
- if (message.type === "assistant") {
1861
+ sessionKey = asString(readProperty(message, "session_id")) ?? sessionKey;
1862
+ if (readProperty(message, "type") === "assistant") {
1689
1863
  const assistantText = extractClaudeMessageTexts(
1690
- message.message
1864
+ readProperty(message, "message")
1691
1865
  ).join("\n\n").trim();
1692
1866
  if (assistantText) {
1693
1867
  responseText = assistantText;
1868
+ await args.onEvent?.({
1869
+ provider: "claude_code",
1870
+ role: "assistant",
1871
+ text: assistantText,
1872
+ status: "completed"
1873
+ });
1694
1874
  }
1695
1875
  continue;
1696
1876
  }
1697
- if (message.type !== "result") {
1877
+ if (readProperty(message, "type") !== "result") {
1698
1878
  continue;
1699
1879
  }
1700
- if (message.subtype === "success") {
1701
- const resultText = asString(message.result);
1880
+ if (readProperty(message, "subtype") === "success") {
1881
+ const resultText = asString(readProperty(message, "result"));
1702
1882
  if (resultText) {
1703
1883
  responseText = resultText;
1884
+ await args.onEvent?.({
1885
+ provider: "claude_code",
1886
+ role: "assistant",
1887
+ text: resultText,
1888
+ status: "completed"
1889
+ });
1704
1890
  }
1705
- model = firstObjectKey(
1706
- message.modelUsage
1707
- );
1891
+ model = firstObjectKey(readProperty(message, "modelUsage"));
1708
1892
  continue;
1709
1893
  }
1710
- const errors = message.errors;
1894
+ const errors = readProperty(message, "errors");
1711
1895
  const detail = Array.isArray(errors) && errors.length > 0 ? errors.join("\n") : "Claude execution failed";
1712
1896
  throw new Error(detail);
1713
1897
  }
@@ -2117,11 +2301,17 @@ function normalizeModelKey(value) {
2117
2301
  return normalized || void 0;
2118
2302
  }
2119
2303
  function asObject(value) {
2120
- return value && typeof value === "object" ? value : void 0;
2304
+ return isRecord(value) ? value : void 0;
2305
+ }
2306
+ function readProperty(value, key) {
2307
+ return isRecord(value) ? Reflect.get(value, key) : void 0;
2121
2308
  }
2122
2309
  function asString(value) {
2123
2310
  return typeof value === "string" && value.trim() ? value : void 0;
2124
2311
  }
2312
+ function isRecord(value) {
2313
+ return value !== null && typeof value === "object";
2314
+ }
2125
2315
  function pushIfPresent(target, value) {
2126
2316
  const text2 = asString(value);
2127
2317
  if (text2) {
@@ -2285,11 +2475,13 @@ function getGitInfo(cwd) {
2285
2475
  const repoRoot = execSync("git rev-parse --show-toplevel", {
2286
2476
  encoding: "utf-8",
2287
2477
  cwd,
2478
+ stdio: ["ignore", "pipe", "ignore"],
2288
2479
  timeout: 3e3
2289
2480
  }).trim();
2290
2481
  const branch = execSync("git rev-parse --abbrev-ref HEAD", {
2291
2482
  encoding: "utf-8",
2292
2483
  cwd,
2484
+ stdio: ["ignore", "pipe", "ignore"],
2293
2485
  timeout: 3e3
2294
2486
  }).trim();
2295
2487
  return {
@@ -2341,6 +2533,8 @@ var BridgeService = class {
2341
2533
  constructor(config) {
2342
2534
  this.timers = [];
2343
2535
  this.terminalPeer = null;
2536
+ this.stopping = false;
2537
+ this.runningLoops = /* @__PURE__ */ new Set();
2344
2538
  this.deviceLiveActivities = [];
2345
2539
  this.config = config;
2346
2540
  this.client = new ConvexHttpClient2(config.convexUrl);
@@ -2366,6 +2560,30 @@ var BridgeService = class {
2366
2560
  await this.handleCommand(cmd);
2367
2561
  }
2368
2562
  }
2563
+ scheduleLoop(name, intervalMs, run) {
2564
+ this.timers.push(
2565
+ setInterval(() => {
2566
+ if (this.stopping || this.runningLoops.has(name)) {
2567
+ return;
2568
+ }
2569
+ this.runningLoops.add(name);
2570
+ run().catch((error) => {
2571
+ const message = error instanceof Error ? error.message : String(error);
2572
+ console.error(`[${ts2()}] ${name} error:`, message);
2573
+ }).finally(() => {
2574
+ this.runningLoops.delete(name);
2575
+ });
2576
+ }, intervalMs)
2577
+ );
2578
+ }
2579
+ async runStartupStep(label, step) {
2580
+ try {
2581
+ await step();
2582
+ } catch (error) {
2583
+ const message = error instanceof Error ? error.message : String(error);
2584
+ console.error(`[${ts2()}] Startup ${label} failed: ${message}`);
2585
+ }
2586
+ }
2369
2587
  async handleCommand(cmd) {
2370
2588
  const claimed = await this.client.mutation(
2371
2589
  api.agentBridge.bridgePublic.claimCommand,
@@ -2378,6 +2596,18 @@ var BridgeService = class {
2378
2596
  if (!claimed) {
2379
2597
  return;
2380
2598
  }
2599
+ if (cmd.kind === "settings_update" || cmd.kind === "queue_update" || cmd.kind === "approval_response" || cmd.kind === "plan_response" || cmd.kind === "question_response" || cmd.kind === "stop" || cmd.kind === "resume") {
2600
+ try {
2601
+ await this.handleAgentControlCommand(cmd);
2602
+ await this.completeCommand(cmd._id, "delivered");
2603
+ } catch (error) {
2604
+ const message = error instanceof Error ? error.message : "Unknown bridge error";
2605
+ console.error(` ! ${message}`);
2606
+ await this.postCommandError(cmd, message);
2607
+ await this.completeCommand(cmd._id, "failed");
2608
+ }
2609
+ return;
2610
+ }
2381
2611
  console.log(` ${cmd.kind}: ${cmd._id}`);
2382
2612
  try {
2383
2613
  switch (cmd.kind) {
@@ -2526,20 +2756,20 @@ var BridgeService = class {
2526
2756
  await this.postAgentMessage(
2527
2757
  activity._id,
2528
2758
  "status",
2529
- `Verified ${providerLabel(attachedSession.process.provider)} in ${activity.tmuxPaneId}`
2759
+ `Verified ${providerLabel2(attachedSession.process.provider)} in ${activity.tmuxPaneId}`
2530
2760
  );
2531
2761
  await this.updateLiveActivity(activity._id, {
2532
2762
  status: "active",
2533
- latestSummary: `Verified ${providerLabel(attachedSession.process.provider)} in ${activity.tmuxPaneId}`,
2763
+ latestSummary: `Verified ${providerLabel2(attachedSession.process.provider)} in ${activity.tmuxPaneId}`,
2534
2764
  processId: attachedSession.processId,
2535
2765
  title: activity.title
2536
2766
  });
2537
2767
  }
2538
2768
  async refreshWorkSessionTerminal(workSessionId, metadata) {
2539
- if (!workSessionId || !metadata.tmuxPaneId) {
2769
+ if (!workSessionId) {
2540
2770
  return;
2541
2771
  }
2542
- const terminalSnapshot = captureTmuxPane(metadata.tmuxPaneId);
2772
+ const terminalSnapshot = metadata.tmuxPaneId ? captureTmuxPane(metadata.tmuxPaneId) : void 0;
2543
2773
  await this.client.mutation(
2544
2774
  api.agentBridge.bridgePublic.updateWorkSessionTerminal,
2545
2775
  {
@@ -2554,7 +2784,13 @@ var BridgeService = class {
2554
2784
  repoRoot: metadata.repoRoot,
2555
2785
  branch: metadata.branch,
2556
2786
  agentProvider: metadata.agentProvider,
2557
- agentSessionKey: metadata.agentSessionKey
2787
+ agentSessionKey: metadata.agentSessionKey,
2788
+ agentProcessId: metadata.agentProcessId,
2789
+ model: metadata.model,
2790
+ permissionMode: metadata.permissionMode,
2791
+ thinkingLevel: metadata.thinkingLevel,
2792
+ fastMode: metadata.fastMode,
2793
+ contextLength: metadata.contextLength
2558
2794
  }
2559
2795
  );
2560
2796
  }
@@ -2584,51 +2820,60 @@ var BridgeService = class {
2584
2820
  );
2585
2821
  }
2586
2822
  console.log("");
2587
- await this.heartbeat();
2588
- await this.reportProcesses();
2589
- await this.refreshLiveActivities();
2590
- await this.syncWorkSessionTerminals(this.deviceLiveActivities);
2823
+ process.on("uncaughtException", (error) => {
2824
+ console.error(`[${ts2()}] Uncaught error:`, error.message);
2825
+ });
2826
+ process.on("unhandledRejection", (reason) => {
2827
+ console.error(
2828
+ `[${ts2()}] Unhandled rejection:`,
2829
+ reason instanceof Error ? reason.message : String(reason)
2830
+ );
2831
+ });
2832
+ await this.runStartupStep("heartbeat", () => this.heartbeat());
2833
+ await this.runStartupStep(
2834
+ "process discovery",
2835
+ () => this.reportProcesses()
2836
+ );
2837
+ await this.runStartupStep(
2838
+ "live activity sync",
2839
+ () => this.refreshLiveActivities()
2840
+ );
2841
+ await this.runStartupStep(
2842
+ "terminal snapshot sync",
2843
+ () => this.syncWorkSessionTerminals(this.deviceLiveActivities)
2844
+ );
2591
2845
  console.log(`[${ts2()}] Service running. Ctrl+C to stop.
2592
2846
  `);
2593
- this.timers.push(
2594
- setInterval(() => {
2595
- this.heartbeat().catch(
2596
- (e) => console.error(`[${ts2()}] Heartbeat error:`, e.message)
2597
- );
2598
- }, HEARTBEAT_INTERVAL_MS)
2847
+ this.scheduleLoop(
2848
+ "Heartbeat",
2849
+ HEARTBEAT_INTERVAL_MS,
2850
+ () => this.heartbeat()
2599
2851
  );
2600
- this.timers.push(
2601
- setInterval(() => {
2602
- this.pollCommands().catch(
2603
- (e) => console.error(`[${ts2()}] Command poll error:`, e.message)
2604
- );
2605
- }, COMMAND_POLL_INTERVAL_MS)
2852
+ this.scheduleLoop(
2853
+ "Command poll",
2854
+ COMMAND_POLL_INTERVAL_MS,
2855
+ () => this.pollCommands()
2606
2856
  );
2607
- this.timers.push(
2608
- setInterval(() => {
2609
- this.refreshLiveActivities().catch(
2610
- (e) => console.error(`[${ts2()}] Live activity sync error:`, e.message)
2611
- );
2612
- }, LIVE_ACTIVITY_SYNC_INTERVAL_MS)
2857
+ this.scheduleLoop(
2858
+ "Live activity sync",
2859
+ LIVE_ACTIVITY_SYNC_INTERVAL_MS,
2860
+ () => this.refreshLiveActivities()
2613
2861
  );
2614
- this.timers.push(
2615
- setInterval(() => {
2616
- this.reportProcesses().catch(
2617
- (e) => console.error(`[${ts2()}] Discovery error:`, e.message)
2618
- );
2619
- }, PROCESS_DISCOVERY_INTERVAL_MS)
2862
+ this.scheduleLoop(
2863
+ "Discovery",
2864
+ PROCESS_DISCOVERY_INTERVAL_MS,
2865
+ () => this.reportProcesses()
2620
2866
  );
2621
- this.timers.push(
2622
- setInterval(() => {
2623
- this.syncWorkSessionTerminals(this.deviceLiveActivities).catch(
2624
- (e) => console.error(
2625
- `[${ts2()}] Terminal snapshot refresh error:`,
2626
- e.message
2627
- )
2628
- );
2629
- }, TERMINAL_SNAPSHOT_REFRESH_INTERVAL_MS)
2867
+ this.scheduleLoop(
2868
+ "Terminal snapshot refresh",
2869
+ TERMINAL_SNAPSHOT_REFRESH_INTERVAL_MS,
2870
+ () => this.syncWorkSessionTerminals(this.deviceLiveActivities)
2630
2871
  );
2631
2872
  const shutdown = () => {
2873
+ if (this.stopping) {
2874
+ return;
2875
+ }
2876
+ this.stopping = true;
2632
2877
  console.log(`
2633
2878
  [${ts2()}] Shutting down...`);
2634
2879
  for (const t of this.timers) clearInterval(t);
@@ -2652,15 +2897,16 @@ var BridgeService = class {
2652
2897
  if (!cmd.liveActivityId) {
2653
2898
  throw new Error("Message command is missing liveActivityId");
2654
2899
  }
2655
- const payload = cmd.payload;
2656
- const body = payload?.body?.trim();
2900
+ const body = readPayloadString(cmd.payload, "body")?.trim();
2657
2901
  if (!body) {
2658
2902
  throw new Error("Message command is missing a body");
2659
2903
  }
2904
+ const issueContext = readPayloadValue(cmd.payload, "issueContext");
2660
2905
  const process9 = cmd.process;
2661
2906
  console.log(` > "${truncateForLog(body)}"`);
2662
2907
  if (cmd.workSession?.tmuxPaneId) {
2663
- sendTextToTmuxPane(cmd.workSession.tmuxPaneId, body);
2908
+ const terminalInput = cmd.workSession.agentProvider && isBridgeProvider(cmd.workSession.agentProvider) ? buildFollowUpPrompt(body, issueContext) : body;
2909
+ sendTextToTmuxPane(cmd.workSession.tmuxPaneId, terminalInput);
2664
2910
  const attachedSession = cmd.workSession.agentProvider && isBridgeProvider(cmd.workSession.agentProvider) ? await this.attachObservedAgentSession(
2665
2911
  cmd.workSession.agentProvider,
2666
2912
  cmd.workSession.workspacePath ?? cmd.workSession.cwd ?? process9?.cwd
@@ -2693,13 +2939,17 @@ var BridgeService = class {
2693
2939
  }
2694
2940
  await this.reportProcess({
2695
2941
  provider: process9.provider,
2696
- providerLabel: process9.providerLabel ?? providerLabel(process9.provider),
2942
+ providerLabel: process9.providerLabel ?? providerLabel2(process9.provider),
2697
2943
  sessionKey: process9.sessionKey,
2698
2944
  cwd: process9.cwd,
2699
2945
  repoRoot: process9.repoRoot,
2700
2946
  branch: process9.branch,
2701
2947
  title: process9.title,
2702
2948
  model: process9.model,
2949
+ permissionMode: process9.permissionMode,
2950
+ thinkingLevel: process9.thinkingLevel,
2951
+ fastMode: process9.fastMode,
2952
+ contextLength: process9.contextLength,
2703
2953
  mode: "managed",
2704
2954
  status: "waiting",
2705
2955
  supportsInboundMessages: true
@@ -2709,14 +2959,20 @@ var BridgeService = class {
2709
2959
  processId: process9._id,
2710
2960
  title: cmd.liveActivity?.title ?? process9.title
2711
2961
  });
2962
+ const liveActivityId = cmd.liveActivityId;
2963
+ let emittedAssistantEvent = false;
2712
2964
  const result = await resumeProviderSession(
2713
2965
  process9.provider,
2714
2966
  process9.sessionKey,
2715
2967
  process9.cwd,
2716
- body
2968
+ buildFollowUpPrompt(body, issueContext),
2969
+ (event) => {
2970
+ if (event.role === "assistant") emittedAssistantEvent = true;
2971
+ return this.postAgentSessionEvent(liveActivityId, event);
2972
+ }
2717
2973
  );
2718
2974
  const processId = await this.reportProcess(result);
2719
- if (result.responseText) {
2975
+ if (result.responseText && !emittedAssistantEvent) {
2720
2976
  await this.postAgentMessage(
2721
2977
  cmd.liveActivityId,
2722
2978
  "assistant",
@@ -2732,9 +2988,8 @@ var BridgeService = class {
2732
2988
  });
2733
2989
  }
2734
2990
  async handleResizeCommand(cmd) {
2735
- const payload = cmd.payload;
2736
- const cols = payload?.cols;
2737
- const rows = payload?.rows;
2991
+ const cols = readPayloadNumber(cmd.payload, "cols");
2992
+ const rows = readPayloadNumber(cmd.payload, "rows");
2738
2993
  const paneId = cmd.workSession?.tmuxPaneId;
2739
2994
  if (!paneId || !cols || !rows) {
2740
2995
  throw new Error("Resize command missing paneId, cols, or rows");
@@ -2754,35 +3009,142 @@ var BridgeService = class {
2754
3009
  });
2755
3010
  }
2756
3011
  }
3012
+ async handleAgentControlCommand(cmd) {
3013
+ if (!cmd.liveActivityId) {
3014
+ throw new Error(`${cmd.kind} command is missing liveActivityId`);
3015
+ }
3016
+ if (cmd.kind === "settings_update") {
3017
+ await this.postAgentMessage(
3018
+ cmd.liveActivityId,
3019
+ "status",
3020
+ "Updated agent settings"
3021
+ );
3022
+ return;
3023
+ }
3024
+ if (cmd.kind === "queue_update") {
3025
+ await this.postAgentMessage(
3026
+ cmd.liveActivityId,
3027
+ "status",
3028
+ "Updated queued agent messages"
3029
+ );
3030
+ return;
3031
+ }
3032
+ if (cmd.kind === "stop") {
3033
+ await this.updateLiveActivity(cmd.liveActivityId, {
3034
+ status: "paused",
3035
+ latestSummary: "Agent turn stop requested"
3036
+ });
3037
+ await this.postAgentMessage(
3038
+ cmd.liveActivityId,
3039
+ "status",
3040
+ "Stop requested for the local agent session"
3041
+ );
3042
+ return;
3043
+ }
3044
+ if (cmd.kind === "resume") {
3045
+ await this.updateLiveActivity(cmd.liveActivityId, {
3046
+ status: "active",
3047
+ latestSummary: "Agent session resumed"
3048
+ });
3049
+ return;
3050
+ }
3051
+ const label = cmd.kind === "approval_response" ? "approval" : cmd.kind === "plan_response" ? "plan approval" : "question response";
3052
+ await this.postAgentMessage(
3053
+ cmd.liveActivityId,
3054
+ "status",
3055
+ `Received ${label}; the provider runtime will continue when supported`
3056
+ );
3057
+ }
2757
3058
  async handleLaunchCommand(cmd) {
2758
3059
  if (!cmd.liveActivityId) {
2759
3060
  throw new Error("Launch command is missing liveActivityId");
2760
3061
  }
2761
- const payload = cmd.payload;
2762
- const workspacePath = payload?.workspacePath?.trim();
3062
+ const workspacePath = readPayloadString(
3063
+ cmd.payload,
3064
+ "workspacePath"
3065
+ )?.trim();
2763
3066
  if (!workspacePath) {
2764
3067
  throw new Error("Launch command is missing workspacePath");
2765
3068
  }
2766
- const requestedProvider = payload?.provider;
3069
+ const requestedProvider = readPayloadAgentProvider(cmd.payload, "provider");
2767
3070
  const provider = requestedProvider && isBridgeProvider(requestedProvider) ? requestedProvider : void 0;
2768
- const issueKey = payload?.issueKey ?? cmd.liveActivity?.issueKey ?? "ISSUE";
2769
- const issueTitle = payload?.issueTitle ?? cmd.liveActivity?.issueTitle ?? "Untitled issue";
2770
- const issueDescription = payload?.issueDescription;
3071
+ const issueKey = readPayloadString(cmd.payload, "issueKey") ?? cmd.liveActivity?.issueKey ?? "ISSUE";
3072
+ const issueTitle = readPayloadString(cmd.payload, "issueTitle") ?? cmd.liveActivity?.issueTitle ?? "Untitled issue";
3073
+ const issueDescription = readPayloadString(cmd.payload, "issueDescription");
3074
+ const issueContext = readPayloadValue(cmd.payload, "issueContext");
3075
+ const model = readPayloadString(cmd.payload, "model");
3076
+ const permissionMode = readPayloadPermissionMode(
3077
+ cmd.payload,
3078
+ "permissionMode"
3079
+ );
3080
+ const thinkingLevel = readPayloadThinkingLevel(
3081
+ cmd.payload,
3082
+ "thinkingLevel"
3083
+ );
3084
+ const fastMode = readPayloadBoolean(cmd.payload, "fastMode");
3085
+ const contextLength = readPayloadContextLength(
3086
+ cmd.payload,
3087
+ "contextLength"
3088
+ );
3089
+ const initialPrompt = readPayloadString(cmd.payload, "initialPrompt");
3090
+ const delegatedRunId = readPayloadId(
3091
+ cmd.payload,
3092
+ "delegatedRunId"
3093
+ );
2771
3094
  const prompt2 = buildLaunchPrompt(
2772
3095
  issueKey,
2773
3096
  issueTitle,
2774
3097
  workspacePath,
2775
- issueDescription
3098
+ issueDescription,
3099
+ issueContext,
3100
+ initialPrompt
2776
3101
  );
2777
- const launchLabel = provider ? providerLabel(provider) : "shell session";
3102
+ const launchLabel = provider ? providerLabel2(provider) : "shell session";
2778
3103
  const workSessionTitle = `${issueKey}: ${issueTitle}`;
2779
3104
  await this.updateLiveActivity(cmd.liveActivityId, {
2780
3105
  status: "active",
2781
3106
  latestSummary: `Launching ${launchLabel} in ${workspacePath}`,
2782
- delegatedRunId: payload?.delegatedRunId,
3107
+ delegatedRunId,
2783
3108
  launchStatus: "launching",
2784
3109
  title: workSessionTitle
2785
3110
  });
3111
+ if (provider) {
3112
+ await this.postAgentMessage(
3113
+ cmd.liveActivityId,
3114
+ "status",
3115
+ `Starting ${launchLabel} session in ${workspacePath}`
3116
+ );
3117
+ const liveActivityId = cmd.liveActivityId;
3118
+ const result = await launchProviderSession(
3119
+ provider,
3120
+ workspacePath,
3121
+ prompt2,
3122
+ (event) => this.postAgentSessionEvent(liveActivityId, event)
3123
+ );
3124
+ const processId = await this.reportProcess(result);
3125
+ await this.refreshWorkSessionTerminal(cmd.workSession?._id, {
3126
+ cwd: workspacePath,
3127
+ repoRoot: result.repoRoot ?? workspacePath,
3128
+ branch: result.branch ?? currentGitBranch(workspacePath),
3129
+ agentProvider: result.provider,
3130
+ agentSessionKey: result.sessionKey,
3131
+ agentProcessId: processId,
3132
+ model,
3133
+ permissionMode,
3134
+ thinkingLevel,
3135
+ fastMode,
3136
+ contextLength
3137
+ });
3138
+ await this.updateLiveActivity(cmd.liveActivityId, {
3139
+ status: "waiting_for_input",
3140
+ latestSummary: summarizeMessage(result.responseText),
3141
+ delegatedRunId,
3142
+ launchStatus: "running",
3143
+ title: workSessionTitle,
3144
+ processId
3145
+ });
3146
+ return;
3147
+ }
2786
3148
  const tmuxSession = createTmuxWorkSession({
2787
3149
  workspacePath,
2788
3150
  issueKey,
@@ -2797,12 +3159,17 @@ var BridgeService = class {
2797
3159
  cwd: workspacePath,
2798
3160
  repoRoot: workspacePath,
2799
3161
  branch: currentGitBranch(workspacePath),
2800
- agentProvider: provider
3162
+ agentProvider: provider,
3163
+ model,
3164
+ permissionMode,
3165
+ thinkingLevel,
3166
+ fastMode,
3167
+ contextLength
2801
3168
  });
2802
3169
  await this.updateLiveActivity(cmd.liveActivityId, {
2803
3170
  status: "active",
2804
3171
  latestSummary: `Running ${launchLabel} in ${tmuxSession.sessionName}`,
2805
- delegatedRunId: payload?.delegatedRunId,
3172
+ delegatedRunId,
2806
3173
  launchStatus: "running",
2807
3174
  title: workSessionTitle
2808
3175
  });
@@ -2836,7 +3203,7 @@ var BridgeService = class {
2836
3203
  async reportProcess(process9) {
2837
3204
  const {
2838
3205
  provider,
2839
- providerLabel: providerLabel2,
3206
+ providerLabel: providerLabel3,
2840
3207
  localProcessId,
2841
3208
  sessionKey,
2842
3209
  cwd,
@@ -2844,6 +3211,10 @@ var BridgeService = class {
2844
3211
  branch,
2845
3212
  title,
2846
3213
  model,
3214
+ permissionMode,
3215
+ thinkingLevel,
3216
+ fastMode,
3217
+ contextLength,
2847
3218
  tmuxSessionName,
2848
3219
  tmuxWindowName,
2849
3220
  tmuxPaneId,
@@ -2857,7 +3228,7 @@ var BridgeService = class {
2857
3228
  deviceId: this.config.deviceId,
2858
3229
  deviceSecret: this.config.deviceSecret,
2859
3230
  provider,
2860
- providerLabel: providerLabel2,
3231
+ providerLabel: providerLabel3,
2861
3232
  localProcessId,
2862
3233
  sessionKey,
2863
3234
  cwd,
@@ -2865,6 +3236,10 @@ var BridgeService = class {
2865
3236
  branch,
2866
3237
  title,
2867
3238
  model,
3239
+ permissionMode,
3240
+ thinkingLevel,
3241
+ fastMode,
3242
+ contextLength,
2868
3243
  tmuxSessionName,
2869
3244
  tmuxWindowName,
2870
3245
  tmuxPaneId,
@@ -2885,13 +3260,24 @@ var BridgeService = class {
2885
3260
  }
2886
3261
  );
2887
3262
  }
2888
- async postAgentMessage(liveActivityId, role, body) {
3263
+ async postAgentMessage(liveActivityId, role, body, structuredPayload) {
2889
3264
  await this.client.mutation(api.agentBridge.bridgePublic.postAgentMessage, {
2890
3265
  deviceId: this.config.deviceId,
2891
3266
  deviceSecret: this.config.deviceSecret,
2892
3267
  liveActivityId,
2893
3268
  role,
2894
- body
3269
+ body,
3270
+ structuredPayload
3271
+ });
3272
+ }
3273
+ async postAgentSessionEvent(liveActivityId, event) {
3274
+ const body = event.text.trim();
3275
+ if (!body) return;
3276
+ await this.postAgentMessage(liveActivityId, event.role, body, {
3277
+ source: "cells_agent_event",
3278
+ provider: event.provider,
3279
+ title: event.title,
3280
+ status: event.status
2895
3281
  });
2896
3282
  }
2897
3283
  async completeCommand(commandId, status) {
@@ -2904,11 +3290,13 @@ var BridgeService = class {
2904
3290
  }
2905
3291
  async postCommandError(cmd, errorMessage) {
2906
3292
  if (cmd.kind === "launch" && cmd.liveActivityId) {
2907
- const payload = cmd.payload;
2908
3293
  await this.updateLiveActivity(cmd.liveActivityId, {
2909
3294
  status: "failed",
2910
3295
  latestSummary: errorMessage,
2911
- delegatedRunId: payload?.delegatedRunId,
3296
+ delegatedRunId: readPayloadId(
3297
+ cmd.payload,
3298
+ "delegatedRunId"
3299
+ ),
2912
3300
  launchStatus: "failed"
2913
3301
  });
2914
3302
  await this.postAgentMessage(cmd.liveActivityId, "status", errorMessage);
@@ -3012,6 +3400,7 @@ function currentGitBranch(cwd) {
3012
3400
  return execSync2("git rev-parse --abbrev-ref HEAD", {
3013
3401
  encoding: "utf-8",
3014
3402
  cwd,
3403
+ stdio: ["ignore", "pipe", "ignore"],
3015
3404
  timeout: 3e3
3016
3405
  }).trim();
3017
3406
  } catch {
@@ -3022,7 +3411,14 @@ function buildManagedLaunchCommand(provider, prompt2) {
3022
3411
  if (provider === "codex") {
3023
3412
  return `codex --no-alt-screen -a never ${shellQuote(prompt2)}`;
3024
3413
  }
3025
- return `claude --permission-mode bypassPermissions --dangerously-skip-permissions ${shellQuote(prompt2)}`;
3414
+ if (provider === "claude_code") {
3415
+ return `claude --permission-mode bypassPermissions --dangerously-skip-permissions ${shellQuote(prompt2)}`;
3416
+ }
3417
+ if (provider === "cursor")
3418
+ return `cursor-agent --print ${shellQuote(prompt2)}`;
3419
+ if (provider === "copilot") return `copilot ${shellQuote(prompt2)}`;
3420
+ if (provider === "opencode") return `opencode run ${shellQuote(prompt2)}`;
3421
+ return `pi ${shellQuote(prompt2)}`;
3026
3422
  }
3027
3423
  function sanitizeTmuxName(value) {
3028
3424
  return value.replace(/[^a-z0-9_-]+/gi, "-").replace(/^-+|-+$/g, "") || "work";
@@ -3079,11 +3475,18 @@ function persistDeviceKey(deviceKey) {
3079
3475
  writeFileSync(DEVICE_KEY_FILE, `${deviceKey}
3080
3476
  `);
3081
3477
  }
3082
- function buildLaunchPrompt(issueKey, issueTitle, workspacePath, issueDescription) {
3478
+ function buildLaunchPrompt(issueKey, issueTitle, workspacePath, issueDescription, issueContext, initialPrompt) {
3083
3479
  const lines = [`You are working on issue ${issueKey}: ${issueTitle}`];
3084
3480
  if (issueDescription?.trim()) {
3085
3481
  lines.push("", "Issue description:", issueDescription.trim());
3086
3482
  }
3483
+ const contextLines = formatIssueContext(issueContext);
3484
+ if (contextLines.length > 0) {
3485
+ lines.push("", "Vector context:", ...contextLines);
3486
+ }
3487
+ if (initialPrompt?.trim()) {
3488
+ lines.push("", "User instruction:", initialPrompt.trim());
3489
+ }
3087
3490
  lines.push(
3088
3491
  "",
3089
3492
  `The repository is at ${workspacePath}.`,
@@ -3093,6 +3496,90 @@ function buildLaunchPrompt(issueKey, issueTitle, workspacePath, issueDescription
3093
3496
  );
3094
3497
  return lines.join("\n");
3095
3498
  }
3499
+ function buildFollowUpPrompt(userMessage, issueContext) {
3500
+ const contextLines = formatIssueContext(issueContext);
3501
+ if (contextLines.length === 0) {
3502
+ return userMessage;
3503
+ }
3504
+ return [
3505
+ "Vector context for the current issue:",
3506
+ ...contextLines,
3507
+ "",
3508
+ "User message:",
3509
+ userMessage
3510
+ ].join("\n");
3511
+ }
3512
+ function formatIssueContext(issueContext) {
3513
+ const lines = [];
3514
+ const organization = readPayloadValue(issueContext, "organization");
3515
+ const organizationName = readPayloadString(organization, "name");
3516
+ const organizationSlug = readPayloadString(organization, "slug");
3517
+ if (organizationName || organizationSlug) {
3518
+ lines.push(
3519
+ `- Organization: ${[organizationName, organizationSlug ? `(${organizationSlug})` : void 0].filter(Boolean).join(" ")}`
3520
+ );
3521
+ }
3522
+ const team = readPayloadValue(issueContext, "team");
3523
+ const teamName = readPayloadString(team, "name");
3524
+ const teamKey = readPayloadString(team, "key");
3525
+ if (teamName || teamKey) {
3526
+ lines.push(
3527
+ `- Team: ${[teamName, teamKey ? `(${teamKey})` : void 0].filter(Boolean).join(" ")}`
3528
+ );
3529
+ }
3530
+ const project = readPayloadValue(issueContext, "project");
3531
+ const projectName = readPayloadString(project, "name");
3532
+ const projectKey = readPayloadString(project, "key");
3533
+ const projectDescription = readPayloadString(project, "description");
3534
+ if (projectName || projectKey) {
3535
+ lines.push(
3536
+ `- Project: ${[projectName, projectKey ? `(${projectKey})` : void 0].filter(Boolean).join(" ")}`
3537
+ );
3538
+ }
3539
+ if (projectDescription) {
3540
+ lines.push(`- Project description: ${projectDescription}`);
3541
+ }
3542
+ const state = readPayloadValue(issueContext, "state");
3543
+ const stateName = readPayloadString(state, "name");
3544
+ const stateType = readPayloadString(state, "type");
3545
+ if (stateName || stateType) {
3546
+ lines.push(
3547
+ `- State: ${[stateName, stateType ? `(${stateType})` : void 0].filter(Boolean).join(" ")}`
3548
+ );
3549
+ }
3550
+ const priority = readPayloadString(issueContext, "priority");
3551
+ if (priority) lines.push(`- Priority: ${priority}`);
3552
+ const reporter = readPayloadString(issueContext, "reporter");
3553
+ if (reporter) lines.push(`- Reporter: ${reporter}`);
3554
+ const assignees = readPayloadStringArray(issueContext, "assignees");
3555
+ if (assignees.length > 0) {
3556
+ lines.push(`- Assignees: ${assignees.join(", ")}`);
3557
+ }
3558
+ const labels = readPayloadStringArray(issueContext, "labels");
3559
+ if (labels.length > 0) {
3560
+ lines.push(`- Labels: ${labels.join(", ")}`);
3561
+ }
3562
+ const dates = readPayloadValue(issueContext, "dates");
3563
+ const startDate = readPayloadString(dates, "startDate");
3564
+ const dueDate = readPayloadString(dates, "dueDate");
3565
+ if (startDate || dueDate) {
3566
+ lines.push(
3567
+ `- Dates: ${[startDate ? `start ${startDate}` : void 0, dueDate ? `due ${dueDate}` : void 0].filter(Boolean).join(", ")}`
3568
+ );
3569
+ }
3570
+ const recentComments = readPayloadArray(issueContext, "recentComments");
3571
+ if (recentComments.length > 0) {
3572
+ lines.push("- Recent comments:");
3573
+ for (const comment of recentComments) {
3574
+ const author = readPayloadString(comment, "authorName") ?? "Unknown";
3575
+ const body = readPayloadString(comment, "body");
3576
+ if (body) {
3577
+ lines.push(` - ${author}: ${body}`);
3578
+ }
3579
+ }
3580
+ }
3581
+ return lines;
3582
+ }
3096
3583
  function summarizeMessage(message) {
3097
3584
  if (!message) {
3098
3585
  return void 0;
@@ -3174,16 +3661,72 @@ function compareLocalSessionRecency(a, b) {
3174
3661
  function sleep(ms) {
3175
3662
  return new Promise((resolve) => setTimeout(resolve, ms));
3176
3663
  }
3664
+ function readPayloadValue(payload, key) {
3665
+ return payload !== null && typeof payload === "object" ? Reflect.get(payload, key) : void 0;
3666
+ }
3667
+ function readPayloadString(payload, key) {
3668
+ const value = readPayloadValue(payload, key);
3669
+ return typeof value === "string" && value.trim() ? value : void 0;
3670
+ }
3671
+ function readPayloadNumber(payload, key) {
3672
+ const value = readPayloadValue(payload, key);
3673
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3674
+ }
3675
+ function readPayloadBoolean(payload, key) {
3676
+ const value = readPayloadValue(payload, key);
3677
+ return typeof value === "boolean" ? value : void 0;
3678
+ }
3679
+ function readPayloadPermissionMode(payload, key) {
3680
+ const value = readPayloadValue(payload, key);
3681
+ return value === "plan" || value === "ask" || value === "bypass" ? value : void 0;
3682
+ }
3683
+ function readPayloadThinkingLevel(payload, key) {
3684
+ const value = readPayloadValue(payload, key);
3685
+ return value === "off" || value === "low" || value === "medium" || value === "high" || value === "max" || value === "xhigh" ? value : void 0;
3686
+ }
3687
+ function readPayloadContextLength(payload, key) {
3688
+ const value = readPayloadValue(payload, key);
3689
+ return value === "default" || value === "extended" ? value : void 0;
3690
+ }
3691
+ function readPayloadArray(payload, key) {
3692
+ const value = readPayloadValue(payload, key);
3693
+ return Array.isArray(value) ? value : [];
3694
+ }
3695
+ function readPayloadStringArray(payload, key) {
3696
+ return readPayloadArray(payload, key).filter(
3697
+ (value) => typeof value === "string" && value.trim() !== ""
3698
+ );
3699
+ }
3700
+ function readPayloadId(payload, key) {
3701
+ const value = readPayloadString(payload, key);
3702
+ return value;
3703
+ }
3704
+ function readPayloadAgentProvider(payload, key) {
3705
+ const value = readPayloadValue(payload, key);
3706
+ return value === "codex" || value === "claude_code" || value === "cursor" || value === "copilot" || value === "opencode" || value === "pi" || value === "vector_cli" ? value : void 0;
3707
+ }
3177
3708
  function isBridgeProvider(provider) {
3178
- return provider === "codex" || provider === "claude_code";
3709
+ return provider === "codex" || provider === "claude_code" || provider === "cursor" || provider === "copilot" || provider === "opencode" || provider === "pi";
3179
3710
  }
3180
- function providerLabel(provider) {
3711
+ function providerLabel2(provider) {
3181
3712
  if (provider === "codex") {
3182
3713
  return "Codex";
3183
3714
  }
3184
3715
  if (provider === "claude_code") {
3185
3716
  return "Claude";
3186
3717
  }
3718
+ if (provider === "cursor") {
3719
+ return "Cursor";
3720
+ }
3721
+ if (provider === "copilot") {
3722
+ return "GitHub Copilot";
3723
+ }
3724
+ if (provider === "opencode") {
3725
+ return "OpenCode";
3726
+ }
3727
+ if (provider === "pi") {
3728
+ return "Pi";
3729
+ }
3187
3730
  return "Vector CLI";
3188
3731
  }
3189
3732
  function installLaunchAgent(vcliPath) {
@@ -3401,13 +3944,33 @@ function killExistingMenuBar() {
3401
3944
  }
3402
3945
  }
3403
3946
  }
3947
+ function getRunningMenuBarPid() {
3948
+ if (!existsSync3(MENUBAR_PID_FILE)) {
3949
+ return null;
3950
+ }
3951
+ try {
3952
+ const pid = Number(readFileSync2(MENUBAR_PID_FILE, "utf-8").trim());
3953
+ if (Number.isFinite(pid) && pid > 0 && isKnownMenuBarProcess(pid)) {
3954
+ process.kill(pid, 0);
3955
+ return pid;
3956
+ }
3957
+ } catch {
3958
+ }
3959
+ try {
3960
+ unlinkSync(MENUBAR_PID_FILE);
3961
+ } catch {
3962
+ }
3963
+ return null;
3964
+ }
3404
3965
  async function launchMenuBar() {
3405
3966
  if (platform() !== "darwin") return;
3406
3967
  removeLegacyMenuBarLaunchAgent();
3407
3968
  const executable = findMenuBarExecutable();
3408
3969
  const cliInvocation = getCurrentCliInvocation();
3409
3970
  if (!executable || !cliInvocation) return;
3410
- killExistingMenuBar();
3971
+ if (getRunningMenuBarPid()) {
3972
+ return;
3973
+ }
3411
3974
  try {
3412
3975
  const { spawn: spawnChild } = await import("child_process");
3413
3976
  const child = spawnChild(executable, [], {
@@ -5725,7 +6288,7 @@ serviceCommand.command("stop").description("Stop the bridge service").action(()
5725
6288
  console.log("Bridge is not running.");
5726
6289
  }
5727
6290
  });
5728
- serviceCommand.command("status").description("Show bridge service status").action((_options, command) => {
6291
+ serviceCommand.command("status").description("Show bridge service status").action(() => {
5729
6292
  const status = getBridgeStatus();
5730
6293
  if (!status.configured) {
5731
6294
  console.log("Bridge not configured. Run: vcli service start");
@@ -6022,7 +6585,7 @@ program.command("update").description("Update the CLI to the latest version").ac
6022
6585
  timeout: 12e4
6023
6586
  });
6024
6587
  s.stop("CLI updated successfully.");
6025
- } catch (err) {
6588
+ } catch {
6026
6589
  s.stop("Update failed.");
6027
6590
  log.error(`Run manually: ${install.command.join(" ")}`);
6028
6591
  return;