@standardagents/code 0.9.10 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import os7, { homedir } from 'os';
3
- import path3 from 'path';
2
+ import os8, { homedir } from 'os';
3
+ import path4 from 'path';
4
4
  import readline2 from 'readline/promises';
5
5
  import { stdout, stdin } from 'process';
6
- import fs4 from 'fs';
6
+ import net from 'net';
7
+ import fs5 from 'fs';
7
8
  import fsp from 'fs/promises';
8
9
  import crypto from 'crypto';
9
10
  import { spawn, execFileSync, spawnSync, execFile } from 'child_process';
@@ -365,6 +366,25 @@ var ApiClient = class {
365
366
  user_id: t.user_id ?? null
366
367
  })).filter((t) => !me || !t.user_id || t.user_id === me).filter((t) => requireTags.every((tag) => t.tags.includes(tag)));
367
368
  }
369
+ /**
370
+ * Fetch one thread's summary (id, tags, created_at, terminated). Used by the
371
+ * daemon's hub wake handler to resolve a cold thread's runner tag + project
372
+ * before attaching. Returns null if the thread is gone or unreadable.
373
+ */
374
+ async getThread(threadId) {
375
+ try {
376
+ const t = await this.json(`/api/threads/${threadId}`);
377
+ if (!t || !t.id) return null;
378
+ return {
379
+ id: t.id,
380
+ tags: Array.isArray(t.tags) ? t.tags : [],
381
+ created_at: t.created_at,
382
+ terminated: t.terminated ?? null
383
+ };
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
368
388
  /** The authenticated user's id (cached). Null when the instance doesn't
369
389
  * report one (super-admin sessions, very old instances). */
370
390
  meUserId;
@@ -805,6 +825,185 @@ var Heartbeat = class {
805
825
  this.onDead();
806
826
  }
807
827
  };
828
+ var ALLOWED_HOST = "chatgpt.com";
829
+ var ALLOWED_PORT = 443;
830
+ var CHUNK_BYTES = 256 * 1024;
831
+ var TunnelManager = class {
832
+ constructor(send) {
833
+ this.send = send;
834
+ }
835
+ send;
836
+ tunnels = /* @__PURE__ */ new Map();
837
+ /** Whether a stream_* frame belongs to this manager. */
838
+ handles(type) {
839
+ return type === "stream_open" || type === "stream_data" || type === "stream_end" || type === "stream_abort";
840
+ }
841
+ /** Dispatch one decoded stream_* frame. */
842
+ handleFrame(msg) {
843
+ const id = typeof msg.id === "string" ? msg.id : null;
844
+ if (!id) return;
845
+ switch (msg.type) {
846
+ case "stream_open":
847
+ this.open(msg);
848
+ return;
849
+ case "stream_data": {
850
+ const tunnel = this.tunnels.get(id);
851
+ if (!tunnel) return;
852
+ if (typeof msg.data !== "string") return;
853
+ try {
854
+ tunnel.socket.write(Buffer.from(msg.data, "base64"));
855
+ } catch {
856
+ this.fail(id, "write to upstream failed");
857
+ }
858
+ return;
859
+ }
860
+ case "stream_end": {
861
+ const tunnel = this.tunnels.get(id);
862
+ if (tunnel) {
863
+ try {
864
+ tunnel.socket.end();
865
+ } catch {
866
+ }
867
+ }
868
+ return;
869
+ }
870
+ case "stream_abort": {
871
+ this.teardown(id);
872
+ return;
873
+ }
874
+ }
875
+ }
876
+ open(frame) {
877
+ const id = frame.id;
878
+ if (frame.tool !== "tcp_tunnel") {
879
+ this.send({ type: "stream_error", id, error: `Unsupported stream tool: ${frame.tool}` });
880
+ return;
881
+ }
882
+ const host = typeof frame.meta?.host === "string" ? frame.meta.host : "";
883
+ const port = typeof frame.meta?.port === "number" ? frame.meta.port : 0;
884
+ if (host !== ALLOWED_HOST || port !== ALLOWED_PORT) {
885
+ this.send({
886
+ type: "stream_error",
887
+ id,
888
+ error: `Tunnel refused: only ${ALLOWED_HOST}:${ALLOWED_PORT} is allowed (requested ${host}:${port}).`
889
+ });
890
+ return;
891
+ }
892
+ let opened = false;
893
+ const socket = net.connect({ host, port });
894
+ socket.setNoDelay(true);
895
+ this.tunnels.set(id, { socket });
896
+ socket.on("connect", () => {
897
+ opened = true;
898
+ this.send({ type: "stream_opened", id });
899
+ });
900
+ socket.on("data", (data) => {
901
+ for (let i = 0; i < data.length; i += CHUNK_BYTES) {
902
+ const slice = data.subarray(i, i + CHUNK_BYTES);
903
+ this.send({ type: "stream_data", id, data: slice.toString("base64") });
904
+ }
905
+ });
906
+ socket.on("end", () => {
907
+ this.send({ type: "stream_end", id });
908
+ this.tunnels.delete(id);
909
+ });
910
+ socket.on("error", (error) => {
911
+ if (!opened) {
912
+ this.send({ type: "stream_error", id, error: `Tunnel connect failed: ${error.message}` });
913
+ } else {
914
+ this.send({ type: "stream_abort", id, error: error.message });
915
+ }
916
+ this.tunnels.delete(id);
917
+ });
918
+ socket.on("close", () => {
919
+ if (this.tunnels.has(id)) {
920
+ this.send({ type: "stream_end", id });
921
+ this.tunnels.delete(id);
922
+ }
923
+ });
924
+ }
925
+ fail(id, error) {
926
+ this.send({ type: "stream_abort", id, error });
927
+ this.teardown(id);
928
+ }
929
+ teardown(id) {
930
+ const tunnel = this.tunnels.get(id);
931
+ if (!tunnel) return;
932
+ this.tunnels.delete(id);
933
+ try {
934
+ tunnel.socket.destroy();
935
+ } catch {
936
+ }
937
+ }
938
+ /** Destroy every open tunnel (bridge teardown). */
939
+ closeAll() {
940
+ for (const id of Array.from(this.tunnels.keys())) {
941
+ this.teardown(id);
942
+ }
943
+ }
944
+ };
945
+ var MAX_ENTRIES = 200;
946
+ var MAX_RESULT_BYTES = 256 * 1024;
947
+ function ledgerDir() {
948
+ return path4.join(os8.homedir(), ".standardagents", "tool-ledger");
949
+ }
950
+ var ToolLedger = class {
951
+ constructor(threadId) {
952
+ this.threadId = threadId;
953
+ const safe = encodeURIComponent(threadId);
954
+ this.file = path4.join(ledgerDir(), `${safe}.json`);
955
+ }
956
+ threadId;
957
+ entries = /* @__PURE__ */ new Map();
958
+ file;
959
+ loaded = false;
960
+ load() {
961
+ if (this.loaded) return;
962
+ this.loaded = true;
963
+ try {
964
+ const raw = fs5.readFileSync(this.file, "utf8");
965
+ const parsed = JSON.parse(raw);
966
+ for (const [id, entry] of Object.entries(parsed)) {
967
+ if (entry && typeof entry.ok === "boolean") this.entries.set(id, entry);
968
+ }
969
+ } catch {
970
+ }
971
+ }
972
+ /** The cached result for a completed call, or null if we have not run it. */
973
+ get(toolCallId) {
974
+ this.load();
975
+ return this.entries.get(toolCallId) ?? null;
976
+ }
977
+ /** Record a completed call's result, then persist (best effort). */
978
+ record(toolCallId, result) {
979
+ this.load();
980
+ this.entries.set(toolCallId, { ...result, at: Date.now() });
981
+ this.evict();
982
+ this.persist();
983
+ }
984
+ evict() {
985
+ if (this.entries.size <= MAX_ENTRIES) return;
986
+ const overflow = this.entries.size - MAX_ENTRIES;
987
+ let dropped = 0;
988
+ for (const key of this.entries.keys()) {
989
+ if (dropped >= overflow) break;
990
+ this.entries.delete(key);
991
+ dropped++;
992
+ }
993
+ }
994
+ persist() {
995
+ try {
996
+ fs5.mkdirSync(ledgerDir(), { recursive: true });
997
+ const out = {};
998
+ for (const [id, entry] of this.entries) {
999
+ const size = entry.result ? Buffer.byteLength(entry.result) : 0;
1000
+ out[id] = size > MAX_RESULT_BYTES ? { ...entry, result: void 0 } : entry;
1001
+ }
1002
+ fs5.writeFileSync(this.file, JSON.stringify(out), { mode: 384 });
1003
+ } catch {
1004
+ }
1005
+ }
1006
+ };
808
1007
 
809
1008
  // src/render.ts
810
1009
  var RESET = "\x1B[0m";
@@ -1025,6 +1224,8 @@ var Bridge = class {
1025
1224
  this.perm = perm;
1026
1225
  this.hooks = hooks;
1027
1226
  this.identity = identity;
1227
+ this.tunnels = new TunnelManager((payload) => this.send(payload));
1228
+ this.ledger = new ToolLedger(this.threadId);
1028
1229
  }
1029
1230
  api;
1030
1231
  threadId;
@@ -1042,6 +1243,14 @@ var Bridge = class {
1042
1243
  // Durable forwarded calls we've started handling, so a server re-send (after a
1043
1244
  // reconnect) doesn't prompt or run them twice.
1044
1245
  handledDurable = /* @__PURE__ */ new Set();
1246
+ // Raw TCP tunnels (Sama One's direct egress): the Worker terminates TLS over
1247
+ // these; we only shuttle ciphertext. Bound to this socket, torn down with it.
1248
+ tunnels;
1249
+ // Durable, exactly-once ledger of completed forwarded calls. Survives a crash
1250
+ // AFTER execution but BEFORE delivery: on redelivery we return the cached
1251
+ // result instead of re-running the side-effecting operation. A cache — the
1252
+ // server's pending table stays the source of truth.
1253
+ ledger;
1045
1254
  // Whether this client currently holds execution ownership. Starts true so an
1046
1255
  // instance that predates the owner concept (no owner field in bridge_ready)
1047
1256
  // behaves exactly as before; an owner-aware instance sets it on every connect.
@@ -1144,6 +1353,7 @@ var Bridge = class {
1144
1353
  if (this.ws !== ws) return;
1145
1354
  this.ws = null;
1146
1355
  this.stopHeartbeat();
1356
+ this.tunnels.closeAll();
1147
1357
  this.scheduleReconnect();
1148
1358
  }
1149
1359
  scheduleReconnect() {
@@ -1176,6 +1386,7 @@ var Bridge = class {
1176
1386
  close() {
1177
1387
  this.closed = true;
1178
1388
  this.stopHeartbeat();
1389
+ this.tunnels.closeAll();
1179
1390
  if (this.reconnectTimer) {
1180
1391
  clearTimeout(this.reconnectTimer);
1181
1392
  this.reconnectTimer = null;
@@ -1249,6 +1460,11 @@ var Bridge = class {
1249
1460
  this.hooks.onSuperseded?.();
1250
1461
  return;
1251
1462
  }
1463
+ if (this.tunnels.handles(msg.type)) {
1464
+ if (!this.owner) return;
1465
+ this.tunnels.handleFrame(msg);
1466
+ return;
1467
+ }
1252
1468
  if (msg.type !== "tool_request") return;
1253
1469
  if (!this.owner) return;
1254
1470
  const req = msg;
@@ -1281,6 +1497,11 @@ var Bridge = class {
1281
1497
  if (req.durable && req.toolCallId) {
1282
1498
  if (this.handledDurable.has(req.toolCallId)) return;
1283
1499
  this.handledDurable.add(req.toolCallId);
1500
+ const cached = this.ledger.get(req.toolCallId);
1501
+ if (cached) {
1502
+ this.respond(req, cached.ok, cached.result, cached.error);
1503
+ return;
1504
+ }
1284
1505
  }
1285
1506
  const summary = describe(req);
1286
1507
  let effectiveRisk = typeof req.risk === "number" ? req.risk : req.requestPermission ? 3 : 1;
@@ -1328,6 +1549,13 @@ var Bridge = class {
1328
1549
  } finally {
1329
1550
  this.hooks.onStatus?.(callKey, null);
1330
1551
  }
1552
+ if (req.durable && req.toolCallId) {
1553
+ this.ledger.record(req.toolCallId, {
1554
+ ok: result.ok,
1555
+ result: result.ok ? result.result ?? "" : void 0,
1556
+ error: result.ok ? void 0 : result.error
1557
+ });
1558
+ }
1331
1559
  if (result.ok) {
1332
1560
  const { display, detail } = activityPresentation(req, summary);
1333
1561
  this.hooks.onActivity(`\u2713 ${display}${detailSuffix(req.tool, result.result)}`, detail);
@@ -1433,7 +1661,7 @@ function detailSuffix(tool, result) {
1433
1661
  const lines = result.split("\n").length;
1434
1662
  return ` (${lines} line${lines === 1 ? "" : "s"})`;
1435
1663
  }
1436
- var LOG_DIR = path3.join(os7.homedir(), ".standardagents", "process-logs");
1664
+ var LOG_DIR = path4.join(os8.homedir(), ".standardagents", "process-logs");
1437
1665
  var KEY2 = "bg_processes";
1438
1666
  function isAlive(pid) {
1439
1667
  try {
@@ -1510,11 +1738,11 @@ var ProcessRegistry = class {
1510
1738
  }
1511
1739
  };
1512
1740
  function configFile() {
1513
- return process.env.STANDARDAGENTS_MCP_CONFIG || path3.join(os7.homedir(), ".standardagents", "mcp.json");
1741
+ return process.env.STANDARDAGENTS_MCP_CONFIG || path4.join(os8.homedir(), ".standardagents", "mcp.json");
1514
1742
  }
1515
1743
  function loadMcpConfig() {
1516
1744
  try {
1517
- const raw = fs4.readFileSync(configFile(), "utf8");
1745
+ const raw = fs5.readFileSync(configFile(), "utf8");
1518
1746
  const parsed = JSON.parse(raw);
1519
1747
  if (!parsed.servers || typeof parsed.servers !== "object") parsed.servers = {};
1520
1748
  return parsed;
@@ -1545,8 +1773,8 @@ function setMcpServerEnabled(name, enabled) {
1545
1773
  }
1546
1774
  function write(cfg) {
1547
1775
  const file2 = configFile();
1548
- fs4.mkdirSync(path3.dirname(file2), { recursive: true });
1549
- fs4.writeFileSync(file2, JSON.stringify(cfg, null, 2), { mode: 384 });
1776
+ fs5.mkdirSync(path4.dirname(file2), { recursive: true });
1777
+ fs5.writeFileSync(file2, JSON.stringify(cfg, null, 2), { mode: 384 });
1550
1778
  }
1551
1779
  function parseServerSpec(spec) {
1552
1780
  const trimmed = spec.trim();
@@ -1629,13 +1857,13 @@ var HostTools = class {
1629
1857
  /** Resolve a user/model-supplied path against the project directory. */
1630
1858
  resolve(p) {
1631
1859
  if (!p || p === ".") return this.projectDir;
1632
- return path3.resolve(this.projectDir, p);
1860
+ return path4.resolve(this.projectDir, p);
1633
1861
  }
1634
1862
  /** True when the resolved path escapes the project directory. */
1635
1863
  isOutsideProject(p) {
1636
1864
  const abs = this.resolve(p);
1637
- const rel = path3.relative(this.projectDir, abs);
1638
- return rel.startsWith("..") || path3.isAbsolute(rel);
1865
+ const rel = path4.relative(this.projectDir, abs);
1866
+ return rel.startsWith("..") || path4.isAbsolute(rel);
1639
1867
  }
1640
1868
  async execute(tool, args) {
1641
1869
  try {
@@ -1722,18 +1950,18 @@ var HostTools = class {
1722
1950
  const matches = await this.walkGlob(base, pattern);
1723
1951
  return { ok: true, result: matches.slice(0, 300).join("\n") || "(no files)" };
1724
1952
  }
1725
- const rel = rg.stdout.trim().split("\n").filter(Boolean).map((p) => path3.relative(this.projectDir, p)).slice(0, 300);
1953
+ const rel = rg.stdout.trim().split("\n").filter(Boolean).map((p) => path4.relative(this.projectDir, p)).slice(0, 300);
1726
1954
  return { ok: true, result: rel.join("\n") || "(no files)" };
1727
1955
  }
1728
1956
  async writeFile(args) {
1729
1957
  const file2 = this.resolve(String(args.path || ""));
1730
1958
  const content = String(args.content ?? "");
1731
- await fsp.mkdir(path3.dirname(file2), { recursive: true });
1732
- const existed = fs4.existsSync(file2);
1959
+ await fsp.mkdir(path4.dirname(file2), { recursive: true });
1960
+ const existed = fs5.existsSync(file2);
1733
1961
  await fsp.writeFile(file2, content, "utf8");
1734
1962
  return {
1735
1963
  ok: true,
1736
- result: `${existed ? "Overwrote" : "Created"} ${path3.relative(this.projectDir, file2)} (${Buffer.byteLength(content)} bytes)`
1964
+ result: `${existed ? "Overwrote" : "Created"} ${path4.relative(this.projectDir, file2)} (${Buffer.byteLength(content)} bytes)`
1737
1965
  };
1738
1966
  }
1739
1967
  async editFile(args) {
@@ -1752,7 +1980,7 @@ var HostTools = class {
1752
1980
  }
1753
1981
  const updated = replaceAll ? content.split(oldStr).join(newStr) : content.replace(oldStr, newStr);
1754
1982
  await fsp.writeFile(file2, updated, "utf8");
1755
- return { ok: true, result: `Edited ${path3.relative(this.projectDir, file2)} (${count} replacement${count === 1 ? "" : "s"})` };
1983
+ return { ok: true, result: `Edited ${path4.relative(this.projectDir, file2)} (${count} replacement${count === 1 ? "" : "s"})` };
1756
1984
  }
1757
1985
  /**
1758
1986
  * Copy a file from the THREAD filesystem (e.g. a generated /attachments/*
@@ -1769,12 +1997,12 @@ var HostTools = class {
1769
1997
  const bytes = await this.api.fetchFile(this.threadId, source);
1770
1998
  if (!bytes) return { ok: false, error: `Could not download ${source} from the thread filesystem.` };
1771
1999
  const dest = this.resolve(destArg);
1772
- await fsp.mkdir(path3.dirname(dest), { recursive: true });
1773
- const existed = fs4.existsSync(dest);
2000
+ await fsp.mkdir(path4.dirname(dest), { recursive: true });
2001
+ const existed = fs5.existsSync(dest);
1774
2002
  await fsp.writeFile(dest, bytes);
1775
2003
  return {
1776
2004
  ok: true,
1777
- result: `${existed ? "Overwrote" : "Saved"} ${path3.relative(this.projectDir, dest)} (${bytes.length} bytes) from ${source}`
2005
+ result: `${existed ? "Overwrote" : "Saved"} ${path4.relative(this.projectDir, dest)} (${bytes.length} bytes) from ${source}`
1778
2006
  };
1779
2007
  }
1780
2008
  /**
@@ -1812,16 +2040,16 @@ var HostTools = class {
1812
2040
  }
1813
2041
  }
1814
2042
  const hash = crypto.createHash("sha256").update(JSON.stringify(files)).digest("hex").slice(0, 12);
1815
- const skillDir = path3.join(os7.tmpdir(), "standardcode-skills", `${skill}-${hash}`);
2043
+ const skillDir = path4.join(os8.tmpdir(), "standardcode-skills", `${skill}-${hash}`);
1816
2044
  for (const f of files) {
1817
- const dest = path3.resolve(skillDir, f.path);
1818
- if (path3.relative(skillDir, dest).startsWith("..")) {
2045
+ const dest = path4.resolve(skillDir, f.path);
2046
+ if (path4.relative(skillDir, dest).startsWith("..")) {
1819
2047
  return { ok: false, error: `Skill file escapes its directory: ${f.path}` };
1820
2048
  }
1821
- await fsp.mkdir(path3.dirname(dest), { recursive: true });
2049
+ await fsp.mkdir(path4.dirname(dest), { recursive: true });
1822
2050
  await fsp.writeFile(dest, f.content, "utf8");
1823
2051
  }
1824
- const entryPath = path3.resolve(skillDir, entry);
2052
+ const entryPath = path4.resolve(skillDir, entry);
1825
2053
  const entryContent = files.find((f) => f.path === entry).content;
1826
2054
  let cmd;
1827
2055
  let argv;
@@ -1830,7 +2058,7 @@ var HostTools = class {
1830
2058
  cmd = entryPath;
1831
2059
  argv = scriptArgs;
1832
2060
  } else {
1833
- const ext = path3.extname(entry).toLowerCase();
2061
+ const ext = path4.extname(entry).toLowerCase();
1834
2062
  const interp = {
1835
2063
  ".py": ["python3"],
1836
2064
  ".sh": ["bash"],
@@ -1900,15 +2128,15 @@ ${truncated}`
1900
2128
  const command = String(args.command || "");
1901
2129
  if (!command.trim()) return { ok: false, error: "command is required" };
1902
2130
  const cwd = args.cwd ? this.resolve(String(args.cwd)) : this.projectDir;
1903
- if (!fs4.existsSync(cwd)) {
2131
+ if (!fs5.existsSync(cwd)) {
1904
2132
  return { ok: false, error: `cwd does not exist: ${cwd}` };
1905
2133
  }
1906
2134
  const id = crypto.randomUUID().slice(0, 8);
1907
- const logPath = path3.join(LOG_DIR, `${id}.log`);
2135
+ const logPath = path4.join(LOG_DIR, `${id}.log`);
1908
2136
  let out;
1909
2137
  try {
1910
2138
  await fsp.mkdir(LOG_DIR, { recursive: true });
1911
- out = fs4.openSync(logPath, "a");
2139
+ out = fs5.openSync(logPath, "a");
1912
2140
  } catch (err) {
1913
2141
  return { ok: false, error: `Could not open log file: ${err instanceof Error ? err.message : String(err)}` };
1914
2142
  }
@@ -1916,14 +2144,14 @@ ${truncated}`
1916
2144
  try {
1917
2145
  child = spawn("bash", ["-lc", command], { cwd, detached: true, stdio: ["ignore", out, out] });
1918
2146
  } catch (err) {
1919
- fs4.closeSync(out);
2147
+ fs5.closeSync(out);
1920
2148
  return { ok: false, error: `Failed to start: ${err instanceof Error ? err.message : String(err)}` };
1921
2149
  }
1922
2150
  let spawnError = null;
1923
2151
  child.on("error", (err) => {
1924
2152
  spawnError = err;
1925
2153
  });
1926
- fs4.closeSync(out);
2154
+ fs5.closeSync(out);
1927
2155
  const pid = child.pid;
1928
2156
  let earlyExit;
1929
2157
  const onEarlyExit = (code) => {
@@ -2179,10 +2407,10 @@ ${tail}` : " No output was captured.")
2179
2407
  const entries = await fsp.readdir(dir2, { withFileTypes: true }).catch(() => []);
2180
2408
  for (const e of entries) {
2181
2409
  if (e.name === ".git" || e.name === "node_modules") continue;
2182
- const full = path3.join(dir2, e.name);
2410
+ const full = path4.join(dir2, e.name);
2183
2411
  if (e.isDirectory()) await walk(full);
2184
2412
  else {
2185
- const rel = path3.relative(this.projectDir, full);
2413
+ const rel = path4.relative(this.projectDir, full);
2186
2414
  if (re.test(rel) || re.test(e.name)) out.push(rel);
2187
2415
  }
2188
2416
  }
@@ -3052,15 +3280,44 @@ function parseToolCalls(message) {
3052
3280
  return [{ id, name, arguments: parseObject(item?.function?.arguments ?? item?.arguments ?? item?.args) }];
3053
3281
  });
3054
3282
  }
3055
- function deriveSessionActivity(messages) {
3283
+ var EMPTY_BOUNDARY_STALE_MS = 15e4;
3284
+ var WAITING_FOR_CLIENT_MARKER = "Waiting for your machine to reconnect";
3285
+ var PENDING_TAIL_STALE_MS = 10 * 6e4;
3286
+ var BUSY_TAIL_STALE_MS = 30 * 6e4;
3287
+ function toMs(raw) {
3288
+ return raw > 1e14 ? raw / 1e3 : raw < 1e12 ? raw * 1e3 : raw;
3289
+ }
3290
+ function deriveSessionActivity(messages, nowMs = Date.now()) {
3056
3291
  const visible = messages.filter((message) => message.silent !== true && message.metadata?.silent !== true).sort((a, b) => createdAt(a) - createdAt(b));
3057
- if (visible.some((message) => message.status === "pending")) return { busy: true, currentTool: unresolvedTool(visible) };
3292
+ if (visible.some((message) => message.status === "pending")) {
3293
+ const lastPending = [...visible].reverse().find((m) => m.status === "pending");
3294
+ const pendingMs = lastPending ? toMs(createdAt(lastPending)) : 0;
3295
+ if (pendingMs > 0 && nowMs - pendingMs > PENDING_TAIL_STALE_MS) {
3296
+ return { busy: false, currentTool: null };
3297
+ }
3298
+ return { busy: true, currentTool: unresolvedTool(visible) };
3299
+ }
3058
3300
  const last = visible.at(-1);
3059
3301
  if (!last) return { busy: false, currentTool: null };
3302
+ if (last.role === "tool" && messageText(last.content).includes(WAITING_FOR_CLIENT_MARKER)) {
3303
+ const tool = unresolvedTool(visible);
3304
+ return { busy: true, currentTool: { ...tool ?? { id: last.tool_call_id ?? "", name: "forwarded", arguments: {} }, waitingForHost: true } };
3305
+ }
3060
3306
  const currentTool = unresolvedTool(visible);
3061
3307
  if (currentTool) return { busy: true, currentTool };
3062
- if (last.role === "user" || last.role === "tool") return { busy: true, currentTool: null };
3308
+ if (last.role === "user" || last.role === "tool") {
3309
+ const lastMs = toMs(createdAt(last));
3310
+ if (lastMs > 0 && nowMs - lastMs > BUSY_TAIL_STALE_MS) {
3311
+ return { busy: false, currentTool: null };
3312
+ }
3313
+ return { busy: true, currentTool: null };
3314
+ }
3063
3315
  if (last.role === "assistant" && last.status !== "failed" && !messageText(last.content).trim()) {
3316
+ const raw = createdAt(last);
3317
+ const lastMs = raw > 1e14 ? raw / 1e3 : raw < 1e12 ? raw * 1e3 : raw;
3318
+ if (lastMs > 0 && nowMs - lastMs > EMPTY_BOUNDARY_STALE_MS) {
3319
+ return { busy: false, currentTool: null };
3320
+ }
3064
3321
  return { busy: true, currentTool: null };
3065
3322
  }
3066
3323
  return { busy: false, currentTool: null };
@@ -3803,18 +4060,18 @@ function run(cmd, args, maxBuffer = MAX_IMAGE_BYTES * 2) {
3803
4060
  });
3804
4061
  }
3805
4062
  function fromFile(filePath) {
3806
- const mime = FILE_MIMES[path3.extname(filePath).toLowerCase()];
4063
+ const mime = FILE_MIMES[path4.extname(filePath).toLowerCase()];
3807
4064
  if (!mime) return null;
3808
4065
  try {
3809
- const stat = fs4.statSync(filePath);
4066
+ const stat = fs5.statSync(filePath);
3810
4067
  if (!stat.isFile() || stat.size === 0 || stat.size > MAX_IMAGE_BYTES) return null;
3811
- return { data: fs4.readFileSync(filePath).toString("base64"), mime };
4068
+ return { data: fs5.readFileSync(filePath).toString("base64"), mime };
3812
4069
  } catch {
3813
4070
  return null;
3814
4071
  }
3815
4072
  }
3816
4073
  async function readDarwin() {
3817
- const tmp = path3.join(os7.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
4074
+ const tmp = path4.join(os8.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
3818
4075
  const script = [
3819
4076
  `set d to the clipboard as \xABclass PNGf\xBB`,
3820
4077
  `set f to open for access POSIX file "${tmp}" with write permission`,
@@ -3826,7 +4083,7 @@ async function readDarwin() {
3826
4083
  if (png.ok) {
3827
4084
  const img = fromFile(tmp);
3828
4085
  try {
3829
- fs4.unlinkSync(tmp);
4086
+ fs5.unlinkSync(tmp);
3830
4087
  } catch {
3831
4088
  }
3832
4089
  if (img) return img;
@@ -3851,7 +4108,7 @@ async function readLinux() {
3851
4108
  return null;
3852
4109
  }
3853
4110
  async function readWindows() {
3854
- const tmp = path3.join(os7.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
4111
+ const tmp = path4.join(os8.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
3855
4112
  const ps = [
3856
4113
  "Add-Type -AssemblyName System.Windows.Forms;",
3857
4114
  "$img = [System.Windows.Forms.Clipboard]::GetImage();",
@@ -3860,7 +4117,7 @@ async function readWindows() {
3860
4117
  await run("powershell", ["-NoProfile", "-STA", "-Command", ps]);
3861
4118
  const img = fromFile(tmp);
3862
4119
  try {
3863
- fs4.unlinkSync(tmp);
4120
+ fs5.unlinkSync(tmp);
3864
4121
  } catch {
3865
4122
  }
3866
4123
  return img;
@@ -5797,10 +6054,10 @@ ${C.cyan}\u2503${C.reset} ${question}
5797
6054
 
5798
6055
  // src/history.ts
5799
6056
  var HISTORY_KEY = "input_history";
5800
- var MAX_ENTRIES = 100;
6057
+ var MAX_ENTRIES2 = 100;
5801
6058
  function clean(value) {
5802
6059
  if (!Array.isArray(value)) return [];
5803
- return value.filter((e) => typeof e === "string" && e.trim() !== "").slice(-MAX_ENTRIES);
6060
+ return value.filter((e) => typeof e === "string" && e.trim() !== "").slice(-MAX_ENTRIES2);
5804
6061
  }
5805
6062
  async function loadHistory(store, threadId, seedThreadId) {
5806
6063
  const own = clean(await store.kvGet(threadId, HISTORY_KEY));
@@ -5818,12 +6075,12 @@ function appendHistory(store, threadId, history, text) {
5818
6075
  const t = text.trim();
5819
6076
  if (!t || history[history.length - 1] === t) return history;
5820
6077
  history.push(t);
5821
- if (history.length > MAX_ENTRIES) history.splice(0, history.length - MAX_ENTRIES);
6078
+ if (history.length > MAX_ENTRIES2) history.splice(0, history.length - MAX_ENTRIES2);
5822
6079
  void store.kvSet(threadId, HISTORY_KEY, [...history]);
5823
6080
  return history;
5824
6081
  }
5825
- var DIR = path3.join(os7.homedir(), ".standardagents");
5826
- var FILE = path3.join(DIR, "credentials");
6082
+ var DIR = path4.join(os8.homedir(), ".standardagents");
6083
+ var FILE = path4.join(DIR, "credentials");
5827
6084
  function normalizeEndpoint(endpoint) {
5828
6085
  let e = endpoint.trim();
5829
6086
  if (!/^https?:\/\//i.test(e)) e = "http://" + e;
@@ -5831,7 +6088,7 @@ function normalizeEndpoint(endpoint) {
5831
6088
  }
5832
6089
  function loadCredentials() {
5833
6090
  try {
5834
- const raw = fs4.readFileSync(FILE, "utf8");
6091
+ const raw = fs5.readFileSync(FILE, "utf8");
5835
6092
  const parsed = JSON.parse(raw);
5836
6093
  if (!parsed.instances) parsed.instances = {};
5837
6094
  return parsed;
@@ -5850,20 +6107,20 @@ function saveCredential(cred, options = {}) {
5850
6107
  if (options.updateDefault ?? true) {
5851
6108
  creds.default_endpoint = endpoint;
5852
6109
  }
5853
- fs4.mkdirSync(DIR, { recursive: true });
5854
- fs4.writeFileSync(FILE, JSON.stringify(creds, null, 2), { mode: 384 });
6110
+ fs5.mkdirSync(DIR, { recursive: true });
6111
+ fs5.writeFileSync(FILE, JSON.stringify(creds, null, 2), { mode: 384 });
5855
6112
  try {
5856
- fs4.chmodSync(FILE, 384);
6113
+ fs5.chmodSync(FILE, 384);
5857
6114
  } catch {
5858
6115
  }
5859
6116
  }
5860
6117
  function deleteCredential(endpoint) {
5861
6118
  const creds = loadCredentials();
5862
6119
  delete creds.instances[normalizeEndpoint(endpoint)];
5863
- fs4.mkdirSync(DIR, { recursive: true });
5864
- fs4.writeFileSync(FILE, JSON.stringify(creds, null, 2), { mode: 384 });
6120
+ fs5.mkdirSync(DIR, { recursive: true });
6121
+ fs5.writeFileSync(FILE, JSON.stringify(creds, null, 2), { mode: 384 });
5865
6122
  try {
5866
- fs4.chmodSync(FILE, 384);
6123
+ fs5.chmodSync(FILE, 384);
5867
6124
  } catch {
5868
6125
  }
5869
6126
  }
@@ -5873,10 +6130,10 @@ function defaultEndpoint() {
5873
6130
  function saveDefaultEndpoint(endpoint) {
5874
6131
  const creds = loadCredentials();
5875
6132
  creds.default_endpoint = normalizeEndpoint(endpoint);
5876
- fs4.mkdirSync(DIR, { recursive: true });
5877
- fs4.writeFileSync(FILE, JSON.stringify(creds, null, 2), { mode: 384 });
6133
+ fs5.mkdirSync(DIR, { recursive: true });
6134
+ fs5.writeFileSync(FILE, JSON.stringify(creds, null, 2), { mode: 384 });
5878
6135
  try {
5879
- fs4.chmodSync(FILE, 384);
6136
+ fs5.chmodSync(FILE, 384);
5880
6137
  } catch {
5881
6138
  }
5882
6139
  }
@@ -5937,7 +6194,7 @@ var AGENT_CHOICES = [
5937
6194
  var PRODUCTION_ENDPOINT = "https://api.standardcode.ai";
5938
6195
  function readVersion() {
5939
6196
  try {
5940
- const pkg = JSON.parse(fs4.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
6197
+ const pkg = JSON.parse(fs5.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
5941
6198
  return typeof pkg.version === "string" ? pkg.version : "";
5942
6199
  } catch {
5943
6200
  return "";
@@ -5963,11 +6220,11 @@ function relaxTlsForLocalEndpoint(endpoint) {
5963
6220
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5964
6221
  return true;
5965
6222
  }
5966
- var dir = () => path3.join(os7.homedir(), ".standardagents");
5967
- var file = () => path3.join(dir(), "machine.json");
6223
+ var dir = () => path4.join(os8.homedir(), ".standardagents");
6224
+ var file = () => path4.join(dir(), "machine.json");
5968
6225
  function loadMachineIdentity() {
5969
6226
  try {
5970
- const parsed = JSON.parse(fs4.readFileSync(file(), "utf8"));
6227
+ const parsed = JSON.parse(fs5.readFileSync(file(), "utf8"));
5971
6228
  if (typeof parsed.machine_id === "string" && parsed.machine_id.length > 0) {
5972
6229
  return { machine_id: parsed.machine_id, created_at: parsed.created_at ?? Date.now() };
5973
6230
  }
@@ -5981,8 +6238,8 @@ function loadMachineIdentity() {
5981
6238
  return identity;
5982
6239
  }
5983
6240
  function saveMachineIdentity(identity) {
5984
- fs4.mkdirSync(dir(), { recursive: true });
5985
- fs4.writeFileSync(file(), JSON.stringify(identity, null, 2), { mode: 384 });
6241
+ fs5.mkdirSync(dir(), { recursive: true });
6242
+ fs5.writeFileSync(file(), JSON.stringify(identity, null, 2), { mode: 384 });
5986
6243
  }
5987
6244
  function daemonClientId(identity) {
5988
6245
  return `daemon:${identity.machine_id}`;
@@ -6163,8 +6420,8 @@ function newRecord(identity) {
6163
6420
  const now = Date.now();
6164
6421
  return {
6165
6422
  id: identity.machine_id,
6166
- name: os7.hostname(),
6167
- hostname: os7.hostname(),
6423
+ name: os8.hostname(),
6424
+ hostname: os8.hostname(),
6168
6425
  platform: process.platform,
6169
6426
  arch: process.arch,
6170
6427
  version: readVersion() || void 0,
@@ -6177,7 +6434,7 @@ function newRecord(identity) {
6177
6434
  async function updateOwnMachineRecord(api, identity, mutate) {
6178
6435
  const existing = await loadRawMachine(api, identity.machine_id);
6179
6436
  const record2 = existing ?? newRecord(identity);
6180
- record2.hostname = os7.hostname();
6437
+ record2.hostname = os8.hostname();
6181
6438
  record2.platform = process.platform;
6182
6439
  record2.arch = process.arch;
6183
6440
  record2.version = readVersion() || record2.version;
@@ -6201,9 +6458,9 @@ function projectRepository(projectDir) {
6201
6458
  function normalizeProjectDir(projectDir) {
6202
6459
  let p = projectDir.trim();
6203
6460
  if (!p) return p;
6204
- if (p === "~") p = os7.homedir();
6205
- else if (p.startsWith("~/")) p = path3.join(os7.homedir(), p.slice(2));
6206
- return path3.resolve(p);
6461
+ if (p === "~") p = os8.homedir();
6462
+ else if (p.startsWith("~/")) p = path4.join(os8.homedir(), p.slice(2));
6463
+ return path4.resolve(p);
6207
6464
  }
6208
6465
  async function registerProject(api, identity, projectDir) {
6209
6466
  const dir2 = normalizeProjectDir(projectDir);
@@ -6281,16 +6538,16 @@ async function clearMachineCommands(api, machineId, appliedIds) {
6281
6538
  async function applyMachineCommand(api, identity, cmd) {
6282
6539
  switch (cmd.kind) {
6283
6540
  case "add_project": {
6284
- const path14 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
6285
- if (!path14) return "add_project: ignored (no path)";
6286
- await registerProject(api, identity, path14);
6287
- return `added project ${path14}`;
6541
+ const path15 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
6542
+ if (!path15) return "add_project: ignored (no path)";
6543
+ await registerProject(api, identity, path15);
6544
+ return `added project ${path15}`;
6288
6545
  }
6289
6546
  case "remove_project": {
6290
- const path14 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
6291
- if (!path14) return "remove_project: ignored (no path)";
6292
- await unregisterProject(api, identity, path14);
6293
- return `removed project ${path14}`;
6547
+ const path15 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
6548
+ if (!path15) return "remove_project: ignored (no path)";
6549
+ await unregisterProject(api, identity, path15);
6550
+ return `removed project ${path15}`;
6294
6551
  }
6295
6552
  case "update":
6296
6553
  return "update requested";
@@ -6352,6 +6609,101 @@ async function awaitApprovalViaRelay(api, threadId, request, options = {}) {
6352
6609
  });
6353
6610
  }
6354
6611
  }
6612
+
6613
+ // src/hub.ts
6614
+ var HubSocket = class {
6615
+ constructor(api, clientId, hooks, clientName) {
6616
+ this.api = api;
6617
+ this.clientId = clientId;
6618
+ this.hooks = hooks;
6619
+ this.clientName = clientName;
6620
+ }
6621
+ api;
6622
+ clientId;
6623
+ hooks;
6624
+ clientName;
6625
+ ws = null;
6626
+ closed = false;
6627
+ heartbeat = null;
6628
+ reconnectAttempt = 0;
6629
+ reconnectTimer = null;
6630
+ /** Open the socket and keep it connected (reconnect forever on drop). */
6631
+ start() {
6632
+ this.openSocket();
6633
+ }
6634
+ openSocket() {
6635
+ if (this.closed) return;
6636
+ let url = `${this.api.wsEndpoint}/api/users/me/hub?token=${encodeURIComponent(this.api.bearer)}&client_id=${encodeURIComponent(this.clientId)}&client_kind=daemon`;
6637
+ if (this.clientName) url += `&client_name=${encodeURIComponent(this.clientName)}`;
6638
+ let ws;
6639
+ try {
6640
+ ws = new WebSocket(url);
6641
+ } catch {
6642
+ this.scheduleReconnect();
6643
+ return;
6644
+ }
6645
+ this.ws = ws;
6646
+ ws.addEventListener("open", () => {
6647
+ this.reconnectAttempt = 0;
6648
+ this.startHeartbeat(ws);
6649
+ this.hooks.onConnection?.("connected", 0);
6650
+ });
6651
+ ws.addEventListener("message", (ev) => {
6652
+ if (this.ws === ws) this.heartbeat?.markAlive();
6653
+ this.onMessage(String(ev.data));
6654
+ });
6655
+ ws.addEventListener("error", () => this.handleDrop(ws));
6656
+ ws.addEventListener("close", () => this.handleDrop(ws));
6657
+ }
6658
+ onMessage(raw) {
6659
+ let msg;
6660
+ try {
6661
+ msg = JSON.parse(raw);
6662
+ } catch {
6663
+ return;
6664
+ }
6665
+ if (msg && typeof msg === "object" && msg.type === "wake" && typeof msg.threadId === "string") {
6666
+ this.hooks.onWake(msg.threadId);
6667
+ }
6668
+ }
6669
+ handleDrop(ws) {
6670
+ if (this.ws !== ws) return;
6671
+ this.ws = null;
6672
+ this.stopHeartbeat();
6673
+ this.scheduleReconnect();
6674
+ }
6675
+ scheduleReconnect() {
6676
+ if (this.closed || this.reconnectTimer) return;
6677
+ this.reconnectAttempt++;
6678
+ this.hooks.onConnection?.("reconnecting", this.reconnectAttempt);
6679
+ const base = Math.min(500 * 2 ** (this.reconnectAttempt - 1), 15e3);
6680
+ const delay = base + Math.floor(Math.random() * 400);
6681
+ this.reconnectTimer = setTimeout(() => {
6682
+ this.reconnectTimer = null;
6683
+ this.openSocket();
6684
+ }, delay);
6685
+ }
6686
+ startHeartbeat(ws) {
6687
+ this.stopHeartbeat();
6688
+ this.heartbeat = new Heartbeat(ws, () => this.handleDrop(ws), { request: "stream_ping" });
6689
+ this.heartbeat.start();
6690
+ }
6691
+ stopHeartbeat() {
6692
+ if (this.heartbeat) {
6693
+ this.heartbeat.stop();
6694
+ this.heartbeat = null;
6695
+ }
6696
+ }
6697
+ close() {
6698
+ this.closed = true;
6699
+ this.stopHeartbeat();
6700
+ if (this.reconnectTimer) {
6701
+ clearTimeout(this.reconnectTimer);
6702
+ this.reconnectTimer = null;
6703
+ }
6704
+ this.ws?.close();
6705
+ }
6706
+ };
6355
6707
  var PROJECT_MARKERS = [
6356
6708
  ".git",
6357
6709
  "package.json",
@@ -6365,14 +6717,14 @@ var PROJECT_MARKERS = [
6365
6717
  "requirements.txt",
6366
6718
  ".standardagents"
6367
6719
  ];
6368
- var MAX_ENTRIES2 = 500;
6720
+ var MAX_ENTRIES3 = 500;
6369
6721
  function resolveBrowsePath(input3) {
6370
- const home = os7.homedir();
6722
+ const home = os8.homedir();
6371
6723
  let p = (input3 ?? "").trim();
6372
6724
  if (!p) return home;
6373
6725
  if (p === "~") return home;
6374
- if (p.startsWith("~/")) p = path3.join(home, p.slice(2));
6375
- return path3.resolve(p);
6726
+ if (p.startsWith("~/")) p = path4.join(home, p.slice(2));
6727
+ return path4.resolve(p);
6376
6728
  }
6377
6729
  function markers(dirPath) {
6378
6730
  let repo = false;
@@ -6380,7 +6732,7 @@ function markers(dirPath) {
6380
6732
  for (const marker of PROJECT_MARKERS) {
6381
6733
  let hit = false;
6382
6734
  try {
6383
- hit = fs4.existsSync(path3.join(dirPath, marker));
6735
+ hit = fs5.existsSync(path4.join(dirPath, marker));
6384
6736
  } catch {
6385
6737
  hit = false;
6386
6738
  }
@@ -6392,9 +6744,9 @@ function markers(dirPath) {
6392
6744
  return { project, repo };
6393
6745
  }
6394
6746
  function browseDirectory(input3, opts = {}) {
6395
- const home = os7.homedir();
6747
+ const home = os8.homedir();
6396
6748
  const abs = resolveBrowsePath(input3);
6397
- const parent = path3.dirname(abs);
6749
+ const parent = path4.dirname(abs);
6398
6750
  const base = {
6399
6751
  path: abs,
6400
6752
  parent: parent === abs ? null : parent,
@@ -6402,11 +6754,11 @@ function browseDirectory(input3, opts = {}) {
6402
6754
  };
6403
6755
  let dirents;
6404
6756
  try {
6405
- const stat = fs4.statSync(abs);
6757
+ const stat = fs5.statSync(abs);
6406
6758
  if (!stat.isDirectory()) {
6407
6759
  return { ...base, entries: [], truncated: false, error: "Not a directory" };
6408
6760
  }
6409
- dirents = fs4.readdirSync(abs, { withFileTypes: true });
6761
+ dirents = fs5.readdirSync(abs, { withFileTypes: true });
6410
6762
  } catch (err) {
6411
6763
  const code = err?.code;
6412
6764
  const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
@@ -6421,13 +6773,13 @@ function browseDirectory(input3, opts = {}) {
6421
6773
  let isDir = d.isDirectory();
6422
6774
  if (d.isSymbolicLink()) {
6423
6775
  try {
6424
- isDir = fs4.statSync(path3.join(abs, name)).isDirectory();
6776
+ isDir = fs5.statSync(path4.join(abs, name)).isDirectory();
6425
6777
  } catch {
6426
6778
  isDir = false;
6427
6779
  }
6428
6780
  }
6429
6781
  if (isDir) {
6430
- const { project, repo } = markers(path3.join(abs, name));
6782
+ const { project, repo } = markers(path4.join(abs, name));
6431
6783
  rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
6432
6784
  } else {
6433
6785
  rawFiles.push({ name, dir: false });
@@ -6437,8 +6789,8 @@ function browseDirectory(input3, opts = {}) {
6437
6789
  rawDirs.sort(cmp);
6438
6790
  rawFiles.sort(cmp);
6439
6791
  const all = [...rawDirs, ...rawFiles];
6440
- const truncated = all.length > MAX_ENTRIES2;
6441
- return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES2) : all, truncated };
6792
+ const truncated = all.length > MAX_ENTRIES3;
6793
+ return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES3) : all, truncated };
6442
6794
  }
6443
6795
  function mkdirDirectory(parentInput, name, opts = {}) {
6444
6796
  const parent = resolveBrowsePath(parentInput);
@@ -6447,12 +6799,12 @@ function mkdirDirectory(parentInput, name, opts = {}) {
6447
6799
  if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
6448
6800
  return { ...listParent(), error: "Invalid folder name." };
6449
6801
  }
6450
- const target = path3.join(parent, clean2);
6451
- if (path3.dirname(target) !== parent) {
6802
+ const target = path4.join(parent, clean2);
6803
+ if (path4.dirname(target) !== parent) {
6452
6804
  return { ...listParent(), error: "Invalid folder name." };
6453
6805
  }
6454
6806
  try {
6455
- fs4.mkdirSync(target, { recursive: false });
6807
+ fs5.mkdirSync(target, { recursive: false });
6456
6808
  } catch (err) {
6457
6809
  const code = err?.code;
6458
6810
  const message = code === "EEXIST" ? "A folder with that name already exists." : code === "EACCES" || code === "EPERM" ? "Permission denied." : code === "ENOENT" ? "The parent folder no longer exists." : "Could not create the folder.";
@@ -6469,14 +6821,14 @@ var CACHE_TTL_MS = 1e3 * 60 * 60 * 6;
6469
6821
  var CHECK_TIMEOUT_MS = 4e3;
6470
6822
  var IN_FLIGHT_TTL_MS = 15 * 60 * 1e3;
6471
6823
  function cacheDir() {
6472
- return path3.join(homedir(), CACHE_REL_DIR);
6824
+ return path4.join(homedir(), CACHE_REL_DIR);
6473
6825
  }
6474
6826
  function cachePath() {
6475
- return path3.join(cacheDir(), CACHE_FILE);
6827
+ return path4.join(cacheDir(), CACHE_FILE);
6476
6828
  }
6477
6829
  function readCache() {
6478
6830
  try {
6479
- const raw = fs4.readFileSync(cachePath(), "utf-8");
6831
+ const raw = fs5.readFileSync(cachePath(), "utf-8");
6480
6832
  return JSON.parse(raw);
6481
6833
  } catch {
6482
6834
  return null;
@@ -6485,14 +6837,14 @@ function readCache() {
6485
6837
  function writeCache(latest) {
6486
6838
  try {
6487
6839
  const dir2 = cacheDir();
6488
- if (!fs4.existsSync(dir2)) fs4.mkdirSync(dir2, { recursive: true });
6489
- fs4.writeFileSync(cachePath(), JSON.stringify({ latest, timestamp: Date.now() }));
6840
+ if (!fs5.existsSync(dir2)) fs5.mkdirSync(dir2, { recursive: true });
6841
+ fs5.writeFileSync(cachePath(), JSON.stringify({ latest, timestamp: Date.now() }));
6490
6842
  } catch {
6491
6843
  }
6492
6844
  }
6493
6845
  function readAutoUpdateState(dir2 = cacheDir()) {
6494
6846
  try {
6495
- const raw = fs4.readFileSync(path3.join(dir2, STATE_FILE), "utf-8");
6847
+ const raw = fs5.readFileSync(path4.join(dir2, STATE_FILE), "utf-8");
6496
6848
  const state = JSON.parse(raw);
6497
6849
  return typeof state?.version === "string" ? state : null;
6498
6850
  } catch {
@@ -6501,14 +6853,14 @@ function readAutoUpdateState(dir2 = cacheDir()) {
6501
6853
  }
6502
6854
  function writeAutoUpdateState(state, dir2 = cacheDir()) {
6503
6855
  try {
6504
- if (!fs4.existsSync(dir2)) fs4.mkdirSync(dir2, { recursive: true });
6505
- fs4.writeFileSync(path3.join(dir2, STATE_FILE), JSON.stringify(state));
6856
+ if (!fs5.existsSync(dir2)) fs5.mkdirSync(dir2, { recursive: true });
6857
+ fs5.writeFileSync(path4.join(dir2, STATE_FILE), JSON.stringify(state));
6506
6858
  } catch {
6507
6859
  }
6508
6860
  }
6509
6861
  function clearAutoUpdateState(dir2 = cacheDir()) {
6510
6862
  try {
6511
- fs4.unlinkSync(path3.join(dir2, STATE_FILE));
6863
+ fs5.unlinkSync(path4.join(dir2, STATE_FILE));
6512
6864
  } catch {
6513
6865
  }
6514
6866
  }
@@ -6551,7 +6903,7 @@ function decideAutoUpdate(info, opts) {
6551
6903
  function startBackgroundUpdate(latest, pm, dir2 = cacheDir()) {
6552
6904
  const startedAt = Date.now();
6553
6905
  writeAutoUpdateState({ version: latest, startedAt, exitCode: null }, dir2);
6554
- const stateFile = path3.join(dir2, STATE_FILE);
6906
+ const stateFile = path4.join(dir2, STATE_FILE);
6555
6907
  const { cmd, args } = updateCommand(pm);
6556
6908
  const script = `const cp=require('child_process');const fs=require('fs');const r=cp.spawnSync(${JSON.stringify(cmd)},${JSON.stringify(args)},{shell:process.platform==='win32',encoding:'utf8'});const out=((r.stdout||'')+(r.stderr||'')).slice(-2000);fs.writeFileSync(${JSON.stringify(stateFile)},JSON.stringify({version:${JSON.stringify(latest)},startedAt:${startedAt},exitCode:r.status==null?-1:r.status,finishedAt:Date.now(),output:out}));`;
6557
6909
  try {
@@ -6646,17 +6998,17 @@ var UPDATE_CHECK_MS = 6 * 60 * 6e4;
6646
6998
  var SWEEP_MS = 10 * 6e4;
6647
6999
  var MAX_WORKERS = 30;
6648
7000
  var LOG_MAX_BYTES = 1e6;
6649
- var LOG_FILE = path3.join(os7.homedir(), ".standardagents", "daemon.log");
7001
+ var LOG_FILE = path4.join(os8.homedir(), ".standardagents", "daemon.log");
6650
7002
  function daemonLog(line) {
6651
7003
  try {
6652
- fs4.mkdirSync(path3.dirname(LOG_FILE), { recursive: true });
7004
+ fs5.mkdirSync(path4.dirname(LOG_FILE), { recursive: true });
6653
7005
  try {
6654
- if (fs4.statSync(LOG_FILE).size > LOG_MAX_BYTES) {
6655
- fs4.renameSync(LOG_FILE, `${LOG_FILE}.old`);
7006
+ if (fs5.statSync(LOG_FILE).size > LOG_MAX_BYTES) {
7007
+ fs5.renameSync(LOG_FILE, `${LOG_FILE}.old`);
6656
7008
  }
6657
7009
  } catch {
6658
7010
  }
6659
- fs4.appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
7011
+ fs5.appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
6660
7012
  `);
6661
7013
  } catch {
6662
7014
  }
@@ -6665,7 +7017,7 @@ function pathFromTags(tags) {
6665
7017
  const tag = tags.find((t) => t.startsWith("path:"));
6666
7018
  if (!tag) return null;
6667
7019
  const raw = tag.slice("path:".length);
6668
- return raw.replace(/^~(?=\/|$)/, os7.homedir());
7020
+ return raw.replace(/^~(?=\/|$)/, os8.homedir());
6669
7021
  }
6670
7022
  var ThreadWorker = class {
6671
7023
  constructor(api, identity, machineName, threadId, projectDir, createdAt2) {
@@ -6797,7 +7149,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6797
7149
  process.exit(1);
6798
7150
  }
6799
7151
  let displayName = machineDisplayName(await loadMachine(api, identity.machine_id).catch(() => null) ?? {
6800
- hostname: os7.hostname(),
7152
+ hostname: os8.hostname(),
6801
7153
  id: identity.machine_id
6802
7154
  });
6803
7155
  const applied = consumeAppliedUpdate(version);
@@ -6835,7 +7187,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6835
7187
  daemonLog(`detached ${oldest.threadId.slice(0, 8)} (worker cap)`);
6836
7188
  }
6837
7189
  try {
6838
- fs4.mkdirSync(projectDir, { recursive: true });
7190
+ fs5.mkdirSync(projectDir, { recursive: true });
6839
7191
  } catch (e) {
6840
7192
  daemonLog(`cannot prepare project dir ${projectDir}: ${e instanceof Error ? e.message : String(e)}`);
6841
7193
  return;
@@ -6884,6 +7236,31 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6884
7236
  onThreadDeleted: (id) => detach(id)
6885
7237
  });
6886
7238
  events.connect();
7239
+ const hub = new HubSocket(
7240
+ api,
7241
+ daemonClientId(identity),
7242
+ {
7243
+ onWake: (threadId) => {
7244
+ if (workers.has(threadId)) return;
7245
+ void (async () => {
7246
+ try {
7247
+ const thread = await api.getThread(threadId);
7248
+ if (!thread || thread.terminated) return;
7249
+ if (!thread.tags?.includes(runnerTag)) return;
7250
+ daemonLog(`wake: attaching ${threadId.slice(0, 8)} on hub signal`);
7251
+ await attach(threadId, thread.tags ?? [], (thread.created_at ?? 0) * 1e3);
7252
+ } catch (e) {
7253
+ daemonLog(`wake attach failed: ${e instanceof Error ? e.message : String(e)}`);
7254
+ }
7255
+ })();
7256
+ },
7257
+ onConnection: (state, attempt) => {
7258
+ if (state === "reconnecting" && attempt === 1) daemonLog("hub: reconnecting");
7259
+ }
7260
+ },
7261
+ displayName
7262
+ );
7263
+ hub.start();
6887
7264
  await sweep();
6888
7265
  const reclaim = setInterval(() => {
6889
7266
  for (const worker of workers.values()) {
@@ -7007,6 +7384,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
7007
7384
  clearInterval(sweeper);
7008
7385
  for (const worker of workers.values()) worker.stop();
7009
7386
  events.close();
7387
+ hub.close();
7010
7388
  daemonLog(`daemon exiting (code ${code})`);
7011
7389
  process.exit(code);
7012
7390
  };
@@ -7019,19 +7397,19 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
7019
7397
  var SERVICE_LABEL = "ai.standardcode.daemon";
7020
7398
  var SYSTEMD_UNIT = "standardcode-daemon.service";
7021
7399
  function resolveDaemonCommand(extraArgs = []) {
7022
- const entry = path3.resolve(process.argv[1] ?? "");
7400
+ const entry = path4.resolve(process.argv[1] ?? "");
7023
7401
  if (!entry) throw new Error("Cannot determine how this CLI was launched.");
7024
7402
  const argv = [process.execPath];
7025
7403
  if (entry.endsWith(".ts")) {
7026
- let dir2 = path3.dirname(entry);
7404
+ let dir2 = path4.dirname(entry);
7027
7405
  let tsx = null;
7028
- for (let i = 0; i < 6 && dir2 !== path3.dirname(dir2); i++) {
7029
- const candidate = path3.join(dir2, "node_modules", "tsx", "dist", "cli.mjs");
7030
- if (fs4.existsSync(candidate)) {
7406
+ for (let i = 0; i < 6 && dir2 !== path4.dirname(dir2); i++) {
7407
+ const candidate = path4.join(dir2, "node_modules", "tsx", "dist", "cli.mjs");
7408
+ if (fs5.existsSync(candidate)) {
7031
7409
  tsx = candidate;
7032
7410
  break;
7033
7411
  }
7034
- dir2 = path3.dirname(dir2);
7412
+ dir2 = path4.dirname(dir2);
7035
7413
  }
7036
7414
  if (!tsx) {
7037
7415
  throw new Error(
@@ -7045,7 +7423,7 @@ function resolveDaemonCommand(extraArgs = []) {
7045
7423
  }
7046
7424
  function servicePath() {
7047
7425
  const parts = [
7048
- path3.dirname(process.execPath),
7426
+ path4.dirname(process.execPath),
7049
7427
  "/opt/homebrew/bin",
7050
7428
  "/usr/local/bin",
7051
7429
  "/usr/bin",
@@ -7061,8 +7439,8 @@ function run2(cmd, args) {
7061
7439
  const output4 = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
7062
7440
  return { ok: res.status === 0, output: output4 };
7063
7441
  }
7064
- var plistPath = () => path3.join(os7.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
7065
- var unitPath = () => path3.join(os7.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
7442
+ var plistPath = () => path4.join(os8.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
7443
+ var unitPath = () => path4.join(os8.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
7066
7444
  var xmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7067
7445
  function installService(command, endpoint) {
7068
7446
  if (process.platform === "darwin") return installLaunchd(command, endpoint);
@@ -7074,9 +7452,9 @@ function installService(command, endpoint) {
7074
7452
  };
7075
7453
  }
7076
7454
  function installLaunchd(command, endpoint) {
7077
- const logDir = path3.join(os7.homedir(), ".standardagents");
7078
- fs4.mkdirSync(logDir, { recursive: true });
7079
- fs4.mkdirSync(path3.dirname(plistPath()), { recursive: true });
7455
+ const logDir = path4.join(os8.homedir(), ".standardagents");
7456
+ fs5.mkdirSync(logDir, { recursive: true });
7457
+ fs5.mkdirSync(path4.dirname(plistPath()), { recursive: true });
7080
7458
  const envEntries = [
7081
7459
  ` <key>PATH</key><string>${xmlEscape(servicePath())}</string>`,
7082
7460
  ...endpoint ? [` <key>STANDARD_CODE_DAEMON_ENDPOINT</key><string>${xmlEscape(endpoint)}</string>`] : []
@@ -7093,8 +7471,8 @@ ${command.argv.map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n")}
7093
7471
  <key>RunAtLoad</key><true/>
7094
7472
  <key>KeepAlive</key><true/>
7095
7473
  <key>ThrottleInterval</key><integer>5</integer>
7096
- <key>StandardOutPath</key><string>${xmlEscape(path3.join(logDir, "daemon.out.log"))}</string>
7097
- <key>StandardErrorPath</key><string>${xmlEscape(path3.join(logDir, "daemon.err.log"))}</string>
7474
+ <key>StandardOutPath</key><string>${xmlEscape(path4.join(logDir, "daemon.out.log"))}</string>
7475
+ <key>StandardErrorPath</key><string>${xmlEscape(path4.join(logDir, "daemon.err.log"))}</string>
7098
7476
  <key>EnvironmentVariables</key>
7099
7477
  <dict>
7100
7478
  ${envEntries}
@@ -7102,7 +7480,7 @@ ${envEntries}
7102
7480
  </dict>
7103
7481
  </plist>
7104
7482
  `;
7105
- fs4.writeFileSync(plistPath(), plist);
7483
+ fs5.writeFileSync(plistPath(), plist);
7106
7484
  const uid = typeof process.getuid === "function" ? process.getuid() : 501;
7107
7485
  run2("launchctl", ["bootout", `gui/${uid}`, plistPath()]);
7108
7486
  const boot = run2("launchctl", ["bootstrap", `gui/${uid}`, plistPath()]);
@@ -7119,7 +7497,7 @@ ${envEntries}
7119
7497
  return { ok: true, detail: `LaunchAgent installed (${plistPath()})` };
7120
7498
  }
7121
7499
  function installSystemd(command, endpoint) {
7122
- fs4.mkdirSync(path3.dirname(unitPath()), { recursive: true });
7500
+ fs5.mkdirSync(path4.dirname(unitPath()), { recursive: true });
7123
7501
  const unit = `[Unit]
7124
7502
  Description=Standard Code daemon (headless coding-agent execution client)
7125
7503
  After=network-online.target
@@ -7134,7 +7512,7 @@ ${endpoint ? `Environment=STANDARD_CODE_DAEMON_ENDPOINT=${endpoint}
7134
7512
  [Install]
7135
7513
  WantedBy=default.target
7136
7514
  `;
7137
- fs4.writeFileSync(unitPath(), unit);
7515
+ fs5.writeFileSync(unitPath(), unit);
7138
7516
  const reload = run2("systemctl", ["--user", "daemon-reload"]);
7139
7517
  if (!reload.ok) {
7140
7518
  return {
@@ -7147,10 +7525,10 @@ WantedBy=default.target
7147
7525
  if (!enable.ok) {
7148
7526
  return { ok: false, detail: `systemctl enable failed: ${enable.output}` };
7149
7527
  }
7150
- const linger = run2("loginctl", ["enable-linger", os7.userInfo().username]);
7528
+ const linger = run2("loginctl", ["enable-linger", os8.userInfo().username]);
7151
7529
  return {
7152
7530
  ok: true,
7153
- detail: `systemd user unit installed (${unitPath()})` + (linger.ok ? ", lingering enabled" : ` \u2014 enable lingering manually: sudo loginctl enable-linger ${os7.userInfo().username}`)
7531
+ detail: `systemd user unit installed (${unitPath()})` + (linger.ok ? ", lingering enabled" : ` \u2014 enable lingering manually: sudo loginctl enable-linger ${os8.userInfo().username}`)
7154
7532
  };
7155
7533
  }
7156
7534
  function uninstallService() {
@@ -7158,7 +7536,7 @@ function uninstallService() {
7158
7536
  const uid = typeof process.getuid === "function" ? process.getuid() : 501;
7159
7537
  run2("launchctl", ["bootout", `gui/${uid}`, plistPath()]);
7160
7538
  try {
7161
- fs4.unlinkSync(plistPath());
7539
+ fs5.unlinkSync(plistPath());
7162
7540
  } catch {
7163
7541
  }
7164
7542
  return { ok: true, detail: "LaunchAgent removed" };
@@ -7166,7 +7544,7 @@ function uninstallService() {
7166
7544
  if (process.platform === "linux") {
7167
7545
  run2("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
7168
7546
  try {
7169
- fs4.unlinkSync(unitPath());
7547
+ fs5.unlinkSync(unitPath());
7170
7548
  } catch {
7171
7549
  }
7172
7550
  run2("systemctl", ["--user", "daemon-reload"]);
@@ -7176,7 +7554,7 @@ function uninstallService() {
7176
7554
  }
7177
7555
  function serviceStatus() {
7178
7556
  if (process.platform === "darwin") {
7179
- const installed = fs4.existsSync(plistPath());
7557
+ const installed = fs5.existsSync(plistPath());
7180
7558
  const list = run2("launchctl", ["list", SERVICE_LABEL]);
7181
7559
  const pidMatch = list.output.match(/"PID"\s*=\s*(\d+)/);
7182
7560
  return {
@@ -7186,7 +7564,7 @@ function serviceStatus() {
7186
7564
  };
7187
7565
  }
7188
7566
  if (process.platform === "linux") {
7189
- const installed = fs4.existsSync(unitPath());
7567
+ const installed = fs5.existsSync(unitPath());
7190
7568
  const active = run2("systemctl", ["--user", "is-active", SYSTEMD_UNIT]);
7191
7569
  return {
7192
7570
  installed,
@@ -7278,7 +7656,7 @@ async function installCommand(endpointFlag) {
7278
7656
  const api = await ensureSignedIn(endpoint);
7279
7657
  const identity = loadMachineIdentity();
7280
7658
  const existing = await loadMachine(api, identity.machine_id).catch(() => null);
7281
- const suggested = machineDisplayName(existing ?? { hostname: os7.hostname(), id: identity.machine_id });
7659
+ const suggested = machineDisplayName(existing ?? { hostname: os8.hostname(), id: identity.machine_id });
7282
7660
  const rl = readline2.createInterface({ input: stdin, output: stdout });
7283
7661
  const answer = (await rl.question(
7284
7662
  `${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
@@ -7338,7 +7716,7 @@ async function statusCommand() {
7338
7716
  const cred = getCredential(endpoint);
7339
7717
  if (!cred) {
7340
7718
  stdout.write(
7341
- `${c2.bold}Machine:${c2.reset} ${os7.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
7719
+ `${c2.bold}Machine:${c2.reset} ${os8.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
7342
7720
  `
7343
7721
  );
7344
7722
  stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
@@ -7349,7 +7727,7 @@ async function statusCommand() {
7349
7727
  const api = new ApiClient(endpoint, cred.access_token);
7350
7728
  const record2 = await loadMachine(api, identity.machine_id).catch(() => null);
7351
7729
  stdout.write(
7352
- `${c2.bold}Machine:${c2.reset} ${machineDisplayName(record2 ?? { hostname: os7.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
7730
+ `${c2.bold}Machine:${c2.reset} ${machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
7353
7731
  `
7354
7732
  );
7355
7733
  if (!record2) {
@@ -7373,8 +7751,8 @@ async function projectCommand(action, target) {
7373
7751
  `);
7374
7752
  process.exit(1);
7375
7753
  }
7376
- const dir2 = path3.resolve(target);
7377
- if (action === "add" && !fs4.existsSync(dir2)) {
7754
+ const dir2 = path4.resolve(target);
7755
+ if (action === "add" && !fs5.existsSync(dir2)) {
7378
7756
  stdout.write(`${c2.red}\u2717${c2.reset} ${dir2} does not exist on this machine.
7379
7757
  `);
7380
7758
  process.exit(1);
@@ -7383,7 +7761,7 @@ async function projectCommand(action, target) {
7383
7761
  const api = await ensureSignedIn(endpoint);
7384
7762
  const identity = loadMachineIdentity();
7385
7763
  const record2 = await loadMachine(api, identity.machine_id).catch(() => null);
7386
- const displayName = machineDisplayName(record2 ?? { hostname: os7.hostname(), id: identity.machine_id });
7764
+ const displayName = machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id });
7387
7765
  if (action === "add") {
7388
7766
  await registerProject(api, identity, dir2);
7389
7767
  stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
@@ -7598,7 +7976,7 @@ ${c3.teal}\u25C7${c3.reset} ${c3.dim}Standard Code \u2014 see you soon.${c3.rese
7598
7976
  `);
7599
7977
  }
7600
7978
  function printWelcome(endpoint, projectDir) {
7601
- const home = os7.homedir();
7979
+ const home = os8.homedir();
7602
7980
  const dir2 = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
7603
7981
  const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
7604
7982
  const version = readVersion();
@@ -7668,8 +8046,8 @@ async function main() {
7668
8046
  const endpointArg = cliArgs.endpoint;
7669
8047
  const endpointOverride = cliArgs.promptEndpoint || typeof endpointArg === "string" && endpointArg.trim() !== "";
7670
8048
  const dirArg = cliArgs.dir;
7671
- const projectDir = path3.resolve(dirArg || process.cwd());
7672
- const machine = os7.hostname();
8049
+ const projectDir = path4.resolve(dirArg || process.cwd());
8050
+ const machine = os8.hostname();
7673
8051
  const reader = { rl: null };
7674
8052
  let handoffClosing = false;
7675
8053
  let preflightArmed = false;
@@ -7883,7 +8261,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
7883
8261
  const ok = await ensureSamaAuth(tui, api);
7884
8262
  if (!ok) process.exit(0);
7885
8263
  }
7886
- const home = os7.homedir();
8264
+ const home = os8.homedir();
7887
8265
  const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
7888
8266
  const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
7889
8267
  const session = { mode: "local", identity };
@@ -9057,7 +9435,10 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9057
9435
  }
9058
9436
  const polledBusy = (serverBusy ?? false) || threadBusy(msgs);
9059
9437
  if (serverTool && !activeSteps.has(serverTool.id)) {
9060
- activeSteps.set(serverTool.id, serverTool.name || "working");
9438
+ activeSteps.set(
9439
+ serverTool.id,
9440
+ serverTool.waitingForHost ? "waiting for your machine to reconnect" : serverTool.name || "working"
9441
+ );
9061
9442
  refreshStatus();
9062
9443
  } else if (serverBusy !== null && !serverTool && activeSteps.size) {
9063
9444
  activeSteps.clear();
@@ -9314,12 +9695,12 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
9314
9695
  );
9315
9696
  if (!picked) return;
9316
9697
  if (picked === ADD) {
9317
- const path14 = await tui.prompt(
9698
+ const path15 = await tui.prompt(
9318
9699
  `Absolute project path on ${machine.name}`,
9319
9700
  machine.id === self.machine_id ? process.cwd() : "/home/you/project"
9320
9701
  );
9321
- if (!path14 || !path14.trim()) return;
9322
- const trimmed = path14.trim();
9702
+ if (!path15 || !path15.trim()) return;
9703
+ const trimmed = path15.trim();
9323
9704
  if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
9324
9705
  tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
9325
9706
  return;