@standardagents/code 0.9.11 → 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
  }
@@ -3053,14 +3281,37 @@ function parseToolCalls(message) {
3053
3281
  });
3054
3282
  }
3055
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
+ }
3056
3290
  function deriveSessionActivity(messages, nowMs = Date.now()) {
3057
3291
  const visible = messages.filter((message) => message.silent !== true && message.metadata?.silent !== true).sort((a, b) => createdAt(a) - createdAt(b));
3058
- 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
+ }
3059
3300
  const last = visible.at(-1);
3060
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
+ }
3061
3306
  const currentTool = unresolvedTool(visible);
3062
3307
  if (currentTool) return { busy: true, currentTool };
3063
- 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
+ }
3064
3315
  if (last.role === "assistant" && last.status !== "failed" && !messageText(last.content).trim()) {
3065
3316
  const raw = createdAt(last);
3066
3317
  const lastMs = raw > 1e14 ? raw / 1e3 : raw < 1e12 ? raw * 1e3 : raw;
@@ -3809,18 +4060,18 @@ function run(cmd, args, maxBuffer = MAX_IMAGE_BYTES * 2) {
3809
4060
  });
3810
4061
  }
3811
4062
  function fromFile(filePath) {
3812
- const mime = FILE_MIMES[path3.extname(filePath).toLowerCase()];
4063
+ const mime = FILE_MIMES[path4.extname(filePath).toLowerCase()];
3813
4064
  if (!mime) return null;
3814
4065
  try {
3815
- const stat = fs4.statSync(filePath);
4066
+ const stat = fs5.statSync(filePath);
3816
4067
  if (!stat.isFile() || stat.size === 0 || stat.size > MAX_IMAGE_BYTES) return null;
3817
- return { data: fs4.readFileSync(filePath).toString("base64"), mime };
4068
+ return { data: fs5.readFileSync(filePath).toString("base64"), mime };
3818
4069
  } catch {
3819
4070
  return null;
3820
4071
  }
3821
4072
  }
