@standardagents/code 0.6.4 → 0.6.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/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
- if (state.allowRisk.has(effectiveRisk)) return "allow";
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,
@@ -3350,7 +3375,7 @@ var Tui = class _Tui {
3350
3375
  const options = [
3351
3376
  { value: "allow", label: "Allow once", shortcut: "y", color: C.green },
3352
3377
  { value: "always", label: "Always allow this tool", shortcut: "a", color: C.cyan },
3353
- { value: "always_risk", label: `Allow all level ${risk} this session`, shortcut: "l", color: C.cyan },
3378
+ { value: "always_risk", label: `Allow level ${risk} and below this session`, shortcut: "l", color: C.cyan },
3354
3379
  { value: "deny", label: "Deny", shortcut: "n", color: C.red }
3355
3380
  ];
3356
3381
  let idx = 0;
@@ -4917,6 +4942,8 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
4917
4942
  let editingQueued = false;
4918
4943
  const shownIds = /* @__PURE__ */ new Set();
4919
4944
  const pendingSent = /* @__PURE__ */ new Map();
4945
+ let lastSent = null;
4946
+ let upgradeInFlight = false;
4920
4947
  let tokensIn = 0;
4921
4948
  let tokensOut = 0;
4922
4949
  let liveOut = 0;
@@ -4986,7 +5013,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
4986
5013
  tui.setGoal(data);
4987
5014
  }
4988
5015
  },
4989
- onError: () => {
5016
+ // A failed turn whose message is the lease service's at-limit denial → offer
5017
+ // an in-terminal upgrade right where the friction happened. (offerUpgrade /
5018
+ // isSessionLimitError are defined below; this callback only fires mid-session.)
5019
+ onError: (err) => {
5020
+ if (isSessionLimitError(err)) void offerUpgrade({ auto: true });
4990
5021
  }
4991
5022
  });
4992
5023
  const activeSubagents = /* @__PURE__ */ new Map();
@@ -5119,6 +5150,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
5119
5150
  const extFor = (mime) => ({ "image/png": "png", "image/jpeg": "jpg", "image/gif": "gif", "image/webp": "webp" })[mime] ?? "bin";
5120
5151
  const toAttachments = (images) => images.map((img) => ({ name: `image-${img.seq}.${extFor(img.mime)}`, mimeType: img.mime, data: img.data }));
5121
5152
  const sendNow = async (text, images = []) => {
5153
+ lastSent = { text, images };
5122
5154
  tui.printUserMessage(text);
5123
5155
  const key = text.trim();
5124
5156
  pendingSent.set(key, (pendingSent.get(key) ?? 0) + 1);
@@ -5157,6 +5189,88 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
5157
5189
  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
5190
  );
5159
5191
  };
