@standardagents/code 0.13.2 → 0.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,9 +5,7 @@ import readline3 from 'readline/promises';
5
5
  import { stdout, stdin } from 'process';
6
6
  import * as api_star from '@standardagents/code-network/api';
7
7
  import * as stream_star from '@standardagents/code-network/stream';
8
- import crypto2 from 'crypto';
9
- import * as heartbeat_star from '@standardagents/code-network/heartbeat';
10
- import * as events_stream_star from '@standardagents/code-network/events-stream';
8
+ import * as hub_star from '@standardagents/code-network/hub';
11
9
  import * as subagent_streams_star from '@standardagents/code-network/subagent-streams';
12
10
  import * as session_state_star from '@standardagents/code-network/session-state';
13
11
  import * as transcript_delivery_star from '@standardagents/code-network/transcript-delivery';
@@ -26,6 +24,7 @@ import { startDeviceLogin, pollDeviceLogin } from '@standardagents/code-network/
26
24
  import * as recorder_star from '@standardagents/code-network/recorder';
27
25
  import * as recorder_node_star from '@standardagents/code-network/recorder-node';
28
26
  import { PRODUCTION_ENDPOINT, OPENSAMA_AGENT_ID, AGENT_ID, AGENT_ID_VARIANTS, AGENT_CHOICES } from '@standardagents/code-network/agents';
27
+ import crypto from 'crypto';
29
28
  import { parseStopProcessArgs } from '@standardagents/code-network/registry';
30
29
  import * as relay_star from '@standardagents/code-network/relay';
31
30
  import fsp2 from 'fs/promises';
@@ -71,33 +70,37 @@ var themeGray = isLightTheme ? "\x1B[38;5;244m" : "\x1B[90m";
71
70
  var stream_exports = {};
72
71
  __reExport(stream_exports, stream_star);
73
72
 
74
- // src/heartbeat.ts
75
- var heartbeat_exports = {};
76
- __reExport(heartbeat_exports, heartbeat_star);
73
+ // src/hub.ts
74
+ var hub_exports = {};
75
+ __reExport(hub_exports, hub_star);
77
76
 
78
77
  // src/account-user-stream.ts
79
78
  var AccountUserStream = class {
80
- constructor(api, clientId, options) {
81
- this.api = api;
82
- this.clientId = clientId;
83
- this.options = options;
84
- }
85
- api;
86
- clientId;
87
- options;
88
- ws = null;
89
- closed = false;
90
79
  started = false;
91
- heartbeat = null;
92
- reconnectAttempt = 0;
93
- reconnectTimer = null;
80
+ hub;
94
81
  eventListeners = /* @__PURE__ */ new Set();
95
82
  connectionListeners = /* @__PURE__ */ new Set();
96
83
  connected = false;
84
+ constructor(api, clientId, options) {
85
+ this.hub = new hub_exports.HubSocket(
86
+ api,
87
+ clientId,
88
+ {
89
+ onEvent: (event, data) => this.emitEvent(event, data),
90
+ onConnection: (state, attempt) => {
91
+ this.connected = state === "connected";
92
+ this.emitConnection(state, attempt);
93
+ }
94
+ },
95
+ options.clientName,
96
+ options.clientKind,
97
+ options.uniqueConnectionId
98
+ );
99
+ }
97
100
  start() {
98
- if (this.started || this.closed) return;
101
+ if (this.started) return;
99
102
  this.started = true;
100
- this.openSocket();
103
+ this.hub.start();
101
104
  }
102
105
  onEvent(listener) {
103
106
  this.eventListeners.add(listener);
@@ -125,102 +128,65 @@ var AccountUserStream = class {
125
128
  }, timeoutMs);
126
129
  });
127
130
  }
128
- openSocket() {
129
- if (this.closed) return;
130
- const clientId = this.options.uniqueConnectionId ? `${this.clientId}:${crypto2.randomBytes(8).toString("hex")}` : this.clientId;
131
- let url = `${this.api.wsEndpoint}/api/users/me/stream?token=${encodeURIComponent(this.api.bearer)}&client_id=${encodeURIComponent(clientId)}&client_kind=${encodeURIComponent(this.options.clientKind)}`;
132
- if (this.options.clientName) url += `&client_name=${encodeURIComponent(this.options.clientName)}`;
133
- let ws;
134
- try {
135
- ws = new WebSocket(url);
136
- } catch {
137
- this.scheduleReconnect();
138
- return;
139
- }
140
- this.ws = ws;
141
- ws.addEventListener("open", () => {
142
- if (this.ws !== ws) return;
143
- this.connected = true;
144
- this.reconnectAttempt = 0;
145
- this.startHeartbeat(ws);
146
- this.emitConnection("connected", 0);
147
- });
148
- ws.addEventListener("message", (event) => {
149
- if (this.ws === ws) this.heartbeat?.markAlive();
150
- this.onMessage(String(event.data));
151
- });
152
- ws.addEventListener("error", () => this.handleDrop(ws));
153
- ws.addEventListener("close", () => this.handleDrop(ws));
154
- }
155
- onMessage(raw) {
156
- let message;
157
- try {
158
- message = JSON.parse(raw);
159
- } catch {
160
- return;
161
- }
162
- if (!message || typeof message !== "object") return;
163
- const frame = message;
164
- if (typeof frame.event === "string") {
165
- this.emitEvent(frame.event, frame.data);
166
- return;
167
- }
168
- if (frame.type === "wake" && typeof frame.threadId === "string") {
169
- this.emitEvent("standardagents.wake", { thread_id: frame.threadId });
170
- }
171
- }
172
131
  emitEvent(event, data) {
173
132
  for (const listener of this.eventListeners) listener(event, data);
174
133
  }