3822
4073
  async function readDarwin() {
3823
- 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`);
3824
4075
  const script = [
3825
4076
  `set d to the clipboard as \xABclass PNGf\xBB`,
3826
4077
  `set f to open for access POSIX file "${tmp}" with write permission`,
@@ -3832,7 +4083,7 @@ async function readDarwin() {
3832
4083
  if (png.ok) {
3833
4084
  const img = fromFile(tmp);
3834
4085
  try {
3835
- fs4.unlinkSync(tmp);
4086
+ fs5.unlinkSync(tmp);
3836
4087
  } catch {
3837
4088
  }
3838
4089
  if (img) return img;
@@ -3857,7 +4108,7 @@ async function readLinux() {
3857
4108
  return null;
3858
4109
  }
3859
4110
  async function readWindows() {
3860
- 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`);
3861
4112
  const ps = [
3862
4113
  "Add-Type -AssemblyName System.Windows.Forms;",
3863
4114
  "$img = [System.Windows.Forms.Clipboard]::GetImage();",
@@ -3866,7 +4117,7 @@ async function readWindows() {
3866
4117
  await run("powershell", ["-NoProfile", "-STA", "-Command", ps]);
3867
4118
  const img = fromFile(tmp);
3868
4119
  try {
3869
- fs4.unlinkSync(tmp);
4120
+ fs5.unlinkSync(tmp);
3870
4121
  } catch {
3871
4122
  }
3872
4123
  return img;
@@ -5803,10 +6054,10 @@ ${C.cyan}\u2503${C.reset} ${question}
5803
6054
 
5804
6055
  // src/history.ts
5805
6056
  var HISTORY_KEY = "input_history";
5806
- var MAX_ENTRIES = 100;
6057
+ var MAX_ENTRIES2 = 100;
5807
6058
  function clean(value) {
5808
6059
  if (!Array.isArray(value)) return [];
5809
- 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);
5810
6061
  }
5811
6062
  async function loadHistory(store, threadId, seedThreadId) {
5812
6063
  const own = clean(await store.kvGet(threadId, HISTORY_KEY));
@@ -5824,12 +6075,12 @@ function appendHistory(store, threadId, history, text) {
5824
6075
  const t = text.trim();
5825
6076
  if (!t || history[history.length - 1] === t) return history;
5826
6077
  history.push(t);
5827
- 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);
5828
6079
  void store.kvSet(threadId, HISTORY_KEY, [...history]);
5829
6080
  return history;
5830
6081
  }
5831
- var DIR = path3.join(os7.homedir(), ".standardagents");
5832
- var FILE = path3.join(DIR, "credentials");
6082
+ var DIR = path4.join(os8.homedir(), ".standardagents");
6083
+ var FILE = path4.join(DIR, "credentials");
5833
6084
  function normalizeEndpoint(endpoint) {
5834
6085
  let e = endpoint.trim();
5835
6086
  if (!/^https?:\/\//i.test(e)) e = "http://" + e;
@@ -5837,7 +6088,7 @@ function normalizeEndpoint(endpoint) {
5837
6088
  }
5838
6089
  function loadCredentials() {
5839
6090
  try {
5840
- const raw = fs4.readFileSync(FILE, "utf8");
6091
+ const raw = fs5.readFileSync(FILE, "utf8");
5841
6092
  const parsed = JSON.parse(raw);
5842
6093
  if (!parsed.instances) parsed.instances = {};
5843
6094
  return parsed;
@@ -5856,20 +6107,20 @@ function saveCredential(cred, options = {}) {
5856
6107
  if (options.updateDefault ?? true) {
5857
6108
  creds.default_endpoint = endpoint;
5858
6109
  }
5859
- fs4.mkdirSync(DIR, { recursive: true });
5860
- 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 });
5861
6112
  try {
5862
- fs4.chmodSync(FILE, 384);
6113
+ fs5.chmodSync(FILE, 384);
5863
6114
  } catch {
5864
6115
  }
5865
6116
  }
5866
6117
  function deleteCredential(endpoint) {
5867
6118
  const creds = loadCredentials();
5868
6119
  delete creds.instances[normalizeEndpoint(endpoint)];
5869
- fs4.mkdirSync(DIR, { recursive: true });
5870
- 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 });
5871
6122
  try {
5872
- fs4.chmodSync(FILE, 384);
6123
+ fs5.chmodSync(FILE, 384);
5873
6124
  } catch {
5874
6125
  }
5875
6126
  }
@@ -5879,10 +6130,10 @@ function defaultEndpoint() {
5879
6130
  function saveDefaultEndpoint(endpoint) {
5880
6131
  const creds = loadCredentials();
5881
6132
  creds.default_endpoint = normalizeEndpoint(endpoint);
5882
- fs4.mkdirSync(DIR, { recursive: true });
5883
- 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 });
5884
6135
  try {
5885
- fs4.chmodSync(FILE, 384);
6136
+ fs5.chmodSync(FILE, 384);
5886
6137
  } catch {
5887
6138
  }
5888
6139
  }
