@4pm/cli 1.7.0 → 1.8.4-b

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.
Files changed (2) hide show
  1. package/dist/index.js +485 -408
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -29667,7 +29667,7 @@ async function checkAndUpdate(serverUrl, autoUpdate) {
29667
29667
  init_wrapper();
29668
29668
  import { randomUUID as randomUUID2 } from "crypto";
29669
29669
  import { basename as basename7, join as join28 } from "path";
29670
- import { existsSync as existsSync15, mkdirSync as mkdirSync7 } from "fs";
29670
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7 } from "fs";
29671
29671
 
29672
29672
  // vendor/@4pm/utils/helpers.ts
29673
29673
  function backoffJitterMs(attempt, baseMs = 500, maxMs = 1e4) {
@@ -34367,9 +34367,240 @@ var activityHeartbeatSchema = external_exports.object({
34367
34367
  projectId: external_exports.string().uuid().optional()
34368
34368
  });
34369
34369
 
34370
+ // src/core/scaffold.ts
34371
+ import { cp, mkdir, writeFile } from "fs/promises";
34372
+ import { existsSync as existsSync4 } from "fs";
34373
+ import { execFile as execFile2 } from "child_process";
34374
+ import { dirname as dirname3, join as join6, resolve } from "path";
34375
+ import { fileURLToPath as fileURLToPath3 } from "url";
34376
+ import { promisify as promisify2 } from "util";
34377
+
34378
+ // src/core/ai-assist.ts
34379
+ import { execFile } from "child_process";
34380
+ import { promisify } from "util";
34381
+ var run = promisify(execFile);
34382
+ function aiCli() {
34383
+ return process.env.AI_CLI || "claude";
34384
+ }
34385
+ function aiArgs(cli, prompt) {
34386
+ if (cli === "claude") return ["-p", prompt, "--output-format", "json"];
34387
+ if (cli === "codex") return ["exec", "--json", prompt];
34388
+ return ["-p", prompt];
34389
+ }
34390
+ function estimateTokens(prompt, output) {
34391
+ return Math.max(1, Math.ceil((prompt.length + output.length) / 4));
34392
+ }
34393
+ function parseAi(cli, stdout, prompt) {
34394
+ if (cli === "claude") {
34395
+ try {
34396
+ const j = JSON.parse(stdout);
34397
+ const text2 = (j.result ?? "").trim();
34398
+ const tokens = (j.usage?.input_tokens ?? 0) + (j.usage?.output_tokens ?? 0);
34399
+ if (text2 || tokens) return { text: text2, tokens: tokens || estimateTokens(prompt, text2) };
34400
+ } catch {
34401
+ }
34402
+ }
34403
+ if (cli === "codex") {
34404
+ try {
34405
+ let text2 = "";
34406
+ let tokens = 0;
34407
+ for (const line of stdout.split("\n").filter(Boolean)) {
34408
+ const obj = JSON.parse(line);
34409
+ if (obj.text) text2 = obj.text;
34410
+ else if (obj.message) text2 = obj.message;
34411
+ if (obj.usage) {
34412
+ tokens = obj.usage.total_tokens ?? (obj.usage.input_tokens ?? 0) + (obj.usage.output_tokens ?? 0);
34413
+ }
34414
+ }
34415
+ if (text2 || tokens) return { text: text2.trim(), tokens: tokens || estimateTokens(prompt, text2) };
34416
+ } catch {
34417
+ }
34418
+ }
34419
+ const text = stdout.trim();
34420
+ return { text, tokens: estimateTokens(prompt, text) };
34421
+ }
34422
+ async function runAi(prompt) {
34423
+ const cli = aiCli();
34424
+ const { stdout } = await run(cli, aiArgs(cli, prompt), {
34425
+ timeout: 12e4,
34426
+ maxBuffer: 8 * 1024 * 1024
34427
+ });
34428
+ return parseAi(cli, stdout, prompt);
34429
+ }
34430
+ async function aiGenerate(prompt) {
34431
+ const { text } = await runAi(prompt);
34432
+ return text;
34433
+ }
34434
+ async function aiOutboundReview(input, allowedRepos, projectPath) {
34435
+ const prompt = `You are a security reviewer for an autonomous coding agent. Decide if the following INPUT is SAFE to run. It is UNSAFE if it: (1) asks the AI using secret VALUES (api keys, env values, passwords); (2) would affect other environments (only the sandbox project is allowed, no ssh/deploy/prod); (3) edits outside the project folder "${projectPath}"; (4) commits/pushes to a repo not in this allowlist: ${allowedRepos.length ? allowedRepos.join(", ") : "(none declared)"}. Reply with ONLY JSON: {"ok": <true|false>, "reasons": ["secret"|"environment"|"path"|"repo", ...]}. INPUT:
34436
+ ${input}`;
34437
+ const { text, tokens } = await runAi(prompt);
34438
+ const start = text.indexOf("{");
34439
+ const end = text.lastIndexOf("}");
34440
+ if (start < 0 || end <= start) return { ok: false, reasons: ["ai_parse"], tokens };
34441
+ try {
34442
+ const j = JSON.parse(text.slice(start, end + 1));
34443
+ const reasons = Array.isArray(j.reasons) ? j.reasons.filter((r) => typeof r === "string") : [];
34444
+ return { ok: j.ok === true && reasons.length === 0, reasons, tokens };
34445
+ } catch {
34446
+ return { ok: false, reasons: ["ai_parse"], tokens };
34447
+ }
34448
+ }
34449
+
34450
+ // src/core/scaffold.ts
34451
+ var run2 = promisify2(execFile2);
34452
+ function reposOf(spec) {
34453
+ const repos = spec?.repos;
34454
+ return Array.isArray(repos) ? repos : [];
34455
+ }
34456
+ function subDirName(repo) {
34457
+ const raw = (repo.subdir || repo.role || repoName(repo.url ?? "") || "repo").trim();
34458
+ return raw.replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
34459
+ }
34460
+ async function provisionRepos(target, repos, emit, opts = {}) {
34461
+ for (const repo of repos) {
34462
+ if (repo.primary) {
34463
+ if (opts.clonePrimary && repo.url) {
34464
+ if (existsSync4(join6(target, ".git"))) {
34465
+ emit("git", `Primary repo already present \u2014 skipping clone.`);
34466
+ continue;
34467
+ }
34468
+ emit("git", `Cloning ${repo.url}\u2026`);
34469
+ await run2("git", ["clone", repo.url, target], { timeout: 12e4 });
34470
+ continue;
34471
+ }
34472
+ emit("git", "Initializing the primary repo\u2026");
34473
+ await run2("git", ["init"], { cwd: target, timeout: 2e4 });
34474
+ if (repo.url) {
34475
+ await run2("git", ["remote", "add", "origin", repo.url], { cwd: target, timeout: 2e4 }).catch(
34476
+ () => void 0
34477
+ );
34478
+ }
34479
+ continue;
34480
+ }
34481
+ const dir2 = join6(target, subDirName(repo));
34482
+ if (repo.url) {
34483
+ if (existsSync4(join6(dir2, ".git"))) {
34484
+ emit("git", `Sub-repo ${subDirName(repo)} already present \u2014 skipping clone.`);
34485
+ continue;
34486
+ }
34487
+ emit("git", `Cloning sub-repo ${repo.url}\u2026`);
34488
+ await run2("git", ["clone", repo.url, dir2], { timeout: 12e4 });
34489
+ } else {
34490
+ emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
34491
+ await mkdir(dir2, { recursive: true });
34492
+ await run2("git", ["init"], { cwd: dir2, timeout: 2e4 });
34493
+ }
34494
+ }
34495
+ }
34496
+ async function ensureReposCloned(physicRoot, repos, emit) {
34497
+ if (repos.length === 0) return;
34498
+ await provisionRepos(physicRoot, repos, emit ?? (() => void 0), { clonePrimary: true });
34499
+ }
34500
+ function sampleDir() {
34501
+ if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
34502
+ const here = dirname3(fileURLToPath3(import.meta.url));
34503
+ const bundled = resolve(here, "project-sample");
34504
+ for (const candidate of [bundled, resolve(here, "../project-sample"), resolve(here, "../../project-sample")]) {
34505
+ if (existsSync4(candidate)) return candidate;
34506
+ }
34507
+ return bundled;
34508
+ }
34509
+ async function scaffoldProject(payload, profileDir2, onProgress) {
34510
+ let lastStep = "start";
34511
+ const emit = (step, message) => {
34512
+ lastStep = step;
34513
+ onProgress?.({ projectId: payload.projectId, step, message });
34514
+ };
34515
+ try {
34516
+ const root = process.env.SCAFFOLD_ROOT ? resolve(process.env.SCAFFOLD_ROOT) : resolve(profileDir2);
34517
+ const target = join6(root, payload.projectName);
34518
+ await mkdir(target, { recursive: true });
34519
+ emit("copy", "Copying the sample template\u2026");
34520
+ await cp(sampleDir(), target, { recursive: true, force: false, errorOnExist: false });
34521
+ if (payload.spec) {
34522
+ emit("spec", "Writing project.spec.json\u2026");
34523
+ await writeFile(
34524
+ join6(target, "project.spec.json"),
34525
+ JSON.stringify(payload.spec, null, 2),
34526
+ "utf8"
34527
+ );
34528
+ }
34529
+ const repos = reposOf(payload.spec);
34530
+ if (repos.length > 0) await provisionRepos(target, repos, emit);
34531
+ if (payload.spec) await aiInit(target, payload.spec, emit);
34532
+ emit("version", "Writing .4pm/.4pm.json\u2026");
34533
+ await writeTemplateMarker(target);
34534
+ onProgress?.({ projectId: payload.projectId, step: "done", message: "Scaffold complete.", done: true });
34535
+ return { ok: true, path: target };
34536
+ } catch (err) {
34537
+ return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
34538
+ }
34539
+ }
34540
+ async function writeTemplateMarker(target) {
34541
+ const dir2 = join6(target, ".4pm");
34542
+ await mkdir(dir2, { recursive: true });
34543
+ const marker = { templateVersion: PROJECT_TEMPLATE.version, scaffoldedAt: (/* @__PURE__ */ new Date()).toISOString() };
34544
+ await writeFile(join6(dir2, ".4pm.json"), JSON.stringify(marker, null, 2) + "\n", "utf8");
34545
+ }
34546
+ async function aiInit(target, spec, emit) {
34547
+ emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
34548
+ const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
34549
+ if (subagents.length > 0) {
34550
+ await mkdir(join6(target, ".claude", "agents"), { recursive: true });
34551
+ for (const sa of subagents) {
34552
+ const name = (sa.name || "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
34553
+ if (!name) continue;
34554
+ const body = `---
34555
+ name: ${name}
34556
+ ---
34557
+
34558
+ ${(sa.description || "").trim()}
34559
+ `;
34560
+ await writeFile(join6(target, ".claude", "agents", `${name}.md`), body, "utf8");
34561
+ }
34562
+ }
34563
+ const specJson = JSON.stringify(spec);
34564
+ await generateFile(
34565
+ join6(target, "README.md"),
34566
+ `Write a concise README.md (Markdown only, no preamble) for this project from its spec JSON:
34567
+ ${specJson}`
34568
+ );
34569
+ await generateFile(
34570
+ join6(target, "CLAUDE.md"),
34571
+ `Write a CLAUDE.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
34572
+ ${specJson}`
34573
+ );
34574
+ }
34575
+ async function generateFile(path, prompt) {
34576
+ try {
34577
+ const text = await aiGenerate(prompt);
34578
+ if (text.trim()) await writeFile(path, text.trim() + "\n", "utf8");
34579
+ } catch {
34580
+ }
34581
+ }
34582
+ async function addProject(payload, profileDir2, onProgress) {
34583
+ let lastStep = "start";
34584
+ const emit = (step, message) => {
34585
+ lastStep = step;
34586
+ onProgress?.({ projectId: payload.projectId, step, message });
34587
+ };
34588
+ try {
34589
+ const root = process.env.SCAFFOLD_ROOT ? resolve(process.env.SCAFFOLD_ROOT) : resolve(profileDir2);
34590
+ const target = join6(root, payload.projectName);
34591
+ await mkdir(target, { recursive: true });
34592
+ const repos = payload.repos ?? [];
34593
+ if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true });
34594
+ onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
34595
+ return { ok: true, path: target };
34596
+ } catch (err) {
34597
+ return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
34598
+ }
34599
+ }
34600
+
34370
34601
  // src/core/worker-metrics.ts