175
134
  emitConnection(state, attempt) {
176
135
  for (const listener of this.connectionListeners) listener(state, attempt);
177
136
  }
178
- handleDrop(ws) {
179
- if (this.ws !== ws) return;
180
- this.ws = null;
181
- this.connected = false;
182
- this.stopHeartbeat();
183
- this.scheduleReconnect();
184
- }
185
- scheduleReconnect() {
186
- if (this.closed || this.reconnectTimer) return;
187
- this.reconnectAttempt++;
188
- this.emitConnection("reconnecting", this.reconnectAttempt);
189
- const base = Math.min(500 * 2 ** (this.reconnectAttempt - 1), 15e3);
190
- const delay = base + Math.floor(Math.random() * 400);
191
- this.reconnectTimer = setTimeout(() => {
192
- this.reconnectTimer = null;
193
- this.openSocket();
194
- }, delay);
195
- }
196
- startHeartbeat(ws) {
197
- this.stopHeartbeat();
198
- this.heartbeat = new heartbeat_exports.Heartbeat(ws, () => this.handleDrop(ws), { request: "stream_ping" });
199
- this.heartbeat.start();
200
- }
201
- stopHeartbeat() {
202
- this.heartbeat?.stop();
203
- this.heartbeat = null;
204
- }
205
137
  close() {
206
- this.closed = true;
207
138
  this.connected = false;
208
- this.stopHeartbeat();
209
- if (this.reconnectTimer) {
210
- clearTimeout(this.reconnectTimer);
211
- this.reconnectTimer = null;
212
- }
213
- const ws = this.ws;
214
- this.ws = null;
215
- ws?.close();
139
+ this.hub.close();
216
140
  this.eventListeners.clear();
217
141
  this.connectionListeners.clear();
218
142
  }
219
143
  };
220
144
 
221
- // src/events-stream.ts
222
- var events_stream_exports = {};
223
- __reExport(events_stream_exports, events_stream_star);
145
+ // packages/core/src/session-events.ts
146
+ var SESSION_CHANGED_EVENT = "standardcode.sessions_changed";
147
+ var SESSION_CHANGES = /* @__PURE__ */ new Set([
148
+ "created",
149
+ "updated",
150
+ "deleted",
151
+ "metadata",
152
+ "activity",
153
+ "subagents"
154
+ ]);
155
+ function parseSessionChangedData(value) {
156
+ if (!value || typeof value !== "object") return null;
157
+ const raw = value;
158
+ if (typeof raw.change !== "string" || !SESSION_CHANGES.has(raw.change)) {
159
+ return null;
160
+ }
161
+ const parsed = {
162
+ change: raw.change,
163
+ ...typeof raw.thread_id === "string" && raw.thread_id ? { thread_id: raw.thread_id } : {}
164
+ };
165
+ if (Object.prototype.hasOwnProperty.call(raw, "name") && (raw.name === null || typeof raw.name === "string")) {
166
+ parsed.name = raw.name;
167
+ }
168
+ if (typeof raw.archived === "boolean") parsed.archived = raw.archived;
169
+ if (raw.thread && typeof raw.thread === "object" && !Array.isArray(raw.thread)) {
170
+ const thread = raw.thread;
171
+ const id = typeof thread.id === "string" && thread.id ? thread.id : parsed.thread_id;
172
+ if (id && (!parsed.thread_id || parsed.thread_id === id)) {
173
+ const delta = { id };
174
+ if (typeof thread.agent_id === "string") delta.agent_id = thread.agent_id;
175
+ if (Array.isArray(thread.tags)) {
176
+ delta.tags = thread.tags.filter((tag) => typeof tag === "string");
177
+ }
178
+ for (const key of ["created_at", "updated_at", "last_message_at"]) {
179
+ if (typeof thread[key] === "number" && Number.isFinite(thread[key])) delta[key] = thread[key];
180
+ }
181
+ for (const key of ["title", "preview"]) {
182
+ if (typeof thread[key] === "string") delta[key] = thread[key];
183
+ }
184
+ if (typeof thread.executing === "boolean") delta.executing = thread.executing;
185
+ parsed.thread = delta;
186
+ }
187
+ }
188
+ return parsed;
189
+ }
224
190
 
