@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 +239 -216
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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/
|
|
75
|
-
var
|
|
76
|
-
__reExport(
|
|
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
|
-
|
|
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
|
|
101
|
+
if (this.started) return;
|
|
99
102
|
this.started = true;
|
|
100
|
-
this.
|
|
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.
|
|
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
|
|
222
|
-
var
|
|
223
|
-
|
|
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
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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.
|
|
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:
|
|
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}:${
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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: (
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
if (
|
|
6237
|
-
else if (
|
|
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
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
(
|
|
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
|
-
|
|
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.
|
|
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")
|
|
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
|
|
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
|
-
|
|
6716
|
-
displayName =
|
|
6717
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
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
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
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
|
-
|
|
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
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
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
|
-
|
|
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}
|
|
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);
|