5192
+ const ordinal = (n) => {
5193
+ const s = ["th", "st", "nd", "rd"], v = n % 100;
5194
+ return `${n}${s[(v - 20) % 10] ?? s[v] ?? s[0]}`;
5195
+ };
5196
+ const fmtCost = (q) => typeof q.amount_cents === "number" && q.amount_cents !== 0 ? `$${(Math.abs(q.amount_cents) / 100).toFixed(2)}` : null;
5197
+ const renderUpgradePanel = (q) => {
5198
+ const dots = [];
5199
+ for (let i = 0; i < q.max; i++) {
5200
+ if (i < q.current) dots.push(`${c.teal}\u25CF${c.reset}`);
5201
+ else if (i === q.current) dots.push(`${c.bold}${gradientText("\uFF0B")}${c.reset}`);
5202
+ else dots.push(`${c.dim}\xB7${c.reset}`);
5203
+ }
5204
+ const cost = fmtCost(q);
5205
+ const lines = [
5206
+ "",
5207
+ `${c.bold}${gradientText("\u2726 Add a parallel session")}${c.reset}`,
5208
+ "",
5209
+ `${dots.join(" ")} ${c.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c.reset}`
5210
+ ];
5211
+ if (q.ends_trial) {
5212
+ lines.push(
5213
+ `${c.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c.reset}`,
5214
+ `${c.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c.bold}${cost} charged today${c.reset}${c.yellow}` : ""}.${c.reset}`
5215
+ );
5216
+ } else if (cost) {
5217
+ lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c.bold}${cost} charged now${c.reset}.`);
5218
+ } else {
5219
+ lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 prorated on your next invoice.`);
5220
+ }
5221
+ lines.push("");
5222
+ return lines.join("\n");
5223
+ };
5224
+ const offerUpgrade = async (opts = {}) => {
5225
+ if (upgradeInFlight) return;
5226
+ upgradeInFlight = true;
5227
+ try {
5228
+ const quote = await api.sessionsQuote(threadId);
5229
+ if (!quote) {
5230
+ const link = await api.accountLink(threadId).catch(() => null);
5231
+ const target = link?.url ?? "https://standardcode.ai/account";
5232
+ openUrl(target);
5233
+ tui.print(`${c.gray}\u2192 opened ${target} to manage your plan${c.reset}`);
5234
+ return;
5235
+ }
5236
+ if (quote.current >= quote.max) {
5237
+ tui.print(`${c.yellow}You're at the maximum of ${quote.max} parallel sessions.${c.reset}`);
5238
+ return;
5239
+ }
5240
+ tui.print(renderUpgradePanel(quote));
5241
+ const cost = fmtCost(quote);
5242
+ const confirmLabel = quote.ends_trial ? `End trial & start now${cost ? ` \u2014 pay ${cost} today` : ""}` : `Confirm${cost ? ` \u2014 pay ${cost} now` : " \u2014 add the session"}`;
5243
+ const choice = await tui.select(`Add a ${ordinal(quote.sessions)} parallel session?`, [
5244
+ { label: confirmLabel, value: "go" },
5245
+ { label: "Not now", value: "no" }
5246
+ ]);
5247
+ if (choice !== "go") {
5248
+ tui.print(`${c.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c.reset}`);
5249
+ return;
5250
+ }
5251
+ tui.print(`${c.gray}Applying\u2026${c.reset}`);
5252
+ let applied;
5253
+ try {
5254
+ applied = await api.sessionsUpgrade(threadId, quote.sessions);
5255
+ } catch (e) {
5256
+ tui.print(`${c.red}\u2717${c.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
5257
+ return;
5258
+ }
5259
+ if (!applied?.ok) {
5260
+ tui.print(`${c.red}\u2717${c.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
5261
+ return;
5262
+ }
5263
+ const n = applied.sessions ?? quote.sessions;
5264
+ tui.print(`${c.green}\u2713${c.reset} ${c.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c.reset}`);
5265
+ if (opts.auto && lastSent) {
5266
+ tui.print(`${c.gray}Continuing\u2026${c.reset}`);
5267
+ await sendNow(lastSent.text, lastSent.images);
5268
+ }
5269
+ } finally {
5270
+ upgradeInFlight = false;
5271
+ }
5272
+ };
5273
+ const isSessionLimitError = (msg) => /thread limit reached|simultaneous (thread|session)s?.*in use|no active standard code subscription/i.test(msg);
5160
5274
  const skillsCtl = {
5161
5275
  list: () => api.listSkills(),
5162
5276
  setEnabled: (name, enabled) => api.setSkillEnabled(name, enabled),
@@ -5214,6 +5328,12 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
5214
5328
  hint: "billing & sessions, opens in browser",
5215
5329
  run: () => runAccountCommand()
5216
5330
  },
5331
+ {
5332
+ name: "upgrade",
5333
+ label: "Add a parallel session",
5334
+ hint: "run more sessions at once",
5335
+ run: () => offerUpgrade()
5336
+ },
5217
5337
  { name: "background", label: "Background processes", hint: "list / stop", run: () => runProcessMenu(tui, bgMgr) },
5218
5338
  { name: "keybindings", label: "Keyboard shortcuts", run: () => showKeybindings(tui) },
5219
5339
  { name: "update", label: "Check for updates", hint: "check for a newer version", run: () => runUpdateCommand(tui) },
@@ -5314,6 +5434,11 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
5314
5434
  if (shownIds.has(m.id) || m.status === "pending") continue;
5315
5435
  shownIds.add(m.id);
5316
5436
  const text = messageText(m.content).trim();
5437
+ const denial = typeof m.error === "string" && m.error || text;
5438
+ if (denial && isSessionLimitError(denial)) {
5439
+ void offerUpgrade({ auto: true });
5440
+ continue;
5441
+ }
5317
5442
  if (m.role === "assistant" && text) printAssistant(tui, text);
5318
5443
  else if (m.role === "system" && text) tui.print(`${c.dim}${text}${c.reset}`);
5319
5444
  else if (m.role === "user" && text) {
@@ -5552,7 +5677,7 @@ async function runApprovalsMenu(tui, perm, save) {
5552
5677
  }
5553
5678
  const items = [
5554
5679
  ...tools.map((t) => ({ label: `Tool: ${t}`, hint: "always allowed", value: `tool:${t}` })),
5555
- ...risks.map((r) => ({ label: `All level ${r} risk`, hint: "always allowed", value: `risk:${r}` })),
5680
+ ...risks.map((r) => ({ label: `Risk level ${r} and below`, hint: "auto-accepted", value: `risk:${r}` })),
5556
5681
  { label: "Clear all approvals", hint: "", value: "clear" }
5557
5682
  ];
5558
5683
  const picked = await tui.select(