225
191
  // src/subagent-streams.ts
226
192
  var subagent_streams_exports = {};
@@ -1628,6 +1594,11 @@ function ensureImagePlaceholders(text, images) {
1628
1594
  if (!missing.length) return text;
1629
1595
  return [text.trimEnd(), ...missing].filter(Boolean).join(" ");
1630
1596
  }
1597
+ function restoreTuiInput(input3 = process.stdin) {
1598
+ readline.emitKeypressEvents(input3);
1599
+ if (input3.isTTY) input3.setRawMode(true);
1600
+ input3.resume();
1601
+ }
1631
1602
  var INPUT_BOX_MARGIN = 1;
1632
1603
  function inputBoxBorderColor() {
1633
1604
  return "\x1B[38;5;240m";
@@ -1903,10 +1874,10 @@ function sanitizeHudRow(s) {
1903
1874
  var Tui = class _Tui {
1904
1875
  constructor(level = 1) {
1905
1876
  this.level = level;
1906
- readline.emitKeypressEvents(process.stdin);
1907
- if (process.stdin.isTTY) process.stdin.setRawMode(true);
1908
- process.stdin.on("keypress", (str, key) => this.dispatch(str, key));
1909
- process.stdin.resume();
1877
+ process.stdin.on("keypress", (str, key) => {
1878
+ if (this.inputSuspendDepth === 0) this.dispatch(str, key);
1879
+ });
1880
+ restoreTuiInput();
1910
1881
  process.stdout.write("\x1B[?2004h");
1911
1882
  process.on("exit", () => process.stdout.write("\x1B[?2004l\x1B[?25h"));
1912
1883
  process.stdout.on("resize", () => this.scheduleResizeRedraw());
@@ -1989,6 +1960,10 @@ var Tui = class _Tui {
1989
1960
  // takeover (approval / menu) state
1990
1961
  takeoverHandler = null;
1991
1962
  bufferedPrints = [];
1963
+ // Plain readline prompts (notably the inline daemon installer) temporarily
1964
+ // own stdin. While they do, ignore the duplicate keypress events delivered
1965
+ // to this TUI; the outermost handoff restores raw/resumed input on return.
1966
+ inputSuspendDepth = 0;
1992
1967
  // double-press-to-quit state: the first ctrl-c arms a brief window and shows a
1993
1968
  // transient hint; a second ctrl-c within the window actually quits.
1994
1969
  quitArmed = false;
@@ -2034,6 +2009,25 @@ var Tui = class _Tui {
2034
2009
  lastDraftSeen = "";
2035
2010
  onQuit = () => process.exit(0);
2036
2011
  levelListeners = [];
2012
+ /**
2013
+ * Let a plain readline flow own stdin, then reliably hand it back to the TUI.
2014
+ * Supports nesting so a shared command may open more than one prompt.
2015
+ */
2016
+ async withSuspendedInput(run3) {
2017
+ this.inputSuspendDepth++;
2018
+ if (this.inputSuspendDepth === 1) {
2019
+ process.stdout.write("\x1B[?2004l\x1B[?25h");
2020
+ }
2021
+ try {
2022
+ return await run3();
2023
+ } finally {
2024
+ this.inputSuspendDepth--;
2025
+ if (this.inputSuspendDepth === 0) {
2026
+ restoreTuiInput();
2027
+ process.stdout.write("\x1B[?2004h");
2028
+ }
2029
+ }
2030
+ }
2037
2031
  /**
2038
2032
  * Terminal resize fires continuously while the user drags. Redrawing on every
2039
2033
  * event desyncs the region-height math (each paint uses a half-rewrapped
@@ -3910,7 +3904,7 @@ function readVersion() {
3910
3904
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
3911
3905
  } catch {
3912
3906
  }
3913
- return "0.13.2" ;
3907
+ return "0.13.4" ;
3914
3908
  }
3915
3909
  function isLocalHost(host) {
3916
3910
  return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.endsWith(".localhost") || /^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host);
@@ -3943,7 +3937,7 @@ function loadMachineIdentity() {
3943
3937
  } catch {
3944
3938
  }
3945
3939
  const identity = {
3946
- machine_id: crypto2.randomUUID(),
3940
+ machine_id: crypto.randomUUID(),
3947
3941
  created_at: Date.now()
3948
3942
  };
3949
3943
  saveMachineIdentity(identity);
@@ -3957,7 +3951,7 @@ function daemonClientId(identity) {
3957
3951
  return `daemon:${identity.machine_id}`;
3958
3952
  }
3959
3953
  function interactiveClientId(identity) {
3960
- return `cli:${identity.machine_id}:${crypto2.randomBytes(4).toString("hex")}`;
3954
+ return `cli:${identity.machine_id}:${crypto.randomBytes(4).toString("hex")}`;
3961
3955
  }
3962
3956
  var KEY_PREFIX = "standardcode.machine.";
3963
3957
  var CMD_SUFFIX = ".cmd";
@@ -4338,7 +4332,7 @@ async function enqueueMachineCommand(api, machineId, kind, args) {
4338
4332
  const key = commandKey(machineId);
4339
4333
  const queue = parseCommands(await api.userKvGet(key));
4340
4334
  const cmd = {
4341
- id: crypto2.randomBytes(6).toString("hex"),
4335
+ id: crypto.randomBytes(6).toString("hex"),
4342
4336
  kind,
4343
4337
  args,
4344
4338
  requested_at: Date.now()
@@ -4576,7 +4570,7 @@ function parseBrowseResult(value) {
4576
4570
  }
4577
4571
  function remoteBrowseBackend(api, accountStream, machineId, label) {
4578
4572
  const rpc = async (req) => {
4579
- const nonce = crypto2.randomBytes(8).toString("hex");
4573
+ const nonce = crypto.randomBytes(8).toString("hex");
4580
4574
  const res = await requestFsBrowse(api, accountStream, machineId, { nonce, ...req }).catch(
4581
4575
  (error) => {
4582
4576
  throw new Error(
@@ -4793,7 +4787,7 @@ function download(url, dest, redirects = 5) {
4793
4787
  });
4794
4788
  }
4795
4789
  async function sha256File(file2) {
4796
- const hash = crypto2.createHash("sha256");
4790
+ const hash = crypto.createHash("sha256");
4797
4791
  await new Promise((resolve, reject) => {
4798
4792
  const stream = fs11.createReadStream(file2);
4799
4793
  stream.on("data", (chunk) => hash.update(chunk));
@@ -5105,7 +5099,7 @@ var HostTools = class {
5105
5099
  return { ok: false, error: "args_json must be a JSON array of strings" };
5106
5100
  }
5107
5101
  }
5108
- const hash = crypto2.createHash("sha256").update(JSON.stringify(files)).digest("hex").slice(0, 12);
5102
+ const hash = crypto.createHash("sha256").update(JSON.stringify(files)).digest("hex").slice(0, 12);
5109
5103
  const skillDir = path9.join(os6.tmpdir(), "standardcode-skills", `${skill}-${hash}`);
5110
5104
  for (const f of files) {
5111
5105
  const dest = path9.resolve(skillDir, f.path);
@@ -5197,7 +5191,7 @@ ${truncated}`
5197
5191
  if (!fs11.existsSync(cwd)) {
5198
5192
  return { ok: false, error: `cwd does not exist: ${cwd}` };
5199
5193
  }
5200
- const id = crypto2.randomUUID().slice(0, 8);
5194
+ const id = crypto.randomUUID().slice(0, 8);
5201
5195
  const logPath = path9.join(LOG_DIR, `${id}.log`);
5202
5196
  let out;
5203
5197
  try {
@@ -5601,7 +5595,7 @@ ${stderrTail.trim()}` : msg;
5601
5595
  target,
5602
5596
  argsSha256: sha256(canonicalJson(args)),
5603
5597
  resultSha256: sha256(text),
5604
- nonce: crypto2.randomBytes(8).toString("hex"),
5598
+ nonce: crypto.randomBytes(8).toString("hex"),
5605
5599
  isError,
5606
5600
  at: Date.now()
5607
5601
  };
@@ -5871,7 +5865,7 @@ function sortKeys(value) {
5871
5865
  return value;
5872
5866
  }
5873
5867
  function sha256(input3) {
5874
- return crypto2.createHash("sha256").update(input3).digest("hex");
5868
+ return crypto.createHash("sha256").update(input3).digest("hex");
5875
5869
  }
5876
5870
 
5877
5871
  // src/execution-session.ts
@@ -5993,6 +5987,27 @@ var DaemonUserStream = class {
5993
5987
  this.stream.close();
5994
5988
  }
5995
5989
  };
5990
+
5991
+ // src/daemon-activity.ts
5992
+ var DaemonWorkerActivity = class {
5993
+ activeTools = /* @__PURE__ */ new Set();
5994
+ approvalLeases = /* @__PURE__ */ new Set();
5995
+ setToolActive(toolCallId, active) {
5996
+ if (active) this.activeTools.add(toolCallId);
5997
+ else this.activeTools.delete(toolCallId);
5998
+ }
5999
+ /** Hold the worker active until the returned idempotent release runs. */
6000
+ beginApproval() {
6001
+ const lease = /* @__PURE__ */ Symbol("approval");
6002
+ this.approvalLeases.add(lease);
6003
+ return () => {
6004
+ this.approvalLeases.delete(lease);
6005
+ };
6006
+ }
6007
+ get busy() {
6008
+ return this.activeTools.size > 0 || this.approvalLeases.size > 0;
6009
+ }
6010
+ };
5996
6011
  var PKG_NAME = "@standardagents/code";
5997
6012
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
5998
6013
  var CACHE_REL_DIR = ".config/standardagents-cli";
@@ -6171,19 +6186,7 @@ function runUpdate(pm) {
6171
6186
  }
6172
6187
 
6173
6188
  // src/daemon.ts
6174
- var approvalWakes = /* @__PURE__ */ new Map();
6175
- function subscribeApprovalWake(threadId, wake) {
6176
- const set = approvalWakes.get(threadId) ?? /* @__PURE__ */ new Set();
6177
- set.add(wake);
6178
- approvalWakes.set(threadId, set);
6179
- return () => {
6180
- set.delete(wake);
6181
- if (set.size === 0) approvalWakes.delete(threadId);
6182
- };
6183
- }
6184
- function wakeApprovals(threadId) {
6185
- for (const wake of approvalWakes.get(threadId) ?? []) wake();
6186
- }
6189
+ var approvalWakes = new relay_exports.ApprovalWakeRegistry();
6187
6190
  var UPDATE_CHECK_MS = 6 * 60 * 6e4;
6188
6191
  var MAX_WORKERS = 30;
6189
6192
  var BRIDGE_ATTACH_GRACE_MS = 15e3;
@@ -6230,11 +6233,11 @@ var ThreadWorker = class {
6230
6233
  },
6231
6234
  hooks: {
6232
6235
  onActivity: (line) => daemonLog(`[${threadId.slice(0, 8)}] ${line}`),
6233
- onStatus: (_id, summary) => {
6234
- this.inFlight += summary ? 1 : -1;
6235
- if (this.inFlight < 0) this.inFlight = 0;
6236
- if (summary) this.clearIdleTimer();
6237
- else if (this.inFlight === 0) this.scheduleIdle(BRIDGE_IDLE_MS);
6236
+ onStatus: (id, summary) => {
6237
+ const wasBusy = this.busy;
6238
+ this.activity.setToolActive(id, summary !== null);
6239
+ if (this.busy) this.clearIdleTimer();
6240
+ else if (wasBusy) this.scheduleIdle(BRIDGE_IDLE_MS);
6238
6241
  },
6239
6242
  onConnection: (state, attempt) => {
6240
6243
  if (state !== "reconnecting" || attempt === 1 || attempt % 10 === 0) {
@@ -6272,29 +6275,36 @@ var ThreadWorker = class {
6272
6275
  return { choice: "deny", reason: "No one is available to approve this right now." };
6273
6276
  }
6274
6277
  daemonLog(`[${threadId.slice(0, 8)}] relaying approval: ${summary} (risk ${effectiveRisk})`);
6275
- const response = await (0, relay_exports.awaitApprovalViaRelay)(
6276
- api,
6277
- threadId,
6278
- {
6279
- tool_call_id: req.toolCallId,
6280
- tool: req.tool,
6281
- summary,
6282
- permission: req.requestPermission,
6283
- risk: effectiveRisk,
6284
- machine: machineName,
6285
- requested_at: Date.now()
6286
- },
6287
- { subscribe: (wake) => subscribeApprovalWake(threadId, wake) }
6288
- );
6289
- if (!response) {
6290
- return { choice: "deny", reason: "The approval request timed out with no one to approve it." };
6291
- }
6292
- if (response.choice === "always") this.perm.alwaysAllow.add(permKey);
6293
- if (response.choice === "always_risk") this.perm.allowRisk.add(effectiveRisk);
6294
- if (response.choice === "always" || response.choice === "always_risk") {
6295
- (0, approvals_exports.saveApprovals)(api, threadId, this.perm);
6278
+ const releaseApproval = this.activity.beginApproval();
6279
+ this.clearIdleTimer();
6280
+ try {
6281
+ const response = await (0, relay_exports.awaitApprovalViaRelay)(
6282
+ api,
6283
+ threadId,
6284
+ {
6285
+ tool_call_id: req.toolCallId,
6286
+ tool: req.tool,
6287
+ summary,
6288
+ permission: req.requestPermission,
6289
+ risk: effectiveRisk,
6290
+ machine: machineName,
6291
+ requested_at: Date.now()
6292
+ },
6293
+ { subscribe: (wake) => approvalWakes.subscribe(threadId, wake) }
6294
+ );
6295
+ if (!response) {
6296
+ return { choice: "deny", reason: "The approval request timed out with no one to approve it." };
6297
+ }
6298
+ if (response.choice === "always") this.perm.alwaysAllow.add(permKey);
6299
+ if (response.choice === "always_risk") this.perm.allowRisk.add(effectiveRisk);
6300
+ if (response.choice === "always" || response.choice === "always_risk") {
6301
+ (0, approvals_exports.saveApprovals)(api, threadId, this.perm);
6302
+ }
6303
+ return { choice: response.choice, reason: response.reason };
6304
+ } finally {
6305
+ releaseApproval();
6306
+ if (!this.busy) this.scheduleIdle(BRIDGE_IDLE_MS);
6296
6307
  }
6297
- return { choice: response.choice, reason: response.reason };
6298
6308
  }
6299
6309
  }
6300
6310
  });
@@ -6309,7 +6319,7 @@ var ThreadWorker = class {
6309
6319
  createdAt;
6310
6320
  session;
6311
6321
  perm;
6312
- inFlight = 0;
6322
+ activity = new DaemonWorkerActivity();
6313
6323
  idleTimer = null;
6314
6324
  watchedAnotherOwner = false;
6315
6325
  /** Set by the daemon so a superseded worker can remove itself. */
@@ -6317,7 +6327,7 @@ var ThreadWorker = class {
6317
6327
  /** Set by the daemon so an inactive bridge removes itself. */
6318
6328
  onIdle;
6319
6329
  get busy() {
6320
- return this.inFlight > 0;
6330
+ return this.activity.busy;
6321
6331
  }
6322
6332
  clearIdleTimer() {
6323
6333
  if (!this.idleTimer) return;
@@ -6686,7 +6696,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6686
6696
  onEvent: (event, data) => {
6687
6697
  if (event === "standardcode.approval_changed") {
6688
6698
  const threadId = data && typeof data === "object" ? data.thread_id : null;
6689
- if (typeof threadId === "string") wakeApprovals(threadId);
6699
+ if (typeof threadId === "string") approvalWakes.wake(threadId);
6690
6700
  return;
6691
6701
  }
6692
6702
  if (event === "standardcode.lines_changed") {
@@ -6706,16 +6716,21 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6706
6716
  return;
6707
6717
  }
6708
6718
  if (event !== "standardcode.machine_changed") return;
6709
- const key = data && typeof data === "object" ? data.key : null;
6719
+ const frame = data && typeof data === "object" ? data : null;
6720
+ const key = frame?.key;
6710
6721
  daemonLog(`machine event: ${typeof key === "string" ? key : "missing key"}`);
6711
6722
  if (key === `${machinePrefix}.cmd`) void drainCommands();
6712
6723
  else if (key === `${machinePrefix}.fsreq`) {
6713
6724
  void drainFsRequests();
6714
6725
  } else if (key === `${machinePrefix}.name`) {
6715
- void getMachineName(api, identity.machine_id).then((name) => {
6716
- displayName = name || os6.hostname();
6717
- }).catch(() => {
6718
- });
6726
+ if (frame && Object.prototype.hasOwnProperty.call(frame, "value")) {
6727
+ displayName = typeof frame.value === "string" && frame.value.trim() ? frame.value.trim() : os6.hostname();
6728
+ } else {
6729
+ void getMachineName(api, identity.machine_id).then((name) => {
6730
+ displayName = name || os6.hostname();
6731
+ }).catch(() => {
6732
+ });
6733
+ }
6719
6734
  }
6720
6735
  },
6721
6736
  onConnection: (state, attempt) => {
@@ -6727,6 +6742,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6727
6742
  return;
6728
6743
  }
6729
6744
  daemonLog("user stream: connected");
6745
+ approvalWakes.wakeAll();
6730
6746
  void publishPresence(true).then(() => recoverOnConnect());
6731
6747
  }
6732
6748
  },
@@ -7125,6 +7141,11 @@ async function runDaemonCommand(argv) {
7125
7141
  const { endpoint, rest } = parseEndpointFlag(restArgs);
7126
7142
  switch (command) {
7127
7143
  case "run":
7144
+ if (rest.includes("--ephemeral") || process.env.STANDARD_CODE_EPHEMERAL === "1") {
7145
+ process.stdin.resume();
7146
+ process.stdin.on("end", () => process.exit(0));
7147
+ process.stdin.on("close", () => process.exit(0));
7148
+ }
7128
7149
  await runDaemon({ endpoint: endpoint || process.env.STANDARD_CODE_DAEMON_ENDPOINT });
7129
7150
  return;
7130
7151
  case "install":
@@ -7802,45 +7823,47 @@ ${c5.dim}Install it with \`standardcode daemon install\`, or run this session on
7802
7823
  return false;
7803
7824
  }
7804
7825
  localOfferAnswered = true;
7805
- stdout.write(
7806
- `
7826
+ return tui.withSuspendedInput(async () => {
7827
+ stdout.write(
7828
+ `
7807
7829
  ${c5.bold}This machine has no always-on daemon${c5.reset} ${c5.dim}\u2014 the daemon is what runs sessions here.${c5.reset}
7808
7830
  `
7809
- );
7810
- const promptRl = readline3.createInterface({ input: stdin, output: stdout });
7811
- let answer = "";
7812
- try {
7813
- answer = (await promptRl.question(
7814
- `${c5.white}Install it now so this machine can run sessions?${c5.reset} ${c5.dim}[Y/n]${c5.reset} `
7815
- )).trim().toLowerCase();
7816
- } catch {
7817
- answer = "n";
7818
- } finally {
7819
- promptRl.close();
7820
- }
7821
- if (answer !== "" && answer !== "y" && answer !== "yes") {
7822
- savePrefs({ daemon_install_declined_at: Date.now() });
7823
- stdout.write(
7824
- `${c5.dim}Okay \u2014 this machine can't run sessions until the daemon is installed.
7831
+ );
7832
+ const promptRl = readline3.createInterface({ input: stdin, output: stdout });
7833
+ let answer = "";
7834
+ try {
7835
+ answer = (await promptRl.question(
7836
+ `${c5.white}Install it now so this machine can run sessions?${c5.reset} ${c5.dim}[Y/n]${c5.reset} `
7837
+ )).trim().toLowerCase();
7838
+ } catch {
7839
+ answer = "n";
7840
+ } finally {
7841
+ promptRl.close();
7842
+ }
7843
+ if (answer !== "" && answer !== "y" && answer !== "yes") {
7844
+ savePrefs({ daemon_install_declined_at: Date.now() });
7845
+ stdout.write(
7846
+ `${c5.dim}Okay \u2014 this machine can't run sessions until the daemon is installed.
7825
7847
  Run \`standardcode daemon install\` any time, or pick another machine.${c5.reset}
7826
7848
 
7827
7849
  `
7828
- );
7829
- return false;
7830
- }
7831
- await runDaemonCommand(endpointOverride ? ["install", "--endpoint", endpoint] : ["install"]);
7832
- stdout.write("\n");
7833
- self = await loadMachine(api, identity.machine_id).catch(() => null) ?? self;
7834
- localDaemonOnline = Boolean(self && daemonOnline(self));
7835
- if (self) session.localName = machineDisplayName(self);
7836
- if (!localDaemonOnline) {
7837
- stdout.write(
7838
- `${c5.dim}The daemon hasn't connected yet \u2014 check \`standardcode daemon status\`, or pick another machine.${c5.reset}
7850
+ );
7851
+ return false;
7852
+ }
7853
+ await runDaemonCommand(endpointOverride ? ["install", "--endpoint", endpoint] : ["install"]);
7854
+ stdout.write("\n");
7855
+ self = await loadMachine(api, identity.machine_id).catch(() => null) ?? self;
7856
+ localDaemonOnline = Boolean(self && daemonOnline(self));
7857
+ if (self) session.localName = machineDisplayName(self);
7858
+ if (!localDaemonOnline) {
7859
+ stdout.write(
7860
+ `${c5.dim}The daemon hasn't connected yet \u2014 check \`standardcode daemon status\`, or pick another machine.${c5.reset}
7839
7861
 
7840
7862
  `
7841
- );
7842
- }
7843
- return localDaemonOnline;
7863
+ );
7864
+ }
7865
+ return localDaemonOnline;
7866
+ });
7844
7867
  };
7845
7868
  const launchDir = projectDir;
7846
7869
  let tags = [];
@@ -7855,6 +7878,14 @@ Run \`standardcode daemon install\` any time, or pick another machine.${c5.reset
7855
7878
  void enqueueMachineCommand(api, session.runner.id, "add_project", { path: runnerPath }).catch(() => {
7856
7879
  });
7857
7880
  }
7881
+ const createdAt = Math.floor(Date.now() / 1e3);
7882
+ await api.notifySessionsChanged(id, "created", {
7883
+ id,
7884
+ agent_id: selectedAgent,
7885
+ tags,
7886
+ created_at: createdAt,
7887
+ updated_at: createdAt
7888
+ });
7858
7889
  return id;
7859
7890
  };
7860
7891
  let threadId = "";
@@ -8121,6 +8152,7 @@ async function runAgentSwitchMenu(tui, api, threadId) {
8121
8152
  const title = AGENT_CHOICES.find((choice) => choice.id === picked)?.title ?? picked;
8122
8153
  try {
8123
8154
  await api.setThreadAgent(threadId, picked);
8155
+ await api.notifySessionsChanged(threadId, "updated", { id: threadId, agent_id: picked });
8124
8156
  tui.setAgentLabel(title);
8125
8157
  tui.print(`${c5.green}\u2713${c5.reset} Session handed to ${c5.bold}${title}${c5.reset} \u2014 applies from your next message.`);
8126
8158
  } catch (e) {
@@ -8351,15 +8383,12 @@ async function runInteractive(tui, api, accountStream, threadId, projectDir, mac
8351
8383
  if (eventType === "generation" && typeof data?.outputTokens === "number") {
8352
8384
  liveOut = data.outputTokens;
8353
8385
  refreshStatus();
8354
- scheduleProjectionRefresh();
8355
8386
  } else if (eventType === "tool_call_started" && data?.id) {
8356
8387
  activeSteps.set(data.id, data.progress || data.name || "working");
8357
8388
  refreshStatus();
8358
- scheduleProjectionRefresh();
8359
8389
  } else if (eventType === "tool_call_done" && data?.id) {
8360
8390
  activeSteps.delete(data.id);
8361
8391
  refreshStatus();
8362
- scheduleProjectionRefresh();
8363
8392
  } else if (eventType === "goal_updated" && data) {
8364
8393
  tui.setGoal(data);
8365
8394
  } else if (eventType === "standardcode.approval_changed") {
@@ -8423,25 +8452,18 @@ async function runInteractive(tui, api, accountStream, threadId, projectDir, mac
8423
8452
  }
8424
8453
  }, 150);
8425
8454
  };
8426
- const events = new events_stream_exports.SystemEvents(api, {
8427
- onOpen: () => scheduleReconcile(),
8428
- onThreadCreated: (t) => {
8429
- if (t.parent === threadId) scheduleReconcile();
8430
- },
8431
- onThreadUpdated: (t) => {
8432
- if (t.parent === threadId) scheduleReconcile();
8433
- },
8434
- onThreadDeleted: (id) => {
8435
- if (activeSubagents.has(id)) scheduleReconcile();
8436
- }
8455
+ const stopSessionEvents = accountStream.onEvent((event, data) => {
8456
+ if (event !== SESSION_CHANGED_EVENT) return;
8457
+ const changed = parseSessionChangedData(data);
8458
+ if (changed?.thread_id === threadId && changed.change === "subagents") scheduleReconcile();
8437
8459
  });
8438
8460
  let endSession;
8439
8461
  const sessionEnded = new Promise((r) => endSession = r);
8440
8462
  const quit = async () => {
8441
8463
  tui.end();
8442
8464
  stopWatchingConnection();
8465
+ stopSessionEvents();
8443
8466
  stream.close();
8444
- events.close();
8445
8467
  subActivity.closeAll();
8446
8468
  farewell(busy ? runnerName : void 0);
8447
8469
  process.exit(0);
@@ -9023,16 +9045,17 @@ ${c5.gray}Close another session (its slot frees within ~90s), then resend your m
9023
9045
  if (attempt >= 4) tui.setConnected(false);
9024
9046
  } else {
9025
9047
  tui.setConnected(true);
9048
+ scheduleReconcile();
9026
9049
  }
9027
9050
  });
9028
- events.connect();
9051
+ scheduleReconcile();
9029
9052
  await stream.connect();
9030
9053
  void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
9031
9054
  });
9032
9055
  attaching.stop();
9033
9056
  const header = `${c5.bold}${c5.magenta}Standard Code${c5.reset} ${c5.dim}\u2014 ${agentTitle}${c5.reset}`;
9034
9057
  const daemonVersion = remote ? session.runner?.daemon?.version : (await loadMachine(api, session.identity.machine_id).catch(() => null))?.daemon?.version;
9035
- const execLine = `${c5.gray}tool execution:${c5.reset} daemon${daemonVersion ? ` v${daemonVersion}` : ""} on ${runnerName} ${c5.dim}(this terminal watches)${c5.reset}`;
9058
+ const execLine = `${c5.gray}tool execution:${c5.reset} daemon${daemonVersion ? ` v${daemonVersion}` : ""} on ${runnerName}`;
9036
9059
  tui.setAgentLabel(agentTitle);
9037
9060
  tui.banner(
9038
9061
  remote ? [
@@ -9222,8 +9245,8 @@ ${c5.dim}runs on ${request.machine || runnerName}${c5.reset}`,
9222
9245
  const stopped = api.stopThread(threadId).catch(() => {
9223
9246
  });
9224
9247
  stopWatchingConnection();
9248
+ stopSessionEvents();
9225
9249
  stream.close();
9226
- events.close();
9227
9250
  subActivity.closeAll();
9228
9251
  await Promise.race([stopped, new Promise((r) => setTimeout(r, 1500))]);
9229
9252
  tui.setWorking(false);