@standardagents/code 0.5.1 → 0.6.1

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
@@ -8,6 +8,7 @@ import { stdout, stdin } from 'process';
8
8
  import fsp from 'fs/promises';
9
9
  import crypto from 'crypto';
10
10
  import readline from 'readline';
11
+ import { fileURLToPath } from 'url';
11
12
 
12
13
  // src/api.ts
13
14
  function classifyConnectError(err, endpoint) {
@@ -795,13 +796,7 @@ var Bridge = class {
795
796
  this.hooks.onStatus?.(callKey, null);
796
797
  }
797
798
  if (result.ok) {
798
- const display = req.tool === "bash" ? `bash: ${highlightBash(oneLineCommand(String(req.args.command ?? ""), Math.max(40, (process.stdout.columns || 80) - 20)))}` : summary;
799
- let detail;
800
- if (req.tool === "edit_file") {
801
- detail = diffLines(String(req.args.old_string ?? ""), String(req.args.new_string ?? ""));
802
- } else if (req.tool === "write_file") {
803
- detail = newFileLines(String(req.args.content ?? ""));
804
- }
799
+ const { display, detail } = activityPresentation(req, summary);
805
800
  this.hooks.onActivity(`\u2713 ${display}${detailSuffix(req.tool, result.result)}`, detail);
806
801
  this.respond(req, true, result.result ?? "");
807
802
  } else {
@@ -819,6 +814,35 @@ function permissionKey(req) {
819
814
  if (action === "list_tools") return `mcp:${server}/list`;
820
815
  return `mcp:${server}/${String(a.tool || "?")}`;
821
816
  }
817
+ var DIM2 = "\x1B[2m";
818
+ var RESET2 = "\x1B[0m";
819
+ function activityPresentation(req, summary) {
820
+ const a = req.args;
821
+ const cols2 = Math.max(40, process.stdout.columns || 80);
822
+ if (req.tool === "bash") {
823
+ return { display: `bash: ${highlightBash(oneLineCommand(String(a.command ?? ""), cols2 - 20))}` };
824
+ }
825
+ if (req.tool === "edit_file") {
826
+ return { display: summary, detail: diffLines(String(a.old_string ?? ""), String(a.new_string ?? "")) };
827
+ }
828
+ if (req.tool === "write_file") {
829
+ return { display: summary, detail: newFileLines(String(a.content ?? "")) };
830
+ }
831
+ if (req.tool === "background_process") {
832
+ const action = String(a.action || "list");
833
+ const id = a.id ? ` ${String(a.id)}` : "";
834
+ const display = action === "start" ? "bg start" : `bg ${action}${id}`;
835
+ const detail = [];
836
+ if (action === "start" && a.command) {
837
+ detail.push(` ${DIM2}${oneLineCommand(String(a.command), cols2 - 8)}${RESET2}`);
838
+ }
839
+ if (typeof a.description === "string" && a.description.trim()) {
840
+ detail.push(` ${DIM2}${oneLineCommand(a.description, cols2 - 8)}${RESET2}`);
841
+ }
842
+ return { display, detail: detail.length ? detail : void 0 };
843
+ }
844
+ return { display: summary };
845
+ }
822
846
  function describe(req) {
823
847
  const a = req.args;
824
848
  switch (req.tool) {
@@ -845,6 +869,12 @@ function describe(req) {
845
869
  return `skill ${a.skill}: run ${a.entry}`;
846
870
  case "bash":
847
871
  return `bash: ${oneLineCommand(String(a.command ?? ""), 80)}`;
872
+ case "background_process": {
873
+ const action = String(a.action || "list");
874
+ if (action === "start") return `bg start: ${oneLineCommand(String(a.command ?? ""), 80)}`;
875
+ if (action === "list") return "bg list";
876
+ return `bg ${action}${a.id ? ` ${String(a.id)}` : ""}`;
877
+ }
848
878
  default:
849
879
  return `${req.tool} ${JSON.stringify(a).slice(0, 80)}`;
850
880
  }
@@ -1888,7 +1918,7 @@ var LAZY_MS = 320;
1888
1918
  var LAZY_PERIOD = 200;
1889
1919
  var FAST_PERIOD = 70;
1890
1920
  var BOLD = "\x1B[1m";
1891
- var DIM2 = "\x1B[2m";
1921
+ var DIM3 = "\x1B[2m";
1892
1922
  var OFF = "\x1B[22m";
1893
1923
  function glyphAt(slot, bucket) {
1894
1924
  let h = (slot + 1) * 2654435761 ^ (bucket + 1) * 40503;
@@ -1960,7 +1990,7 @@ var WordMill = class {
1960
1990
  } else if (t < s.land) {
1961
1991
  const age = t - s.start;
1962
1992
  const period = age < LAZY_MS ? LAZY_PERIOD : FAST_PERIOD;
1963
- out += `${DIM2}${glyphAt(i, Math.floor(t / period))}${OFF}`;
1993
+ out += `${DIM3}${glyphAt(i, Math.floor(t / period))}${OFF}`;
1964
1994
  } else {
1965
1995
  const ch = target[i];
1966
1996
  if (ch) out += `${BOLD}${ch}${OFF}`;
@@ -3052,7 +3082,8 @@ var Tui = class _Tui {
3052
3082
  const t = s.length > max ? s.slice(0, Math.max(0, max - 1)) + "\u2026" : s;
3053
3083
  return wrap ? `${lead}${wrap}${t}${C.reset}` : `${lead}${t}`;
3054
3084
  };
3055
- const realLines = (text) => text.replace(/\r/g, "").split("\n").filter((l) => l.trim() !== "");
3085
+ const leakedMarkup = /<\/?[||]DSML[||]|^\s*\[\/?SESSION\]\s*<?\s*$/;
3086
+ const realLines = (text) => text.replace(/\r/g, "").split("\n").filter((l) => l.trim() !== "" && !leakedMarkup.test(l));
3056
3087
  if (this.streamResponse) {
3057
3088
  const all = realLines(this.streamResponse);
3058
3089
  const shown = all.slice(-20);
@@ -3500,7 +3531,7 @@ function appendHistory(store, threadId, history, text) {
3500
3531
  var ESC = "\x1B[";
3501
3532
  var R = ESC + "0m";
3502
3533
  var BOLD2 = ESC + "1m";
3503
- var DIM3 = ESC + "2m";
3534
+ var DIM4 = ESC + "2m";
3504
3535
  var ITAL = ESC + "3m";
3505
3536
  var UNDER = ESC + "4m";
3506
3537
  var TEAL = ESC + "38;5;37m";
@@ -3522,11 +3553,11 @@ function inline(s) {
3522
3553
  });
3523
3554
  s = s.replace(
3524
3555
  /\[([^\]]+)\]\(([^)\s]+)\)/g,
3525
- (_, text, url) => `${CYAN}${UNDER}${text}${R} ${DIM3}${url}${R}`
3556
+ (_, text, url) => `${CYAN}${UNDER}${text}${R} ${DIM4}${url}${R}`
3526
3557
  );
3527
3558
  s = s.replace(/\*\*([^*]+)\*\*/g, (_, t) => `${BOLD2}${t}${R}`);
3528
3559
  s = s.replace(/\*([^*\n]+)\*/g, (_, t) => `${ITAL}${t}${R}`);
3529
- s = s.replace(/~~([^~]+)~~/g, (_, t) => `${DIM3}${t}${R}`);
3560
+ s = s.replace(/~~([^~]+)~~/g, (_, t) => `${DIM4}${t}${R}`);
3530
3561
  s = s.replace(/\x00(\d+)\x00/g, (_, i) => `${TEAL}${codes[+i].replace(/ /g, String.fromCharCode(160))}${R}`);
3531
3562
  return s;
3532
3563
  }
@@ -3631,7 +3662,7 @@ function renderMarkdown(src, cols2 = 80) {
3631
3662
  const quote = line.match(/^\s*>\s?(.*)$/);
3632
3663
  if (quote) {
3633
3664
  for (const ln of wrapStyled(inline(quote[1]), Math.max(8, cols2 - 2))) {
3634
- out.push(`${GRAY}\u2502${R} ${DIM3}${ln}${R}`);
3665
+ out.push(`${GRAY}\u2502${R} ${DIM4}${ln}${R}`);
3635
3666
  }
3636
3667
  i++;
3637
3668
  continue;
@@ -4096,8 +4127,10 @@ var PKG_NAME = "@standardagents/code";
4096
4127
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
4097
4128
  var CACHE_REL_DIR = ".config/standardagents-cli";
4098
4129
  var CACHE_FILE = "update-check.json";
4099
- var CACHE_TTL_MS = 1e3 * 60 * 60 * 24;
4130
+ var STATE_FILE = "auto-update.json";
4131
+ var CACHE_TTL_MS = 1e3 * 60 * 60 * 6;
4100
4132
  var CHECK_TIMEOUT_MS = 4e3;
4133
+ var IN_FLIGHT_TTL_MS = 15 * 60 * 1e3;
4101
4134
  function cacheDir() {
4102
4135
  return path3.join(homedir(), CACHE_REL_DIR);
4103
4136
  }
@@ -4120,16 +4153,86 @@ function writeCache(latest) {
4120
4153
  } catch {
4121
4154
  }
4122
4155
  }
4123
- async function checkForUpdate(currentVersion) {
4124
- if (!currentVersion) return null;
4125
- if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4126
- const cached = readCache();
4127
- if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
4128
- if (cached.latest !== currentVersion) {
4129
- return { current: currentVersion, latest: cached.latest };
4130
- }
4156
+ function readAutoUpdateState(dir = cacheDir()) {
4157
+ try {
4158
+ const raw = fs4.readFileSync(path3.join(dir, STATE_FILE), "utf-8");
4159
+ const state = JSON.parse(raw);
4160
+ return typeof state?.version === "string" ? state : null;
4161
+ } catch {
4131
4162
  return null;
4132
4163
  }
4164
+ }
4165
+ function writeAutoUpdateState(state, dir = cacheDir()) {
4166
+ try {
4167
+ if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
4168
+ fs4.writeFileSync(path3.join(dir, STATE_FILE), JSON.stringify(state));
4169
+ } catch {
4170
+ }
4171
+ }
4172
+ function clearAutoUpdateState(dir = cacheDir()) {
4173
+ try {
4174
+ fs4.unlinkSync(path3.join(dir, STATE_FILE));
4175
+ } catch {
4176
+ }
4177
+ }
4178
+ function detectPackageManager(selfPath = fileURLToPath(import.meta.url), env = process.env) {
4179
+ const norm = selfPath.split(/[\\/]/).join("/");
4180
+ if (!norm.includes("node_modules/@standardagents/code")) return null;
4181
+ const pnpmHome = (env.PNPM_HOME || "").split(/[\\/]/).join("/");
4182
+ if (pnpmHome && norm.startsWith(pnpmHome)) return "pnpm";
4183
+ if (norm.includes("/.pnpm/") || /\/pnpm\/global\//.test(norm)) return "pnpm";
4184
+ if (norm.includes("/.bun/")) return "bun";
4185
+ if (norm.includes("/.config/yarn/") || norm.includes("/.yarn/")) return "yarn";
4186
+ return "npm";
4187
+ }
4188
+ function updateCommand(pm) {
4189
+ switch (pm) {
4190
+ case "pnpm":
4191
+ return { cmd: "pnpm", args: ["add", "-g", `${PKG_NAME}@latest`], display: `pnpm add -g ${PKG_NAME}@latest` };
4192
+ case "yarn":
4193
+ return { cmd: "yarn", args: ["global", "add", `${PKG_NAME}@latest`], display: `yarn global add ${PKG_NAME}@latest` };
4194
+ case "bun":
4195
+ return { cmd: "bun", args: ["add", "-g", `${PKG_NAME}@latest`], display: `bun add -g ${PKG_NAME}@latest` };
4196
+ default:
4197
+ return { cmd: "npm", args: ["i", "-g", `${PKG_NAME}@latest`], display: `npm i -g ${PKG_NAME}@latest` };
4198
+ }
4199
+ }
4200
+ function decideAutoUpdate(info, opts) {
4201
+ const env = opts.env ?? process.env;
4202
+ if (env.STANDARD_CODE_NO_AUTO_UPDATE) return "disabled";
4203
+ if (!opts.pm) return "dev_checkout";
4204
+ const state = opts.state;
4205
+ if (state && state.version === info.latest) {
4206
+ if (state.exitCode === null) {
4207
+ const now = opts.now ?? Date.now();
4208
+ return now - state.startedAt < IN_FLIGHT_TTL_MS ? "in_flight" : "start";
4209
+ }
4210
+ return state.exitCode === 0 ? "path_shadowed" : "already_failed";
4211
+ }
4212
+ return "start";
4213
+ }
4214
+ function startBackgroundUpdate(latest, pm, dir = cacheDir()) {
4215
+ const startedAt = Date.now();
4216
+ writeAutoUpdateState({ version: latest, startedAt, exitCode: null }, dir);
4217
+ const stateFile = path3.join(dir, STATE_FILE);
4218
+ const { cmd, args } = updateCommand(pm);
4219
+ const script = `const cp=require('child_process');const fs=require('fs');const r=cp.spawnSync(${JSON.stringify(cmd)},${JSON.stringify(args)},{shell:process.platform==='win32',encoding:'utf8'});const out=((r.stdout||'')+(r.stderr||'')).slice(-2000);fs.writeFileSync(${JSON.stringify(stateFile)},JSON.stringify({version:${JSON.stringify(latest)},startedAt:${startedAt},exitCode:r.status==null?-1:r.status,finishedAt:Date.now(),output:out}));`;
4220
+ try {
4221
+ const child = spawn(process.execPath, ["-e", script], { detached: true, stdio: "ignore" });
4222
+ child.unref();
4223
+ return true;
4224
+ } catch {
4225
+ writeAutoUpdateState({ version: latest, startedAt, exitCode: -1, finishedAt: Date.now() }, dir);
4226
+ return false;
4227
+ }
4228
+ }
4229
+ function consumeAppliedUpdate(currentVersion, dir = cacheDir()) {
4230
+ const state = readAutoUpdateState(dir);
4231
+ if (!state || state.version !== currentVersion) return null;
4232
+ clearAutoUpdateState(dir);
4233
+ return state.exitCode === 0 ? state : null;
4234
+ }
4235
+ async function fetchLatest(currentVersion) {
4133
4236
  const controller = new AbortController();
4134
4237
  const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
4135
4238
  try {
@@ -4142,55 +4245,48 @@ async function checkForUpdate(currentVersion) {
4142
4245
  });
4143
4246
  if (!res.ok) return null;
4144
4247
  const data = await res.json();
4145
- const latest = data["dist-tags"]?.latest;
4146
- if (!latest) return null;
4147
- writeCache(latest);
4148
- if (latest !== currentVersion) {
4149
- return { current: currentVersion, latest };
4150
- }
4151
- return null;
4248
+ return data["dist-tags"]?.latest ?? null;
4152
4249
  } catch {
4153
4250
  return null;
4154
4251
  } finally {
4155
4252
  clearTimeout(timeout);
4156
4253
  }
4157
4254
  }
4158
- async function forceCheckForUpdate(currentVersion) {
4255
+ async function checkForUpdate(currentVersion) {
4159
4256
  if (!currentVersion) return null;
4160
4257
  if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4161
- const controller = new AbortController();
4162
- const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
4163
- try {
4164
- const res = await fetch(REGISTRY_URL, {
4165
- signal: controller.signal,
4166
- headers: {
4167
- Accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
4168
- "User-Agent": `${PKG_NAME}/${currentVersion}`
4169
- }
4170
- });
4171
- if (!res.ok) return null;
4172
- const data = await res.json();
4173
- const latest = data["dist-tags"]?.latest;
4174
- if (!latest) return null;
4175
- writeCache(latest);
4176
- if (latest !== currentVersion) {
4177
- return { current: currentVersion, latest };
4258
+ const cached = readCache();
4259
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
4260
+ if (cached.latest !== currentVersion) {
4261
+ return { current: currentVersion, latest: cached.latest };
4178
4262
  }
4179
4263
  return null;
4180
- } catch {
4181
- return null;
4182
- } finally {
4183
- clearTimeout(timeout);
4184
4264
  }
4265
+ const latest = await fetchLatest(currentVersion);
4266
+ if (!latest) return null;
4267
+ writeCache(latest);
4268
+ return latest !== currentVersion ? { current: currentVersion, latest } : null;
4269
+ }
4270
+ async function forceCheckForUpdate(currentVersion) {
4271
+ if (!currentVersion) return null;
4272
+ if (process.env.NO_UPDATE_NOTIFIER || process.env.CI) return null;
4273
+ const latest = await fetchLatest(currentVersion);
4274
+ if (!latest) return null;
4275
+ writeCache(latest);
4276
+ return latest !== currentVersion ? { current: currentVersion, latest } : null;
4185
4277
  }
4186
- function runNpmUpdate() {
4278
+ function runUpdate(pm) {
4187
4279
  return new Promise((resolve) => {
4188
- const child = spawn("npm", ["i", "-g", `${PKG_NAME}@latest`], {
4189
- stdio: "inherit",
4190
- shell: true
4280
+ const { cmd, args } = updateCommand(pm);
4281
+ const child = spawn(cmd, args, {
4282
+ shell: process.platform === "win32",
4283
+ stdio: ["ignore", "pipe", "pipe"]
4191
4284
  });
4192
- child.on("close", (code) => resolve(code === 0));
4193
- child.on("error", () => resolve(false));
4285
+ let out = "";
4286
+ child.stdout?.on("data", (d) => out += d);
4287
+ child.stderr?.on("data", (d) => out += d);
4288
+ child.on("close", (code) => resolve({ ok: code === 0, output: out }));
4289
+ child.on("error", (err) => resolve({ ok: false, output: String(err) }));
4194
4290
  });
4195
4291
  }
4196
4292
 
@@ -4483,13 +4579,36 @@ ${c.dim}Press Control-C again to exit${c.reset}
4483
4579
  const loading = startLoader("Checking for updates");
4484
4580
  updateAvailable = await checkForUpdate(version);
4485
4581
  loading.stop();
4582
+ const applied = consumeAppliedUpdate(version);
4583
+ if (applied) {
4584
+ stdout.write(` ${c.green}\u2713${c.reset} ${c.dim}Standard Code updated to v${version}.${c.reset}
4585
+
4586
+ `);
4587
+ }
4486
4588
  if (updateAvailable) {
4487
- stdout.write(
4488
- ` ${c.teal}\u25C7${c.reset} ${c.dim}Update available:${c.reset} ${c.dim}v${updateAvailable.current}${c.reset} \u2192 ${c.bold}v${updateAvailable.latest}${c.reset}
4489
- ${c.dim}Run ${c.reset}${c.bold}npm i -g @standardagents/code@latest${c.reset}${c.dim} to update${c.reset}
4589
+ const pm = detectPackageManager();
4590
+ const decision = decideAutoUpdate(updateAvailable, { state: readAutoUpdateState(), pm });
4591
+ if (decision === "start" && pm && startBackgroundUpdate(updateAvailable.latest, pm)) {
4592
+ stdout.write(
4593
+ ` ${c.teal}\u27F3${c.reset} ${c.dim}Standard Code ${c.reset}${c.bold}v${updateAvailable.latest}${c.reset}${c.dim} is installing in the background \u2014 it applies on your next launch.${c.reset}
4490
4594
 
4491
4595
  `
4492
- );
4596
+ );
4597
+ } else if (decision === "in_flight") {
4598
+ stdout.write(
4599
+ ` ${c.teal}\u27F3${c.reset} ${c.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c.reset}
4600
+
4601
+ `
4602
+ );
4603
+ } else {
4604
+ const display = updateCommand(pm ?? "npm").display;
4605
+ stdout.write(
4606
+ ` ${c.teal}\u25C7${c.reset} ${c.dim}Update available:${c.reset} ${c.dim}v${updateAvailable.current}${c.reset} \u2192 ${c.bold}v${updateAvailable.latest}${c.reset}
4607
+ ${c.dim}Run ${c.reset}${c.bold}${display}${c.reset}${c.dim} to update${c.reset}
4608
+
4609
+ `
4610
+ );
4611
+ }
4493
4612
  }
4494
4613
  }
4495
4614
  const stored = getCredential(endpoint);
@@ -4619,6 +4738,7 @@ ${c.dim}Press Control-C again to exit${c.reset}
4619
4738
  await runInteractive(tui, api, threadId, projectDir, machine, resumed, historySeed);
4620
4739
  historySeed = threadId;
4621
4740
  threadId = await api.createThread(AGENT_ID, tags);
4741
+ await api.kvSet(threadId, "lease_supersedes", historySeed);
4622
4742
  resumed = false;
4623
4743
  }
4624
4744
  }
@@ -5048,7 +5168,7 @@ ${c.bold}why: ${req.requestPermission}${c.reset}` : ""}`,
5048
5168
  },
5049
5169
  { name: "background", label: "Background processes", hint: "list / stop", run: () => runProcessMenu(tui, bgMgr) },
5050
5170
  { name: "keybindings", label: "Keyboard shortcuts", run: () => showKeybindings(tui) },
5051
- { name: "update", label: "Check for updates", hint: "check npm for a newer version", run: () => runUpdateCommand(tui) },
5171
+ { name: "update", label: "Check for updates", hint: "check for a newer version", run: () => runUpdateCommand(tui) },
5052
5172
  { name: "logout", label: "Sign out", hint: "delete the saved token & quit", run: () => logout() },
5053
5173
  { name: "quit", label: "Quit", run: () => quit() }
5054
5174
  ]);
@@ -5315,21 +5435,26 @@ async function runUpdateCommand(tui) {
5315
5435
  const { latest } = result;
5316
5436
  tui.print(`
5317
5437
  ${c.yellow}\u27F3${c.reset} Update available: ${c.gray}v${version}${c.reset} \u2192 ${c.green}v${latest}${c.reset}`);
5318
- const choice = await tui.select(`Update now with \`npm i -g @standardagents/code@latest\`?`, [
5438
+ const pm = detectPackageManager();
5439
+ if (!pm) {
5440
+ tui.print(` ${c.gray}This is a source checkout \u2014 pull the repo to update.${c.reset}`);
5441
+ return;
5442
+ }
5443
+ const { display } = updateCommand(pm);
5444
+ const choice = await tui.select(`Update now with \`${display}\`?`, [
5319
5445
  { label: "Yes, update now", value: "yes" },
5320
5446
  { label: "No, skip", value: "no" }
5321
5447
  ]);
5322
5448
  if (choice === "yes") {
5323
- tui.print(` ${c.gray}Running npm i -g @standardagents/code@latest\u2026${c.reset}`);
5324
- try {
5325
- const ok = await runNpmUpdate();
5326
- if (ok) {
5327
- tui.print(` ${c.green}\u2713${c.reset} Updated to v${latest}. Restart to use the new version.`);
5328
- } else {
5329
- tui.print(` ${c.red}\u2717${c.reset} Update failed.`);
5449
+ tui.print(` ${c.gray}Running ${display}\u2026${c.reset}`);
5450
+ const { ok, output: pmOutput } = await runUpdate(pm);
5451
+ if (ok) {
5452
+ tui.print(` ${c.green}\u2713${c.reset} Updated to v${latest}. Restart to use the new version.`);
5453
+ } else {
5454
+ tui.print(` ${c.red}\u2717${c.reset} Update failed:`);
5455
+ for (const line of pmOutput.trim().split("\n").slice(-6)) {
5456
+ tui.print(` ${c.dim}${line}${c.reset}`);
5330
5457
  }
5331
- } catch (e) {
5332
- tui.print(` ${c.red}\u2717${c.reset} Update failed: ${e}`);
5333
5458
  }
5334
5459
  } else {
5335
5460
  tui.print(` ${c.gray}Skipped. Run /update later.${c.reset}`);