@standardagents/code 0.13.4 → 0.13.6
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/README.md +7 -4
- package/dist/index.js +148 -41
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -76,8 +76,9 @@ we're letting new folks in every week.
|
|
|
76
76
|
thread and keep running across reconnects and resumes.
|
|
77
77
|
- **Idle attachments stay dormant.** Queue/draft snapshots refresh from events and lifecycle
|
|
78
78
|
transitions, stream and idle execution-bridge heartbeats use Durable Object hibernation
|
|
79
|
-
auto-responses
|
|
80
|
-
|
|
79
|
+
auto-responses on a 30-second quiet cadence (real traffic suppresses the keepalive), and
|
|
80
|
+
transcript safety requests run only while the agent is active. The bridge switches to a
|
|
81
|
+
5-second observable liveness heartbeat only during a forwarded tool or approval, so
|
|
81
82
|
leaving a terminal attached does not continuously wake an idle thread. Each running client owns
|
|
82
83
|
one account user stream; a machine daemon opens a per-thread execution bridge only while
|
|
83
84
|
forwarded work is active. Stream transitions update machine presence once. Remote filesystem
|
|
@@ -134,8 +135,10 @@ Sama One is BYOK: the first time you pick it, the CLI opens
|
|
|
134
135
|
`https://standardcode.ai/app` where you connect your ChatGPT account once.
|
|
135
136
|
That connection installs the authorization in your account's secret
|
|
136
137
|
environment on the instance — shared by the CLI, web console, and macOS app —
|
|
137
|
-
and the CLI itself never sees or stores the OpenAI key.
|
|
138
|
-
|
|
138
|
+
and the CLI itself never sees or stores the OpenAI key. Connection changes are
|
|
139
|
+
pushed over the existing account WebSocket to every open client; the clients
|
|
140
|
+
do not poll account metadata. Sama One sessions are not limited by the
|
|
141
|
+
simultaneous-thread lease.
|
|
139
142
|
|
|
140
143
|
You only do this once per machine. Session start then asks **where the session should run** —
|
|
141
144
|
this machine or any machine whose daemon is online — and offers to **resume** a session for that
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as session_state_star from '@standardagents/code-network/session-state'
|
|
|
11
11
|
import * as transcript_delivery_star from '@standardagents/code-network/transcript-delivery';
|
|
12
12
|
import * as approvals_star from '@standardagents/code-network/approvals';
|
|
13
13
|
import net from 'net';
|
|
14
|
+
import { setImmediate } from 'timers';
|
|
14
15
|
import fs11 from 'fs';
|
|
15
16
|
import { permissionKey, Bridge as Bridge$1 } from '@standardagents/code-network/bridge';
|
|
16
17
|
import readline from 'readline';
|
|
@@ -237,6 +238,45 @@ __reExport(approvals_exports, approvals_star);
|
|
|
237
238
|
var ALLOWED_HOST = "chatgpt.com";
|
|
238
239
|
var ALLOWED_PORT = 443;
|
|
239
240
|
var CHUNK_BYTES = 256 * 1024;
|
|
241
|
+
var TunnelFrameCoalescer = class {
|
|
242
|
+
constructor(emit) {
|
|
243
|
+
this.emit = emit;
|
|
244
|
+
}
|
|
245
|
+
emit;
|
|
246
|
+
chunks = [];
|
|
247
|
+
bytes = 0;
|
|
248
|
+
scheduled = false;
|
|
249
|
+
push(chunk) {
|
|
250
|
+
if (chunk.length === 0) return;
|
|
251
|
+
let offset = 0;
|
|
252
|
+
while (offset < chunk.length) {
|
|
253
|
+
const remaining = CHUNK_BYTES - this.bytes;
|
|
254
|
+
const take = Math.min(remaining, chunk.length - offset);
|
|
255
|
+
this.chunks.push(chunk.subarray(offset, offset + take));
|
|
256
|
+
this.bytes += take;
|
|
257
|
+
offset += take;
|
|
258
|
+
if (this.bytes === CHUNK_BYTES) this.flush();
|
|
259
|
+
}
|
|
260
|
+
if (this.bytes > 0 && !this.scheduled) {
|
|
261
|
+
this.scheduled = true;
|
|
262
|
+
setImmediate(() => {
|
|
263
|
+
this.scheduled = false;
|
|
264
|
+
this.flush();
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
flush() {
|
|
269
|
+
if (this.bytes === 0) return;
|
|
270
|
+
const chunk = this.chunks.length === 1 ? this.chunks[0] : Buffer.concat(this.chunks, this.bytes);
|
|
271
|
+
this.chunks = [];
|
|
272
|
+
this.bytes = 0;
|
|
273
|
+
this.emit(chunk);
|
|
274
|
+
}
|
|
275
|
+
discard() {
|
|
276
|
+
this.chunks = [];
|
|
277
|
+
this.bytes = 0;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
240
280
|
var TunnelManager = class {
|
|
241
281
|
constructor(send) {
|
|
242
282
|
this.send = send;
|
|
@@ -302,18 +342,19 @@ var TunnelManager = class {
|
|
|
302
342
|
const socket = net.connect({ host, port });
|
|
303
343
|
socket.setNoDelay(true);
|
|
304
344
|
socket.setKeepAlive(true, 15e3);
|
|
305
|
-
|
|
345
|
+
const outbound = new TunnelFrameCoalescer((chunk) => {
|
|
346
|
+
this.send({ type: "stream_data", id, data: chunk.toString("base64") });
|
|
347
|
+
});
|
|
348
|
+
this.tunnels.set(id, { socket, outbound });
|
|
306
349
|
socket.on("connect", () => {
|
|
307
350
|
opened = true;
|
|
308
351
|
this.send({ type: "stream_opened", id });
|
|
309
352
|
});
|
|
310
353
|
socket.on("data", (data) => {
|
|
311
|
-
|
|
312
|
-
const slice = data.subarray(i, i + CHUNK_BYTES);
|
|
313
|
-
this.send({ type: "stream_data", id, data: slice.toString("base64") });
|
|
314
|
-
}
|
|
354
|
+
outbound.push(data);
|
|
315
355
|
});
|
|
316
356
|
socket.on("end", () => {
|
|
357
|
+
outbound.flush();
|
|
317
358
|
this.send({ type: "stream_end", id });
|
|
318
359
|
this.tunnels.delete(id);
|
|
319
360
|
});
|
|
@@ -321,12 +362,14 @@ var TunnelManager = class {
|
|
|
321
362
|
if (!opened) {
|
|
322
363
|
this.send({ type: "stream_error", id, error: `Tunnel connect failed: ${error.message}` });
|
|
323
364
|
} else {
|
|
365
|
+
outbound.flush();
|
|
324
366
|
this.send({ type: "stream_abort", id, error: error.message });
|
|
325
367
|
}
|
|
326
368
|
this.tunnels.delete(id);
|
|
327
369
|
});
|
|
328
370
|
socket.on("close", () => {
|
|
329
371
|
if (this.tunnels.has(id)) {
|
|
372
|
+
outbound.flush();
|
|
330
373
|
this.send({ type: "stream_end", id });
|
|
331
374
|
this.tunnels.delete(id);
|
|
332
375
|
}
|
|
@@ -340,6 +383,7 @@ var TunnelManager = class {
|
|
|
340
383
|
const tunnel = this.tunnels.get(id);
|
|
341
384
|
if (!tunnel) return;
|
|
342
385
|
this.tunnels.delete(id);
|
|
386
|
+
tunnel.outbound.discard();
|
|
343
387
|
try {
|
|
344
388
|
tunnel.socket.destroy();
|
|
345
389
|
} catch {
|
|
@@ -2520,7 +2564,7 @@ var Tui = class _Tui {
|
|
|
2520
2564
|
/**
|
|
2521
2565
|
* Set the context-window fill percentage (0–100), or null to hide it.
|
|
2522
2566
|
* Driven by the runtime's `context_usage` KV, scaled to the compaction
|
|
2523
|
-
* trigger
|
|
2567
|
+
* trigger. Always painted on the status line far right.
|
|
2524
2568
|
*/
|
|
2525
2569
|
setContextPct(pct) {
|
|
2526
2570
|
const next = pct == null ? null : Math.max(0, Math.min(100, Math.round(pct)));
|
|
@@ -2855,8 +2899,8 @@ var Tui = class _Tui {
|
|
|
2855
2899
|
// (which renders full markdown), so there's no double-render.
|
|
2856
2900
|
static STREAM_TAIL = 20;
|
|
2857
2901
|
// How long a reasoning-only preview may sit untouched before it's wiped. A
|
|
2858
|
-
// visible answer must NEVER expire: it is the only copy until
|
|
2859
|
-
// promotes the
|
|
2902
|
+
// visible answer must NEVER expire: it is the only copy until the durable
|
|
2903
|
+
// boundary event promotes the message into terminal scrollback.
|
|
2860
2904
|
static STREAM_IDLE_MS = 1e4;
|
|
2861
2905
|
/** Append a fragment of streamed answer text (rendered as progressive Markdown). */
|
|
2862
2906
|
streamResponseDelta(delta, messageId) {
|
|
@@ -3904,7 +3948,7 @@ function readVersion() {
|
|
|
3904
3948
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
3905
3949
|
} catch {
|
|
3906
3950
|
}
|
|
3907
|
-
return "0.13.
|
|
3951
|
+
return "0.13.6" ;
|
|
3908
3952
|
}
|
|
3909
3953
|
function isLocalHost(host) {
|
|
3910
3954
|
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);
|
|
@@ -6955,8 +6999,11 @@ function usage() {
|
|
|
6955
6999
|
`${c3.bold}standardcode daemon${c3.reset} \u2014 headless execution client for this machine`,
|
|
6956
7000
|
"",
|
|
6957
7001
|
`${c3.bold}Commands${c3.reset}`,
|
|
6958
|
-
" install [--endpoint url]
|
|
7002
|
+
" install [--endpoint url] [--name label | --yes]",
|
|
7003
|
+
" Sign in (if needed), name this machine, and install",
|
|
6959
7004
|
" the always-on service (launchd / systemd).",
|
|
7005
|
+
" --name/--yes skip the name prompt (non-interactive,",
|
|
7006
|
+
" e.g. when another app drives the install).",
|
|
6960
7007
|
" uninstall Stop and remove the service.",
|
|
6961
7008
|
" status Service + registry status for this machine.",
|
|
6962
7009
|
" run [--endpoint url] Run the daemon in the foreground (what the service runs).",
|
|
@@ -6981,6 +7028,32 @@ function parseEndpointFlag(args) {
|
|
|
6981
7028
|
}
|
|
6982
7029
|
return { endpoint, rest };
|
|
6983
7030
|
}
|
|
7031
|
+
function parseInstallFlags(args) {
|
|
7032
|
+
const rest = [];
|
|
7033
|
+
let name;
|
|
7034
|
+
let yes = false;
|
|
7035
|
+
for (let i = 0; i < args.length; i++) {
|
|
7036
|
+
const arg = args[i];
|
|
7037
|
+
if (arg === "--yes" || arg === "-y") {
|
|
7038
|
+
yes = true;
|
|
7039
|
+
} else if (arg === "--name") {
|
|
7040
|
+
const next = args[i + 1];
|
|
7041
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
7042
|
+
name = next;
|
|
7043
|
+
i++;
|
|
7044
|
+
} else {
|
|
7045
|
+
yes = true;
|
|
7046
|
+
}
|
|
7047
|
+
} else if (arg.startsWith("--name=")) {
|
|
7048
|
+
const value = arg.slice("--name=".length);
|
|
7049
|
+
if (value) name = value;
|
|
7050
|
+
else yes = true;
|
|
7051
|
+
} else {
|
|
7052
|
+
rest.push(arg);
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
return { name: name?.trim() || void 0, yes, rest };
|
|
7056
|
+
}
|
|
6984
7057
|
function resolveEndpoint(flag) {
|
|
6985
7058
|
return (0, credentials_exports.normalizeEndpoint)(
|
|
6986
7059
|
flag || process.env.STANDARD_CODE_DAEMON_ENDPOINT || (0, credentials_exports.defaultEndpoint)() || PRODUCTION_ENDPOINT
|
|
@@ -7014,17 +7087,22 @@ async function ensureSignedIn(endpoint) {
|
|
|
7014
7087
|
`);
|
|
7015
7088
|
return api;
|
|
7016
7089
|
}
|
|
7017
|
-
async function installCommand(endpointFlag) {
|
|
7090
|
+
async function installCommand(endpointFlag, opts = {}) {
|
|
7018
7091
|
const endpoint = resolveEndpoint(endpointFlag);
|
|
7019
7092
|
const api = await ensureSignedIn(endpoint);
|
|
7020
7093
|
const identity = loadMachineIdentity();
|
|
7021
7094
|
const existing = await loadMachine(api, identity.machine_id).catch(() => null);
|
|
7022
7095
|
const suggested = machineDisplayName(existing ?? { hostname: os6.hostname(), id: identity.machine_id });
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7096
|
+
let answer = "";
|
|
7097
|
+
if (opts.name) {
|
|
7098
|
+
answer = opts.name;
|
|
7099
|
+
} else if (!opts.yes) {
|
|
7100
|
+
const rl = readline3.createInterface({ input: stdin, output: stdout });
|
|
7101
|
+
answer = (await rl.question(
|
|
7102
|
+
`${c3.bold}Machine name${c3.reset} ${c3.dim}(shown in the session picker)${c3.reset} [${suggested}]: `
|
|
7103
|
+
)).trim();
|
|
7104
|
+
rl.close();
|
|
7105
|
+
}
|
|
7028
7106
|
if (answer) await setMachineName(api, identity.machine_id, answer);
|
|
7029
7107
|
await updateOwnMachineRecord(api, identity);
|
|
7030
7108
|
const displayName = answer || suggested;
|
|
@@ -7141,16 +7219,13 @@ async function runDaemonCommand(argv) {
|
|
|
7141
7219
|
const { endpoint, rest } = parseEndpointFlag(restArgs);
|
|
7142
7220
|
switch (command) {
|
|
7143
7221
|
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
|
-
}
|
|
7149
7222
|
await runDaemon({ endpoint: endpoint || process.env.STANDARD_CODE_DAEMON_ENDPOINT });
|
|
7150
7223
|
return;
|
|
7151
|
-
case "install":
|
|
7152
|
-
|
|
7224
|
+
case "install": {
|
|
7225
|
+
const flags = parseInstallFlags(rest);
|
|
7226
|
+
await installCommand(endpoint, flags);
|
|
7153
7227
|
return;
|
|
7228
|
+
}
|
|
7154
7229
|
case "uninstall": {
|
|
7155
7230
|
const result = uninstallService();
|
|
7156
7231
|
stdout.write(`${result.ok ? c3.green + "\u2713" : c3.red + "\u2717"}${c3.reset} ${result.detail}
|
|
@@ -8028,7 +8103,7 @@ ${c5.dim}Install this machine's daemon (\`standardcode daemon install\`) or run
|
|
|
8028
8103
|
const threadAgent = existing.find((t) => t.id === picked)?.agent_id;
|
|
8029
8104
|
selectedAgent = threadAgent === OPENSAMA_AGENT_ID ? OPENSAMA_AGENT_ID : AGENT_ID;
|
|
8030
8105
|
if (selectedAgent === OPENSAMA_AGENT_ID) {
|
|
8031
|
-
const ok = await ensureSamaAuth(tui, api);
|
|
8106
|
+
const ok = await ensureSamaAuth(tui, api, accountStream);
|
|
8032
8107
|
if (!ok) continue;
|
|
8033
8108
|
}
|
|
8034
8109
|
break flow;
|
|
@@ -8060,7 +8135,7 @@ ${c5.dim}Install this machine's daemon (\`standardcode daemon install\`) or run
|
|
|
8060
8135
|
selectedAgent = picked;
|
|
8061
8136
|
}
|
|
8062
8137
|
if (selectedAgent === OPENSAMA_AGENT_ID) {
|
|
8063
|
-
const ok = await ensureSamaAuth(tui, api);
|
|
8138
|
+
const ok = await ensureSamaAuth(tui, api, accountStream);
|
|
8064
8139
|
if (!ok) {
|
|
8065
8140
|
if (agentOverride) process.exit(0);
|
|
8066
8141
|
continue;
|
|
@@ -8095,7 +8170,7 @@ ${c5.dim}Install this machine's daemon (\`standardcode daemon install\`) or run
|
|
|
8095
8170
|
function shortenPath(p, max = 38) {
|
|
8096
8171
|
return p.length > max ? "\u2026" + p.slice(-(max - 1)) : p;
|
|
8097
8172
|
}
|
|
8098
|
-
async function ensureSamaAuth(tui, api) {
|
|
8173
|
+
async function ensureSamaAuth(tui, api, accountStream) {
|
|
8099
8174
|
const checking = startLoader("Checking your ChatGPT connection");
|
|
8100
8175
|
const already = await api.openSamaStatus().catch(() => false);
|
|
8101
8176
|
checking.stop();
|
|
@@ -8117,24 +8192,55 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8117
8192
|
`${c5.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c5.reset}`
|
|
8118
8193
|
);
|
|
8119
8194
|
const waiting = startLoader("Waiting for your ChatGPT authorization");
|
|
8120
|
-
const
|
|
8121
|
-
while (Date.now() < deadline) {
|
|
8122
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
8123
|
-
if (await api.openSamaStatus().catch(() => false)) {
|
|
8124
|
-
waiting.stop();
|
|
8125
|
-
tui.print(
|
|
8126
|
-
`${c5.green}\u2713${c5.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
|
|
8127
|
-
);
|
|
8128
|
-
return true;
|
|
8129
|
-
}
|
|
8130
|
-
}
|
|
8195
|
+
const connected = await waitForSamaAuthorization(api, accountStream, 5 * 60 * 1e3);
|
|
8131
8196
|
waiting.stop();
|
|
8197
|
+
if (connected) {
|
|
8198
|
+
tui.print(
|
|
8199
|
+
`${c5.green}\u2713${c5.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
|
|
8200
|
+
);
|
|
8201
|
+
return true;
|
|
8202
|
+
}
|
|
8132
8203
|
tui.print(
|
|
8133
8204
|
`${c5.yellow}Still not connected.${c5.reset} Finish the flow at ${c5.teal}standardcode.ai/app${c5.reset} and pick Sama One again.`
|
|
8134
8205
|
);
|
|
8135
8206
|
return false;
|
|
8136
8207
|
}
|
|
8137
|
-
|
|
8208
|
+
function waitForSamaAuthorization(api, accountStream, timeoutMs) {
|
|
8209
|
+
return new Promise((resolve) => {
|
|
8210
|
+
let settled = false;
|
|
8211
|
+
let checking = false;
|
|
8212
|
+
let stopEvent = () => {
|
|
8213
|
+
};
|
|
8214
|
+
let stopConnection = () => {
|
|
8215
|
+
};
|
|
8216
|
+
let timer;
|
|
8217
|
+
const finish = (connected) => {
|
|
8218
|
+
if (settled) return;
|
|
8219
|
+
settled = true;
|
|
8220
|
+
clearTimeout(timer);
|
|
8221
|
+
stopEvent();
|
|
8222
|
+
stopConnection();
|
|
8223
|
+
resolve(connected);
|
|
8224
|
+
};
|
|
8225
|
+
timer = setTimeout(() => finish(false), timeoutMs);
|
|
8226
|
+
const reconcile = async () => {
|
|
8227
|
+
if (settled || checking) return;
|
|
8228
|
+
checking = true;
|
|
8229
|
+
const connected = await api.openSamaStatus().catch(() => false);
|
|
8230
|
+
checking = false;
|
|
8231
|
+
if (connected) finish(true);
|
|
8232
|
+
};
|
|
8233
|
+
stopEvent = accountStream.onEvent((event, data) => {
|
|
8234
|
+
if (event !== "standardcode.opensama_changed") return;
|
|
8235
|
+
if (data?.connected === true) finish(true);
|
|
8236
|
+
});
|
|
8237
|
+
stopConnection = accountStream.onConnection((state) => {
|
|
8238
|
+
if (state === "connected") void reconcile();
|
|
8239
|
+
});
|
|
8240
|
+
void reconcile();
|
|
8241
|
+
});
|
|
8242
|
+
}
|
|
8243
|
+
async function runAgentSwitchMenu(tui, api, accountStream, threadId) {
|
|
8138
8244
|
const picked = await tui.select(
|
|
8139
8245
|
`${c5.bold}Switch agent${c5.reset} ${c5.dim}takes effect on the next message${c5.reset}`,
|
|
8140
8246
|
AGENT_CHOICES.map((choice) => ({
|
|
@@ -8146,7 +8252,7 @@ async function runAgentSwitchMenu(tui, api, threadId) {
|
|
|
8146
8252
|
);
|
|
8147
8253
|
if (!picked) return;
|
|
8148
8254
|
if (picked === OPENSAMA_AGENT_ID) {
|
|
8149
|
-
const ok = await ensureSamaAuth(tui, api);
|
|
8255
|
+
const ok = await ensureSamaAuth(tui, api, accountStream);
|
|
8150
8256
|
if (!ok) return;
|
|
8151
8257
|
}
|
|
8152
8258
|
const title = AGENT_CHOICES.find((choice) => choice.id === picked)?.title ?? picked;
|
|
@@ -8367,7 +8473,8 @@ async function runInteractive(tui, api, accountStream, threadId, projectDir, mac
|
|
|
8367
8473
|
},
|
|
8368
8474
|
// Live streaming preview: answer text and (opt-in) internal reasoning feed
|
|
8369
8475
|
// the TUI's ephemeral preview; the committed message still renders from
|
|
8370
|
-
//
|
|
8476
|
+
// the durable boundary event, which clears the preview before printing so
|
|
8477
|
+
// there's no double-render.
|
|
8371
8478
|
onChunk: (text, mid) => tui.streamResponseDelta(text, mid),
|
|
8372
8479
|
onReasoningChunk: (text, mid) => tui.streamThinkingDelta(text, mid),
|
|
8373
8480
|
onAssistantText: () => {
|
|
@@ -8848,7 +8955,7 @@ ${c5.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
8848
8955
|
name: "agent",
|
|
8849
8956
|
label: "Switch agent",
|
|
8850
8957
|
hint: "hand this session to a different agent",
|
|
8851
|
-
run: () => runAgentSwitchMenu(tui, api, threadId)
|
|
8958
|
+
run: () => runAgentSwitchMenu(tui, api, accountStream, threadId)
|
|
8852
8959
|
},
|
|
8853
8960
|
{
|
|
8854
8961
|
name: "machines",
|