@standardagents/code 0.13.3 → 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 = {};
@@ -3938,7 +3904,7 @@ function readVersion() {
3938
3904
  if (typeof pkg.version === "string" && pkg.version) return pkg.version;
3939
3905
  } catch {
3940
3906
  }
3941
- return "0.13.3" ;
3907
+ return "0.13.4" ;
3942
3908
  }
3943
3909
  function isLocalHost(host) {
3944
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);
@@ -3971,7 +3937,7 @@ function loadMachineIdentity() {
3971
3937
  } catch {
3972
3938
  }
3973
3939
  const identity = {
3974
- machine_id: crypto2.randomUUID(),
3940
+ machine_id: crypto.randomUUID(),
3975
3941
  created_at: Date.now()
3976
3942
  };
3977
3943
  saveMachineIdentity(identity);
@@ -3985,7 +3951,7 @@ function daemonClientId(identity) {
3985
3951
  return `daemon:${identity.machine_id}`;
3986
3952
  }
3987
3953
  function interactiveClientId(identity) {
3988
- return `cli:${identity.machine_id}:${crypto2.randomBytes(4).toString("hex")}`;
3954
+ return `cli:${identity.machine_id}:${crypto.randomBytes(4).toString("hex")}`;
3989
3955
  }
3990
3956
  var KEY_PREFIX = "standardcode.machine.";
3991
3957
  var CMD_SUFFIX = ".cmd";
