@standardagents/code 0.6.4 → 0.6.7
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 +224 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -319,6 +319,25 @@ var ApiClient = class {
|
|
|
319
319
|
return null;
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
|
+
/** Preview the cost of changing to `sessions` parallel sessions (default +1).
|
|
323
|
+
* Returns null when billing is unavailable / there's no subscription. */
|
|
324
|
+
async sessionsQuote(threadId, sessions) {
|
|
325
|
+
try {
|
|
326
|
+
return await this.json(`/api/threads/${threadId}/sessions_quote`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
body: JSON.stringify(typeof sessions === "number" ? { sessions } : {})
|
|
329
|
+
});
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** Apply a parallel-session change (charges via Stripe). Throws on failure. */
|
|
335
|
+
async sessionsUpgrade(threadId, sessions) {
|
|
336
|
+
return this.json(`/api/threads/${threadId}/sessions_upgrade`, {
|
|
337
|
+
method: "POST",
|
|
338
|
+
body: JSON.stringify({ sessions })
|
|
339
|
+
});
|
|
340
|
+
}
|
|
322
341
|
// ── skills (instance-global Agent Skills library) ─────────────────────────
|
|
323
342
|
/** Installed skills — metadata only. Includes disabled skills. */
|
|
324
343
|
async listSkills() {
|
|
@@ -365,11 +384,17 @@ var ApiClient = class {
|
|
|
365
384
|
};
|
|
366
385
|
|
|
367
386
|
// src/permissions.ts
|
|
387
|
+
function riskCeiling(state) {
|
|
388
|
+
let ceiling = state.level;
|
|
389
|
+
for (const granted of state.allowRisk) {
|
|
390
|
+
if (granted > ceiling) ceiling = granted;
|
|
391
|
+
}
|
|
392
|
+
return ceiling;
|
|
393
|
+
}
|
|
368
394
|
function decide(state, tool, risk, hasPermissionRequest) {
|
|
369
395
|
const effectiveRisk = typeof risk === "number" ? Math.min(5, Math.max(1, risk)) : hasPermissionRequest ? 3 : 1;
|
|
370
396
|
if (state.alwaysAllow.has(tool)) return "allow";
|
|
371
|
-
|
|
372
|
-
return effectiveRisk <= state.level ? "allow" : "ask";
|
|
397
|
+
return effectiveRisk <= riskCeiling(state) ? "allow" : "ask";
|
|
373
398
|
}
|
|
374
399
|
var CATASTROPHIC_PATTERNS = [
|
|
375
400
|
/\brm\s+(-[a-z]*\s+)*-[a-z]*f[a-z]*\s+(-[a-z]*\s+)*(\/|~|\$HOME|\/\*|\.\s*$|\/\s*$)/i,
|
|
@@ -1891,6 +1916,90 @@ var SystemEvents = class {
|
|
|
1891
1916
|
}
|
|
1892
1917
|
};
|
|
1893
1918
|
|
|
1919
|
+
// src/subagent-streams.ts
|
|
1920
|
+
var MAX_PHRASE = 120;
|
|
1921
|
+
function cleanPhrase(raw) {
|
|
1922
|
+
const s = raw.replace(/\s+/g, " ").trim();
|
|
1923
|
+
return s.length > MAX_PHRASE ? `${s.slice(0, MAX_PHRASE - 1)}\u2026` : s;
|
|
1924
|
+
}
|
|
1925
|
+
function prettyToolName(name) {
|
|
1926
|
+
return name.replace(/^provider:/, "").replace(/[_:-]+/g, " ").trim();
|
|
1927
|
+
}
|
|
1928
|
+
var SubagentActivity = class {
|
|
1929
|
+
constructor(api, onChange, makeStream = (threadId, hooks) => new MessageStream(api, threadId, hooks)) {
|
|
1930
|
+
this.onChange = onChange;
|
|
1931
|
+
this.makeStream = makeStream;
|
|
1932
|
+
}
|
|
1933
|
+
onChange;
|
|
1934
|
+
makeStream;
|
|
1935
|
+
entries = /* @__PURE__ */ new Map();
|
|
1936
|
+
/** The current activity phrase for a subagent's child thread, if any. */
|
|
1937
|
+
phraseFor(threadId) {
|
|
1938
|
+
return this.entries.get(threadId)?.phrase ?? null;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Reconcile the open streams against the authoritative set of RUNNING
|
|
1942
|
+
* subagent child-thread ids (from the parent's registry): open newcomers,
|
|
1943
|
+
* close and forget departed ones.
|
|
1944
|
+
*/
|
|
1945
|
+
sync(activeIds) {
|
|
1946
|
+
const want = new Set(activeIds);
|
|
1947
|
+
for (const [id, entry] of [...this.entries]) {
|
|
1948
|
+
if (!want.has(id)) {
|
|
1949
|
+
entry.stream.close();
|
|
1950
|
+
this.entries.delete(id);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
for (const id of want) {
|
|
1954
|
+
if (!this.entries.has(id)) this.open(id);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
open(threadId) {
|
|
1958
|
+
const entry = { stream: null, steps: /* @__PURE__ */ new Map(), phrase: null };
|
|
1959
|
+
const setPhrase = (phrase) => {
|
|
1960
|
+
if (phrase === entry.phrase) return;
|
|
1961
|
+
entry.phrase = phrase;
|
|
1962
|
+
this.onChange(threadId);
|
|
1963
|
+
};
|
|
1964
|
+
const phraseFromSteps = () => {
|
|
1965
|
+
let last = null;
|
|
1966
|
+
for (const v of entry.steps.values()) last = v;
|
|
1967
|
+
return last ?? entry.phrase;
|
|
1968
|
+
};
|
|
1969
|
+
entry.stream = this.makeStream(threadId, {
|
|
1970
|
+
// Streamed output with no tool in flight means the model is composing —
|
|
1971
|
+
// reasoning reads as "thinking", visible answer text as "writing". These
|
|
1972
|
+
// only fire on TRANSITIONS (phrase comparison), not per chunk.
|
|
1973
|
+
onChunk: () => {
|
|
1974
|
+
if (entry.steps.size === 0) setPhrase("writing");
|
|
1975
|
+
},
|
|
1976
|
+
onReasoningChunk: () => {
|
|
1977
|
+
if (entry.steps.size === 0) setPhrase("thinking");
|
|
1978
|
+
},
|
|
1979
|
+
onAssistantText: () => {
|
|
1980
|
+
},
|
|
1981
|
+
onEvent: (eventType, data) => {
|
|
1982
|
+
if (eventType === "tool_call_started" && data?.id) {
|
|
1983
|
+
entry.steps.set(String(data.id), cleanPhrase(String(data.progress || prettyToolName(String(data.name || "")) || "working")));
|
|
1984
|
+
setPhrase(phraseFromSteps());
|
|
1985
|
+
} else if (eventType === "tool_call_done" && data?.id) {
|
|
1986
|
+
entry.steps.delete(String(data.id));
|
|
1987
|
+
setPhrase(phraseFromSteps());
|
|
1988
|
+
}
|
|
1989
|
+
},
|
|
1990
|
+
onError: () => {
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
this.entries.set(threadId, entry);
|
|
1994
|
+
void entry.stream.connect();
|
|
1995
|
+
}
|
|
1996
|
+
/** Close every stream (session teardown). */
|
|
1997
|
+
closeAll() {
|
|
1998
|
+
for (const entry of this.entries.values()) entry.stream.close();
|
|
1999
|
+
this.entries.clear();
|
|
2000
|
+
}
|
|
2001
|
+
};
|
|
2002
|
+
|
|
1894
2003
|
// src/wordmill.ts
|
|
1895
2004
|
var MILL_WORDS = [
|
|
1896
2005
|
"Working",
|
|
@@ -3350,7 +3459,7 @@ var Tui = class _Tui {
|
|
|
3350
3459
|
const options = [
|
|
3351
3460
|
{ value: "allow", label: "Allow once", shortcut: "y", color: C.green },
|
|
3352
3461
|
{ value: "always", label: "Always allow this tool", shortcut: "a", color: C.cyan },
|
|
3353
|
-
{ value: "always_risk", label: `Allow
|
|
3462
|
+
{ value: "always_risk", label: `Allow level ${risk} and below this session`, shortcut: "l", color: C.cyan },
|
|
3354
3463
|
{ value: "deny", label: "Deny", shortcut: "n", color: C.red }
|
|
3355
3464
|
];
|
|
3356
3465
|
let idx = 0;
|
|
@@ -4917,6 +5026,8 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
|
|
|
4917
5026
|
let editingQueued = false;
|
|
4918
5027
|
const shownIds = /* @__PURE__ */ new Set();
|
|
4919
5028
|
const pendingSent = /* @__PURE__ */ new Map();
|
|
5029
|
+
let lastSent = null;
|
|
5030
|
+
let upgradeInFlight = false;
|
|
4920
5031
|
let tokensIn = 0;
|
|
4921
5032
|
let tokensOut = 0;
|
|
4922
5033
|
let liveOut = 0;
|
|
@@ -4986,7 +5097,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4986
5097
|
tui.setGoal(data);
|
|
4987
5098
|
}
|
|
4988
5099
|
},
|
|
4989
|
-
|
|
5100
|
+
// A failed turn whose message is the lease service's at-limit denial → offer
|
|
5101
|
+
// an in-terminal upgrade right where the friction happened. (offerUpgrade /
|
|
5102
|
+
// isSessionLimitError are defined below; this callback only fires mid-session.)
|
|
5103
|
+
onError: (err) => {
|
|
5104
|
+
if (isSessionLimitError(err)) void offerUpgrade({ auto: true });
|
|
4990
5105
|
}
|
|
4991
5106
|
});
|
|
4992
5107
|
const activeSubagents = /* @__PURE__ */ new Map();
|
|
@@ -4994,8 +5109,12 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
4994
5109
|
const agentTitlesReady = api.listAgents().then((list) => list.forEach((a) => agentTitles.set(a.name, a.title))).catch(() => {
|
|
4995
5110
|
});
|
|
4996
5111
|
const pushSubagents = () => tui.setSubagents(
|
|
4997
|
-
[...activeSubagents.entries()].map(([id, s]) =>
|
|
5112
|
+
[...activeSubagents.entries()].map(([id, s]) => {
|
|
5113
|
+
const detail = subActivity.phraseFor(id) ?? s.registryDetail;
|
|
5114
|
+
return { id, label: `${s.title}${detail ? ` \u2014 ${detail}` : ""}`, agentName: s.agentName };
|
|
5115
|
+
})
|
|
4998
5116
|
);
|
|
5117
|
+
const subActivity = new SubagentActivity(api, () => pushSubagents());
|
|
4999
5118
|
const reconcileSubagents = async () => {
|
|
5000
5119
|
try {
|
|
5001
5120
|
await agentTitlesReady;
|
|
@@ -5005,12 +5124,13 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5005
5124
|
const status = (s.status || "").trim();
|
|
5006
5125
|
if (status === "idle" || status === "terminated") continue;
|
|
5007
5126
|
const oneLineStatus = status.replace(/\s+/g, " ");
|
|
5008
|
-
const detail = oneLineStatus && oneLineStatus !== "running" ? ` \u2014 ${oneLineStatus.slice(0, 80)}` : "";
|
|
5009
5127
|
activeSubagents.set(s.id, {
|
|
5010
|
-
|
|
5128
|
+
title: subagentLabel(s, agentTitles),
|
|
5129
|
+
registryDetail: oneLineStatus && oneLineStatus !== "running" ? oneLineStatus.slice(0, 80) : "",
|
|
5011
5130
|
agentName: s.agent_name ?? void 0
|
|
5012
5131
|
});
|
|
5013
5132
|
}
|
|
5133
|
+
subActivity.sync(activeSubagents.keys());
|
|
5014
5134
|
pushSubagents();
|
|
5015
5135
|
} catch {
|
|
5016
5136
|
}
|
|
@@ -5057,6 +5177,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5057
5177
|
bridge.close();
|
|
5058
5178
|
stream.close();
|
|
5059
5179
|
events.close();
|
|
5180
|
+
subActivity.closeAll();
|
|
5060
5181
|
mcp.closeAll();
|
|
5061
5182
|
const [, killed2] = await Promise.race([
|
|
5062
5183
|
Promise.all([stopped2, procsStopped2]),
|
|
@@ -5119,6 +5240,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5119
5240
|
const extFor = (mime) => ({ "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp" })[mime] ?? "bin";
|
|
5120
5241
|
const toAttachments = (images) => images.map((img) => ({ name: `image-${img.seq}.${extFor(img.mime)}`, mimeType: img.mime, data: img.data }));
|
|
5121
5242
|
const sendNow = async (text, images = []) => {
|
|
5243
|
+
lastSent = { text, images };
|
|
5122
5244
|
tui.printUserMessage(text);
|
|
5123
5245
|
const key = text.trim();
|
|
5124
5246
|
pendingSent.set(key, (pendingSent.get(key) ?? 0) + 1);
|
|
@@ -5157,6 +5279,88 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5157
5279
|
link?.preauthed ? `${c.gray}\u2192 account dashboard opened in your browser (signed in)${c.reset}` : `${c.gray}\u2192 opened ${target} \u2014 sign in with your account email${c.reset}`
|
|
5158
5280
|
);
|
|
5159
5281
|
};
|
|
5282
|
+
const ordinal = (n) => {
|
|
5283
|
+
const s = ["th", "st", "nd", "rd"], v = n % 100;
|
|
5284
|
+
return `${n}${s[(v - 20) % 10] ?? s[v] ?? s[0]}`;
|
|
5285
|
+
};
|
|
5286
|
+
const fmtCost = (q) => typeof q.amount_cents === "number" && q.amount_cents !== 0 ? `$${(Math.abs(q.amount_cents) / 100).toFixed(2)}` : null;
|
|
5287
|
+
const renderUpgradePanel = (q) => {
|
|
5288
|
+
const dots = [];
|
|
5289
|
+
for (let i = 0; i < q.max; i++) {
|
|
5290
|
+
if (i < q.current) dots.push(`${c.teal}\u25CF${c.reset}`);
|
|
5291
|
+
else if (i === q.current) dots.push(`${c.bold}${gradientText("\uFF0B")}${c.reset}`);
|
|
5292
|
+
else dots.push(`${c.dim}\xB7${c.reset}`);
|
|
5293
|
+
}
|
|
5294
|
+
const cost = fmtCost(q);
|
|
5295
|
+
const lines = [
|
|
5296
|
+
"",
|
|
5297
|
+
`${c.bold}${gradientText("\u2726 Add a parallel session")}${c.reset}`,
|
|
5298
|
+
"",
|
|
5299
|
+
`${dots.join(" ")} ${c.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c.reset}`
|
|
5300
|
+
];
|
|
5301
|
+
if (q.ends_trial) {
|
|
5302
|
+
lines.push(
|
|
5303
|
+
`${c.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c.reset}`,
|
|
5304
|
+
`${c.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c.bold}${cost} charged today${c.reset}${c.yellow}` : ""}.${c.reset}`
|
|
5305
|
+
);
|
|
5306
|
+
} else if (cost) {
|
|
5307
|
+
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c.bold}${cost} charged now${c.reset}.`);
|
|
5308
|
+
} else {
|
|
5309
|
+
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 prorated on your next invoice.`);
|
|
5310
|
+
}
|
|
5311
|
+
lines.push("");
|
|
5312
|
+
return lines.join("\n");
|
|
5313
|
+
};
|
|
5314
|
+
const offerUpgrade = async (opts = {}) => {
|
|
5315
|
+
if (upgradeInFlight) return;
|
|
5316
|
+
upgradeInFlight = true;
|
|
5317
|
+
try {
|
|
5318
|
+
const quote = await api.sessionsQuote(threadId);
|
|
5319
|
+
if (!quote) {
|
|
5320
|
+
const link = await api.accountLink(threadId).catch(() => null);
|
|
5321
|
+
const target = link?.url ?? "https://standardcode.ai/account";
|
|
5322
|
+
openUrl(target);
|
|
5323
|
+
tui.print(`${c.gray}\u2192 opened ${target} to manage your plan${c.reset}`);
|
|
5324
|
+
return;
|
|
5325
|
+
}
|
|
5326
|
+
if (quote.current >= quote.max) {
|
|
5327
|
+
tui.print(`${c.yellow}You're at the maximum of ${quote.max} parallel sessions.${c.reset}`);
|
|
5328
|
+
return;
|
|
5329
|
+
}
|
|
5330
|
+
tui.print(renderUpgradePanel(quote));
|
|
5331
|
+
const cost = fmtCost(quote);
|
|
5332
|
+
const confirmLabel = quote.ends_trial ? `End trial & start now${cost ? ` \u2014 pay ${cost} today` : ""}` : `Confirm${cost ? ` \u2014 pay ${cost} now` : " \u2014 add the session"}`;
|
|
5333
|
+
const choice = await tui.select(`Add a ${ordinal(quote.sessions)} parallel session?`, [
|
|
5334
|
+
{ label: confirmLabel, value: "go" },
|
|
5335
|
+
{ label: "Not now", value: "no" }
|
|
5336
|
+
]);
|
|
5337
|
+
if (choice !== "go") {
|
|
5338
|
+
tui.print(`${c.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c.reset}`);
|
|
5339
|
+
return;
|
|
5340
|
+
}
|
|
5341
|
+
tui.print(`${c.gray}Applying\u2026${c.reset}`);
|
|
5342
|
+
let applied;
|
|
5343
|
+
try {
|
|
5344
|
+
applied = await api.sessionsUpgrade(threadId, quote.sessions);
|
|
5345
|
+
} catch (e) {
|
|
5346
|
+
tui.print(`${c.red}\u2717${c.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
if (!applied?.ok) {
|
|
5350
|
+
tui.print(`${c.red}\u2717${c.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
const n = applied.sessions ?? quote.sessions;
|
|
5354
|
+
tui.print(`${c.green}\u2713${c.reset} ${c.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c.reset}`);
|
|
5355
|
+
if (opts.auto && lastSent) {
|
|
5356
|
+
tui.print(`${c.gray}Continuing\u2026${c.reset}`);
|
|
5357
|
+
await sendNow(lastSent.text, lastSent.images);
|
|
5358
|
+
}
|
|
5359
|
+
} finally {
|
|
5360
|
+
upgradeInFlight = false;
|
|
5361
|
+
}
|
|
5362
|
+
};
|
|
5363
|
+
const isSessionLimitError = (msg) => /thread limit reached|simultaneous (thread|session)s?.*in use|no active standard code subscription/i.test(msg);
|
|
5160
5364
|
const skillsCtl = {
|
|
5161
5365
|
list: () => api.listSkills(),
|
|
5162
5366
|
setEnabled: (name, enabled) => api.setSkillEnabled(name, enabled),
|
|
@@ -5214,6 +5418,12 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5214
5418
|
hint: "billing & sessions, opens in browser",
|
|
5215
5419
|
run: () => runAccountCommand()
|
|
5216
5420
|
},
|
|
5421
|
+
{
|
|
5422
|
+
name: "upgrade",
|
|
5423
|
+
label: "Add a parallel session",
|
|
5424
|
+
hint: "run more sessions at once",
|
|
5425
|
+
run: () => offerUpgrade()
|
|
5426
|
+
},
|
|
5217
5427
|
{ name: "background", label: "Background processes", hint: "list / stop", run: () => runProcessMenu(tui, bgMgr) },
|
|
5218
5428
|
{ name: "keybindings", label: "Keyboard shortcuts", run: () => showKeybindings(tui) },
|
|
5219
5429
|
{ name: "update", label: "Check for updates", hint: "check for a newer version", run: () => runUpdateCommand(tui) },
|
|
@@ -5314,6 +5524,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5314
5524
|
if (shownIds.has(m.id) || m.status === "pending") continue;
|
|
5315
5525
|
shownIds.add(m.id);
|
|
5316
5526
|
const text = messageText(m.content).trim();
|
|
5527
|
+
const denial = typeof m.error === "string" && m.error || text;
|
|
5528
|
+
if (denial && isSessionLimitError(denial)) {
|
|
5529
|
+
void offerUpgrade({ auto: true });
|
|
5530
|
+
continue;
|
|
5531
|
+
}
|
|
5317
5532
|
if (m.role === "assistant" && text) printAssistant(tui, text);
|
|
5318
5533
|
else if (m.role === "system" && text) tui.print(`${c.dim}${text}${c.reset}`);
|
|
5319
5534
|
else if (m.role === "user" && text) {
|
|
@@ -5383,6 +5598,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
|
|
|
5383
5598
|
bridge.close();
|
|
5384
5599
|
stream.close();
|
|
5385
5600
|
events.close();
|
|
5601
|
+
subActivity.closeAll();
|
|
5386
5602
|
mcp.closeAll();
|
|
5387
5603
|
const [, killed] = await Promise.race([
|
|
5388
5604
|
Promise.all([stopped, procsStopped]),
|
|
@@ -5552,7 +5768,7 @@ async function runApprovalsMenu(tui, perm, save) {
|
|
|
5552
5768
|
}
|
|
5553
5769
|
const items = [
|
|
5554
5770
|
...tools.map((t) => ({ label: `Tool: ${t}`, hint: "always allowed", value: `tool:${t}` })),
|
|
5555
|
-
...risks.map((r) => ({ label: `
|
|
5771
|
+
...risks.map((r) => ({ label: `Risk level ${r} and below`, hint: "auto-accepted", value: `risk:${r}` })),
|
|
5556
5772
|
{ label: "Clear all approvals", hint: "", value: "clear" }
|
|
5557
5773
|
];
|
|
5558
5774
|
const picked = await tui.select(
|