34371
34602
  import { statfs, readFile } from "fs/promises";
34372
- import { existsSync as existsSync4 } from "fs";
34603
+ import { existsSync as existsSync5 } from "fs";
34373
34604
  import { totalmem, freemem, cpus, platform, arch } from "os";
34374
34605
  var CGROUP = "/sys/fs/cgroup";
34375
34606
  var MB = 1024 * 1024;
@@ -34419,7 +34650,7 @@ function parseMaybeMax(text) {
34419
34650
  return Number.isFinite(n) ? n : null;
34420
34651
  }
34421
34652
  function createWorkerMetricsSampler(diskPath) {
34422
- const cgroupV2 = existsSync4(`${CGROUP}/cpu.stat`) && existsSync4(`${CGROUP}/memory.current`);
34653
+ const cgroupV2 = existsSync5(`${CGROUP}/cpu.stat`) && existsSync5(`${CGROUP}/memory.current`);
34423
34654
  let cgPrev = null;
34424
34655
  let osPrev = null;
34425
34656
  async function cgroupCpuCount() {
@@ -34734,6 +34965,10 @@ var WsChannels = {
34734
34965
  // serving cli so a change (e.g. aiRunTimeoutSec) applies on the next run, not only after a
34735
34966
  // reconnect. `ws_token` seeds these on connect; this channel only carries updates.
34736
34967
  PROJECT_TOKENS: "project.tokens",
34968
+ // server → cli (ADR-0289): update the cli to the latest version now (idle-aware self-update +
34969
+ // re-exec). Sent by the manual operator "Update" action and the update-on-connect hook when the
34970
+ // connecting cli is outdated. No payload; honours the machine's local autoUpdate opt-out.
34971
+ CLI_UPDATE: "cli.update",
34737
34972
  // command lifecycle
34738
34973
  COMMAND_ANNOUNCE: "command.announce",
34739
34974
  // console transcript sync (ADR-0150): the cli streams its authoritative SessionBus
@@ -34847,9 +35082,9 @@ function decryptPayload(key, payloadBase64, nonceBase64) {
34847
35082
  import { spawn as spawn2 } from "child_process";
34848
35083
 
34849
35084
  // src/common/logger/logger.ts
34850
- import { appendFile, mkdir, readdir, unlink } from "fs/promises";
35085
+ import { appendFile, mkdir as mkdir2, readdir, unlink } from "fs/promises";
34851
35086
  import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync } from "fs";
34852
- import { join as join6 } from "path";
35087
+ import { join as join7 } from "path";
34853
35088
  var LEVEL_ORDER = { error: 0, warn: 1, info: 2, debug: 3 };
34854
35089
  var DAY_MS = 24 * 60 * 60 * 1e3;
34855
35090
  var DEFAULT_RETENTION_DAYS = 14;
@@ -34898,8 +35133,8 @@ var CliLogger = class {
34898
35133
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
34899
35134
  const entry = { timestamp, level, ...this.base, event, ...fields };
34900
35135
  const line = JSON.stringify(entry) + "\n";
34901
- const path = join6(dir2, `cli-${timestamp.slice(0, 10)}.jsonl`);
34902
- this.queue = this.queue.then(() => mkdir(dir2, { recursive: true })).then(() => appendFile(path, line, "utf8")).catch((err) => {
35136
+ const path = join7(dir2, `cli-${timestamp.slice(0, 10)}.jsonl`);
35137
+ this.queue = this.queue.then(() => mkdir2(dir2, { recursive: true })).then(() => appendFile(path, line, "utf8")).catch((err) => {
34903
35138
  console.warn(`Failed to write cli log: ${err}`);
34904
35139
  });
34905
35140
  }
@@ -34913,7 +35148,7 @@ var CliLogger = class {
34913
35148
  const match = /^cli-(\d{4}-\d{2}-\d{2})\.jsonl$/.exec(file);
34914
35149
  if (!match) continue;
34915
35150
  if ((/* @__PURE__ */ new Date(`${match[1]}T00:00:00Z`)).getTime() < cutoff) {
34916
- await unlink(join6(dir2, file)).catch(() => void 0);
35151
+ await unlink(join7(dir2, file)).catch(() => void 0);
34917
35152
  }
34918
35153
  }
34919
35154
  }
@@ -34924,7 +35159,7 @@ function readRecentLogLines(dir2, limit) {
34924
35159
  const files = readdirSync2(dir2).filter((f) => /^cli-\d{4}-\d{2}-\d{2}\.jsonl$/.test(f)).sort();
34925
35160
  const latest = files[files.length - 1];
34926
35161
  if (!latest) return [];
34927
- const lines = readFileSync6(join6(dir2, latest), "utf8").split("\n").filter(Boolean);
35162
+ const lines = readFileSync6(join7(dir2, latest), "utf8").split("\n").filter(Boolean);
34928
35163
  return lines.slice(-limit);
34929
35164
  } catch {
34930
35165
  return [];
@@ -34937,8 +35172,8 @@ function readLogUpload(dir2) {
34937
35172
  const latest = files[files.length - 1];
34938
35173
  if (!latest) return null;
34939
35174
  let totalBytes = 0;
34940
- for (const f of files) totalBytes += statSync(join6(dir2, f)).size;
34941
- let content = readFileSync6(join6(dir2, latest), "utf8");
35175
+ for (const f of files) totalBytes += statSync(join7(dir2, f)).size;
35176
+ let content = readFileSync6(join7(dir2, latest), "utf8");
34942
35177
  if (content.length > LOG_UPLOAD_CONTENT_MAX) {
34943
35178
  content = `\u2026[truncated]
34944
35179
  ${content.slice(-LOG_UPLOAD_CONTENT_MAX)}`;
@@ -34981,7 +35216,7 @@ function classifyToolFailure(code, out) {
34981
35216
  }
34982
35217
  return { reason: "other", retryable: false };
34983
35218
  }
34984
- function run(cmd, args, onLine, timeoutMs = 18e4) {
35219
+ function run3(cmd, args, onLine, timeoutMs = 18e4) {
34985
35220
  return new Promise((resolve10) => {
34986
35221
  let out = "";
34987
35222
  let buf = "";
@@ -35021,13 +35256,13 @@ function run(cmd, args, onLine, timeoutMs = 18e4) {
35021
35256
  });
35022
35257
  }
35023
35258
  async function detectOne(cmd, versionArg) {
35024
- const { code, out } = await run(cmd, [versionArg], void 0, 15e3);
35259
+ const { code, out } = await run3(cmd, [versionArg], void 0, 15e3);
35025
35260
  if (code !== 0) return null;
35026
35261
  const first = out.split("\n").map((l) => l.trim()).find(Boolean);
35027
35262
  return first ?? "";
35028
35263
  }
35029
35264
  async function listExtras(autoUpdateSet = /* @__PURE__ */ new Set()) {
35030
- const { out } = await run("npm", ["ls", "-g", "--depth=0", "--json"], void 0, 2e4);
35265
+ const { out } = await run3("npm", ["ls", "-g", "--depth=0", "--json"], void 0, 2e4);
35031
35266
  if (!out.trim()) return [];
35032
35267
  let deps = {};
35033
35268
  try {
@@ -35103,7 +35338,7 @@ async function runWorkerToolOp(op, name, manager, onLine, timeoutMs = TOOL_INSTA
35103
35338
  return { ok: false, exitCode: 1, error: resolved.error ?? "Invalid package." };
35104
35339
  }
35105
35340
  onLine(`$ ${manager} ${opArgs(op, manager, resolved.pkg).join(" ")}`);
35106
- const { code } = await run(manager, opArgs(op, manager, resolved.pkg), onLine, timeoutMs);
35341
+ const { code } = await run3(manager, opArgs(op, manager, resolved.pkg), onLine, timeoutMs);
35107
35342
  return { ok: code === 0, exitCode: code, error: code === 0 ? void 0 : `${manager} exited ${code}` };
35108
35343
  }
35109
35344
  function setToolAutoUpdate(profileDir2, name, enabled) {
@@ -35134,7 +35369,7 @@ async function installPinned(name, version, manager, onLine, timeoutMs = TOOL_IN
35134
35369
  for (let attempt = 1; attempt <= TOOL_INSTALL_MAX_ATTEMPTS; attempt++) {
35135
35370
  const suffix = attempt > 1 ? ` (attempt ${attempt}/${TOOL_INSTALL_MAX_ATTEMPTS})` : "";
35136
35371
  onLine(`$ ${manager} ${args.join(" ")}${suffix}`);
35137
- const { code, out } = await run(manager, args, onLine, timeoutMs);
35372
+ const { code, out } = await run3(manager, args, onLine, timeoutMs);
35138
35373
  if (code === 0) return { ok: true };
35139
35374
  const { reason, retryable } = classifyToolFailure(code, out);
35140
35375
  if (!retryable || attempt === TOOL_INSTALL_MAX_ATTEMPTS) {
@@ -35200,6 +35435,9 @@ var UpdateScheduler = class {
35200
35435
  lastDoneDateKey = null;
35201
35436
  /** Date key marked due but deferred because the cli was busy (stays set past the hour). */
35202
35437
  pendingDateKey = null;
35438
+ /** A push/connect-triggered update requested now (ADR-0289) — runs when idle, independent of the
35439
+ * daily policy; cleared once it runs. */
35440
+ forcedPending = false;
35203
35441
  /** Guards against a second update while one is in flight. */
35204
35442
  updating = false;
35205
35443
  /** Apply the latest policy (called on each ws_token) and start the tick timer once. */
@@ -35210,6 +35448,25 @@ var UpdateScheduler = class {
35210
35448
  this.timer.unref?.();
35211
35449
  }
35212
35450
  }
35451
+ /**
35452
+ * Request an update to latest NOW (ADR-0289) — the server's `cli.update` push or the
35453
+ * update-on-connect hook. Idle-aware like the daily tick (defers while a command runs) and
35454
+ * honours the local `autoUpdate:false` opt-out, but is independent of the daily org policy. Also
35455
+ * ensures the tick timer is running so the request is served even before the first ws_token.
35456
+ */
35457
+ updateNow() {
35458
+ if (this.optedOutLocally()) {
35459
+ logger.info("update.push.optedOut");
35460
+ return;
35461
+ }
35462
+ if (CLI_VERSION.startsWith("0.0.0")) return;
35463
+ this.forcedPending = true;
35464
+ if (!this.timer) {
35465
+ this.timer = setInterval(() => void this.tick(), TICK_MS);
35466
+ this.timer.unref?.();
35467
+ }
35468
+ void this.tick();
35469
+ }
35213
35470
  /** Stop the scheduler when the session ends (logout / replaced / stopped). */
35214
35471
  stop() {
35215
35472
  if (this.timer) {
@@ -35242,12 +35499,19 @@ var UpdateScheduler = class {
35242
35499
  return null;
35243
35500
  }
35244
35501
  }
35245
- /** One evaluation: mark the update due at the target hour, then update once idle. */
35502
+ /** One evaluation: run a forced (push/connect) update if pending, else the daily-scheduled one. */
35246
35503
  async tick() {
35247
- const p = this.policy;
35248
- if (!p || !p.autoUpdateDaily || this.updating) return;
35504
+ if (this.updating) return;
35249
35505
  if (CLI_VERSION.startsWith("0.0.0")) return;
35250
35506
  if (this.optedOutLocally()) return;
35507
+ if (this.forcedPending) {
35508
+ if (this.bus.busy !== null) return;
35509
+ this.forcedPending = false;
35510
+ await this.runUpdate();
35511
+ return;
35512
+ }
35513
+ const p = this.policy;
35514
+ if (!p || !p.autoUpdateDaily) return;
35251
35515
  const clock = this.orgClock();
35252
35516
  if (!clock) return;
35253
35517
  if (clock.dateKey === this.lastDoneDateKey) return;
@@ -35305,14 +35569,14 @@ var UpdateScheduler = class {
35305
35569
  };
35306
35570
 
35307
35571
  // src/core/ai-profile-state.ts
35308
- import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
35309
- import { join as join7 } from "path";
35572
+ import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
35573
+ import { join as join8 } from "path";
35310
35574
  function statePath(profileDir2) {
35311
- return join7(profileDir2, "ai-profiles.json");
35575
+ return join8(profileDir2, "ai-profiles.json");
35312
35576
  }
35313
35577
  function readState(profileDir2) {
35314
35578
  const path = statePath(profileDir2);
35315
- if (!existsSync5(path)) return {};
35579
+ if (!existsSync6(path)) return {};
35316
35580
  try {
35317
35581
  return JSON.parse(readFileSync7(path, "utf8"));
35318
35582
  } catch {
@@ -35370,7 +35634,7 @@ function clearPinnedCredential(profileDir2) {
35370
35634
  // src/utils/ai-cli.ts
35371
35635
  import { readFileSync as readFileSync8 } from "fs";
35372
35636
  import { homedir as homedir3 } from "os";
35373
- import { basename, isAbsolute, join as join8 } from "path";
35637
+ import { basename, isAbsolute, join as join9 } from "path";
35374
35638
  var REQUIRED_AI_ARGS = {
35375
35639
  claude: ["-p", "--output-format", "stream-json", "--verbose"],
35376
35640
  codex: ["exec", "--json"],
@@ -35507,9 +35771,9 @@ function claudeCredentialKeys(config) {
35507
35771
  return claudeProfilesOf(config).filter(isUsableProfile).map((p) => credentialKey("claude", resolveHomePath(p.profile)));
35508
35772
  }
35509
35773
  function resolveHomePath(value) {
35510
- if (value.startsWith("~")) return join8(homedir3(), value.slice(1).replace(/^[/\\]/, ""));
35774
+ if (value.startsWith("~")) return join9(homedir3(), value.slice(1).replace(/^[/\\]/, ""));
35511
35775
  if (isAbsolute(value)) return value;
35512
- return join8(homedir3(), value);
35776
+ return join9(homedir3(), value);
35513
35777
  }
35514
35778
  function profileDirs(profiles) {
35515
35779
  if (!profiles) return [];
@@ -35555,11 +35819,11 @@ function buildRunArgs(profile, cmd, resumeId, oneShot = false, override, readOnl
35555
35819
  }
35556
35820
  function claudeHomeDirs(config) {
35557
35821
  const dirs = profileDirs(claudeProfilesOf(config));
35558
- return dirs.length > 0 ? dirs : [join8(homedir3(), ".claude")];
35822
+ return dirs.length > 0 ? dirs : [join9(homedir3(), ".claude")];
35559
35823
  }
35560
35824
  function profileDisplayLabel(dir2) {
35561
35825
  try {
35562
- const raw = JSON.parse(readFileSync8(join8(dir2, ".claude.json"), "utf8"));
35826
+ const raw = JSON.parse(readFileSync8(join9(dir2, ".claude.json"), "utf8"));
35563
35827
  const email = raw.oauthAccount?.emailAddress;
35564
35828
  if (email && email.trim()) return email;
35565
35829
  } catch {
@@ -35661,11 +35925,11 @@ function planLegacyRun(prompt, config, workingDir, resume, oneShot, override, re
35661
35925
  // src/core/claude-usage.ts
35662
35926
  import { spawn as spawn3 } from "child_process";
35663
35927
  import { readFileSync as readFileSync9 } from "fs";
35664
- import { join as join9 } from "path";
35928
+ import { join as join10 } from "path";
35665
35929
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
35666
35930
  function readCreds(dir2) {
35667
35931
  try {
35668
- const raw = JSON.parse(readFileSync9(join9(dir2, ".credentials.json"), "utf8"));
35932
+ const raw = JSON.parse(readFileSync9(join10(dir2, ".credentials.json"), "utf8"));
35669
35933
  const oauth = raw.claudeAiOauth ?? raw;
35670
35934
  return oauth.accessToken ? oauth : null;
35671
35935
  } catch {
@@ -35774,8 +36038,8 @@ async function checkClaudeUsage(dirs, aiCli2 = "claude") {
35774
36038
  }
35775
36039
 
35776
36040
  // src/core/command-history.ts
35777
- import { existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
35778
- import { join as join10 } from "path";
36041
+ import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
36042
+ import { join as join11 } from "path";
35779
36043
  var MAX_ENTRIES = 1e3;
35780
36044
  var filePath = null;
35781
36045
  var entries = [];
@@ -35837,8 +36101,8 @@ async function uploadHistory(cfg) {
35837
36101
  }
35838
36102
  }
35839
36103
  function initCommandHistory(profileDir2, profileName, uploadMinutes = 10) {
35840
- filePath = join10(profileDir2, "command-history.json");
35841
- if (existsSync6(filePath)) {
36104
+ filePath = join11(profileDir2, "command-history.json");
36105
+ if (existsSync7(filePath)) {
35842
36106
  try {
35843
36107
  entries = JSON.parse(readFileSync10(filePath, "utf8"));
35844
36108
  } catch {
@@ -35894,16 +36158,16 @@ function finishCommand(commandId, exitCode, usage) {
35894
36158
  }
35895
36159
 
35896
36160
  // src/core/command-images.ts
35897
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
35898
- import { join as join11 } from "path";
36161
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
36162
+ import { join as join12 } from "path";
35899
36163
  function attachmentsBaseDir(physicRoot) {
35900
- return join11(physicRoot, ".4pm", "attachments");
36164
+ return join12(physicRoot, ".4pm", "attachments");
35901
36165
  }
35902
36166
  var ATTACHMENT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
35903
36167
  async function materializeImages(physicRoot, commandId, images, fetch2) {
35904
36168
  const map = /* @__PURE__ */ new Map();
35905
36169
  if (!images.length) return map;
35906
- const dir2 = join11(attachmentsBaseDir(physicRoot), commandId);
36170
+ const dir2 = join12(attachmentsBaseDir(physicRoot), commandId);
35907
36171
  mkdirSync3(dir2, { recursive: true });
35908
36172
  let n = 0;
35909
36173
  for (const img of images) {
@@ -35915,7 +36179,7 @@ async function materializeImages(physicRoot, commandId, images, fetch2) {
35915
36179
  continue;
35916
36180
  }
35917
36181
  const ext = commandImageExt(reply.mime ?? img.mime);
35918
- const file = join11(dir2, `img-${n}.${ext}`);
36182
+ const file = join12(dir2, `img-${n}.${ext}`);
35919
36183
  writeFileSync6(file, Buffer.from(reply.dataBase64, "base64"));
35920
36184
  map.set(img.placeholder, file);
35921
36185
  } catch (err) {
@@ -35937,12 +36201,12 @@ function rewriteImagePlaceholders(prompt, paths) {
35937
36201
  }
35938
36202
  function sweepOldAttachments(physicRoot, maxAgeMs = ATTACHMENT_MAX_AGE_MS) {
35939
36203
  const base = attachmentsBaseDir(physicRoot);
35940
- if (!existsSync7(base)) return 0;
36204
+ if (!existsSync8(base)) return 0;
35941
36205
  const cutoff = Date.now() - maxAgeMs;
35942
36206
  let removed = 0;
35943
36207
  try {
35944
36208
  for (const entry of readdirSync3(base)) {
35945
- const dir2 = join11(base, entry);
36209
+ const dir2 = join12(base, entry);
35946
36210
  try {
35947
36211
  if (statSync2(dir2).mtimeMs < cutoff) {
35948
36212
  rmSync3(dir2, { recursive: true, force: true });
@@ -35959,18 +36223,18 @@ function sweepOldAttachments(physicRoot, maxAgeMs = ATTACHMENT_MAX_AGE_MS) {
35959
36223
  // src/core/command-output-store.ts
35960
36224
  import {
35961
36225
  appendFileSync,
35962
- existsSync as existsSync8,
36226
+ existsSync as existsSync9,
35963
36227
  mkdirSync as mkdirSync4,
35964
36228
  readFileSync as readFileSync11,
35965
36229
  readdirSync as readdirSync4,
35966
36230
  rmSync as rmSync4,
35967
36231
  statSync as statSync3
35968
36232
  } from "fs";
35969
- import { join as join12 } from "path";
36233
+ import { join as join13 } from "path";
35970
36234
  var KEEP_FILES = 200;
35971
36235
  var dir = null;
35972
36236
  function initCommandOutput(profileDir2) {
35973
- dir = join12(profileDir2, "command-output");
36237
+ dir = join13(profileDir2, "command-output");
35974
36238
  try {
35975
36239
  mkdirSync4(dir, { recursive: true });
35976
36240
  prune();
@@ -35980,14 +36244,14 @@ function initCommandOutput(profileDir2) {
35980
36244
  function appendCommandOutput(commandId, text) {
35981
36245
  if (!dir || !text) return;
35982
36246
  try {
35983
- appendFileSync(join12(dir, `${commandId}.log`), text, "utf8");
36247
+ appendFileSync(join13(dir, `${commandId}.log`), text, "utf8");
35984
36248
  } catch {
35985
36249
  }
35986
36250
  }
35987
36251
  function readCommandOutput(commandId) {
35988
36252
  if (!dir) return null;
35989
- const path = join12(dir, `${commandId}.log`);
35990
- if (!existsSync8(path)) return null;
36253
+ const path = join13(dir, `${commandId}.log`);
36254
+ if (!existsSync9(path)) return null;
35991
36255
  try {
35992
36256
  return readFileSync11(path, "utf8");
35993
36257
  } catch {
@@ -35997,8 +36261,8 @@ function readCommandOutput(commandId) {
35997
36261
  function prune() {
35998
36262
  if (!dir) return;
35999
36263
  try {
36000
- const files = readdirSync4(dir).filter((f) => f.endsWith(".log")).map((f) => ({ f, t: statSync3(join12(dir, f)).mtimeMs })).sort((a, b) => b.t - a.t);
36001
- for (const { f } of files.slice(KEEP_FILES)) rmSync4(join12(dir, f), { force: true });
36264
+ const files = readdirSync4(dir).filter((f) => f.endsWith(".log")).map((f) => ({ f, t: statSync3(join13(dir, f)).mtimeMs })).sort((a, b) => b.t - a.t);
36265
+ for (const { f } of files.slice(KEEP_FILES)) rmSync4(join13(dir, f), { force: true });
36002
36266
  } catch {
36003
36267
  }
36004
36268
  }
@@ -36007,7 +36271,7 @@ function pruneCommandOutputByAge(days) {
36007
36271
  const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
36008
36272
  try {
36009
36273
  for (const f of readdirSync4(dir).filter((n) => n.endsWith(".log"))) {
36010
- const p = join12(dir, f);
36274
+ const p = join13(dir, f);
36011
36275
  if (statSync3(p).mtimeMs < cutoff) rmSync4(p, { force: true });
36012
36276
  }
36013
36277
  } catch {
@@ -36016,12 +36280,12 @@ function pruneCommandOutputByAge(days) {
36016
36280
 
36017
36281
  // src/core/support-answer.ts
36018
36282
  import { spawn as spawn4 } from "child_process";
36019
- import { execFile } from "child_process";
36283
+ import { execFile as execFile3 } from "child_process";
36020
36284
  import { createHash as createHash3 } from "crypto";
36021
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
36285
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
36022
36286
  import { randomUUID } from "crypto";
36023
- import { join as join13 } from "path";
36024
- import { promisify } from "util";
36287
+ import { join as join14 } from "path";
36288
+ import { promisify as promisify3 } from "util";
36025
36289
 
36026
36290
  // src/core/ai-stream.ts
36027
36291
  function assistantText(ev) {
@@ -36116,22 +36380,22 @@ function createAiStreamParser(cli) {
36116
36380
  apiError: () => ({ ...apiError })
36117
36381
  };
36118
36382
  }
36119
- function estimateTokens(text) {
36383
+ function estimateTokens2(text) {
36120
36384
  return Math.max(1, Math.ceil(text.length / 4));
36121
36385
  }
36122
36386
 
36123
36387
  // src/core/support-answer.ts
36124
36388
  var NO_USAGE = { tokens: 0, input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
36125
- var execFileAsync = promisify(execFile);
36389
+ var execFileAsync = promisify3(execFile3);
36126
36390
  var MAX_CONTEXT_CHARS = 12e4;
36127
36391
  var ANSWER_TIMEOUT_MS = 12e4;
36128
36392
  var DOC_EXTENSIONS = [".md", ".mdx", ".txt"];
36129
36393
  function kbRoot(profileDir2) {
36130
- return join13(profileDir2, "support-kb");
36394
+ return join14(profileDir2, "support-kb");
36131
36395
  }
36132
36396
  function repoDir(profileDir2, url, branch) {
36133
36397
  const key = createHash3("sha1").update(`${url}#${branch}`).digest("hex").slice(0, 16);
36134
- return join13(kbRoot(profileDir2), key);
36398
+ return join14(kbRoot(profileDir2), key);
36135
36399
  }
36136
36400
  function authedUrl(url, token) {
36137
36401
  if (!token) return url;
@@ -36140,7 +36404,7 @@ function authedUrl(url, token) {
36140
36404
  }
36141
36405
  async function ensureRepo(profileDir2, repo) {
36142
36406
  const dir2 = repoDir(profileDir2, repo.url, repo.branch);
36143
- if (existsSync9(join13(dir2, ".git"))) return dir2;
36407
+ if (existsSync10(join14(dir2, ".git"))) return dir2;
36144
36408
  const url = authedUrl(repo.url, repo.token);
36145
36409
  const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
36146
36410
  mkdirSync5(kbRoot(profileDir2), { recursive: true });
@@ -36151,11 +36415,11 @@ async function ensureRepo(profileDir2, repo) {
36151
36415
  }
36152
36416
  async function refreshSupportKb(profileDir2) {
36153
36417
  const root = kbRoot(profileDir2);
36154
- if (!existsSync9(root)) return;
36418
+ if (!existsSync10(root)) return;
36155
36419
  const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
36156
36420
  for (const entry of readdirSync5(root)) {
36157
- const dir2 = join13(root, entry);
36158
- if (!existsSync9(join13(dir2, ".git"))) continue;
36421
+ const dir2 = join14(root, entry);
36422
+ if (!existsSync10(join14(dir2, ".git"))) continue;
36159
36423
  try {
36160
36424
  await execFileAsync("git", ["-C", dir2, "pull", "--ff-only"], { env: gitEnv });
36161
36425
  } catch (err) {
@@ -36170,7 +36434,7 @@ function collectDocs(dir2) {
36170
36434
  if (total >= MAX_CONTEXT_CHARS) return;
36171
36435
  for (const entry of readdirSync5(current2)) {
36172
36436
  if (entry === ".git" || entry === "node_modules") continue;
36173
- const full = join13(current2, entry);
36437
+ const full = join14(current2, entry);
36174
36438
  const st = statSync4(full);
36175
36439
  if (st.isDirectory()) {
36176
36440
  walk2(full);
@@ -36235,11 +36499,11 @@ function imageExt(mime) {
36235
36499
  }
36236
36500
  }
36237
36501
  function materializeImages2(profileDir2, images, question) {
36238
- const dir2 = join13(profileDir2, "help-images", randomUUID());
36502
+ const dir2 = join14(profileDir2, "help-images", randomUUID());
36239
36503
  mkdirSync5(dir2, { recursive: true });
36240
36504
  let rewritten = question;
36241
36505
  images.forEach((img, i) => {
36242
- const file = join13(dir2, `image-${i + 1}.${imageExt(img.mime)}`);
36506
+ const file = join14(dir2, `image-${i + 1}.${imageExt(img.mime)}`);
36243
36507
  writeFileSync7(file, Buffer.from(img.dataBase64, "base64"));
36244
36508
  if (img.placeholder) rewritten = rewritten.split(img.placeholder).join(file);
36245
36509
  });
@@ -36334,7 +36598,7 @@ async function runSupportAnswer(req, ai, profileDir2) {
36334
36598
  if (!text) return { body: "", error: "empty answer" };
36335
36599
  const { body, moderation } = parseModeratedAnswer(text);
36336
36600
  if (!body) return { body: "", error: "empty answer" };
36337
- const u = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens: estimateTokens(body) };
36601
+ const u = usage.tokens > 0 ? usage : { ...NO_USAGE, tokens: estimateTokens2(body) };
36338
36602
  return {
36339
36603
  body,
36340
36604
  ...moderation ? { moderation } : {},
@@ -36381,12 +36645,12 @@ function reportToolResult(tool, ok, message) {
36381
36645
  }
36382
36646
 
36383
36647
  // src/core/autonomous.ts
36384
- import { readFile as readFile2, writeFile, chmod } from "fs/promises";
36648
+ import { readFile as readFile2, writeFile as writeFile2, chmod } from "fs/promises";
36385
36649
  import { readFileSync as readFileSync13 } from "fs";
36386
- import { execFile as execFile2, spawn as spawn5 } from "child_process";
36387
- import { join as join14 } from "path";
36388
- import { promisify as promisify2 } from "util";
36389
- var run2 = promisify2(execFile2);
36650
+ import { execFile as execFile4, spawn as spawn5 } from "child_process";
36651
+ import { join as join15 } from "path";
36652
+ import { promisify as promisify4 } from "util";
36653
+ var run4 = promisify4(execFile4);
36390
36654
  var SETTINGS_REL = ".claude/.autonomous.settings.json";
36391
36655
  var HISTORIES_REL = ".claude/.autonomous.histories.json";
36392
36656
  var APPROVALS_REL = ".claude/.autonomous.approvals.json";
@@ -36419,11 +36683,11 @@ function tableCell(value) {
36419
36683
  return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
36420
36684
  }
36421
36685
  function tickScript(root) {
36422
- return join14(root, TICK_REL);
36686
+ return join15(root, TICK_REL);
36423
36687
  }
36424
36688
  async function crontabList() {
36425
36689
  try {
36426
- const { stdout } = await run2("crontab", ["-l"], { timeout: 1e4 });
36690
+ const { stdout } = await run4("crontab", ["-l"], { timeout: 1e4 });
36427
36691
  return stdout;
36428
36692
  } catch {
36429
36693
  return "";
@@ -36442,8 +36706,8 @@ async function cronInstalled(root) {
36442
36706
  return (await crontabList()).split("\n").some((l) => l.includes(script) && !l.trim().startsWith("#"));
36443
36707
  }
36444
36708
  async function getAutonomousStatus(root) {
36445
- const settings = parseJson(await readText(join14(root, SETTINGS_REL))) ?? {};
36446
- const hist = parseJson(await readText(join14(root, HISTORIES_REL))) ?? {};
36709
+ const settings = parseJson(await readText(join15(root, SETTINGS_REL))) ?? {};
36710
+ const hist = parseJson(await readText(join15(root, HISTORIES_REL))) ?? {};
36447
36711
  const records = Array.isArray(hist.records) ? hist.records : [];
36448
36712
  const last = records[records.length - 1];
36449
36713
  const ticks = hist.ticks ?? {};
@@ -36460,9 +36724,9 @@ async function getAutonomousStatus(root) {
36460
36724
  }
36461
36725
  function isAutonomousRunning(root) {
36462
36726
  try {
36463
- const s = JSON.parse(readFileSync13(join14(root, SETTINGS_REL), "utf8"));
36727
+ const s = JSON.parse(readFileSync13(join15(root, SETTINGS_REL), "utf8"));
36464
36728
  if (s.paused === true) return false;
36465
- const h = JSON.parse(readFileSync13(join14(root, HISTORIES_REL), "utf8"));
36729
+ const h = JSON.parse(readFileSync13(join15(root, HISTORIES_REL), "utf8"));
36466
36730
  const ts = h.records?.[h.records.length - 1]?.ts;
36467
36731
  if (!ts) return false;
36468
36732
  const t2 = Date.parse(ts.replace(" ", "T"));
@@ -36473,10 +36737,10 @@ function isAutonomousRunning(root) {
36473
36737
  }
36474
36738
  async function readAutonomous(root) {
36475
36739
  const [settings, approvals, status, ...books] = await Promise.all([
36476
- readText(join14(root, SETTINGS_REL)),
36477
- readText(join14(root, APPROVALS_REL), "{}"),
36740
+ readText(join15(root, SETTINGS_REL)),
36741
+ readText(join15(root, APPROVALS_REL), "{}"),
36478
36742
  getAutonomousStatus(root),
36479
- ...Object.values(BOOK_FILES).map((f) => readText(join14(root, f)))
36743
+ ...Object.values(BOOK_FILES).map((f) => readText(join15(root, f)))
36480
36744
  ]);
36481
36745
  const keys = Object.keys(BOOK_FILES);
36482
36746
  const bookMap = {};
@@ -36485,7 +36749,7 @@ async function readAutonomous(root) {
36485
36749
  }
36486
36750
  async function readAutonomousLogs(root, date) {
36487
36751
  const day = date && /^\d{4}-\d{2}-\d{2}$/.test(date) ? date : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
36488
- const text = await readText(join14(root, LOG_DIR_REL, `autonomous-tick-${day}.log`));
36752
+ const text = await readText(join15(root, LOG_DIR_REL, `autonomous-tick-${day}.log`));
36489
36753
  const lines = text ? text.split("\n").filter((l) => l.length > 0).slice(-500) : [];
36490
36754
  return { date: day, lines };
36491
36755
  }
@@ -36509,22 +36773,22 @@ async function writeAutonomous(root, req, by) {
36509
36773
  switch (req.kind) {
36510
36774
  case "settings": {
36511
36775
  if (!parseJson(req.settings)) return { ok: false, error: "settings is not valid JSON" };
36512
- await writeFile(join14(root, SETTINGS_REL), req.settings, "utf8");
36776
+ await writeFile2(join15(root, SETTINGS_REL), req.settings, "utf8");
36513
36777
  break;
36514
36778
  }
36515
36779
  case "approvals": {
36516
- const map = parseJson(await readText(join14(root, APPROVALS_REL), "{}")) ?? {};
36780
+ const map = parseJson(await readText(join15(root, APPROVALS_REL), "{}")) ?? {};
36517
36781
  if (req.approved) map[req.taskId] = { approved: true, by, at: (/* @__PURE__ */ new Date()).toISOString() };
36518
36782
  else delete map[req.taskId];
36519
- await writeFile(join14(root, APPROVALS_REL), JSON.stringify(map, null, 2) + "\n", "utf8");
36783
+ await writeFile2(join15(root, APPROVALS_REL), JSON.stringify(map, null, 2) + "\n", "utf8");
36520
36784
  break;
36521
36785
  }
36522
36786
  case "userTodo": {
36523
- const cur = await readText(join14(root, BOOK_FILES.userTodo));
36787
+ const cur = await readText(join15(root, BOOK_FILES.userTodo));
36524
36788
  const trace = `posted by ${tableCell(by)} at ${(/* @__PURE__ */ new Date()).toISOString()}`;
36525
36789
  const row = `| | ${tableCell(req.content)} | ${trace} |
36526
36790
  `;
36527
- await writeFile(join14(root, BOOK_FILES.userTodo), cur.replace(/\s*$/, "\n") + row, "utf8");
36791
+ await writeFile2(join15(root, BOOK_FILES.userTodo), cur.replace(/\s*$/, "\n") + row, "utf8");
36528
36792
  break;
36529
36793
  }
36530
36794
  case "cron": {
@@ -36540,7 +36804,7 @@ async function writeAutonomous(root, req, by) {
36540
36804
  }
36541
36805
 
36542
36806
  // src/core/network-probe.ts
36543
- import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
36807
+ import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
36544
36808
  import { connect } from "net";
36545
36809
  var CANARY_TARGETS = [
36546
36810
  { host: "1.1.1.1", port: 443 },
@@ -36549,7 +36813,7 @@ var CANARY_TARGETS = [
36549
36813
  var PROBE_TIMEOUT_MS = 2500;
36550
36814
  function detectContainerized() {
36551
36815
  try {
36552
- if (existsSync10("/.dockerenv")) return true;
36816
+ if (existsSync11("/.dockerenv")) return true;
36553
36817
  const cgroup = readFileSync14("/proc/1/cgroup", "utf8");
36554
36818
  return /docker|containerd|kubepods|libpod/.test(cgroup);
36555
36819
  } catch {
@@ -36619,7 +36883,7 @@ async function probeNetwork() {
36619
36883
  // src/core/git-auth.ts
36620
36884
  import { writeFileSync as writeFileSync8 } from "fs";
36621
36885
  import { homedir as homedir4 } from "os";
36622
- import { join as join15 } from "path";
36886
+ import { join as join16 } from "path";
36623
36887
  function tokenTarget(method) {
36624
36888
  if (method === "gitlab-group-token") {
36625
36889
  return { host: process.env.FOURPM_GIT_HOST || "gitlab.com", user: "oauth2" };
@@ -36637,7 +36901,7 @@ function applyGitAuth(method) {
36637
36901
  try {
36638
36902
  const line = `https://${target.user}:${token}@${target.host}
36639
36903
  `;
36640
- writeFileSync8(join15(homedir4(), ".git-credentials"), line, { encoding: "utf8", mode: 384 });
36904
+ writeFileSync8(join16(homedir4(), ".git-credentials"), line, { encoding: "utf8", mode: 384 });
36641
36905
  return true;
36642
36906
  } catch {
36643
36907
  return false;
@@ -36822,7 +37086,7 @@ async function runAiFailover(plan, commandId, cwd2, handlers, timeoutMs = 0) {
36822
37086
  const apiErr = parser.apiError();
36823
37087
  if (finalExit === 0 && !apiErr.isError) {
36824
37088
  const usage = parser.usage();
36825
- if (usage.tokens === 0) usage.tokens = estimateTokens(captured);
37089
+ if (usage.tokens === 0) usage.tokens = estimateTokens2(captured);
36826
37090
  reportToolResult(attempt.cmd, true);
36827
37091
  return {
36828
37092
  exitCode: 0,
@@ -36874,8 +37138,8 @@ function formatTimestampInZone(date, timeZone) {
36874
37138
  }
36875
37139
 
36876
37140
  // src/ui/slash-commands.ts
36877
- import { existsSync as existsSync11 } from "fs";
36878
- import { join as join16 } from "path";
37141
+ import { existsSync as existsSync12 } from "fs";
37142
+ import { join as join17 } from "path";
36879
37143
  var CONFIG_KEYS = [
36880
37144
  "aiCli",
36881
37145
  "claudeHome",
@@ -36887,7 +37151,7 @@ var CONFIG_KEYS = [
36887
37151
  "aiEnv"
36888
37152
  ];
36889
37153
  function configPath(profileDir2) {
36890
- return join16(profileDir2, "config.json");
37154
+ return join17(profileDir2, "config.json");
36891
37155
  }
36892
37156
  function parseConfigAssignment(key, valueTokens) {
36893
37157
  const value = valueTokens.join(" ").trim();
@@ -36937,7 +37201,7 @@ function runConfig(ctx) {
36937
37201
  ctx.print(t("slash.config.wroteDefault", { verb, path }));
36938
37202
  ctx.print(t("slash.config.addClaudeHint"));
36939
37203
  };
36940
- if (existsSync11(path)) {
37204
+ if (existsSync12(path)) {
36941
37205
  ctx.confirm(
36942
37206
  t("slash.config.replaceConfirm", { path }),
36943
37207
  () => writeDefaults(t("slash.config.verbReplaced"))
@@ -37022,7 +37286,7 @@ function runOutput(ctx) {
37022
37286
  }
37023
37287
  function runLogs(ctx) {
37024
37288
  const n = Math.max(1, Math.min(200, Number(ctx.args[0]) || 20));
37025
- const dir2 = join16(ctx.info.profileDir, "logs");
37289
+ const dir2 = join17(ctx.info.profileDir, "logs");
37026
37290
  const lines = readRecentLogLines(dir2, n);
37027
37291
  if (lines.length === 0) {
37028
37292
  ctx.print(t("slash.logs.none"));
@@ -37337,13 +37601,13 @@ async function runMemoryCompaction(ai, cwd2, input) {
37337
37601
  }
37338
37602
 
37339
37603
  // src/core/git-commit-identity.ts
37340
- import { execFile as execFile3 } from "child_process";
37341
- import { promisify as promisify3 } from "util";
37342
- var run3 = promisify3(execFile3);
37604
+ import { execFile as execFile5 } from "child_process";
37605
+ import { promisify as promisify5 } from "util";
37606
+ var run5 = promisify5(execFile5);
37343
37607
  async function setCommitAuthor(cwd2, machineUsername) {
37344
37608
  if (!machineUsername) return;
37345
37609
  try {
37346
- await run3("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
37610
+ await run5("git", ["config", "--local", "user.name", machineUsername], { cwd: cwd2, timeout: 8e3 });
37347
37611
  } catch {
37348
37612
  }
37349
37613
  }
@@ -37442,7 +37706,7 @@ async function runAiPrompt(ctx, prompt, commandId, origin, oneShot = false, imag
37442
37706
  const aiRunTimeoutMs = timeoutSec > 0 ? timeoutSec * 1e3 : 0;
37443
37707
  const perPromptLimit = config.perPromptTokenLimit ?? 0;
37444
37708
  if (perPromptLimit > 0) {
37445
- const estimated = estimateTokens(prompt);
37709
+ const estimated = estimateTokens2(prompt);
37446
37710
  if (estimated > perPromptLimit) {
37447
37711
  rejectPromptOverLimit(ctx, commandId, origin, prompt, estimated, perPromptLimit);
37448
37712
  return;
@@ -37860,11 +38124,11 @@ function rejectByReview(ctx, commandId, origin, prompt, reasons) {
37860
38124
 
37861
38125
  // src/core/fs-browse.ts
37862
38126
  import { readdir as readdir2 } from "fs/promises";
37863
- import { resolve, sep as sep2 } from "path";
38127
+ import { resolve as resolve2, sep as sep2 } from "path";
37864
38128
  async function listDir(path, root) {
37865
38129
  if (!root) return { path: "", entries: [] };
37866
- const base = resolve(root);
37867
- const requested = resolve(base, path && path.trim() ? path : ".");
38130
+ const base = resolve2(root);
38131
+ const requested = resolve2(base, path && path.trim() ? path : ".");
37868
38132
  const target = requested === base || requested.startsWith(base + sep2) ? requested : base;
37869
38133
  try {
37870
38134
  const dirents = await readdir2(target, { withFileTypes: true });
@@ -37883,12 +38147,12 @@ async function listDir(path, root) {
37883
38147
 
37884
38148
  // src/core/fs-read.ts
37885
38149
  import { readFile as readFile3 } from "fs/promises";
37886
- import { resolve as resolve2, sep as sep3 } from "path";
38150
+ import { resolve as resolve3, sep as sep3 } from "path";
37887
38151
  var MAX_BYTES = 256 * 1024;
37888
38152
  function resolveInRoot(root, relPath) {
37889
38153
  if (!root) return null;
37890
- const base = resolve2(root);
37891
- const target = resolve2(base, relPath || ".");
38154
+ const base = resolve3(root);
38155
+ const target = resolve3(base, relPath || ".");
37892
38156
  return target === base || target.startsWith(base + sep3) ? target : null;
37893
38157
  }
37894
38158
  async function readWorkerFile(root, path) {
@@ -37907,13 +38171,13 @@ async function readWorkerFile(root, path) {
37907
38171
  }
37908
38172
 
37909
38173
  // src/core/fs-write.ts
37910
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
37911
- import { dirname as dirname3, resolve as resolve3, sep as sep4 } from "path";
38174
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
38175
+ import { dirname as dirname4, resolve as resolve4, sep as sep4 } from "path";
37912
38176
  var MAX_BYTES2 = 2 * 1024 * 1024;
37913
38177
  function resolveInRoot2(root, relPath) {
37914
38178
  if (!root) return null;
37915
- const base = resolve3(root);
37916
- const target = resolve3(base, relPath || ".");
38179
+ const base = resolve4(root);
38180
+ const target = resolve4(base, relPath || ".");
37917
38181
  return target === base || target.startsWith(base + sep4) ? target : null;
37918
38182
  }
37919
38183
  async function writeWorkerFile(root, relPath, content) {
@@ -37926,8 +38190,8 @@ async function writeWorkerFile(root, relPath, content) {
37926
38190
  return { ok: false, path: target, bytes: 0, error: `content too large (max ${MAX_BYTES2} bytes)` };
37927
38191
  }
37928
38192
  try {
37929
- await mkdir2(dirname3(target), { recursive: true });
37930
- await writeFile2(target, content, "utf8");
38193
+ await mkdir3(dirname4(target), { recursive: true });
38194
+ await writeFile3(target, content, "utf8");
37931
38195
  return { ok: true, path: target, bytes };
37932
38196
  } catch (err) {
37933
38197
  return { ok: false, path: target, bytes: 0, error: err instanceof Error ? err.message : String(err) };
@@ -37935,12 +38199,12 @@ async function writeWorkerFile(root, relPath, content) {
37935
38199
  }
37936
38200
 
37937
38201
  // src/core/fs-mutate.ts
37938
- import { mkdir as mkdir3, writeFile as writeFile3, rename, rm, stat } from "fs/promises";
37939
- import { basename as basename3, dirname as dirname4, resolve as resolve4, sep as sep5 } from "path";
38202
+ import { mkdir as mkdir4, writeFile as writeFile4, rename, rm, stat } from "fs/promises";
38203
+ import { basename as basename3, dirname as dirname5, resolve as resolve5, sep as sep5 } from "path";
37940
38204
  var MAX_BYTES3 = 2 * 1024 * 1024;
37941
38205
  function resolveInRoot3(root, rel) {
37942
- const base = resolve4(root);
37943
- const target = resolve4(base, rel || ".");
38206
+ const base = resolve5(root);
38207
+ const target = resolve5(base, rel || ".");
37944
38208
  return target === base || target.startsWith(base + sep5) ? target : null;
37945
38209
  }
37946
38210
  async function isDir(p) {
@@ -37965,38 +38229,38 @@ async function mutateFs(root, req) {
37965
38229
  switch (req.op) {
37966
38230
  case "mkdir": {
37967
38231
  const target = resolveInRoot3(root, req.path ?? "");
37968
- if (!target || target === resolve4(root)) return escaped(req.path ?? "");
37969
- await mkdir3(target, { recursive: true });
38232
+ if (!target || target === resolve5(root)) return escaped(req.path ?? "");
38233
+ await mkdir4(target, { recursive: true });
37970
38234
  return { ok: true, path: target };
37971
38235
  }
37972
38236
  case "create": {
37973
38237
  const target = resolveInRoot3(root, req.path ?? "");
37974
- if (!target || target === resolve4(root)) return escaped(req.path ?? "");
38238
+ if (!target || target === resolve5(root)) return escaped(req.path ?? "");
37975
38239
  if (await exists(target)) return { ok: false, path: target, error: "already exists" };
37976
38240
  const content = req.content ?? "";
37977
38241
  if (Buffer.byteLength(content, "utf8") > MAX_BYTES3) {
37978
38242
  return { ok: false, path: target, error: `content too large (max ${MAX_BYTES3} bytes)` };
37979
38243
  }
37980
- await mkdir3(dirname4(target), { recursive: true });
37981
- await writeFile3(target, content, "utf8");
38244
+ await mkdir4(dirname5(target), { recursive: true });
38245
+ await writeFile4(target, content, "utf8");
37982
38246
  return { ok: true, path: target };
37983
38247
  }
37984
38248
  case "move": {
37985
38249
  const from = resolveInRoot3(root, req.from ?? "");
37986
38250
  let to = resolveInRoot3(root, req.to ?? "");
37987
38251
  if (!from || !to) return escaped(req.from ?? req.to ?? "");
37988
- if (from === resolve4(root)) return { ok: false, path: from, error: "cannot move the project root" };
37989
- if (await isDir(to)) to = resolve4(to, basename3(from));
37990
- if (to !== resolve4(root) && !to.startsWith(resolve4(root) + sep5)) return escaped(req.to ?? "");
38252
+ if (from === resolve5(root)) return { ok: false, path: from, error: "cannot move the project root" };
38253
+ if (await isDir(to)) to = resolve5(to, basename3(from));
38254
+ if (to !== resolve5(root) && !to.startsWith(resolve5(root) + sep5)) return escaped(req.to ?? "");
37991
38255
  if (await exists(to)) return { ok: false, path: to, error: "destination already exists" };
37992
- await mkdir3(dirname4(to), { recursive: true });
38256
+ await mkdir4(dirname5(to), { recursive: true });
37993
38257
  await rename(from, to);
37994
38258
  return { ok: true, path: to };
37995
38259
  }
37996
38260
  case "delete": {
37997
38261
  const target = resolveInRoot3(root, req.path ?? "");
37998
38262
  if (!target) return escaped(req.path ?? "");
37999
- if (target === resolve4(root)) return { ok: false, path: target, error: "cannot delete the project root" };
38263
+ if (target === resolve5(root)) return { ok: false, path: target, error: "cannot delete the project root" };
38000
38264
  await rm(target, { recursive: true, force: true });
38001
38265
  return { ok: true, path: target };
38002
38266
  }
@@ -38009,12 +38273,12 @@ async function mutateFs(root, req) {
38009
38273
  }
38010
38274
 
38011
38275
  // src/core/fs-transfer.ts
38012
- import { mkdir as mkdir4, readFile as readFile4, stat as stat2, writeFile as writeFile4 } from "fs/promises";
38013
- import { basename as basename4, dirname as dirname5, extname, resolve as resolve5, sep as sep6 } from "path";
38276
+ import { mkdir as mkdir5, readFile as readFile4, stat as stat2, writeFile as writeFile5 } from "fs/promises";
38277
+ import { basename as basename4, dirname as dirname6, extname, resolve as resolve6, sep as sep6 } from "path";
38014
38278
  function resolveInRoot4(root, relPath) {
38015
38279
  if (!root) return null;
38016
- const base = resolve5(root);
38017
- const target = resolve5(base, relPath || ".");
38280
+ const base = resolve6(root);
38281
+ const target = resolve6(base, relPath || ".");
38018
38282
  return target === base || target.startsWith(base + sep6) ? target : null;
38019
38283
  }
38020
38284
  var MIME_BY_EXT = {
@@ -38053,8 +38317,8 @@ async function uploadBinaryFile(root, relPath, contentBase64) {
38053
38317
  return { ok: false, path: target, bytes: 0, error: `file too large (max ${FS_TRANSFER_MAX_BYTES} bytes)` };
38054
38318
  }
38055
38319
  try {
38056
- await mkdir4(dirname5(target), { recursive: true });
38057
- await writeFile4(target, buf);
38320
+ await mkdir5(dirname6(target), { recursive: true });
38321
+ await writeFile5(target, buf);
38058
38322
  return { ok: true, path: target, bytes: buf.byteLength };
38059
38323
  } catch (err) {
38060
38324
  return { ok: false, path: target, bytes: 0, error: err instanceof Error ? err.message : String(err) };
@@ -38129,14 +38393,14 @@ function handleFsChannels(ctx, message, payload) {
38129
38393
 
38130
38394
  // src/core/git-diff.ts
38131
38395
  import { readFile as readFile5 } from "fs/promises";
38132
- import { execFile as execFile4 } from "child_process";
38133
- import { basename as basename5, dirname as dirname6, resolve as resolve6, sep as sep7 } from "path";
38134
- import { promisify as promisify4 } from "util";
38135
- var run4 = promisify4(execFile4);
38396
+ import { execFile as execFile6 } from "child_process";
38397
+ import { basename as basename5, dirname as dirname7, resolve as resolve7, sep as sep7 } from "path";
38398
+ import { promisify as promisify6 } from "util";
38399
+ var run6 = promisify6(execFile6);
38136
38400
  function resolveInRoot5(root, relPath) {
38137
38401
  if (!root) return null;
38138
- const base = resolve6(root);
38139
- const target = resolve6(base, relPath || ".");
38402
+ const base = resolve7(root);
38403
+ const target = resolve7(base, relPath || ".");
38140
38404
  return target === base || target.startsWith(base + sep7) ? target : null;
38141
38405
  }
38142
38406
  function looksBinary(buf) {
@@ -38151,7 +38415,7 @@ async function gitDiff(root, path) {
38151
38415
  if (!target) {
38152
38416
  return { path, oldContent: "", newContent: "", isBinary: false };
38153
38417
  }
38154
- const dir2 = dirname6(target);
38418
+ const dir2 = dirname7(target);
38155
38419
  const base = basename5(target);
38156
38420
  let newBuf = null;
38157
38421
  try {
@@ -38160,7 +38424,7 @@ async function gitDiff(root, path) {
38160
38424
  }
38161
38425
  let oldBuf = null;
38162
38426
  try {
38163
- const { stdout } = await run4("git", ["show", `HEAD:./${base}`], {
38427
+ const { stdout } = await run6("git", ["show", `HEAD:./${base}`], {
38164
38428
  cwd: dir2,
38165
38429
  timeout: 15e3,
38166
38430
  maxBuffer: FS_TRANSFER_MAX_BYTES + 1024,
@@ -38190,12 +38454,12 @@ async function gitDiff(root, path) {
38190
38454
  }
38191
38455
 
38192
38456
  // src/core/git-env.ts
38193
- import { execFile as execFile5 } from "child_process";
38194
- import { promisify as promisify5 } from "util";
38195
- var run5 = promisify5(execFile5);
38457
+ import { execFile as execFile7 } from "child_process";
38458
+ import { promisify as promisify7 } from "util";
38459
+ var run7 = promisify7(execFile7);
38196
38460
  async function hasBinary(bin, args = ["--version"]) {
38197
38461
  try {
38198
- await run5(bin, args, { timeout: 5e3 });
38462
+ await run7(bin, args, { timeout: 5e3 });
38199
38463
  return true;
38200
38464
  } catch {
38201
38465
  return false;
@@ -38208,7 +38472,7 @@ async function checkGitEnv(provider) {
38208
38472
  let account = null;
38209
38473
  if (installed) {
38210
38474
  try {
38211
- const { stdout, stderr } = await run5(bin, ["auth", "status"], { timeout: 8e3 });
38475
+ const { stdout, stderr } = await run7(bin, ["auth", "status"], { timeout: 8e3 });
38212
38476
  const out = `${stdout}
38213
38477
  ${stderr}`;
38214
38478
  authenticated = /logged in|logged into|✓/i.test(out);
@@ -38221,14 +38485,14 @@ ${stderr}`;
38221
38485
  }
38222
38486
 
38223
38487
  // src/core/git-ssh-key.ts
38224
- import { execFile as execFile6 } from "child_process";
38225
- import { readFile as readFile6, rm as rm2, mkdir as mkdir5 } from "fs/promises";
38226
- import { join as join17 } from "path";
38227
- import { promisify as promisify6 } from "util";
38228
- var run6 = promisify6(execFile6);
38488
+ import { execFile as execFile8 } from "child_process";
38489
+ import { readFile as readFile6, rm as rm2, mkdir as mkdir6 } from "fs/promises";
38490
+ import { join as join18 } from "path";
38491
+ import { promisify as promisify8 } from "util";
38492
+ var run8 = promisify8(execFile8);
38229
38493
  function keyPaths(profileDir2) {
38230
- const dir2 = join17(profileDir2, ".ssh");
38231
- const key = join17(dir2, "id_4pm");
38494
+ const dir2 = join18(profileDir2, ".ssh");
38495
+ const key = join18(dir2, "id_4pm");
38232
38496
  return { dir: dir2, key, pub: `${key}.pub` };
38233
38497
  }
38234
38498
  async function readPublicKey(pub) {
@@ -38240,14 +38504,14 @@ async function readPublicKey(pub) {
38240
38504
  }
38241
38505
  async function fingerprintOf(pub) {
38242
38506
  try {
38243
- const { stdout } = await run6("ssh-keygen", ["-lf", pub], { timeout: 5e3 });
38507
+ const { stdout } = await run8("ssh-keygen", ["-lf", pub], { timeout: 5e3 });
38244
38508
  return stdout.match(/SHA256:[A-Za-z0-9+/=]+/)?.[0] ?? null;
38245
38509
  } catch {
38246
38510
  return null;
38247
38511
  }
38248
38512
  }
38249
38513
  async function configureGit(key) {
38250
- await run6(
38514
+ await run8(
38251
38515
  "git",
38252
38516
  ["config", "--global", "core.sshCommand", `ssh -i ${key} -o IdentitiesOnly=yes`],
38253
38517
  { timeout: 5e3 }
@@ -38255,10 +38519,10 @@ async function configureGit(key) {
38255
38519
  }
38256
38520
  async function generate(profileDir2) {
38257
38521
  const { dir: dir2, key, pub } = keyPaths(profileDir2);
38258
- await mkdir5(dir2, { recursive: true });
38522
+ await mkdir6(dir2, { recursive: true });
38259
38523
  await rm2(key, { force: true });
38260
38524
  await rm2(pub, { force: true });
38261
- await run6("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
38525
+ await run8("ssh-keygen", ["-t", "ed25519", "-f", key, "-N", "", "-C", "4pm-deploy-key"], {
38262
38526
  timeout: 15e3
38263
38527
  });
38264
38528
  await configureGit(key);
@@ -38268,7 +38532,7 @@ async function remove(profileDir2) {
38268
38532
  const { key, pub } = keyPaths(profileDir2);
38269
38533
  await rm2(key, { force: true });
38270
38534
  await rm2(pub, { force: true });
38271
- await run6("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
38535
+ await run8("git", ["config", "--global", "--unset", "core.sshCommand"], { timeout: 5e3 }).catch(
38272
38536
  () => void 0
38273
38537
  );
38274
38538
  return { publicKey: null, fingerprint: null };
@@ -38290,21 +38554,21 @@ async function manageSshKey(op, profileDir2) {
38290
38554
 
38291
38555
  // src/core/git-history.ts
38292
38556
  import { readdir as readdir3 } from "fs/promises";
38293
- import { execFile as execFile7 } from "child_process";
38294
- import { resolve as resolve7, sep as sep8 } from "path";
38295
- import { promisify as promisify7 } from "util";
38296
- var run7 = promisify7(execFile7);
38557
+ import { execFile as execFile9 } from "child_process";
38558
+ import { resolve as resolve8, sep as sep8 } from "path";
38559
+ import { promisify as promisify9 } from "util";
38560
+ var run9 = promisify9(execFile9);
38297
38561
  var FS = "";
38298
38562
  var RS = "";
38299
38563
  function resolveRepo(root, repo) {
38300
38564
  if (!root) return null;
38301
- const base = resolve7(root);
38302
- const requested = resolve7(base, repo && repo.trim() ? repo : ".");
38565
+ const base = resolve8(root);
38566
+ const requested = resolve8(base, repo && repo.trim() ? repo : ".");
38303
38567
  return requested === base || requested.startsWith(base + sep8) ? requested : base;
38304
38568
  }
38305
38569
  async function git(cwd2, args) {
38306
38570
  try {
38307
- const { stdout } = await run7("git", args, {
38571
+ const { stdout } = await run9("git", args, {
38308
38572
  cwd: cwd2,
38309
38573
  timeout: 15e3,
38310
38574
  maxBuffer: 8 * 1024 * 1024
@@ -38316,7 +38580,7 @@ async function git(cwd2, args) {
38316
38580
  }
38317
38581
  async function gitRepos(root) {
38318
38582
  if (!root) return { repos: [] };
38319
- const base = resolve7(root);
38583
+ const base = resolve8(root);
38320
38584
  const found = /* @__PURE__ */ new Set();
38321
38585
  const repos = [];
38322
38586
  const add = async (dir2) => {
@@ -38333,13 +38597,13 @@ async function gitRepos(root) {
38333
38597
  const lvl1 = await readdir3(base, { withFileTypes: true });
38334
38598
  for (const d of lvl1) {
38335
38599
  if (!d.isDirectory() || d.name === ".git" || d.name === "node_modules") continue;
38336
- const p1 = resolve7(base, d.name);
38600
+ const p1 = resolve8(base, d.name);
38337
38601
  await add(p1);
38338
38602
  try {
38339
38603
  const lvl2 = await readdir3(p1, { withFileTypes: true });
38340
38604
  for (const d2 of lvl2) {
38341
38605
  if (!d2.isDirectory() || d2.name === ".git" || d2.name === "node_modules") continue;
38342
- await add(resolve7(p1, d2.name));
38606
+ await add(resolve8(p1, d2.name));
38343
38607
  }
38344
38608
  } catch {
38345
38609
  }
@@ -38457,79 +38721,7 @@ function handleGitChannels(ctx, message, payload) {
38457
38721
  }
38458
38722
 
38459
38723
  // src/core/ws-client/handlers/misc.ts
38460
- import { join as join18 } from "path";
38461
-
38462
- // src/core/ai-assist.ts
38463
- import { execFile as execFile8 } from "child_process";
38464
- import { promisify as promisify8 } from "util";
38465
- var run8 = promisify8(execFile8);
38466
- function aiCli() {
38467
- return process.env.AI_CLI || "claude";
38468
- }
38469
- function aiArgs(cli, prompt) {
38470
- if (cli === "claude") return ["-p", prompt, "--output-format", "json"];
38471
- if (cli === "codex") return ["exec", "--json", prompt];
38472
- return ["-p", prompt];
38473
- }
38474
- function estimateTokens2(prompt, output) {
38475
- return Math.max(1, Math.ceil((prompt.length + output.length) / 4));
38476
- }
38477
- function parseAi(cli, stdout, prompt) {
38478
- if (cli === "claude") {
38479
- try {
38480
- const j = JSON.parse(stdout);
38481
- const text2 = (j.result ?? "").trim();
38482
- const tokens = (j.usage?.input_tokens ?? 0) + (j.usage?.output_tokens ?? 0);
38483
- if (text2 || tokens) return { text: text2, tokens: tokens || estimateTokens2(prompt, text2) };
38484
- } catch {
38485
- }
38486
- }
38487
- if (cli === "codex") {
38488
- try {
38489
- let text2 = "";
38490
- let tokens = 0;
38491
- for (const line of stdout.split("\n").filter(Boolean)) {
38492
- const obj = JSON.parse(line);
38493
- if (obj.text) text2 = obj.text;
38494
- else if (obj.message) text2 = obj.message;
38495
- if (obj.usage) {
38496
- tokens = obj.usage.total_tokens ?? (obj.usage.input_tokens ?? 0) + (obj.usage.output_tokens ?? 0);
38497
- }
38498
- }
38499
- if (text2 || tokens) return { text: text2.trim(), tokens: tokens || estimateTokens2(prompt, text2) };
38500
- } catch {
38501
- }
38502
- }
38503
- const text = stdout.trim();
38504
- return { text, tokens: estimateTokens2(prompt, text) };
38505
- }
38506
- async function runAi(prompt) {
38507
- const cli = aiCli();
38508
- const { stdout } = await run8(cli, aiArgs(cli, prompt), {
38509
- timeout: 12e4,
38510
- maxBuffer: 8 * 1024 * 1024
38511
- });
38512
- return parseAi(cli, stdout, prompt);
38513
- }
38514
- async function aiGenerate(prompt) {
38515
- const { text } = await runAi(prompt);
38516
- return text;
38517
- }
38518
- async function aiOutboundReview(input, allowedRepos, projectPath) {
38519
- const prompt = `You are a security reviewer for an autonomous coding agent. Decide if the following INPUT is SAFE to run. It is UNSAFE if it: (1) asks the AI using secret VALUES (api keys, env values, passwords); (2) would affect other environments (only the sandbox project is allowed, no ssh/deploy/prod); (3) edits outside the project folder "${projectPath}"; (4) commits/pushes to a repo not in this allowlist: ${allowedRepos.length ? allowedRepos.join(", ") : "(none declared)"}. Reply with ONLY JSON: {"ok": <true|false>, "reasons": ["secret"|"environment"|"path"|"repo", ...]}. INPUT:
38520
- ${input}`;
38521
- const { text, tokens } = await runAi(prompt);
38522
- const start = text.indexOf("{");
38523
- const end = text.lastIndexOf("}");
38524
- if (start < 0 || end <= start) return { ok: false, reasons: ["ai_parse"], tokens };
38525
- try {
38526
- const j = JSON.parse(text.slice(start, end + 1));
38527
- const reasons = Array.isArray(j.reasons) ? j.reasons.filter((r) => typeof r === "string") : [];
38528
- return { ok: j.ok === true && reasons.length === 0, reasons, tokens };
38529
- } catch {
38530
- return { ok: false, reasons: ["ai_parse"], tokens };
38531
- }
38532
- }
38724
+ import { join as join19 } from "path";
38533
38725
 
38534
38726
  // src/core/outbound-review.ts
38535
38727
  function compilePatterns(sources, fallback) {
@@ -38605,9 +38797,13 @@ async function evaluateReview(payload) {
38605
38797
  // src/core/ws-client/handlers/misc.ts
38606
38798
  function handleMiscChannels(ctx, message, payload) {
38607
38799
  switch (message.channel) {
38800
+ case WsChannels.CLI_UPDATE: {
38801
+ ctx.updateCliNow();
38802
+ return true;
38803
+ }
38608
38804
  case WsChannels.LOG_READ: {
38609
38805
  const limit = Math.min(Math.max(payload.limit ?? 200, 1), 2e3);
38610
- const lines = readRecentLogLines(join18(ctx.profileDir, "logs"), limit);
38806
+ const lines = readRecentLogLines(join19(ctx.profileDir, "logs"), limit);
38611
38807
  ctx.send(WsChannels.LOG_READ, { lines }, message.id);
38612
38808
  return true;
38613
38809
  }
@@ -38638,168 +38834,29 @@ function handleMiscChannels(ctx, message, payload) {
38638
38834
  }
38639
38835
 
38640
38836
  // src/core/ws-client/handlers/project.ts
38641
- import { mkdirSync as mkdirSync6, rmSync as rmSync6 } from "fs";
38837
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, renameSync, rmSync as rmSync6 } from "fs";
38642
38838
  import { join as join20 } from "path";
38643
-
38644
- // src/core/scaffold.ts
38645
- import { cp, mkdir as mkdir6, writeFile as writeFile5 } from "fs/promises";
38646
- import { existsSync as existsSync12 } from "fs";
38647
- import { execFile as execFile9 } from "child_process";
38648
- import { dirname as dirname7, join as join19, resolve as resolve8 } from "path";
38649
- import { fileURLToPath as fileURLToPath3 } from "url";
38650
- import { promisify as promisify9 } from "util";
38651
- var run9 = promisify9(execFile9);
38652
- function reposOf(spec) {
38653
- const repos = spec?.repos;
38654
- return Array.isArray(repos) ? repos : [];
38655
- }
38656
- function subDirName(repo) {
38657
- const raw = (repo.subdir || repo.role || repoName(repo.url ?? "") || "repo").trim();
38658
- return raw.replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
38659
- }
38660
- async function provisionRepos(target, repos, emit, opts = {}) {
38661
- for (const repo of repos) {
38662
- if (repo.primary) {
38663
- if (opts.clonePrimary && repo.url) {
38664
- emit("git", `Cloning ${repo.url}\u2026`);
38665
- await run9("git", ["clone", repo.url, target], { timeout: 12e4 });
38666
- continue;
38667
- }
38668
- emit("git", "Initializing the primary repo\u2026");
38669
- await run9("git", ["init"], { cwd: target, timeout: 2e4 });
38670
- if (repo.url) {
38671
- await run9("git", ["remote", "add", "origin", repo.url], { cwd: target, timeout: 2e4 }).catch(
38672
- () => void 0
38673
- );
38674
- }
38675
- continue;
38676
- }
38677
- const dir2 = join19(target, subDirName(repo));
38678
- if (repo.url) {
38679
- emit("git", `Cloning sub-repo ${repo.url}\u2026`);
38680
- await run9("git", ["clone", repo.url, dir2], { timeout: 12e4 });
38681
- } else {
38682
- emit("git", `Initializing sub-repo ${subDirName(repo)}\u2026`);
38683
- await mkdir6(dir2, { recursive: true });
38684
- await run9("git", ["init"], { cwd: dir2, timeout: 2e4 });
38685
- }
38686
- }
38687
- }
38688
- function sampleDir() {
38689
- if (process.env.SCAFFOLD_SAMPLE_DIR) return process.env.SCAFFOLD_SAMPLE_DIR;
38690
- const here = dirname7(fileURLToPath3(import.meta.url));
38691
- const bundled = resolve8(here, "project-sample");
38692
- for (const candidate of [bundled, resolve8(here, "../project-sample"), resolve8(here, "../../project-sample")]) {
38693
- if (existsSync12(candidate)) return candidate;
38694
- }
38695
- return bundled;
38696
- }
38697
- async function scaffoldProject(payload, profileDir2, onProgress) {
38698
- let lastStep = "start";
38699
- const emit = (step, message) => {
38700
- lastStep = step;
38701
- onProgress?.({ projectId: payload.projectId, step, message });
38702
- };
38703
- try {
38704
- const root = process.env.SCAFFOLD_ROOT ? resolve8(process.env.SCAFFOLD_ROOT) : resolve8(profileDir2);
38705
- const target = join19(root, payload.projectName);
38706
- await mkdir6(target, { recursive: true });
38707
- emit("copy", "Copying the sample template\u2026");
38708
- await cp(sampleDir(), target, { recursive: true, force: false, errorOnExist: false });
38709
- if (payload.spec) {
38710
- emit("spec", "Writing project.spec.json\u2026");
38711
- await writeFile5(
38712
- join19(target, "project.spec.json"),
38713
- JSON.stringify(payload.spec, null, 2),
38714
- "utf8"
38715
- );
38716
- }
38717
- const repos = reposOf(payload.spec);
38718
- if (repos.length > 0) await provisionRepos(target, repos, emit);
38719
- if (payload.spec) await aiInit(target, payload.spec, emit);
38720
- emit("version", "Writing .4pm/.4pm.json\u2026");
38721
- await writeTemplateMarker(target);
38722
- onProgress?.({ projectId: payload.projectId, step: "done", message: "Scaffold complete.", done: true });
38723
- return { ok: true, path: target };
38724
- } catch (err) {
38725
- return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
38726
- }
38727
- }
38728
- async function writeTemplateMarker(target) {
38729
- const dir2 = join19(target, ".4pm");
38730
- await mkdir6(dir2, { recursive: true });
38731
- const marker = { templateVersion: PROJECT_TEMPLATE.version, scaffoldedAt: (/* @__PURE__ */ new Date()).toISOString() };
38732
- await writeFile5(join19(dir2, ".4pm.json"), JSON.stringify(marker, null, 2) + "\n", "utf8");
38733
- }
38734
- async function aiInit(target, spec, emit) {
38735
- emit("ai-init", "AI init: subagents, README, CLAUDE.md\u2026");
38736
- const subagents = Array.isArray(spec.subagents) ? spec.subagents : [];
38737
- if (subagents.length > 0) {
38738
- await mkdir6(join19(target, ".claude", "agents"), { recursive: true });
38739
- for (const sa of subagents) {
38740
- const name = (sa.name || "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
38741
- if (!name) continue;
38742
- const body = `---
38743
- name: ${name}
38744
- ---
38745
-
38746
- ${(sa.description || "").trim()}
38747
- `;
38748
- await writeFile5(join19(target, ".claude", "agents", `${name}.md`), body, "utf8");
38749
- }
38750
- }
38751
- const specJson = JSON.stringify(spec);
38752
- await generateFile(
38753
- join19(target, "README.md"),
38754
- `Write a concise README.md (Markdown only, no preamble) for this project from its spec JSON:
38755
- ${specJson}`
38756
- );
38757
- await generateFile(
38758
- join19(target, "CLAUDE.md"),
38759
- `Write a CLAUDE.md (Markdown only, no preamble) with guidance/conventions for AI agents working in this project, derived from its spec JSON:
38760
- ${specJson}`
38761
- );
38762
- }
38763
- async function generateFile(path, prompt) {
38764
- try {
38765
- const text = await aiGenerate(prompt);
38766
- if (text.trim()) await writeFile5(path, text.trim() + "\n", "utf8");
38767
- } catch {
38768
- }
38769
- }
38770
- async function addProject(payload, profileDir2, onProgress) {
38771
- let lastStep = "start";
38772
- const emit = (step, message) => {
38773
- lastStep = step;
38774
- onProgress?.({ projectId: payload.projectId, step, message });
38775
- };
38776
- try {
38777
- const root = process.env.SCAFFOLD_ROOT ? resolve8(process.env.SCAFFOLD_ROOT) : resolve8(profileDir2);
38778
- const target = join19(root, payload.projectName);
38779
- await mkdir6(target, { recursive: true });
38780
- const repos = payload.repos ?? [];
38781
- if (repos.length > 0) await provisionRepos(target, repos, emit, { clonePrimary: true });
38782
- onProgress?.({ projectId: payload.projectId, step: "done", message: "Project added.", done: true });
38783
- return { ok: true, path: target };
38784
- } catch (err) {
38785
- return { ok: false, error: err instanceof Error ? err.message : String(err), step: lastStep };
38786
- }
38787
- }
38788
-
38789
- // src/core/ws-client/handlers/project.ts
38790
38839
  function handleProjectChannels(ctx, message, payload) {
38791
38840
  switch (message.channel) {
38792
38841
  case WsChannels.PHYSIC_SYNC: {
38793
38842
  const sync = payload;
38794
- if (sync.oldName) {
38795
- const oldRoot = ctx.physicFolderPath(sync.oldName);
38843
+ const oldName = sync.oldName;
38844
+ const oldPath = oldName ? join20(ctx.profileDir, oldName) : null;
38845
+ const newPath = join20(ctx.profileDir, sync.newName);
38846
+ if (oldName) {
38847
+ const oldRoot = ctx.physicFolderPath(oldName);
38796
38848
  if (oldRoot) void uninstallCron(oldRoot).catch(() => void 0);
38797
38849
  }
38798
- if (sync.oldName) rmSync6(join20(ctx.profileDir, sync.oldName), { recursive: true, force: true });
38799
- mkdirSync6(join20(ctx.profileDir, sync.newName), { recursive: true });
38850
+ if (oldPath && oldName !== sync.newName && existsSync13(oldPath) && !existsSync13(newPath)) {
38851
+ renameSync(oldPath, newPath);
38852
+ } else {
38853
+ if (oldPath && oldName !== sync.newName && existsSync13(oldPath)) rmSync6(oldPath, { recursive: true, force: true });
38854
+ mkdirSync6(newPath, { recursive: true });
38855
+ }
38800
38856
  ctx.physicRoot = ctx.physicFolderPath(sync.newName);
38801
38857
  ctx.bus.setProject(sync.newName);
38802
38858
  ctx.bus.log(t("project.folderSynced", { name: sync.newName }));
38859
+ void ctx.cloneServingRepos().catch(() => void 0);
38803
38860
  return true;
38804
38861
  }
38805
38862
  case WsChannels.PHYSIC_DELETE: {
@@ -39587,7 +39644,7 @@ async function buildGraph(root, mode) {
39587
39644
 
39588
39645
  // src/core/rag.ts
39589
39646
  import { readFile as readFile12, writeFile as writeFile10, statfs as statfs2, mkdir as mkdir10 } from "fs/promises";
39590
- import { existsSync as existsSync13 } from "fs";
39647
+ import { existsSync as existsSync14 } from "fs";
39591
39648
  import { execFile as execFile10, spawn as spawn9 } from "child_process";
39592
39649
  import { totalmem as totalmem2, cpus as cpus2 } from "os";
39593
39650
  import { join as join26 } from "path";
@@ -39680,7 +39737,7 @@ async function ragStatus(root) {
39680
39737
  const modelPresent = modelName.length > 0;
39681
39738
  return {
39682
39739
  installed: fastembed && sqliteVec && modelPresent,
39683
- installing: existsSync13(join26(root, RAG_REL, ".installing")),
39740
+ installing: existsSync14(join26(root, RAG_REL, ".installing")),
39684
39741
  python: { found: py.found, version: py.version },
39685
39742
  deps: { pip, fastembed, sqliteVec },
39686
39743
  model: { present: modelPresent, name: modelName },
@@ -39691,7 +39748,7 @@ async function ragStatus(root) {
39691
39748
  };
39692
39749
  }
39693
39750
  async function readIndexMeta(root) {
39694
- const indexing = existsSync13(join26(root, RAG_REL, ".indexing"));
39751
+ const indexing = existsSync14(join26(root, RAG_REL, ".indexing"));
39695
39752
  try {
39696
39753
  const m = JSON.parse(await readFile12(join26(root, RAG_REL, ".index.json"), "utf8"));
39697
39754
  return {
@@ -39766,8 +39823,8 @@ async function ensureEngine(root) {
39766
39823
  async function ragReindex(root) {
39767
39824
  const py = await detectPython();
39768
39825
  if (!py.found) return { ok: false, started: false, error: "python3 not found" };
39769
- if (!existsSync13(join26(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
39770
- if (existsSync13(join26(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
39826
+ if (!existsSync14(join26(root, RAG_REL, ".installed"))) return { ok: false, started: false, error: "RAG is not installed" };
39827
+ if (existsSync14(join26(root, RAG_REL, ".indexing"))) return { ok: true, started: true };
39771
39828
  await ensureEngine(root);
39772
39829
  const script = `
39773
39830
  set +e
@@ -39787,7 +39844,7 @@ rm -f "${RAG_REL}/.indexing"
39787
39844
  async function ragQuery(root, queryText, k = 8) {
39788
39845
  const py = await detectPython();
39789
39846
  if (!py.found) return { results: [], error: "python3 not found" };
39790
- if (!existsSync13(join26(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
39847
+ if (!existsSync14(join26(root, RAG_REL, "index.db"))) return { results: [], error: "no index \u2014 reindex first" };
39791
39848
  await ensureEngine(root);
39792
39849
  try {
39793
39850
  const { stdout } = await run10(
@@ -39807,7 +39864,7 @@ async function ragInstall(root, model) {
39807
39864
  }
39808
39865
  const py = await detectPython();
39809
39866
  if (!py.found) return { ok: false, started: false, error: "python3 not found on the worker" };
39810
- if (existsSync13(join26(root, RAG_REL, ".installing"))) return { ok: true, started: true };
39867
+ if (existsSync14(join26(root, RAG_REL, ".installing"))) return { ok: true, started: true };
39811
39868
  try {
39812
39869
  await mkdir10(join26(root, RAG_REL), { recursive: true });
39813
39870
  } catch {
@@ -39836,7 +39893,7 @@ rm -f "${RAG_REL}/.installing"
39836
39893
  }
39837
39894
 
39838
39895
  // src/core/config-sync.ts
39839
- import { existsSync as existsSync14, readFileSync as readFileSync15 } from "fs";
39896
+ import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
39840
39897
  import { join as join27 } from "path";
39841
39898
  var SERVER_MANAGED_KEYS = [
39842
39899
  "physicPath",
@@ -39855,7 +39912,7 @@ var SERVER_MANAGED_KEYS = [
39855
39912
  ];
39856
39913
  function readConfigText(profileDir2) {
39857
39914
  const path = join27(profileDir2, "config.json");
39858
- if (existsSync14(path)) {
39915
+ if (existsSync15(path)) {
39859
39916
  try {
39860
39917
  return readFileSync15(path, "utf8");
39861
39918
  } catch {
@@ -40155,6 +40212,9 @@ var WsClient = class {
40155
40212
  /** The physic project folder root this cli serves (null = no project) — fs.list is
40156
40213
  * scoped to it so the web FsPicker can only browse inward, never out. */
40157
40214
  physicRoot = null;
40215
+ /** Declared repos of the served project (ADR-0289), from each ws_token — cloned into the physic
40216
+ * root on connect (and after a PHYSIC_SYNC creates the folder) when any is missing its `.git`. */
40217
+ servingRepos = [];
40158
40218
  /** This machine-user's username — the git commit author for this project (ADR-0097).
40159
40219
  * Push uses the account already logged in with gh/glab on the worker. */
40160
40220
  machineUsername = "";
@@ -40272,9 +40332,24 @@ var WsClient = class {
40272
40332
  setConsoleWatching: (on) => self.setConsoleWatching(on),
40273
40333
  setMetricsWatching: (on) => self.setMetricsWatching(on),
40274
40334
  ensurePhysicFolderPath: (folder) => self.ensurePhysicFolderPath(folder),
40275
- physicFolderPath: (projectName) => self.physicFolderPath(projectName)
40335
+ physicFolderPath: (projectName) => self.physicFolderPath(projectName),
40336
+ cloneServingRepos: () => self.ensureServingReposCloned(),
40337
+ updateCliNow: () => self.updateScheduler.updateNow()
40276
40338
  };
40277
40339
  }
40340
+ /**
40341
+ * Clone any declared repo missing from the served physic root (ADR-0289) — idempotent (skips a
40342
+ * repo that already has `.git`). Best-effort: a clone failure never disrupts the session (the web
40343
+ * still surfaces "not provisioned"). No-op when idle (no physic root) or no repos declared.
40344
+ */
40345
+ async ensureServingReposCloned() {
40346
+ if (!this.physicRoot || this.servingRepos.length === 0) return;
40347
+ try {
40348
+ await ensureReposCloned(this.physicRoot, this.servingRepos, (_step, message) => this.bus.log(message));
40349
+ } catch (err) {
40350
+ logger.warn("repos.clone.failed", { error: err instanceof Error ? err.message : String(err) });
40351
+ }
40352
+ }
40278
40353
  /**
40279
40354
  * Detect the worker's tools and report the snapshot to the server (ADR-0254, `tools.report`) so the
40280
40355
  * DB-backed Tools panel + restore target stay current. One-way, best-effort — a detect/send failure
@@ -40458,6 +40533,8 @@ var WsClient = class {
40458
40533
  this.restrictToFolder = token.aiRestrictToFolder === true;
40459
40534
  applyGitAuth(token.gitAuth ?? null);
40460
40535
  this.machineUsername = token.machineUsername ?? "";
40536
+ this.servingRepos = token.repos ?? [];
40537
+ void this.ensureServingReposCloned();
40461
40538
  if (token.scope && token.scope !== this.credential.scope) {
40462
40539
  this.credential.scope = token.scope;
40463
40540
  writeCredential(this.context.profileDir, this.credential);
@@ -40708,7 +40785,7 @@ var WsClient = class {
40708
40785
  /** Create the physic folder at an absolute path if missing (best-effort). */
40709
40786
  ensurePhysicFolderPath(folder) {
40710
40787
  try {
40711
- if (!existsSync15(folder)) {
40788
+ if (!existsSync16(folder)) {
40712
40789
  mkdirSync7(folder, { recursive: true });
40713
40790
  this.bus.log(t("project.createdFolder", { folder }));
40714
40791
  }
@@ -40738,7 +40815,7 @@ var WsClient = class {
40738
40815
  projects: [],
40739
40816
  version: CLI_VERSION,
40740
40817
  physicPath,
40741
- physicPathExists: physicPath ? existsSync15(physicPath) : void 0,
40818
+ physicPathExists: physicPath ? existsSync16(physicPath) : void 0,
40742
40819
  // Real coarse state (ADR-0152): !paused and a recent tick (was hard-coded false).
40743
40820
  autonomousRunning: physicPath ? isAutonomousRunning(physicPath) : false,
40744
40821
  // The machine-user AI-run limit (0 = unlimited) + profile count so a dispatch can resolve the
@@ -47057,7 +47134,7 @@ var import_react21 = __toESM(require_react(), 1);
47057
47134
  var import_react26 = __toESM(require_react(), 1);
47058
47135
 
47059
47136
  // src/core/input-history.ts
47060
- import { existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
47137
+ import { existsSync as existsSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
47061
47138
  import { join as join31 } from "path";
47062
47139
  var MAX_HISTORY = 200;
47063
47140
  function historyPath(profileDir2) {
@@ -47065,7 +47142,7 @@ function historyPath(profileDir2) {
47065
47142
  }
47066
47143
  function loadInputHistory(profileDir2) {
47067
47144
  const path = historyPath(profileDir2);
47068
- if (!existsSync17(path)) return [];
47145
+ if (!existsSync18(path)) return [];
47069
47146
  try {
47070
47147
  const data = JSON.parse(readFileSync18(path, "utf8"));
47071
47148
  return Array.isArray(data) ? data.filter((x) => typeof x === "string") : [];