@@ -4366,7 +4332,7 @@ async function enqueueMachineCommand(api, machineId, kind, args) {
4366
4332
  const key = commandKey(machineId);
4367
4333
  const queue = parseCommands(await api.userKvGet(key));
4368
4334
  const cmd = {
4369
- id: crypto2.randomBytes(6).toString("hex"),
4335
+ id: crypto.randomBytes(6).toString("hex"),
4370
4336
  kind,
4371
4337
  args,
4372
4338
  requested_at: Date.now()
@@ -4604,7 +4570,7 @@ function parseBrowseResult(value) {
4604
4570
  }
4605
4571
  function remoteBrowseBackend(api, accountStream, machineId, label) {
4606
4572
  const rpc = async (req) => {
4607
- const nonce = crypto2.randomBytes(8).toString("hex");
4573
+ const nonce = crypto.randomBytes(8).toString("hex");
4608
4574
  const res = await requestFsBrowse(api, accountStream, machineId, { nonce, ...req }).catch(
4609
4575
  (error) => {
4610
4576
  throw new Error(
@@ -4821,7 +4787,7 @@ function download(url, dest, redirects = 5) {
4821
4787
  });
4822
4788
  }
4823
4789
  async function sha256File(file2) {
4824
- const hash = crypto2.createHash("sha256");
4790
+ const hash = crypto.createHash("sha256");
4825
4791
  await new Promise((resolve, reject) => {
4826
4792
  const stream = fs11.createReadStream(file2);
4827
4793
  stream.on("data", (chunk) => hash.update(chunk));
@@ -5133,7 +5099,7 @@ var HostTools = class {
5133
5099
  return { ok: false, error: "args_json must be a JSON array of strings" };
5134
5100
  }
5135
5101
  }
5136
- 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);
5137
5103
  const skillDir = path9.join(os6.tmpdir(), "standardcode-skills", `${skill}-${hash}`);
5138
5104
  for (const f of files) {
5139
5105
  const dest = path9.resolve(skillDir, f.path);
@@ -5225,7 +5191,7 @@ ${truncated}`
5225
5191
  if (!fs11.existsSync(cwd)) {
5226
5192
  return { ok: false, error: `cwd does not exist: ${cwd}` };
5227
5193
  }
5228
- const id = crypto2.randomUUID().slice(0, 8);
5194
+ const id = crypto.randomUUID().slice(0, 8);
5229
5195
  const logPath = path9.join(LOG_DIR, `${id}.log`);
5230
5196
  let out;
5231
5197
  try {
@@ -5629,7 +5595,7 @@ ${stderrTail.trim()}` : msg;
5629
5595
  target,
5630
5596
  argsSha256: sha256(canonicalJson(args)),
5631
5597
  resultSha256: sha256(text),
5632
- nonce: crypto2.randomBytes(8).toString("hex"),
5598
+ nonce: crypto.randomBytes(8).toString("hex"),
5633
5599
  isError,
5634
5600
  at: Date.now()
5635
5601
  };
@@ -5899,7 +5865,7 @@ function sortKeys(value) {
5899
5865
  return value;
5900
5866
  }
5901
5867
  function sha256(input3) {
5902
- return crypto2.createHash("sha256").update(input3).digest("hex");
5868
+ return crypto.createHash("sha256").update(input3).digest("hex");
5903
5869
  }
5904
5870
 
5905
5871
  // src/execution-session.ts
@@ -6021,6 +5987,27 @@ var DaemonUserStream = class {
6021
5987
  this.stream.close();
6022
5988
  }
6023
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
+ };
6024
6011
  var PKG_NAME = "@standardagents/code";
6025
6012
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
6026
6013
  var CACHE_REL_DIR = ".config/standardagents-cli";
@@ -6199,19 +6186,7 @@ function runUpdate(pm) {
6199
6186
  }
6200
6187
 
6201
6188
  // src/daemon.ts
6202
- var approvalWakes = /* @__PURE__ */ new Map();
6203
- function subscribeApprovalWake(threadId, wake) {
6204
- const set = approvalWakes.get(threadId) ?? /* @__PURE__ */ new Set();
6205
- set.add(wake);
6206
- approvalWakes.set(threadId, set);
6207
- return () => {
6208
- set.delete(wake);
6209
- if (set.size === 0) approvalWakes.delete(threadId);
6210
- };
6211
- }
6212
- function wakeApprovals(threadId) {
6213
- for (const wake of approvalWakes.get(threadId) ?? []) wake();
6214
- }
6189
+ var approvalWakes = new relay_exports.ApprovalWakeRegistry();
6215
6190
  var UPDATE_CHECK_MS = 6 * 60 * 6e4;
6216
6191
  var MAX_WORKERS = 30;
6217
6192
  var BRIDGE_ATTACH_GRACE_MS = 15e3;
@@ -6258,11 +6233,11 @@ var ThreadWorker = class {
6258
6233
  },
6259
6234
  hooks: {
6260
6235
  onActivity: (line) => daemonLog(`[${threadId.slice(0, 8)}] ${line}`),
6261
- onStatus: (_id, summary) => {
6262
- this.inFlight += summary ? 1 : -1;
6263
- if (this.inFlight < 0) this.inFlight = 0;
6264
- if (summary) this.clearIdleTimer();
6265
- 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);
6266
6241
  },
6267
6242
  onConnection: (state, attempt) => {
6268
6243
  if (state !== "reconnecting" || attempt === 1 || attempt % 10 === 0) {
@@ -6300,29 +6275,36 @@ var ThreadWorker = class {
6300
6275
  return { choice: "deny", reason: "No one is available to approve this right now." };
6301
6276
  }
6302
6277
  daemonLog(`[${threadId.slice(0, 8)}] relaying approval: ${summary} (risk ${effectiveRisk})`);
6303
- const response = await (0, relay_exports.awaitApprovalViaRelay)(
6304
- api,
6305
- threadId,
6306
- {
6307
- tool_call_id: req.toolCallId,
6308
- tool: req.tool,
6309
- summary,
6310
- permission: req.requestPermission,
6311
- risk: effectiveRisk,
6312
- machine: machineName,
6313
- requested_at: Date.now()
6314
- },
6315
- { subscribe: (wake) => subscribeApprovalWake(threadId, wake) }
6316
- );
6317
- if (!response) {
6318
- return { choice: "deny", reason: "The approval request timed out with no one to approve it." };
6319
- }
6320
- if (response.choice === "always") this.perm.alwaysAllow.add(permKey);
6321
- if (response.choice === "always_risk") this.perm.allowRisk.add(effectiveRisk);
6322
- if (response.choice === "always" || response.choice === "always_risk") {
6323
- (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);
6324
6307
  }
6325
- return { choice: response.choice, reason: response.reason };
6326
6308
  }
6327
6309
  }
6328
6310
  });
@@ -6337,7 +6319,7 @@ var ThreadWorker = class {
6337
6319
  createdAt;
6338
6320
  session;
6339
6321
  perm;
6340
- inFlight = 0;
6322
+ activity = new DaemonWorkerActivity();
6341
6323
  idleTimer = null;
6342
6324
  watchedAnotherOwner = false;
6343
6325
  /** Set by the daemon so a superseded worker can remove itself. */
@@ -6345,7 +6327,7 @@ var ThreadWorker = class {
6345
6327
  /** Set by the daemon so an inactive bridge removes itself. */
6346
6328
  onIdle;
6347
6329
  get busy() {
6348
- return this.inFlight > 0;
6330
+ return this.activity.busy;
6349
6331
  }
6350
6332
  clearIdleTimer() {
6351
6333
  if (!this.idleTimer) return;
@@ -6714,7 +6696,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6714
6696
  onEvent: (event, data) => {
6715
6697
  if (event === "standardcode.approval_changed") {
6716
6698
  const threadId = data && typeof data === "object" ? data.thread_id : null;
6717
- if (typeof threadId === "string") wakeApprovals(threadId);
6699
+ if (typeof threadId === "string") approvalWakes.wake(threadId);
6718
6700
  return;
6719
6701
  }
6720
6702
  if (event === "standardcode.lines_changed") {
@@ -6734,16 +6716,21 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6734
6716
  return;
6735
6717
  }
6736
6718
  if (event !== "standardcode.machine_changed") return;
6737
- const key = data && typeof data === "object" ? data.key : null;
6719
+ const frame = data && typeof data === "object" ? data : null;
6720
+ const key = frame?.key;
6738
6721
  daemonLog(`machine event: ${typeof key === "string" ? key : "missing key"}`);
6739
6722
  if (key === `${machinePrefix}.cmd`) void drainCommands();
6740
6723
  else if (key === `${machinePrefix}.fsreq`) {
6741
6724
  void drainFsRequests();
6742
6725
  } else if (key === `${machinePrefix}.name`) {
6743
- void getMachineName(api, identity.machine_id).then((name) => {
6744
- displayName = name || os6.hostname();
6745
- }).catch(() => {
6746
- });
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
+ }
6747
6734
  }
6748
6735
  },
6749
6736
  onConnection: (state, attempt) => {
@@ -6755,6 +6742,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
6755
6742
  return;
6756
6743
  }
6757
6744
  daemonLog("user stream: connected");
6745
+ approvalWakes.wakeAll();
6758
6746
  void publishPresence(true).then(() => recoverOnConnect());
6759
6747
  }
6760
6748
  },
@@ -7153,6 +7141,11 @@ async function runDaemonCommand(argv) {
7153
7141
  const { endpoint, rest } = parseEndpointFlag(restArgs);
7154
7142
  switch (command) {
7155
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
+ }
7156
7149
  await runDaemon({ endpoint: endpoint || process.env.STANDARD_CODE_DAEMON_ENDPOINT });
7157
7150
  return;
7158
7151
  case "install":
@@ -7885,6 +7878,14 @@ Run \`standardcode daemon install\` any time, or pick another machine.${c5.reset
7885
7878
  void enqueueMachineCommand(api, session.runner.id, "add_project", { path: runnerPath }).catch(() => {
7886
7879
  });
7887
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
+ });
7888
7889
  return id;
7889
7890
  };
7890
7891
  let threadId = "";
@@ -8151,6 +8152,7 @@ async function runAgentSwitchMenu(tui, api, threadId) {
8151
8152
  const title = AGENT_CHOICES.find((choice) => choice.id === picked)?.title ?? picked;
8152
8153
  try {
8153
8154
  await api.setThreadAgent(threadId, picked);
8155
+ await api.notifySessionsChanged(threadId, "updated", { id: threadId, agent_id: picked });
8154
8156
  tui.setAgentLabel(title);
8155
8157
  tui.print(`${c5.green}\u2713${c5.reset} Session handed to ${c5.bold}${title}${c5.reset} \u2014 applies from your next message.`);
8156
8158
  } catch (e) {
@@ -8381,15 +8383,12 @@ async function runInteractive(tui, api, accountStream, threadId, projectDir, mac
8381
8383
  if (eventType === "generation" && typeof data?.outputTokens === "number") {
8382
8384
  liveOut = data.outputTokens;
8383
8385
  refreshStatus();
8384
- scheduleProjectionRefresh();
8385
8386
  } else if (eventType === "tool_call_started" && data?.id) {
8386
8387
  activeSteps.set(data.id, data.progress || data.name || "working");
8387
8388
  refreshStatus();
8388
- scheduleProjectionRefresh();
8389
8389
  } else if (eventType === "tool_call_done" && data?.id) {
8390
8390
  activeSteps.delete(data.id);
8391
8391
  refreshStatus();
8392
- scheduleProjectionRefresh();
8393
8392
  } else if (eventType === "goal_updated" && data) {
8394
8393
  tui.setGoal(data);
8395
8394
  } else if (eventType === "standardcode.approval_changed") {
@@ -8453,25 +8452,18 @@ async function runInteractive(tui, api, accountStream, threadId, projectDir, mac
8453
8452
  }
8454
8453
  }, 150);
8455
8454
  };
8456
- const events = new events_stream_exports.SystemEvents(api, {
8457
- onOpen: () => scheduleReconcile(),
8458
- onThreadCreated: (t) => {
8459
- if (t.parent === threadId) scheduleReconcile();
8460
- },
8461
- onThreadUpdated: (t) => {
8462
- if (t.parent === threadId) scheduleReconcile();
8463
- },
8464
- onThreadDeleted: (id) => {
8465
- if (activeSubagents.has(id)) scheduleReconcile();
8466
- }
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();
8467
8459
  });
8468
8460
  let endSession;
8469
8461
  const sessionEnded = new Promise((r) => endSession = r);
8470
8462
  const quit = async () => {
8471
8463
  tui.end();
8472
8464
  stopWatchingConnection();
8465
+ stopSessionEvents();
8473
8466
  stream.close();
8474
- events.close();
8475
8467
  subActivity.closeAll();
8476
8468
  farewell(busy ? runnerName : void 0);
8477
8469
  process.exit(0);
@@ -9053,16 +9045,17 @@ ${c5.gray}Close another session (its slot frees within ~90s), then resend your m
9053
9045
  if (attempt >= 4) tui.setConnected(false);
9054
9046
  } else {
9055
9047
  tui.setConnected(true);
9048
+ scheduleReconcile();
9056
9049
  }
9057
9050
  });
9058
- events.connect();
9051
+ scheduleReconcile();
9059
9052
  await stream.connect();
9060
9053
  void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
9061
9054
  });
9062
9055
  attaching.stop();
9063
9056
  const header = `${c5.bold}${c5.magenta}Standard Code${c5.reset} ${c5.dim}\u2014 ${agentTitle}${c5.reset}`;
9064
9057
  const daemonVersion = remote ? session.runner?.daemon?.version : (await loadMachine(api, session.identity.machine_id).catch(() => null))?.daemon?.version;
9065
- 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}`;
9066
9059
  tui.setAgentLabel(agentTitle);
9067
9060
  tui.banner(
9068
9061
  remote ? [
@@ -9252,8 +9245,8 @@ ${c5.dim}runs on ${request.machine || runnerName}${c5.reset}`,
9252
9245
  const stopped = api.stopThread(threadId).catch(() => {
9253
9246
  });
9254
9247
  stopWatchingConnection();
9248
+ stopSessionEvents();
9255
9249
  stream.close();
9256
- events.close();
9257
9250
  subActivity.closeAll();
9258
9251
  await Promise.race([stopped, new Promise((r) => setTimeout(r, 1500))]);
9259
9252
  tui.setWorking(false);