@@ -5943,7 +6194,7 @@ var AGENT_CHOICES = [
5943
6194
  var PRODUCTION_ENDPOINT = "https://api.standardcode.ai";
5944
6195
  function readVersion() {
5945
6196
  try {
5946
- 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"));
5947
6198
  return typeof pkg.version === "string" ? pkg.version : "";
5948
6199
  } catch {
5949
6200
  return "";
@@ -5969,11 +6220,11 @@ function relaxTlsForLocalEndpoint(endpoint) {
5969
6220
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5970
6221
  return true;
5971
6222
  }
5972
- var dir = () => path3.join(os7.homedir(), ".standardagents");
5973
- var file = () => path3.join(dir(), "machine.json");
6223
+ var dir = () => path4.join(os8.homedir(), ".standardagents");
6224
+ var file = () => path4.join(dir(), "machine.json");
5974
6225
  function loadMachineIdentity() {
5975
6226
  try {
5976
- const parsed = JSON.parse(fs4.readFileSync(file(), "utf8"));
6227
+ const parsed = JSON.parse(fs5.readFileSync(file(), "utf8"));
5977
6228
  if (typeof parsed.machine_id === "string" && parsed.machine_id.length > 0) {
5978
6229
  return { machine_id: parsed.machine_id, created_at: parsed.created_at ?? Date.now() };
5979
6230
  }
@@ -5987,8 +6238,8 @@ function loadMachineIdentity() {
5987
6238
  return identity;
5988
6239
  }
5989
6240
  function saveMachineIdentity(identity) {
5990
- fs4.mkdirSync(dir(), { recursive: true });
5991
- 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 });
5992
6243
  }
5993
6244
  function daemonClientId(identity) {
5994
6245
  return `daemon:${identity.machine_id}`;
@@ -6169,8 +6420,8 @@ function newRecord(identity) {
6169
6420
  const now = Date.now();
6170
6421
  return {
6171
6422
  id: identity.machine_id,
6172
- name: os7.hostname(),
6173
- hostname: os7.hostname(),
6423
+ name: os8.hostname(),
6424
+ hostname: os8.hostname(),
6174
6425
  platform: process.platform,
6175
6426
  arch: process.arch,
6176
6427
  version: readVersion() || void 0,
@@ -6183,7 +6434,7 @@ function newRecord(identity) {
6183
6434
  async function updateOwnMachineRecord(api, identity, mutate) {
6184
6435
  const existing = await loadRawMachine(api, identity.machine_id);
6185
6436
  const record2 = existing ?? newRecord(identity);
6186
- record2.hostname = os7.hostname();
6437
+ record2.hostname = os8.hostname();
6187
6438
  record2.platform = process.platform;
6188
6439
  record2.arch = process.arch;
6189
6440
  record2.version = readVersion() || record2.version;
@@ -6207,9 +6458,9 @@ function projectRepository(projectDir) {
6207
6458
  function normalizeProjectDir(projectDir) {
6208
6459
  let p = projectDir.trim();
6209
6460
  if (!p) return p;
6210
- if (p === "~") p = os7.homedir();
6211
- else if (p.startsWith("~/")) p = path3.join(os7.homedir(), p.slice(2));
6212
- 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);
6213
6464
  }
6214
6465
  async function registerProject(api, identity, projectDir) {
6215
6466
  const dir2 = normalizeProjectDir(projectDir);
@@ -6287,16 +6538,16 @@ async function clearMachineCommands(api, machineId, appliedIds) {
6287
6538
  async function applyMachineCommand(api, identity, cmd) {
6288
6539
  switch (cmd.kind) {
6289
6540
  case "add_project": {
6290
- const path14 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
6291
- if (!path14) return "add_project: ignored (no path)";
6292
- await registerProject(api, identity, path14);
6293
- 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}`;
6294
6545
  }
6295
6546
  case "remove_project": {
6296
- const path14 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
6297
- if (!path14) return "remove_project: ignored (no path)";
6298
- await unregisterProject(api, identity, path14);
6299
- 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}`;
6300
6551
  }
6301
6552
  case "update":
6302
6553
  return "update requested";
@@ -6358,6 +6609,101 @@ async function awaitApprovalViaRelay(api, threadId, request, options = {}) {
6358
6609
  });
6359
6610
  }
6360
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
+ };
6361
6707
  var PROJECT_MARKERS = [
6362
6708
  ".git",
6363
6709
  "package.json",
@@ -6371,14 +6717,14 @@ var PROJECT_MARKERS = [
6371
6717
  "requirements.txt",
6372
6718
  ".standardagents"
6373
6719
  ];
6374
- var MAX_ENTRIES2 = 500;
6720
+ var MAX_ENTRIES3 = 500;
6375
6721
  function resolveBrowsePath(input3) {
6376
- const home = os7.homedir();
6722
+ const home = os8.homedir();
6377
6723
  let p = (input3 ?? "").trim();
6378
6724
  if (!p) return home;
6379
6725
  if (p === "~") return home;
6380
- if (p.startsWith("~/")) p = path3.join(home, p.slice(2));
6381
- return path3.resolve(p);
6726
+ if (p.startsWith("~/")) p = path4.join(home, p.slice(2));
6727
+ return path4.resolve(p);
6382
6728
  }
6383
6729
  function markers(dirPath) {
6384
6730
  let repo = false;
@@ -6386,7 +6732,7 @@ function markers(dirPath) {
6386
6732
  for (const marker of PROJECT_MARKERS) {
6387
6733
  let hit = false;
6388
6734
  try {
6389
- hit = fs4.existsSync(path3.join(dirPath, marker));
6735
+ hit = fs5.existsSync(path4.join(dirPath, marker));
6390
6736
  } catch {
6391
6737
  hit = false;
6392
6738
  }
@@ -6398,9 +6744,9 @@ function markers(dirPath) {
6398
6744
  return { project, repo };
6399
6745
  }
6400
6746
  function browseDirectory(input3, opts = {}) {
6401
- const home = os7.homedir();
6747
+ const home = os8.homedir();
6402
6748
  const abs = resolveBrowsePath(input3);
6403
- const parent = path3.dirname(abs);
6749
+ const parent = path4.dirname(abs);
6404
6750
  const base = {
6405
6751
  path: abs,
6406
6752
  parent: parent === abs ? null : parent,
@@ -6408,11 +6754,11 @@ function browseDirectory(input3, opts = {}) {
6408
6754
  };
6409
6755
  let dirents;
6410
6756
  try {
6411
- const stat = fs4.statSync(abs);
6757
+ const stat = fs5.statSync(abs);
6412
6758
  if (!stat.isDirectory()) {
6413
6759
  return { ...base, entries: [], truncated: false, error: "Not a directory" };
6414
6760
  }
6415
- dirents = fs4.readdirSync(abs, { withFileTypes: true });
6761
+ dirents = fs5.readdirSync(abs, { withFileTypes: true });
6416
6762
  } catch (err) {
6417
6763
  const code = err?.code;
6418
6764
  const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
@@ -6427,13 +6773,13 @@ function browseDirectory(input3, opts = {}) {
6427
6773
  let isDir = d.isDirectory();
6428
6774
  if (d.isSymbolicLink()) {
6429
6775
  try {
6430
- isDir = fs4.statSync(path3.join(abs, name)).isDirectory();
6776
+ isDir = fs5.statSync(path4.join(abs, name)).isDirectory();
6431
6777
  } catch {
6432
6778
  isDir = false;
6433
6779
  }
6434
6780
  }
6435
6781
  if (isDir) {
6436
- const { project, repo } = markers(path3.join(abs, name));
6782
+ const { project, repo } = markers(path4.join(abs, name));
6437
6783
  rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
6438
6784
  } else {
6439
6785
  rawFiles.push({ name, dir: false });
@@ -6443,8 +6789,8 @@ function browseDirectory(input3, opts = {}) {
6443
6789
  rawDirs.sort(cmp);
6444
6790
  rawFiles.sort(cmp);
6445
6791
  const all = [...rawDirs, ...rawFiles];
6446
- const truncated = all.length > MAX_ENTRIES2;
6447
- 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 };
6448
6794
  }
6449
6795
  function mkdirDirectory(parentInput, name, opts = {}) {
6450
6796
  const parent = resolveBrowsePath(parentInput);
@@ -6453,12 +6799,12 @@ function mkdirDirectory(parentInput, name, opts = {}) {
6453
6799
  if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
6454
6800
  return { ...listParent(), error: "Invalid folder name." };
6455
6801
  }
6456
- const target = path3.join(parent, clean2);
6457
- if (path3.dirname(target) !== parent) {
6802
+ const target = path4.join(parent, clean2);
6803
+ if (path4.dirname(target) !== parent) {
6458
6804
  return { ...listParent(), error: "Invalid folder name." };
6459
6805
  }
6460
6806
  try {
6461
- fs4.mkdirSync(target, { recursive: false });
6807
+ fs5.mkdirSync(target, { recursive: false });
6462
6808
  } catch (err) {
6463
6809
  const code = err?.code;
6464
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.";
@@ -6475,14 +6821,14 @@ var CACHE_TTL_MS = 1e3 * 60 * 60 * 6;
6475
6821
  var CHECK_TIMEOUT_MS = 4e3;
6476
6822
  var IN_FLIGHT_TTL_MS = 15 * 60 * 1e3;
6477
6823
  function cacheDir() {
6478
- return path3.join(homedir(), CACHE_REL_DIR);
6824
+ return path4.join(homedir(), CACHE_REL_DIR);
6479
6825
  }
6480
6826
  function cachePath() {
6481
- return path3.join(cacheDir(), CACHE_FILE);
6827
+ return path4.join(cacheDir(), CACHE_FILE);
6482
6828
  }
6483
6829
  function readCache() {
6484
6830
  try {
6485
- const raw = fs4.readFileSync(cachePath(), "utf-8");
6831
+ const raw = fs5.readFileSync(cachePath(), "utf-8");
6486
6832
  return JSON.parse(raw);
6487
6833
  } catch {
6488
6834
  return null;
@@ -6491,14 +6837,14 @@ function readCache() {
6491
6837
  function writeCache(latest) {
6492
6838
  try {
6493
6839
  const dir2 = cacheDir();
6494
- if (!fs4.existsSync(dir2)) fs4.mkdirSync(dir2, { recursive: true });
6495
- 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() }));
6496
6842
  } catch {
6497
6843
  }
6498
6844
  }
6499
6845
  function readAutoUpdateState(dir2 = cacheDir()) {
6500
6846
  try {
6501
- const raw = fs4.readFileSync(path3.join(dir2, STATE_FILE), "utf-8");
6847
+ const raw = fs5.readFileSync(path4.join(dir2, STATE_FILE), "utf-8");
6502
6848
  const state = JSON.parse(raw);
6503
6849
  return typeof state?.version === "string" ? state : null;
6504
6850
  } catch {
@@ -6507,14 +6853,14 @@ function readAutoUpdateState(dir2 = cacheDir()) {
6507
6853
  }
6508
6854
  function writeAutoUpdateState(state, dir2 = cacheDir()) {
6509
6855
  try {
6510
- if (!fs4.existsSync(dir2)) fs4.mkdirSync(dir2, { recursive: true });
6511
- 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));
6512
6858
  } catch {
6513
6859
  }
6514
6860
  }
6515
6861
  function clearAutoUpdateState(dir2 = cacheDir()) {
6516
6862
  try {
6517
- fs4.unlinkSync(path3.join(dir2, STATE_FILE));
6863
+ fs5.unlinkSync(path4.join(dir2, STATE_FILE));
6518
6864
  } catch {
6519
6865
  }
6520
6866
  }
@@ -6557,7 +6903,7 @@ function decideAutoUpdate(info, opts) {
6557
6903
  function startBackgroundUpdate(latest, pm, dir2 = cacheDir()) {
6558
6904
  const startedAt = Date.now();
6559
6905
  writeAutoUpdateState({ version: latest, startedAt, exitCode: null }, dir2);
6560
- const stateFile = path3.join(dir2, STATE_FILE);
6906
+ const stateFile = path4.join(dir2, STATE_FILE);
6561
6907
  const { cmd, args } = updateCommand(pm);
6562
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}));`;
6563
6909
  try {
@@ -6652,17 +6998,17 @@ var UPDATE_CHECK_MS = 6 * 60 * 6e4;
6652
6998
  var SWEEP_MS = 10 * 6e4;
6653
6999
  var MAX_WORKERS = 30;
6654
7000
  var LOG_MAX_BYTES = 1e6;
6655
- var LOG_FILE = path3.join(os7.homedir(), ".standardagents", "daemon.log");
7001
+ var LOG_FILE = path4.join(os8.homedir(), ".standardagents", "daemon.log");
6656
7002
  function daemonLog(line) {
6657
7003
  try {
6658
- fs4.mkdirSync(path3.dirname(LOG_FILE), { recursive: true });
7004
+ fs5.mkdirSync(path4.dirname(LOG_FILE), { recursive: true });
6659
7005
  try {
6660
- if (fs4.statSync(LOG_FILE).size > LOG_MAX_BYTES) {
6661
- 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`);
6662
7008
  }
6663
7009
  } catch {
6664
7010
  }
6665
- fs4.appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
7011
+ fs5.appendFileSync(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
6666
7012
  `);
6667
7013
  } catch {
6668
7014
  }
@@ -6671,7 +7017,7 @@ function pathFromTags(tags) {
6671
7017
  const tag = tags.find((t) => t.startsWith("path:"));
6672
7018
  if (!tag) return null;
6673
7019
  const raw = tag.slice("path:".length);
6674
- return raw.replace(/^~(?=\/|$)/, os7.homedir());
7020
+ return raw.replace(/^~(?=\/|$)/, os8.homedir());
6675
7021
  }
6676
7022
  var ThreadWorker = class {
6677
7023
  constructor(api, identity, machineName, threadId, projectDir, createdAt2) {
@@ -6803,7 +7149,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6803
7149
  process.exit(1);
6804
7150
  }
6805
7151
  let displayName = machineDisplayName(await loadMachine(api, identity.machine_id).catch(() => null) ?? {
6806
- hostname: os7.hostname(),
7152
+ hostname: os8.hostname(),
6807
7153
  id: identity.machine_id
6808
7154
  });
6809
7155
  const applied = consumeAppliedUpdate(version);
@@ -6841,7 +7187,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6841
7187
  daemonLog(`detached ${oldest.threadId.slice(0, 8)} (worker cap)`);
6842
7188
  }
6843
7189
  try {
6844
- fs4.mkdirSync(projectDir, { recursive: true });
7190
+ fs5.mkdirSync(projectDir, { recursive: true });
6845
7191
  } catch (e) {
6846
7192
  daemonLog(`cannot prepare project dir ${projectDir}: ${e instanceof Error ? e.message : String(e)}`);
6847
7193
  return;
@@ -6890,6 +7236,31 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6890
7236
  onThreadDeleted: (id) => detach(id)
6891
7237
  });
6892
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();
6893
7264
  await sweep();
6894
7265
  const reclaim = setInterval(() => {
6895
7266
  for (const worker of workers.values()) {
@@ -7013,6 +7384,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
7013
7384
  clearInterval(sweeper);
7014
7385
  for (const worker of workers.values()) worker.stop();
7015
7386
  events.close();
7387
+ hub.close();
7016
7388
  daemonLog(`daemon exiting (code ${code})`);
7017
7389
  process.exit(code);
7018
7390
  };
@@ -7025,19 +7397,19 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
7025
7397
  var SERVICE_LABEL = "ai.standardcode.daemon";
7026
7398
  var SYSTEMD_UNIT = "standardcode-daemon.service";
7027
7399
  function resolveDaemonCommand(extraArgs = []) {
7028
- const entry = path3.resolve(process.argv[1] ?? "");
7400
+ const entry = path4.resolve(process.argv[1] ?? "");
7029
7401
  if (!entry) throw new Error("Cannot determine how this CLI was launched.");
7030
7402
  const argv = [process.execPath];
7031
7403
  if (entry.endsWith(".ts")) {
7032
- let dir2 = path3.dirname(entry);
7404
+ let dir2 = path4.dirname(entry);
7033
7405
  let tsx = null;
7034
- for (let i = 0; i < 6 && dir2 !== path3.dirname(dir2); i++) {
7035
- const candidate = path3.join(dir2, "node_modules", "tsx", "dist", "cli.mjs");
7036
- 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)) {
7037
7409
  tsx = candidate;
7038
7410
  break;
7039
7411
  }
7040
- dir2 = path3.dirname(dir2);
7412
+ dir2 = path4.dirname(dir2);
7041
7413
  }
7042
7414
  if (!tsx) {
7043
7415
  throw new Error(
@@ -7051,7 +7423,7 @@ function resolveDaemonCommand(extraArgs = []) {
7051
7423
  }
7052
7424
  function servicePath() {
7053
7425
  const parts = [
7054
- path3.dirname(process.execPath),
7426
+ path4.dirname(process.execPath),
7055
7427
  "/opt/homebrew/bin",
7056
7428
  "/usr/local/bin",
7057
7429
  "/usr/bin",
@@ -7067,8 +7439,8 @@ function run2(cmd, args) {
7067
7439
  const output4 = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
7068
7440
  return { ok: res.status === 0, output: output4 };
7069
7441
  }
7070
- var plistPath = () => path3.join(os7.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
7071
- 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);
7072
7444
  var xmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7073
7445
  function installService(command, endpoint) {
7074
7446
  if (process.platform === "darwin") return installLaunchd(command, endpoint);
@@ -7080,9 +7452,9 @@ function installService(command, endpoint) {
7080
7452
  };
7081
7453
  }
7082
7454
  function installLaunchd(command, endpoint) {
7083
- const logDir = path3.join(os7.homedir(), ".standardagents");
7084
- fs4.mkdirSync(logDir, { recursive: true });
7085
- 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 });
7086
7458
  const envEntries = [
7087
7459
  ` <key>PATH</key><string>${xmlEscape(servicePath())}</string>`,
7088
7460
  ...endpoint ? [` <key>STANDARD_CODE_DAEMON_ENDPOINT</key><string>${xmlEscape(endpoint)}</string>`] : []
@@ -7099,8 +7471,8 @@ ${command.argv.map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n")}
7099
7471
  <key>RunAtLoad</key><true/>
7100
7472
  <key>KeepAlive</key><true/>
7101
7473
  <key>ThrottleInterval</key><integer>5</integer>
7102
- <key>StandardOutPath</key><string>${xmlEscape(path3.join(logDir, "daemon.out.log"))}</string>
7103
- <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>
7104
7476
  <key>EnvironmentVariables</key>
7105
7477
  <dict>
7106
7478
  ${envEntries}
@@ -7108,7 +7480,7 @@ ${envEntries}
7108
7480
  </dict>
7109
7481
  </plist>
7110
7482
  `;
7111
- fs4.writeFileSync(plistPath(), plist);
7483
+ fs5.writeFileSync(plistPath(), plist);
7112
7484
  const uid = typeof process.getuid === "function" ? process.getuid() : 501;
7113
7485
  run2("launchctl", ["bootout", `gui/${uid}`, plistPath()]);
7114
7486
  const boot = run2("launchctl", ["bootstrap", `gui/${uid}`, plistPath()]);
@@ -7125,7 +7497,7 @@ ${envEntries}
7125
7497
  return { ok: true, detail: `LaunchAgent installed (${plistPath()})` };
7126
7498
  }
7127
7499
  function installSystemd(command, endpoint) {
7128
- fs4.mkdirSync(path3.dirname(unitPath()), { recursive: true });
7500
+ fs5.mkdirSync(path4.dirname(unitPath()), { recursive: true });
7129
7501
  const unit = `[Unit]
7130
7502
  Description=Standard Code daemon (headless coding-agent execution client)
7131
7503
  After=network-online.target
@@ -7140,7 +7512,7 @@ ${endpoint ? `Environment=STANDARD_CODE_DAEMON_ENDPOINT=${endpoint}
7140
7512
  [Install]
7141
7513
  WantedBy=default.target
7142
7514
  `;
7143
- fs4.writeFileSync(unitPath(), unit);
7515
+ fs5.writeFileSync(unitPath(), unit);
7144
7516
  const reload = run2("systemctl", ["--user", "daemon-reload"]);
7145
7517
  if (!reload.ok) {
7146
7518
  return {
@@ -7153,10 +7525,10 @@ WantedBy=default.target
7153
7525
  if (!enable.ok) {
7154
7526
  return { ok: false, detail: `systemctl enable failed: ${enable.output}` };
7155
7527
  }
7156
- const linger = run2("loginctl", ["enable-linger", os7.userInfo().username]);
7528
+ const linger = run2("loginctl", ["enable-linger", os8.userInfo().username]);
7157
7529
  return {
7158
7530
  ok: true,
7159
- 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}`)
7160
7532
  };
7161
7533
  }
7162
7534
  function uninstallService() {
@@ -7164,7 +7536,7 @@ function uninstallService() {
7164
7536
  const uid = typeof process.getuid === "function" ? process.getuid() : 501;
7165
7537
  run2("launchctl", ["bootout", `gui/${uid}`, plistPath()]);
7166
7538
  try {
7167
- fs4.unlinkSync(plistPath());
7539
+ fs5.unlinkSync(plistPath());
7168
7540
  } catch {
7169
7541
  }
7170
7542
  return { ok: true, detail: "LaunchAgent removed" };
@@ -7172,7 +7544,7 @@ function uninstallService() {
7172
7544
  if (process.platform === "linux") {
7173
7545
  run2("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT]);
7174
7546
  try {
7175
- fs4.unlinkSync(unitPath());
7547
+ fs5.unlinkSync(unitPath());
7176
7548
  } catch {
7177
7549
  }
7178
7550
  run2("systemctl", ["--user", "daemon-reload"]);
@@ -7182,7 +7554,7 @@ function uninstallService() {
7182
7554
  }
7183
7555
  function serviceStatus() {
7184
7556
  if (process.platform === "darwin") {
7185
- const installed = fs4.existsSync(plistPath());
7557
+ const installed = fs5.existsSync(plistPath());
7186
7558
  const list = run2("launchctl", ["list", SERVICE_LABEL]);
7187
7559
  const pidMatch = list.output.match(/"PID"\s*=\s*(\d+)/);
7188
7560
  return {
@@ -7192,7 +7564,7 @@ function serviceStatus() {
7192
7564
  };
7193
7565
  }
7194
7566
  if (process.platform === "linux") {
7195
- const installed = fs4.existsSync(unitPath());
7567
+ const installed = fs5.existsSync(unitPath());
7196
7568
  const active = run2("systemctl", ["--user", "is-active", SYSTEMD_UNIT]);
7197
7569
  return {
7198
7570
  installed,
@@ -7284,7 +7656,7 @@ async function installCommand(endpointFlag) {
7284
7656
  const api = await ensureSignedIn(endpoint);
7285
7657
  const identity = loadMachineIdentity();
7286
7658
  const existing = await loadMachine(api, identity.machine_id).catch(() => null);
7287
- const suggested = machineDisplayName(existing ?? { hostname: os7.hostname(), id: identity.machine_id });
7659
+ const suggested = machineDisplayName(existing ?? { hostname: os8.hostname(), id: identity.machine_id });
7288
7660
  const rl = readline2.createInterface({ input: stdin, output: stdout });
7289
7661
  const answer = (await rl.question(
7290
7662
  `${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
@@ -7344,7 +7716,7 @@ async function statusCommand() {
7344
7716
  const cred = getCredential(endpoint);
7345
7717
  if (!cred) {
7346
7718
  stdout.write(
7347
- `${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}
7348
7720
  `
7349
7721
  );
7350
7722
  stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
@@ -7355,7 +7727,7 @@ async function statusCommand() {
7355
7727
  const api = new ApiClient(endpoint, cred.access_token);
7356
7728
  const record2 = await loadMachine(api, identity.machine_id).catch(() => null);
7357
7729
  stdout.write(
7358
- `${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}
7359
7731
  `
7360
7732
  );
7361
7733
  if (!record2) {
@@ -7379,8 +7751,8 @@ async function projectCommand(action, target) {
7379
7751
  `);
7380
7752
  process.exit(1);
7381
7753
  }
7382
- const dir2 = path3.resolve(target);
7383
- if (action === "add" && !fs4.existsSync(dir2)) {
7754
+ const dir2 = path4.resolve(target);
7755
+ if (action === "add" && !fs5.existsSync(dir2)) {
7384
7756
  stdout.write(`${c2.red}\u2717${c2.reset} ${dir2} does not exist on this machine.
7385
7757
  `);
7386
7758
  process.exit(1);
@@ -7389,7 +7761,7 @@ async function projectCommand(action, target) {
7389
7761
  const api = await ensureSignedIn(endpoint);
7390
7762
  const identity = loadMachineIdentity();
7391
7763
  const record2 = await loadMachine(api, identity.machine_id).catch(() => null);
7392
- const displayName = machineDisplayName(record2 ?? { hostname: os7.hostname(), id: identity.machine_id });
7764
+ const displayName = machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id });
7393
7765
  if (action === "add") {
7394
7766
  await registerProject(api, identity, dir2);
7395
7767
  stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
@@ -7604,7 +7976,7 @@ ${c3.teal}\u25C7${c3.reset} ${c3.dim}Standard Code \u2014 see you soon.${c3.rese
7604
7976
  `);
7605
7977
  }
7606
7978
  function printWelcome(endpoint, projectDir) {
7607
- const home = os7.homedir();
7979
+ const home = os8.homedir();
7608
7980
  const dir2 = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
7609
7981
  const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
7610
7982
  const version = readVersion();
@@ -7674,8 +8046,8 @@ async function main() {
7674
8046
  const endpointArg = cliArgs.endpoint;
7675
8047
  const endpointOverride = cliArgs.promptEndpoint || typeof endpointArg === "string" && endpointArg.trim() !== "";
7676
8048
  const dirArg = cliArgs.dir;
7677
- const projectDir = path3.resolve(dirArg || process.cwd());
7678
- const machine = os7.hostname();
8049
+ const projectDir = path4.resolve(dirArg || process.cwd());
8050
+ const machine = os8.hostname();
7679
8051
  const reader = { rl: null };
7680
8052
  let handoffClosing = false;
7681
8053
  let preflightArmed = false;
@@ -7889,7 +8261,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
7889
8261
  const ok = await ensureSamaAuth(tui, api);
7890
8262
  if (!ok) process.exit(0);
7891
8263
  }
7892
- const home = os7.homedir();
8264
+ const home = os8.homedir();
7893
8265
  const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
7894
8266
  const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
7895
8267
  const session = { mode: "local", identity };
@@ -9063,7 +9435,10 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9063
9435
  }
9064
9436
  const polledBusy = (serverBusy ?? false) || threadBusy(msgs);
9065
9437
  if (serverTool && !activeSteps.has(serverTool.id)) {
9066
- 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
+ );
9067
9442
  refreshStatus();
9068
9443
  } else if (serverBusy !== null && !serverTool && activeSteps.size) {
9069
9444
  activeSteps.clear();
@@ -9320,12 +9695,12 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
9320
9695
  );
9321
9696
  if (!picked) return;
9322
9697
  if (picked === ADD) {
9323
- const path14 = await tui.prompt(
9698
+ const path15 = await tui.prompt(
9324
9699
  `Absolute project path on ${machine.name}`,
9325
9700
  machine.id === self.machine_id ? process.cwd() : "/home/you/project"
9326
9701
  );
9327
- if (!path14 || !path14.trim()) return;
9328
- const trimmed = path14.trim();
9702
+ if (!path15 || !path15.trim()) return;
9703
+ const trimmed = path15.trim();
9329
9704
  if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
9330
9705
  tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
9331
9706
  return;