zelari-code 2.43.1 → 2.45.0

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 (34) hide show
  1. package/README.md +22 -5
  2. package/dist/cli/companion/config.js +70 -9
  3. package/dist/cli/companion/config.js.map +1 -1
  4. package/dist/cli/companion/runManager.js +194 -9
  5. package/dist/cli/companion/runManager.js.map +1 -1
  6. package/dist/cli/companion/serve.js +223 -9
  7. package/dist/cli/companion/serve.js.map +1 -1
  8. package/dist/cli/main.bundled.js +750 -252
  9. package/dist/cli/main.bundled.js.map +4 -4
  10. package/dist/cli/safety/toolPermissions.js +43 -17
  11. package/dist/cli/safety/toolPermissions.js.map +1 -1
  12. package/dist/cli/serve/askUserBridge.js +27 -4
  13. package/dist/cli/serve/askUserBridge.js.map +1 -1
  14. package/dist/cli/serve/harnessClient.js +14 -0
  15. package/dist/cli/serve/harnessClient.js.map +1 -1
  16. package/dist/cli/serve/harnessServer.js +38 -10
  17. package/dist/cli/serve/harnessServer.js.map +1 -1
  18. package/dist/cli/serve/permissionBridge.js +37 -8
  19. package/dist/cli/serve/permissionBridge.js.map +1 -1
  20. package/dist/cli/serve/sessionControl.js +13 -0
  21. package/dist/cli/serve/sessionControl.js.map +1 -1
  22. package/dist/cli/slashHandlers/updater.js +10 -2
  23. package/dist/cli/slashHandlers/updater.js.map +1 -1
  24. package/dist/cli/toolRegistry.js +7 -0
  25. package/dist/cli/toolRegistry.js.map +1 -1
  26. package/dist/cli/tools/taskTool.js +10 -4
  27. package/dist/cli/tools/taskTool.js.map +1 -1
  28. package/dist/cli/updater.js +88 -3
  29. package/dist/cli/updater.js.map +1 -1
  30. package/dist/cli/utils/doctor.js +11 -0
  31. package/dist/cli/utils/doctor.js.map +1 -1
  32. package/dist/cli/utils/prereqChecks.js +2 -2
  33. package/dist/cli/utils/prereqChecks.js.map +1 -1
  34. package/package.json +4 -5
@@ -807,7 +807,7 @@ async function openBrowser(url2) {
807
807
  return { bin: "xdg-open", args: [url2] };
808
808
  }
809
809
  })();
810
- return new Promise((resolve10, reject) => {
810
+ return new Promise((resolve11, reject) => {
811
811
  try {
812
812
  const child = spawn19(cmd.bin, cmd.args, {
813
813
  stdio: "ignore",
@@ -816,9 +816,9 @@ async function openBrowser(url2) {
816
816
  child.on("error", (err) => reject(err));
817
817
  child.on("spawn", () => {
818
818
  child.unref();
819
- resolve10();
819
+ resolve11();
820
820
  });
821
- setTimeout(() => resolve10(), 100);
821
+ setTimeout(() => resolve11(), 100);
822
822
  } catch (err) {
823
823
  reject(err);
824
824
  }
@@ -867,7 +867,7 @@ async function waitForLoopbackCallback(options) {
867
867
  const host = options.host ?? "127.0.0.1";
868
868
  const timeoutMs2 = options.timeoutMs ?? 3e5;
869
869
  const expectedPath = options.path.startsWith("/") ? options.path : `/${options.path}`;
870
- return new Promise((resolve10, reject) => {
870
+ return new Promise((resolve11, reject) => {
871
871
  let settled = false;
872
872
  const server = createServer((req, res) => {
873
873
  const url2 = new URL(req.url ?? "/", `http://${host}`);
@@ -896,7 +896,7 @@ async function waitForLoopbackCallback(options) {
896
896
  clearTimeout(timer);
897
897
  server.close(() => {
898
898
  if (err) reject(err);
899
- else resolve10(value ?? {});
899
+ else resolve11(value ?? {});
900
900
  });
901
901
  };
902
902
  server.on("error", (err) => finish2(void 0, err));
@@ -19086,7 +19086,7 @@ function createBashTool(spawnSeam) {
19086
19086
  timeoutMs: 6e4,
19087
19087
  inputSchema: BashArgsSchema,
19088
19088
  execute: async (args, ctx) => {
19089
- return new Promise((resolve10) => {
19089
+ return new Promise((resolve11) => {
19090
19090
  const start = Date.now();
19091
19091
  const cwd = args.cwd ?? ctx.cwd;
19092
19092
  const resolved = resolveShell();
@@ -19113,7 +19113,7 @@ function createBashTool(spawnSeam) {
19113
19113
  if (spawnSeam) {
19114
19114
  const seamResult = spawnSeam({ program, argv, cwd, env, signal: ctx.signal });
19115
19115
  if (seamResult.outcome !== "spawned") {
19116
- resolve10(typedErr(seamResult.reason));
19116
+ resolve11(typedErr(seamResult.reason));
19117
19117
  return;
19118
19118
  }
19119
19119
  child = seamResult.child;
@@ -19140,14 +19140,14 @@ function createBashTool(spawnSeam) {
19140
19140
  });
19141
19141
  const timer = setTimeout(() => {
19142
19142
  child.kill("SIGTERM");
19143
- resolve10(typedErr(`Bash command timed out after ${args.timeoutMs}ms. If it was waiting for interactive input: stdin is closed \u2014 pass non-interactive flags or create the files directly instead of retrying.`));
19143
+ resolve11(typedErr(`Bash command timed out after ${args.timeoutMs}ms. If it was waiting for interactive input: stdin is closed \u2014 pass non-interactive flags or create the files directly instead of retrying.`));
19144
19144
  }, args.timeoutMs);
19145
19145
  child.on("close", (code) => {
19146
19146
  clearTimeout(timer);
19147
19147
  const cappedStdout = stdout.slice(0, maxBuffer);
19148
19148
  const cappedStderr = stderr.slice(0, maxBuffer);
19149
19149
  const interactive = INTERACTIVE_CANCEL_RE.test(cappedStdout) || INTERACTIVE_CANCEL_RE.test(cappedStderr);
19150
- resolve10(typedOk({
19150
+ resolve11(typedOk({
19151
19151
  stdout: cappedStdout,
19152
19152
  stderr: cappedStderr,
19153
19153
  exitCode: code ?? -1,
@@ -19159,7 +19159,7 @@ function createBashTool(spawnSeam) {
19159
19159
  });
19160
19160
  child.on("error", (err) => {
19161
19161
  clearTimeout(timer);
19162
- resolve10(typedErr(err.message));
19162
+ resolve11(typedErr(err.message));
19163
19163
  });
19164
19164
  });
19165
19165
  }
@@ -22296,7 +22296,7 @@ var init_nodeProviders = __esm({
22296
22296
  const isWindows = process.platform === "win32";
22297
22297
  const file2 = isWindows ? "cmd.exe" : "/bin/sh";
22298
22298
  const args = isWindows ? ["/d", "/s", "/c", command] : ["-c", command];
22299
- return await new Promise((resolve10) => {
22299
+ return await new Promise((resolve11) => {
22300
22300
  const child = spawn2(file2, args, {
22301
22301
  cwd,
22302
22302
  shell: false,
@@ -22329,7 +22329,7 @@ var init_nodeProviders = __esm({
22329
22329
  });
22330
22330
  child.on("error", (err) => {
22331
22331
  clearTimeout(timer);
22332
- resolve10({
22332
+ resolve11({
22333
22333
  exitCode: null,
22334
22334
  stdout,
22335
22335
  stderr: `${stderr}${err.message}`,
@@ -22339,7 +22339,7 @@ var init_nodeProviders = __esm({
22339
22339
  });
22340
22340
  child.on("close", (code) => {
22341
22341
  clearTimeout(timer);
22342
- resolve10({ exitCode: code, stdout, stderr, durationMs: Date.now() - started, timedOut });
22342
+ resolve11({ exitCode: code, stdout, stderr, durationMs: Date.now() - started, timedOut });
22343
22343
  });
22344
22344
  });
22345
22345
  }
@@ -22417,7 +22417,7 @@ import path16 from "node:path";
22417
22417
  import crypto2 from "node:crypto";
22418
22418
  import { spawn as spawn3 } from "node:child_process";
22419
22419
  async function git(cwd, args) {
22420
- return await new Promise((resolve10, reject) => {
22420
+ return await new Promise((resolve11, reject) => {
22421
22421
  const child = spawn3("git", args, { cwd, shell: false });
22422
22422
  let stdout = "";
22423
22423
  let stderr = "";
@@ -22426,7 +22426,7 @@ async function git(cwd, args) {
22426
22426
  child.on("error", (err) => reject(err));
22427
22427
  child.on("close", (code) => {
22428
22428
  if (code === 0)
22429
- resolve10(stdout);
22429
+ resolve11(stdout);
22430
22430
  else
22431
22431
  reject(new Error(`git ${args.join(" ")} failed (${code}): ${stderr.trim()}`));
22432
22432
  });
@@ -30565,8 +30565,8 @@ function wrapLegacyStream(legacyStream, params, _messages, _tools) {
30565
30565
  }
30566
30566
  if (done)
30567
30567
  break;
30568
- await new Promise((resolve10) => {
30569
- resolveNext = resolve10;
30568
+ await new Promise((resolve11) => {
30569
+ resolveNext = resolve11;
30570
30570
  });
30571
30571
  }
30572
30572
  await promise2;
@@ -30699,11 +30699,11 @@ var init_sessionJsonl = __esm({
30699
30699
  */
30700
30700
  scheduleFlush() {
30701
30701
  if (this.pending.length > 0 && !this.flushDeferred) {
30702
- let resolve10;
30702
+ let resolve11;
30703
30703
  const promise2 = new Promise((r) => {
30704
- resolve10 = r;
30704
+ resolve11 = r;
30705
30705
  });
30706
- this.flushDeferred = { promise: promise2, resolve: resolve10 };
30706
+ this.flushDeferred = { promise: promise2, resolve: resolve11 };
30707
30707
  }
30708
30708
  if (this.flushTimer === null && this.pending.length > 0) {
30709
30709
  this.flushTimer = setTimeout(() => {
@@ -31010,7 +31010,7 @@ var init_lifecycleHookRunner = __esm({
31010
31010
  }
31011
31011
  /** Spawn `command`, write JSON payload to stdin, read stdout until close. */
31012
31012
  execCommand(command, payload, timeoutMs2, cwd) {
31013
- return new Promise((resolve10, reject) => {
31013
+ return new Promise((resolve11, reject) => {
31014
31014
  const argv = splitHookCommandLine(command);
31015
31015
  if (argv.length === 0) {
31016
31016
  reject(new Error("empty hook command"));
@@ -31044,7 +31044,7 @@ var init_lifecycleHookRunner = __esm({
31044
31044
  reject(new Error(`exited ${code}: ${stderr.trim().slice(0, 200)}`));
31045
31045
  return;
31046
31046
  }
31047
- resolve10(stdout);
31047
+ resolve11(stdout);
31048
31048
  });
31049
31049
  child.stdin?.on("error", () => {
31050
31050
  });
@@ -38898,7 +38898,7 @@ var CORE_VERSION;
38898
38898
  var init_version = __esm({
38899
38899
  "packages/core/dist/version.js"() {
38900
38900
  "use strict";
38901
- CORE_VERSION = "2.43.1";
38901
+ CORE_VERSION = "2.45.0";
38902
38902
  }
38903
38903
  });
38904
38904
 
@@ -40116,15 +40116,48 @@ __export(updater_exports, {
40116
40116
  fetchLatestVersion: () => fetchLatestVersion,
40117
40117
  getCurrentVersion: () => getCurrentVersion,
40118
40118
  looksLikeBrokenShim: () => looksLikeBrokenShim,
40119
+ nonGlobalUpdateAdvisory: () => nonGlobalUpdateAdvisory,
40119
40120
  performUpdate: () => performUpdate,
40120
40121
  registryUrlForTag: () => registryUrlForTag,
40121
- resolveBundledNpmCli: () => resolveBundledNpmCli
40122
+ resolveBundledNpmCli: () => resolveBundledNpmCli,
40123
+ resolveInstallKind: () => resolveInstallKind
40122
40124
  });
40123
40125
  import { createRequire } from "node:module";
40124
- import { spawn as spawn6 } from "node:child_process";
40126
+ import { execSync, spawn as spawn6 } from "node:child_process";
40125
40127
  import { existsSync as existsSync14 } from "node:fs";
40128
+ import os2 from "node:os";
40126
40129
  import path32 from "node:path";
40127
40130
  import { fileURLToPath } from "node:url";
40131
+ function getGlobalPrefix() {
40132
+ try {
40133
+ const out = execSync("npm prefix -g", {
40134
+ encoding: "utf8",
40135
+ stdio: ["ignore", "pipe", "ignore"]
40136
+ }).trim();
40137
+ return out || null;
40138
+ } catch {
40139
+ return null;
40140
+ }
40141
+ }
40142
+ function normalizeForMatch2(p3) {
40143
+ return path32.resolve(p3).replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
40144
+ }
40145
+ function resolveInstallKind(npmPrefix = getGlobalPrefix(), packageRoot2 = PKG_ROOT) {
40146
+ const root = normalizeForMatch2(packageRoot2);
40147
+ const tmp = normalizeForMatch2(os2.tmpdir());
40148
+ const inTmp = tmp !== "" && (root === tmp || root.startsWith(`${tmp}/`));
40149
+ if (root.split("/").includes("_npx") || inTmp) return "npx";
40150
+ if (!npmPrefix) return "unknown";
40151
+ const prefix = normalizeForMatch2(npmPrefix);
40152
+ if (root === prefix || root.startsWith(`${prefix}/`)) return "global";
40153
+ return "local";
40154
+ }
40155
+ function nonGlobalUpdateAdvisory(kind2) {
40156
+ const where = kind2 === "npx" ? "was started via npx (from npm's cache)" : "is a local/source install";
40157
+ return `[update] this zelari-code ${where}, not a global install \u2014 self-update is disabled.
40158
+ update with: npx zelari-code@latest (always fetches the latest)
40159
+ or install persistently: npm install -g zelari-code (enables /update)`;
40160
+ }
40128
40161
  function resolveBundledNpmCli(execPath = process.execPath) {
40129
40162
  const dir = path32.dirname(execPath);
40130
40163
  const candidates = [
@@ -40218,7 +40251,16 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
40218
40251
  updateAvailable: cmp < 0
40219
40252
  };
40220
40253
  }
40221
- async function performUpdate(packageName = "zelari-code", executor = spawn6, resolveNpmCli = resolveBundledNpmCli, channel) {
40254
+ async function performUpdate(packageName = "zelari-code", executor = spawn6, resolveNpmCli = resolveBundledNpmCli, channel, installKind = resolveInstallKind()) {
40255
+ if (installKind === "npx" || installKind === "local") {
40256
+ return {
40257
+ ok: false,
40258
+ output: nonGlobalUpdateAdvisory(installKind),
40259
+ error: "self-update skipped: non-global install",
40260
+ exitCode: null,
40261
+ installKind
40262
+ };
40263
+ }
40222
40264
  const tag = channel ?? distTagForVersion(getCurrentVersion());
40223
40265
  const args = ["install", "-g", `${packageName}@${tag}`];
40224
40266
  const primary = await runNpm(executor, args, "shim");
@@ -40235,7 +40277,7 @@ ${fallback.output}`
40235
40277
  return primary;
40236
40278
  }
40237
40279
  function runNpm(executor, args, mode, npmCliPath) {
40238
- return new Promise((resolve10) => {
40280
+ return new Promise((resolve11) => {
40239
40281
  let stdout = "";
40240
40282
  let stderr = "";
40241
40283
  const stdio = ["ignore", "pipe", "pipe"];
@@ -40247,7 +40289,7 @@ function runNpm(executor, args, mode, npmCliPath) {
40247
40289
  stderr += chunk.toString();
40248
40290
  });
40249
40291
  child.on("error", (err) => {
40250
- resolve10({
40292
+ resolve11({
40251
40293
  ok: false,
40252
40294
  output: stdout + stderr,
40253
40295
  error: err.message,
@@ -40256,7 +40298,7 @@ function runNpm(executor, args, mode, npmCliPath) {
40256
40298
  });
40257
40299
  child.on("close", (code) => {
40258
40300
  const ok = code === 0;
40259
- resolve10({
40301
+ resolve11({
40260
40302
  ok,
40261
40303
  output: stdout + stderr,
40262
40304
  error: ok ? void 0 : `npm exited with code ${code}`,
@@ -40265,13 +40307,14 @@ function runNpm(executor, args, mode, npmCliPath) {
40265
40307
  });
40266
40308
  });
40267
40309
  }
40268
- var require2, __dirname2, REGISTRY_URL;
40310
+ var require2, __dirname2, PKG_ROOT, REGISTRY_URL;
40269
40311
  var init_updater = __esm({
40270
40312
  "src/cli/updater.ts"() {
40271
40313
  "use strict";
40272
40314
  init_cmdline();
40273
40315
  require2 = createRequire(import.meta.url);
40274
40316
  __dirname2 = path32.dirname(fileURLToPath(import.meta.url));
40317
+ PKG_ROOT = path32.resolve(__dirname2, "..", "..");
40275
40318
  REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
40276
40319
  }
40277
40320
  });
@@ -42278,8 +42321,8 @@ var init_storage = __esm({
42278
42321
  const prev2 = this.chains.get(key) ?? Promise.resolve();
42279
42322
  let release = () => {
42280
42323
  };
42281
- const next = new Promise((resolve10) => {
42282
- release = resolve10;
42324
+ const next = new Promise((resolve11) => {
42325
+ release = resolve11;
42283
42326
  });
42284
42327
  const chained = prev2.then(() => next);
42285
42328
  this.chains.set(key, chained);
@@ -43075,14 +43118,14 @@ var init_engine2 = __esm({
43075
43118
  parse: parseRuffJson
43076
43119
  }
43077
43120
  ];
43078
- defaultRunner = (cmd, args, opts) => new Promise((resolve10) => {
43121
+ defaultRunner = (cmd, args, opts) => new Promise((resolve11) => {
43079
43122
  let stdout = "";
43080
43123
  let stderr = "";
43081
43124
  let settled = false;
43082
43125
  const done = (r) => {
43083
43126
  if (settled) return;
43084
43127
  settled = true;
43085
- resolve10(r);
43128
+ resolve11(r);
43086
43129
  };
43087
43130
  let child;
43088
43131
  try {
@@ -44137,7 +44180,7 @@ async function readChecks(cwd) {
44137
44180
  }
44138
44181
  }
44139
44182
  function runShell(command, cwd, timeoutMs2, signal) {
44140
- return new Promise((resolve10) => {
44183
+ return new Promise((resolve11) => {
44141
44184
  const isWin = process.platform === "win32";
44142
44185
  const child = spawn8(isWin ? "cmd.exe" : "/bin/sh", isWin ? ["/c", command] : ["-c", command], {
44143
44186
  cwd,
@@ -44151,7 +44194,7 @@ function runShell(command, cwd, timeoutMs2, signal) {
44151
44194
  const finish2 = (exitCode) => {
44152
44195
  if (settled) return;
44153
44196
  settled = true;
44154
- resolve10({ exitCode, stdout, stderr });
44197
+ resolve11({ exitCode, stdout, stderr });
44155
44198
  };
44156
44199
  const timer = setTimeout(() => {
44157
44200
  try {
@@ -46862,8 +46905,8 @@ ${upstream}` : node.prompt,
46862
46905
  if (ms <= 0) return promise2;
46863
46906
  let timer;
46864
46907
  const TIMED_OUT = Symbol("timeout");
46865
- const timeout = new Promise((resolve10) => {
46866
- timer = setTimeout(() => resolve10(TIMED_OUT), ms);
46908
+ const timeout = new Promise((resolve11) => {
46909
+ timer = setTimeout(() => resolve11(TIMED_OUT), ms);
46867
46910
  });
46868
46911
  const raced = await Promise.race([promise2, timeout]).finally(() => {
46869
46912
  if (timer) clearTimeout(timer);
@@ -46872,8 +46915,8 @@ ${upstream}` : node.prompt,
46872
46915
  controller?.abort();
46873
46916
  const graceMs = this.cancelGraceMs ?? resolveCancelGraceMs();
46874
46917
  let graceTimer;
46875
- const grace = new Promise((resolve10) => {
46876
- graceTimer = setTimeout(() => resolve10(TIMED_OUT), graceMs);
46918
+ const grace = new Promise((resolve11) => {
46919
+ graceTimer = setTimeout(() => resolve11(TIMED_OUT), graceMs);
46877
46920
  });
46878
46921
  const settled = await Promise.race([promise2, grace]).finally(() => {
46879
46922
  if (graceTimer) clearTimeout(graceTimer);
@@ -47640,13 +47683,13 @@ ${findings || "(the reviewer reported FAIL without detail)"}`;
47640
47683
  async function runAutoVerifyAfterGeneral(opts) {
47641
47684
  const g = globalThis;
47642
47685
  g.__zelariGeneralVerifyDebt = { description: opts.original.description };
47643
- const emitVerifyPhase = (detail2, ok) => {
47686
+ const emitVerifyPhase = (detail2, ok, terminal) => {
47644
47687
  const agentId = opts.general.agentId;
47645
47688
  if (agentId) {
47646
47689
  opts.deps.onTentacleEvent?.({
47647
47690
  type: "agent_status",
47648
47691
  agentId,
47649
- status: "running",
47692
+ status: terminal ?? "running",
47650
47693
  message: detail2,
47651
47694
  id: randomUUID4(),
47652
47695
  sessionId: opts.sessionId,
@@ -47714,6 +47757,7 @@ async function runAutoVerifyAfterGeneral(opts) {
47714
47757
  detail: detail2,
47715
47758
  ok: false
47716
47759
  });
47760
+ emitVerifyPhase(`rework round ${round2} failed`, false, "failed");
47717
47761
  return `
47718
47762
 
47719
47763
  [kraken:auto-verify] verify FAIL \u2014 rework round ${round2} failed to run (${rework.error}). Work stays UNVERIFIED; strict done will close this turn blocked.`;
@@ -47725,14 +47769,15 @@ async function runAutoVerifyAfterGeneral(opts) {
47725
47769
  }
47726
47770
  if (verdict === "pass") {
47727
47771
  g.__zelariGeneralVerifyDebt = null;
47728
- emitVerifyPhase("verify PASS", true);
47772
+ emitVerifyPhase("verify PASS", true, "completed");
47729
47773
  return `
47730
47774
 
47731
47775
  [kraken:auto-verify] verify PASS \u2014 general\u21D2verify obligation satisfied.`;
47732
47776
  }
47733
47777
  emitVerifyPhase(
47734
47778
  verdict === "fail" ? "verify FAIL" : verify.ok ? "verify unknown" : "verify failed",
47735
- verdict === "fail" || !verify.ok ? false : void 0
47779
+ verdict === "fail" || !verify.ok ? false : void 0,
47780
+ "failed"
47736
47781
  );
47737
47782
  const detail = verdict === "fail" ? `verify FAIL unresolved (rework budget spent): ${findings || "no findings reported"}` : verify.ok ? "verify produced no parseable VERDICT \u2014 unverified" : `verify tentacle failed: ${verify.error}`;
47738
47783
  g.__zelariGeneralVerifyDebt = { description: opts.original.description, detail };
@@ -50772,13 +50817,13 @@ async function scanTsbuildinfo(root) {
50772
50817
  return found;
50773
50818
  }
50774
50819
  async function gitStatusPorcelain(root) {
50775
- return new Promise((resolve10) => {
50820
+ return new Promise((resolve11) => {
50776
50821
  const child = spawn9("git", ["status", "--porcelain"], { cwd: root, shell: false });
50777
50822
  let out = "";
50778
50823
  child.stdout.on("data", (d) => out += d.toString());
50779
50824
  child.stderr.on("data", (d) => out += d.toString());
50780
- child.on("error", () => resolve10("<git-unavailable>"));
50781
- child.on("close", (code) => resolve10(code === 0 ? out : `<git-exit-${code ?? "none"}>`));
50825
+ child.on("error", () => resolve11("<git-unavailable>"));
50826
+ child.on("close", (code) => resolve11(code === 0 ? out : `<git-exit-${code ?? "none"}>`));
50782
50827
  });
50783
50828
  }
50784
50829
  async function fingerprintWorkspace(root) {
@@ -50826,7 +50871,7 @@ var init_inspectTypecheckSafety = __esm({
50826
50871
  import { spawn as spawn10 } from "node:child_process";
50827
50872
  import { createHash as createHash14 } from "node:crypto";
50828
50873
  import { existsSync as existsSync30, promises as fs23 } from "node:fs";
50829
- import os2 from "node:os";
50874
+ import os3 from "node:os";
50830
50875
  import path52 from "node:path";
50831
50876
  function resolveNodeModuleBin(start, rel2) {
50832
50877
  let dir = path52.resolve(start);
@@ -50912,7 +50957,7 @@ function buildInspectCommand(op, ctx) {
50912
50957
  case "typecheck": {
50913
50958
  const project = path52.resolve(ctx.cwd, op.project ?? "tsconfig.json");
50914
50959
  const hash3 = createHash14("sha256").update(project).digest("hex").slice(0, 16);
50915
- const tsBuildInfoFile = path52.join(os2.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
50960
+ const tsBuildInfoFile = path52.join(os3.tmpdir(), "zelari-inspect", `${hash3}.tsbuildinfo`);
50916
50961
  return {
50917
50962
  ok: true,
50918
50963
  command: process.execPath,
@@ -50936,12 +50981,12 @@ function buildInspectCommand(op, ctx) {
50936
50981
  }
50937
50982
  }
50938
50983
  function runSpawn(command, argv, opts) {
50939
- return new Promise((resolve10) => {
50984
+ return new Promise((resolve11) => {
50940
50985
  let child;
50941
50986
  try {
50942
50987
  child = spawn10(command, argv, { cwd: opts.cwd, shell: false });
50943
50988
  } catch (err) {
50944
- resolve10({ code: null, stdout: "", stderr: String(err), timedOut: false, spawnError: String(err) });
50989
+ resolve11({ code: null, stdout: "", stderr: String(err), timedOut: false, spawnError: String(err) });
50945
50990
  return;
50946
50991
  }
50947
50992
  let stdout = "";
@@ -50963,14 +51008,14 @@ function runSpawn(command, argv, opts) {
50963
51008
  if (settled) return;
50964
51009
  settled = true;
50965
51010
  clearTimeout(timer);
50966
- resolve10({ code: null, stdout, stderr, timedOut, spawnError: err.message });
51011
+ resolve11({ code: null, stdout, stderr, timedOut, spawnError: err.message });
50967
51012
  });
50968
51013
  child.on("close", (code) => {
50969
51014
  if (settled) return;
50970
51015
  settled = true;
50971
51016
  clearTimeout(timer);
50972
51017
  opts.signal?.removeEventListener("abort", onAbort);
50973
- resolve10({ code, stdout, stderr, timedOut });
51018
+ resolve11({ code, stdout, stderr, timedOut });
50974
51019
  });
50975
51020
  });
50976
51021
  }
@@ -51255,7 +51300,7 @@ function createExecProcessTool(root, opts = {}) {
51255
51300
  const msg = err instanceof SandboxViolationError ? err.message : String(err);
51256
51301
  return typedErr(`[sandbox] ${msg}`);
51257
51302
  }
51258
- return new Promise((resolve10) => {
51303
+ return new Promise((resolve11) => {
51259
51304
  const start = Date.now();
51260
51305
  const spec = { ...baseSpec, network: opts.resolveNetwork?.(input) ?? baseSpec.network };
51261
51306
  const res = spawnJailed(spec, {
@@ -51267,11 +51312,11 @@ function createExecProcessTool(root, opts = {}) {
51267
51312
  envExtras: { CI: process.env.CI ?? "1" }
51268
51313
  });
51269
51314
  if (res.outcome === "denied") {
51270
- resolve10(typedErr(res.reason));
51315
+ resolve11(typedErr(res.reason));
51271
51316
  return;
51272
51317
  }
51273
51318
  if (res.outcome === "failed") {
51274
- resolve10(typedErr(`exec_process could not launch ${input.program}: ${res.reason}`));
51319
+ resolve11(typedErr(`exec_process could not launch ${input.program}: ${res.reason}`));
51275
51320
  return;
51276
51321
  }
51277
51322
  const meta3 = res.notice ? { status: "complete", warnings: [`os-jail: ${res.notice}`] } : void 0;
@@ -51287,7 +51332,7 @@ function createExecProcessTool(root, opts = {}) {
51287
51332
  const timeoutMs2 = typeof input.timeoutMs === "number" && input.timeoutMs > 0 ? input.timeoutMs : 3e4;
51288
51333
  const timer = setTimeout(() => {
51289
51334
  child.kill("SIGTERM");
51290
- resolve10(
51335
+ resolve11(
51291
51336
  typedErr(
51292
51337
  `exec_process timed out after ${timeoutMs2}ms running: ${[input.program, ...argv].join(" ")}`
51293
51338
  )
@@ -51295,11 +51340,11 @@ function createExecProcessTool(root, opts = {}) {
51295
51340
  }, timeoutMs2);
51296
51341
  child.on("error", (err) => {
51297
51342
  clearTimeout(timer);
51298
- resolve10(typedErr(`exec_process could not launch ${input.program}: ${err.message}`));
51343
+ resolve11(typedErr(`exec_process could not launch ${input.program}: ${err.message}`));
51299
51344
  });
51300
51345
  child.on("close", (code) => {
51301
51346
  clearTimeout(timer);
51302
- resolve10(
51347
+ resolve11(
51303
51348
  typedOk(
51304
51349
  {
51305
51350
  exitCode: code ?? -1,
@@ -51828,12 +51873,12 @@ var init_client = __esm({
51828
51873
  if (this.closed) return Promise.reject(new Error("LSP transport is closed"));
51829
51874
  const id3 = this.nextId++;
51830
51875
  const msg = { jsonrpc: "2.0", id: id3, method, ...params !== void 0 ? { params } : {} };
51831
- return new Promise((resolve10, reject) => {
51876
+ return new Promise((resolve11, reject) => {
51832
51877
  const timer = setTimeout(() => {
51833
51878
  this.pending.delete(id3);
51834
51879
  reject(new Error(`LSP request "${method}" timed out after ${this.timeoutMs}ms`));
51835
51880
  }, this.timeoutMs);
51836
- this.pending.set(id3, { resolve: resolve10, reject, timer });
51881
+ this.pending.set(id3, { resolve: resolve11, reject, timer });
51837
51882
  this.transport.send(encodeMessage(msg));
51838
51883
  });
51839
51884
  }
@@ -52110,8 +52155,8 @@ var init_manager = __esm({
52110
52155
  if (cached2 !== void 0) return cached2;
52111
52156
  let resolveInit;
52112
52157
  let rejectInit;
52113
- const initialized = new Promise((resolve10, reject) => {
52114
- resolveInit = resolve10;
52158
+ const initialized = new Promise((resolve11, reject) => {
52159
+ resolveInit = resolve11;
52115
52160
  rejectInit = reject;
52116
52161
  });
52117
52162
  let entry = null;
@@ -52636,14 +52681,14 @@ function resolveStreamTimeouts(capabilities) {
52636
52681
  };
52637
52682
  }
52638
52683
  function abortableSleep(ms, signal) {
52639
- return new Promise((resolve10) => {
52640
- if (signal?.aborted) return resolve10();
52641
- const t = setTimeout(resolve10, ms);
52684
+ return new Promise((resolve11) => {
52685
+ if (signal?.aborted) return resolve11();
52686
+ const t = setTimeout(resolve11, ms);
52642
52687
  signal?.addEventListener(
52643
52688
  "abort",
52644
52689
  () => {
52645
52690
  clearTimeout(t);
52646
- resolve10();
52691
+ resolve11();
52647
52692
  },
52648
52693
  { once: true }
52649
52694
  );
@@ -53517,8 +53562,8 @@ async function runBrowserCheck(options, loader) {
53517
53562
  const failedRequests = [];
53518
53563
  const evaluateResults = [];
53519
53564
  const base2 = { ok: false, consoleErrors, pageErrors, failedRequests };
53520
- const resolve10 = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
53521
- const pw = await resolve10();
53565
+ const resolve11 = loader ?? (() => loadPlaywright(options.cwd ?? process.cwd()));
53566
+ const pw = await resolve11();
53522
53567
  if (!pw) {
53523
53568
  return {
53524
53569
  ...base2,
@@ -53681,7 +53726,7 @@ var init_driver = __esm({
53681
53726
 
53682
53727
  // src/cli/browser/tools.ts
53683
53728
  import path58 from "node:path";
53684
- import os3 from "node:os";
53729
+ import os4 from "node:os";
53685
53730
  import { readFile as readFile8 } from "node:fs/promises";
53686
53731
  async function loadImageBlock(filePath) {
53687
53732
  try {
@@ -53708,7 +53753,7 @@ function createBrowserTool(deps = {}) {
53708
53753
  }),
53709
53754
  execute: async (args, ctx) => {
53710
53755
  const a = args;
53711
- const dir = deps.screenshotDir ?? os3.tmpdir();
53756
+ const dir = deps.screenshotDir ?? os4.tmpdir();
53712
53757
  const screenshotPath = a.screenshot === false ? void 0 : path58.join(dir, `zelari-browser-${Date.now()}.png`);
53713
53758
  const result = await runBrowserCheck(
53714
53759
  {
@@ -54111,9 +54156,9 @@ exec node "$(dirname "$0")/askpass.cjs"
54111
54156
  return sh;
54112
54157
  }
54113
54158
  function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
54114
- return new Promise((resolve10) => {
54159
+ return new Promise((resolve11) => {
54115
54160
  if (target.auth === "password" && !getSshPassword(target.id)) {
54116
- resolve10({
54161
+ resolve11({
54117
54162
  code: 1,
54118
54163
  stdout: "",
54119
54164
  stderr: "No password stored for this target \u2014 edit target and set password"
@@ -54145,7 +54190,7 @@ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
54145
54190
  });
54146
54191
  const timer = setTimeout(() => {
54147
54192
  child.kill("SIGTERM");
54148
- resolve10({
54193
+ resolve11({
54149
54194
  code: 124,
54150
54195
  stdout,
54151
54196
  stderr: stderr + "\n[ssh] timeout"
@@ -54153,7 +54198,7 @@ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
54153
54198
  }, timeoutMs2);
54154
54199
  child.on("error", (err) => {
54155
54200
  clearTimeout(timer);
54156
- resolve10({
54201
+ resolve11({
54157
54202
  code: 127,
54158
54203
  stdout,
54159
54204
  stderr: err.message.includes("ENOENT") ? "ssh not found on PATH \u2014 install OpenSSH client" : err.message
@@ -54161,7 +54206,7 @@ function runSsh(target, remoteCommand, timeoutMs2 = 6e4) {
54161
54206
  });
54162
54207
  child.on("close", (code) => {
54163
54208
  clearTimeout(timer);
54164
- resolve10({ code: code ?? 1, stdout, stderr });
54209
+ resolve11({ code: code ?? 1, stdout, stderr });
54165
54210
  });
54166
54211
  });
54167
54212
  }
@@ -54938,14 +54983,14 @@ function backoffDelay2(attempt, retryAfterHeader) {
54938
54983
  return Math.min(BACKOFF_BASE_MS2 * 2 ** attempt, BACKOFF_CAP_MS2);
54939
54984
  }
54940
54985
  function abortableSleep2(ms, signal) {
54941
- return new Promise((resolve10) => {
54942
- if (signal?.aborted) return resolve10();
54943
- const t = setTimeout(resolve10, ms);
54986
+ return new Promise((resolve11) => {
54987
+ if (signal?.aborted) return resolve11();
54988
+ const t = setTimeout(resolve11, ms);
54944
54989
  signal?.addEventListener(
54945
54990
  "abort",
54946
54991
  () => {
54947
54992
  clearTimeout(t);
54948
- resolve10();
54993
+ resolve11();
54949
54994
  },
54950
54995
  { once: true }
54951
54996
  );
@@ -55346,6 +55391,45 @@ var init_crossProviderFailover = __esm({
55346
55391
  }
55347
55392
  });
55348
55393
 
55394
+ // src/cli/serve/sessionControl.ts
55395
+ import { AsyncLocalStorage } from "node:async_hooks";
55396
+ function runWithSession(sessionId2, fn) {
55397
+ return dispatchContext.run({ sessionId: sessionId2, token: {} }, fn);
55398
+ }
55399
+ function getCurrentHarnessSessionId() {
55400
+ return dispatchContext.getStore()?.sessionId;
55401
+ }
55402
+ function registerLiveTurnControl(control) {
55403
+ const store6 = dispatchContext.getStore();
55404
+ if (!store6) return void 0;
55405
+ const registered = { ...control, token: store6.token };
55406
+ liveTurns.set(store6.sessionId, registered);
55407
+ return () => {
55408
+ if (liveTurns.get(store6.sessionId) === registered) {
55409
+ liveTurns.delete(store6.sessionId);
55410
+ }
55411
+ };
55412
+ }
55413
+ function getLiveTurnControl(sessionId2) {
55414
+ return liveTurns.get(sessionId2);
55415
+ }
55416
+ function clearSessionTurnControl(sessionId2) {
55417
+ const store6 = dispatchContext.getStore();
55418
+ if (!store6 || store6.sessionId !== sessionId2) return;
55419
+ const registered = liveTurns.get(sessionId2);
55420
+ if (registered && registered.token === store6.token) {
55421
+ liveTurns.delete(sessionId2);
55422
+ }
55423
+ }
55424
+ var dispatchContext, liveTurns;
55425
+ var init_sessionControl = __esm({
55426
+ "src/cli/serve/sessionControl.ts"() {
55427
+ "use strict";
55428
+ dispatchContext = new AsyncLocalStorage();
55429
+ liveTurns = /* @__PURE__ */ new Map();
55430
+ }
55431
+ });
55432
+
55349
55433
  // src/cli/safety/toolPermissions.ts
55350
55434
  var toolPermissions_exports = {};
55351
55435
  __export(toolPermissions_exports, {
@@ -55362,27 +55446,42 @@ __export(toolPermissions_exports, {
55362
55446
  parsePermissionPreset: () => parsePermissionPreset,
55363
55447
  resolveToolPermission: () => resolveToolPermission
55364
55448
  });
55365
- function grantSessionTool(toolName) {
55449
+ function grantsBucket(sessionId2) {
55450
+ const key = sessionId2 && sessionId2.trim() ? sessionId2 : GLOBAL_BUCKET;
55451
+ let bucket = grantsBySession.get(key);
55452
+ if (!bucket) {
55453
+ bucket = { tools: /* @__PURE__ */ new Set(), categories: /* @__PURE__ */ new Set() };
55454
+ grantsBySession.set(key, bucket);
55455
+ }
55456
+ return bucket;
55457
+ }
55458
+ function grantSessionTool(toolName, sessionId2) {
55366
55459
  const n = toolName.trim();
55367
- if (n) sessionToolGrants.add(n);
55460
+ if (n) grantsBucket(sessionId2).tools.add(n);
55368
55461
  }
55369
- function grantSessionCategory(cat) {
55370
- sessionCategoryGrants.add(cat);
55462
+ function grantSessionCategory(cat, sessionId2) {
55463
+ grantsBucket(sessionId2).categories.add(cat);
55371
55464
  }
55372
- function clearSessionPermissionGrants() {
55373
- sessionToolGrants.clear();
55374
- sessionCategoryGrants.clear();
55465
+ function clearSessionPermissionGrants(sessionId2) {
55466
+ if (sessionId2 === void 0) {
55467
+ grantsBySession.clear();
55468
+ return;
55469
+ }
55470
+ grantsBySession.delete(sessionId2 && sessionId2.trim() ? sessionId2 : GLOBAL_BUCKET);
55375
55471
  }
55376
- function listSessionPermissionGrants() {
55472
+ function listSessionPermissionGrants(sessionId2) {
55473
+ const bucket = grantsBucket(sessionId2);
55377
55474
  return {
55378
- tools: [...sessionToolGrants].sort(),
55379
- categories: [...sessionCategoryGrants]
55475
+ tools: [...bucket.tools].sort(),
55476
+ categories: [...bucket.categories]
55380
55477
  };
55381
55478
  }
55382
- function isSessionGranted(toolName, required2) {
55383
- if (sessionToolGrants.has(toolName)) return true;
55479
+ function isSessionGranted(toolName, required2, sessionId2) {
55480
+ const scope = sessionId2 ?? getCurrentHarnessSessionId();
55481
+ const bucket = grantsBucket(scope);
55482
+ if (bucket.tools.has(toolName)) return true;
55384
55483
  if (!required2.length) return false;
55385
- return required2.every((c) => sessionCategoryGrants.has(c));
55484
+ return required2.every((c) => bucket.categories.has(c));
55386
55485
  }
55387
55486
  function parseAction(raw, fallback) {
55388
55487
  const v = raw?.trim().toLowerCase();
@@ -55464,12 +55563,13 @@ function resolveToolPermission(toolName, required2, policy) {
55464
55563
  categories: hit
55465
55564
  };
55466
55565
  }
55467
- var sessionToolGrants, sessionCategoryGrants, PERMISSION_PRESETS, ACTION_RANK2;
55566
+ var GLOBAL_BUCKET, grantsBySession, PERMISSION_PRESETS, ACTION_RANK2;
55468
55567
  var init_toolPermissions = __esm({
55469
55568
  "src/cli/safety/toolPermissions.ts"() {
55470
55569
  "use strict";
55471
- sessionToolGrants = /* @__PURE__ */ new Set();
55472
- sessionCategoryGrants = /* @__PURE__ */ new Set();
55570
+ init_sessionControl();
55571
+ GLOBAL_BUCKET = "";
55572
+ grantsBySession = /* @__PURE__ */ new Map();
55473
55573
  PERMISSION_PRESETS = {
55474
55574
  standard: { read: "allow", write: "allow", execute: "ask", network: "ask" },
55475
55575
  strict: { read: "allow", write: "ask", execute: "ask", network: "deny" },
@@ -56980,6 +57080,9 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
56980
57080
  }
56981
57081
  const claimHit = claims && claims.effect !== void 0 ? claims.matchedRules.find((x) => x.effect === claims.effect) : void 0;
56982
57082
  const rulePrefix = contractRule ? `[contract] rule '${contractRule.match}'${contractRule.reason ? ` \u2014 ${contractRule.reason}` : ""}` : rule ? `[policy] rule '${rule.match}'${rule.reason ? ` \u2014 ${rule.reason}` : ""}` : claimHit ? `[policy] claim '${claimHit.match}'${claimHit.reason ? ` \u2014 ${claimHit.reason}` : ""}` : "";
57083
+ if (action === "ask" && activePermissionPreset() === "yolo") {
57084
+ action = "allow";
57085
+ }
56983
57086
  if (action === "deny") {
56984
57087
  return typedErr(`[permission] ${rulePrefix || decision.reason}`);
56985
57088
  }
@@ -57510,12 +57613,12 @@ var init_metrics3 = __esm({
57510
57613
  }
57511
57614
  /** Append a single record synchronously (file I/O). */
57512
57615
  append(rec) {
57513
- return new Promise((resolve10, reject) => {
57616
+ return new Promise((resolve11, reject) => {
57514
57617
  try {
57515
57618
  this.maybeRotate();
57516
57619
  const line = JSON.stringify(rec) + "\n";
57517
57620
  appendFileSync3(this.file, line, { encoding: "utf-8" });
57518
- resolve10();
57621
+ resolve11();
57519
57622
  } catch (err) {
57520
57623
  reject(err);
57521
57624
  }
@@ -58900,8 +59003,8 @@ var init_sqliteRpc = __esm({
58900
59003
  const worker = this.worker;
58901
59004
  if (!worker) return Promise.reject(new Error("SQLite worker is not open."));
58902
59005
  const id3 = this.nextId++;
58903
- return new Promise((resolve10, reject) => {
58904
- this.pending.set(id3, { resolve: resolve10, reject });
59006
+ return new Promise((resolve11, reject) => {
59007
+ this.pending.set(id3, { resolve: resolve11, reject });
58905
59008
  worker.postMessage({ id: id3, operation, args });
58906
59009
  });
58907
59010
  }
@@ -58912,7 +59015,7 @@ var init_sqliteRpc = __esm({
58912
59015
  return await this.request(operation, args);
58913
59016
  } catch (error51) {
58914
59017
  if (!isBusy(error51) || attempt >= 3) throw error51;
58915
- await new Promise((resolve10) => setTimeout(resolve10, 25 * 2 ** attempt));
59018
+ await new Promise((resolve11) => setTimeout(resolve11, 25 * 2 ** attempt));
58916
59019
  attempt += 1;
58917
59020
  }
58918
59021
  }
@@ -61831,13 +61934,13 @@ __export(claudeProvider_exports, {
61831
61934
  });
61832
61935
  import { spawn as spawn13 } from "node:child_process";
61833
61936
  function waitForExit(child, timeoutMs2 = 2e3) {
61834
- return new Promise((resolve10) => {
61835
- if (child.exitCode != null) return resolve10(child.exitCode);
61836
- const timer = setTimeout(() => resolve10(child.exitCode ?? null), timeoutMs2);
61937
+ return new Promise((resolve11) => {
61938
+ if (child.exitCode != null) return resolve11(child.exitCode);
61939
+ const timer = setTimeout(() => resolve11(child.exitCode ?? null), timeoutMs2);
61837
61940
  timer.unref?.();
61838
61941
  child.once("exit", (code) => {
61839
61942
  clearTimeout(timer);
61840
- resolve10(code ?? null);
61943
+ resolve11(code ?? null);
61841
61944
  });
61842
61945
  });
61843
61946
  }
@@ -63752,7 +63855,7 @@ var init_mcpClient = __esm({
63752
63855
  if (!transport && !child)
63753
63856
  return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
63754
63857
  const id3 = this.nextId++;
63755
- return new Promise((resolve10, reject) => {
63858
+ return new Promise((resolve11, reject) => {
63756
63859
  const timer = setTimeout(() => {
63757
63860
  this.pending.delete(id3);
63758
63861
  reject(
@@ -63761,7 +63864,7 @@ var init_mcpClient = __esm({
63761
63864
  )
63762
63865
  );
63763
63866
  }, timeoutMs2);
63764
- this.pending.set(id3, { resolve: resolve10, reject, timer });
63867
+ this.pending.set(id3, { resolve: resolve11, reject, timer });
63765
63868
  if (transport) {
63766
63869
  void transport.send({ id: id3, method, params }, timeoutMs2);
63767
63870
  } else {
@@ -66947,12 +67050,12 @@ function startPermissionBroker(socketPath, handlers, opts) {
66947
67050
  });
66948
67051
  }
66949
67052
  }
66950
- return new Promise((resolve10, reject) => {
67053
+ return new Promise((resolve11, reject) => {
66951
67054
  const onError = (err) => reject(err);
66952
67055
  server.once("error", onError);
66953
67056
  server.listen(path104, () => {
66954
67057
  server.removeListener("error", onError);
66955
- resolve10({
67058
+ resolve11({
66956
67059
  socketPath: path104,
66957
67060
  stop: () => new Promise((res) => {
66958
67061
  for (const s of sockets) s.destroy();
@@ -66980,7 +67083,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
66980
67083
  const path104 = safeSocketPath(socketPath);
66981
67084
  const requestTimeoutMs = opts?.requestTimeoutMs ?? PERMISSION_BROKER_DEFAULT_TIMEOUT_MS;
66982
67085
  const connectTimeoutMs = opts?.connectTimeoutMs ?? PERMISSION_BROKER_DEFAULT_CONNECT_TIMEOUT_MS;
66983
- return new Promise((resolve10, reject) => {
67086
+ return new Promise((resolve11, reject) => {
66984
67087
  const socket = connect(path104);
66985
67088
  let buffer = "";
66986
67089
  let settled = false;
@@ -67033,7 +67136,7 @@ function requestBrokerAsk(socketPath, ask, opts) {
67033
67136
  }
67034
67137
  if (msg?.t === "answer" && msg.id === ask.id) {
67035
67138
  const { t: _t, id: _id, ...answer } = msg;
67036
- settle(() => resolve10(answer));
67139
+ settle(() => resolve11(answer));
67037
67140
  return;
67038
67141
  }
67039
67142
  }
@@ -67812,7 +67915,7 @@ __export(prereqChecks_exports, {
67812
67915
  isWslBashPath: () => isWslBashPath2,
67813
67916
  runPrereqChecks: () => runPrereqChecks
67814
67917
  });
67815
- import { execSync, spawnSync as spawnSync2 } from "node:child_process";
67918
+ import { execSync as execSync2, spawnSync as spawnSync2 } from "node:child_process";
67816
67919
  import { existsSync as existsSync54 } from "node:fs";
67817
67920
  import { dirname as dirname13 } from "node:path";
67818
67921
  function isWslBashPath2(p3) {
@@ -67972,7 +68075,7 @@ function probeTool(tool) {
67972
68075
  }
67973
68076
  } else if (process.platform === "win32") {
67974
68077
  try {
67975
- stdout = execSync(`${tool} --version`, {
68078
+ stdout = execSync2(`${tool} --version`, {
67976
68079
  encoding: "utf8",
67977
68080
  stdio: ["ignore", "pipe", "ignore"],
67978
68081
  env
@@ -67981,7 +68084,7 @@ function probeTool(tool) {
67981
68084
  }
67982
68085
  } else {
67983
68086
  try {
67984
- stdout = execSync(`${tool} --version`, {
68087
+ stdout = execSync2(`${tool} --version`, {
67985
68088
  encoding: "utf8",
67986
68089
  stdio: ["ignore", "pipe", "ignore"],
67987
68090
  env
@@ -68106,7 +68209,7 @@ function checkAgentBash() {
68106
68209
  }
68107
68210
  function checkMainNode() {
68108
68211
  try {
68109
- const raw = execSync("node --version", {
68212
+ const raw = execSync2("node --version", {
68110
68213
  encoding: "utf8",
68111
68214
  stdio: ["ignore", "pipe", "ignore"]
68112
68215
  }).trim();
@@ -68166,7 +68269,7 @@ var MIN_NODE_MAJOR, STANDARD_BASH_PATHS2, POWERSHELL_EXES2, STANDARD_POWERSHELL_
68166
68269
  var init_prereqChecks = __esm({
68167
68270
  "src/cli/utils/prereqChecks.ts"() {
68168
68271
  "use strict";
68169
- MIN_NODE_MAJOR = 24;
68272
+ MIN_NODE_MAJOR = 20;
68170
68273
  STANDARD_BASH_PATHS2 = [
68171
68274
  "C:\\Program Files\\Git\\bin\\bash.exe",
68172
68275
  "C:\\Program Files\\Git\\usr\\bin\\bash.exe",
@@ -69697,42 +69800,6 @@ var init_harnessStateEmit = __esm({
69697
69800
  }
69698
69801
  });
69699
69802
 
69700
- // src/cli/serve/sessionControl.ts
69701
- import { AsyncLocalStorage } from "node:async_hooks";
69702
- function runWithSession(sessionId2, fn) {
69703
- return dispatchContext.run({ sessionId: sessionId2, token: {} }, fn);
69704
- }
69705
- function registerLiveTurnControl(control) {
69706
- const store6 = dispatchContext.getStore();
69707
- if (!store6) return void 0;
69708
- const registered = { ...control, token: store6.token };
69709
- liveTurns.set(store6.sessionId, registered);
69710
- return () => {
69711
- if (liveTurns.get(store6.sessionId) === registered) {
69712
- liveTurns.delete(store6.sessionId);
69713
- }
69714
- };
69715
- }
69716
- function getLiveTurnControl(sessionId2) {
69717
- return liveTurns.get(sessionId2);
69718
- }
69719
- function clearSessionTurnControl(sessionId2) {
69720
- const store6 = dispatchContext.getStore();
69721
- if (!store6 || store6.sessionId !== sessionId2) return;
69722
- const registered = liveTurns.get(sessionId2);
69723
- if (registered && registered.token === store6.token) {
69724
- liveTurns.delete(sessionId2);
69725
- }
69726
- }
69727
- var dispatchContext, liveTurns;
69728
- var init_sessionControl = __esm({
69729
- "src/cli/serve/sessionControl.ts"() {
69730
- "use strict";
69731
- dispatchContext = new AsyncLocalStorage();
69732
- liveTurns = /* @__PURE__ */ new Map();
69733
- }
69734
- });
69735
-
69736
69803
  // src/cli/headless/controlReader.ts
69737
69804
  function parseControlEvent(raw) {
69738
69805
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
@@ -74117,8 +74184,8 @@ async function startMemoryMcpServer(options) {
74117
74184
  let stopped = false;
74118
74185
  let finalizePromise;
74119
74186
  let resolveClosed = () => void 0;
74120
- const closed = new Promise((resolve10) => {
74121
- resolveClosed = resolve10;
74187
+ const closed = new Promise((resolve11) => {
74188
+ resolveClosed = resolve11;
74122
74189
  });
74123
74190
  const adapter = new MemoryMcpAdapter({
74124
74191
  service,
@@ -74265,8 +74332,8 @@ function startPermissionMcpServer(opts) {
74265
74332
  });
74266
74333
  let closedResolve = () => {
74267
74334
  };
74268
- const closed = new Promise((resolve10) => {
74269
- closedResolve = resolve10;
74335
+ const closed = new Promise((resolve11) => {
74336
+ closedResolve = resolve11;
74270
74337
  });
74271
74338
  let stopped = false;
74272
74339
  const send = (obj) => {
@@ -74522,9 +74589,10 @@ import {
74522
74589
  existsSync as existsSync61,
74523
74590
  mkdirSync as mkdirSync24,
74524
74591
  readFileSync as readFileSync45,
74592
+ statSync as statSync10,
74525
74593
  writeFileSync as writeFileSync26
74526
74594
  } from "node:fs";
74527
- import { join as join48 } from "node:path";
74595
+ import { join as join48, resolve as resolve9 } from "node:path";
74528
74596
  import { createHash as createHash26, randomBytes as randomBytes7, timingSafeEqual } from "node:crypto";
74529
74597
  function getZelariHome() {
74530
74598
  return zelariHome();
@@ -74613,6 +74681,30 @@ function slugFromPath(p3) {
74613
74681
  const base2 = p3.replace(/[/\\]+$/, "").split(/[/\\]/).pop() || "project";
74614
74682
  return base2.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "project";
74615
74683
  }
74684
+ function resolveFsDirectory(rawPath) {
74685
+ const trimmed = rawPath.trim();
74686
+ if (!trimmed) return { ok: false, error: "cwd is required" };
74687
+ const norm = trimmed.replace(/\\/g, "/");
74688
+ const absolute = /^([a-zA-Z]:\/|\/)/.test(norm);
74689
+ if (!absolute || norm.split("/").includes("..")) {
74690
+ return {
74691
+ ok: false,
74692
+ error: `cwd must be an absolute path without '..': ${trimmed}`
74693
+ };
74694
+ }
74695
+ let stat8;
74696
+ try {
74697
+ stat8 = statSync10(trimmed);
74698
+ } catch {
74699
+ return { ok: false, error: `path not found: ${trimmed}` };
74700
+ }
74701
+ if (!stat8.isDirectory()) {
74702
+ return { ok: false, error: `not a directory: ${trimmed}` };
74703
+ }
74704
+ const abs = resolve9(trimmed);
74705
+ const slug = slugFromPath(abs);
74706
+ return { ok: true, project: { id: slug, name: slug, path: abs } };
74707
+ }
74616
74708
  function mergeProjects(cfg, extraPaths) {
74617
74709
  const byId = /* @__PURE__ */ new Map();
74618
74710
  for (const p3 of cfg.projects) {
@@ -74634,24 +74726,24 @@ function mergeProjects(cfg, extraPaths) {
74634
74726
  }
74635
74727
  return [...byId.values()];
74636
74728
  }
74637
- function resolveProjectPath(projects, cwdOrId) {
74638
- if (!projects.length) {
74729
+ function resolveProjectPath(projects, cwdOrId, opts = {}) {
74730
+ const key = String(cwdOrId ?? "").trim();
74731
+ if (!projects.length && !(opts.fullFs && key)) {
74639
74732
  return {
74640
74733
  ok: false,
74641
74734
  error: "No projects configured. Pass --project <path> or edit ~/.zelari-code/companion.json"
74642
74735
  };
74643
74736
  }
74644
- if (!cwdOrId || !String(cwdOrId).trim()) {
74645
- return { ok: true, project: projects[0] };
74737
+ if (!key) {
74738
+ return { ok: true, project: projects[0], trusted: true };
74646
74739
  }
74647
- const key = String(cwdOrId).trim();
74648
74740
  const byId = projects.find((p3) => p3.id === key || p3.name === key);
74649
- if (byId) return { ok: true, project: byId };
74741
+ if (byId) return { ok: true, project: byId, trusted: true };
74650
74742
  const norm = key.replace(/\\/g, "/").toLowerCase();
74651
74743
  const byPath = projects.find(
74652
74744
  (p3) => p3.path.replace(/\\/g, "/").toLowerCase() === norm
74653
74745
  );
74654
- if (byPath) return { ok: true, project: byPath };
74746
+ if (byPath) return { ok: true, project: byPath, trusted: true };
74655
74747
  const under = projects.find((p3) => {
74656
74748
  const root = p3.path.replace(/\\/g, "/").toLowerCase().replace(/\/$/, "");
74657
74749
  return norm === root || norm.startsWith(root + "/");
@@ -74659,14 +74751,31 @@ function resolveProjectPath(projects, cwdOrId) {
74659
74751
  if (under) {
74660
74752
  return {
74661
74753
  ok: true,
74662
- project: { ...under, path: key }
74754
+ project: { ...under, path: key },
74755
+ trusted: true
74663
74756
  };
74664
74757
  }
74758
+ if (opts.fullFs) {
74759
+ const dir = resolveFsDirectory(key);
74760
+ if (dir.ok) return { ok: true, project: dir.project, trusted: false };
74761
+ return { ok: false, error: dir.error };
74762
+ }
74665
74763
  return {
74666
74764
  ok: false,
74667
74765
  error: `cwd/project not in allowlist: ${key}. Allowed: ${projects.map((p3) => p3.id).join(", ")}`
74668
74766
  };
74669
74767
  }
74768
+ function isUnderRoots(rawPath, projects) {
74769
+ const norm = rawPath.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
74770
+ if (!norm || norm.split("/").includes("..")) return { ok: false };
74771
+ for (const p3 of projects) {
74772
+ const root = p3.path.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
74773
+ if (norm === root || norm.startsWith(root + "/")) {
74774
+ return { ok: true, root: p3, normalized: norm };
74775
+ }
74776
+ }
74777
+ return { ok: false };
74778
+ }
74670
74779
  var DEFAULT_COMPANION_PORT, DEFAULT_COMPANION_BIND;
74671
74780
  var init_config = __esm({
74672
74781
  "src/cli/companion/config.ts"() {
@@ -74705,6 +74814,7 @@ function createServePermissionBridge(write, timeoutMs2 = 12e4) {
74705
74814
  type: "permission.settled",
74706
74815
  requestId,
74707
74816
  decision,
74817
+ ...entry.sessionId ? { sessionId: entry.sessionId } : {},
74708
74818
  ...timedOut ? { timedOut: true } : {}
74709
74819
  })
74710
74820
  );
@@ -74715,15 +74825,17 @@ function createServePermissionBridge(write, timeoutMs2 = 12e4) {
74715
74825
  onPermissionAsk(payload) {
74716
74826
  const requestId = `perm-${Date.now()}-${++seq}`;
74717
74827
  const categories = payload.categories && payload.categories.length > 0 ? payload.categories : payload.category ? payload.category.split(",").map((c) => c.trim()).filter(Boolean) : [];
74718
- return new Promise((resolve10) => {
74828
+ return new Promise((resolve11) => {
74719
74829
  const timer = setTimeout(() => {
74720
74830
  settle(requestId, "deny", true);
74721
74831
  }, timeoutMs2);
74722
- pending.set(requestId, { resolve: resolve10, timer, payload });
74832
+ const sessionId2 = getCurrentHarnessSessionId();
74833
+ pending.set(requestId, { resolve: resolve11, timer, payload, sessionId: sessionId2 });
74723
74834
  write(
74724
74835
  JSON.stringify({
74725
74836
  type: "permission.request",
74726
74837
  requestId,
74838
+ ...sessionId2 ? { sessionId: sessionId2 } : {},
74727
74839
  tool: payload.tool,
74728
74840
  category: payload.category,
74729
74841
  categories,
@@ -74733,12 +74845,19 @@ function createServePermissionBridge(write, timeoutMs2 = 12e4) {
74733
74845
  );
74734
74846
  });
74735
74847
  },
74736
- respond: (requestId, decision) => settle(requestId, decision, false),
74848
+ respond: (requestId, decision, scopeSessionId) => {
74849
+ const entry = pending.get(requestId);
74850
+ if (entry && scopeSessionId && entry.sessionId !== scopeSessionId) {
74851
+ return false;
74852
+ }
74853
+ return settle(requestId, decision, false);
74854
+ },
74855
+ sessionOf: (requestId) => pending.get(requestId)?.sessionId,
74737
74856
  releaseGranted() {
74738
74857
  let n = 0;
74739
74858
  for (const [id3, entry] of [...pending]) {
74740
74859
  const cats = entry.payload.categories && entry.payload.categories.length > 0 ? entry.payload.categories : entry.payload.category ? entry.payload.category.split(",").map((c) => c.trim()).filter(Boolean) : [];
74741
- if (isSessionGranted(entry.payload.tool, cats)) {
74860
+ if (isSessionGranted(entry.payload.tool, cats, entry.sessionId)) {
74742
74861
  if (settle(id3, "allow", false)) n += 1;
74743
74862
  }
74744
74863
  }
@@ -74751,7 +74870,7 @@ function servePermissionRespond(bridge, params) {
74751
74870
  if (!params || typeof params !== "object") {
74752
74871
  return { accepted: false, reason: "permission.respond requires an object params" };
74753
74872
  }
74754
- const { requestId, decision } = params;
74873
+ const { requestId, decision, sessionId: sessionId2 } = params;
74755
74874
  if (typeof requestId !== "string" || requestId.length === 0) {
74756
74875
  return { accepted: false, reason: "permission.respond requires a non-empty string requestId" };
74757
74876
  }
@@ -74761,7 +74880,11 @@ function servePermissionRespond(bridge, params) {
74761
74880
  reason: "permission.respond decision must be 'allow' | 'deny' | 'always-tool' | 'always-category'"
74762
74881
  };
74763
74882
  }
74764
- return { accepted: bridge.respond(requestId, decision) };
74883
+ const scope = typeof sessionId2 === "string" && sessionId2.length > 0 ? sessionId2 : void 0;
74884
+ if (scope && bridge.sessionOf(requestId) !== scope) {
74885
+ return { accepted: false, reason: "session_mismatch: requestId belongs to another session" };
74886
+ }
74887
+ return { accepted: bridge.respond(requestId, decision, scope) };
74765
74888
  }
74766
74889
  function asRegistryAskHandler(bridge) {
74767
74890
  return async (req) => {
@@ -74774,14 +74897,15 @@ function asRegistryAskHandler(bridge) {
74774
74897
  ...req.claims && req.claims.length > 0 ? { inputPreview: req.claims.map((c) => c.summary).join(" \xB7 ") } : {}
74775
74898
  });
74776
74899
  if (decision === "deny") return false;
74900
+ const sessionId2 = getCurrentHarnessSessionId();
74777
74901
  if (decision === "always-tool") {
74778
- grantSessionTool(req.toolName);
74902
+ grantSessionTool(req.toolName, sessionId2);
74779
74903
  bridge.releaseGranted();
74780
74904
  } else if (decision === "always-category") {
74781
74905
  for (const cat of req.categories) {
74782
- grantSessionCategory(cat);
74906
+ grantSessionCategory(cat, sessionId2);
74783
74907
  }
74784
- grantSessionTool(req.toolName);
74908
+ grantSessionTool(req.toolName, sessionId2);
74785
74909
  bridge.releaseGranted();
74786
74910
  }
74787
74911
  return true;
@@ -74792,6 +74916,7 @@ var init_permissionBridge = __esm({
74792
74916
  "src/cli/serve/permissionBridge.ts"() {
74793
74917
  "use strict";
74794
74918
  init_toolPermissions();
74919
+ init_sessionControl();
74795
74920
  SERVE_PERMISSION_PRESETS = ["standard", "strict", "yolo"];
74796
74921
  PRESET_ENV = "ZELARI_PERMISSION_PRESET";
74797
74922
  PERMISSION_DECISIONS = [
@@ -74817,6 +74942,7 @@ function createServeAskUserBridge(write, timeoutMs2 = askUserTimeoutMs()) {
74817
74942
  type: "ask_user.settled",
74818
74943
  requestId,
74819
74944
  answer,
74945
+ ...entry.sessionId ? { sessionId: entry.sessionId } : {},
74820
74946
  ...timedOut ? { timedOut: true } : {}
74821
74947
  })
74822
74948
  );
@@ -74829,15 +74955,17 @@ function createServeAskUserBridge(write, timeoutMs2 = askUserTimeoutMs()) {
74829
74955
  const choices = req.choices.map((c) => c.trim()).filter(Boolean);
74830
74956
  if (choices.length < 2) return Promise.resolve(null);
74831
74957
  const requestId = `ask-${Date.now()}-${++seq}`;
74832
- return new Promise((resolve10) => {
74958
+ return new Promise((resolve11) => {
74833
74959
  const timer = setTimeout(() => {
74834
74960
  settle(requestId, null, true);
74835
74961
  }, timeoutMs2);
74836
- pending.set(requestId, { resolve: resolve10, timer });
74962
+ const sessionId2 = getCurrentHarnessSessionId();
74963
+ pending.set(requestId, { resolve: resolve11, timer, sessionId: sessionId2 });
74837
74964
  write(
74838
74965
  JSON.stringify({
74839
74966
  type: "ask_user.request",
74840
74967
  requestId,
74968
+ ...sessionId2 ? { sessionId: sessionId2 } : {},
74841
74969
  question,
74842
74970
  choices,
74843
74971
  ...req.context ? { context: req.context } : {}
@@ -74845,7 +74973,14 @@ function createServeAskUserBridge(write, timeoutMs2 = askUserTimeoutMs()) {
74845
74973
  );
74846
74974
  });
74847
74975
  },
74848
- respond: (requestId, answer) => settle(requestId, answer, false),
74976
+ respond: (requestId, answer, scopeSessionId) => {
74977
+ const entry = pending.get(requestId);
74978
+ if (entry && scopeSessionId && entry.sessionId && entry.sessionId !== scopeSessionId) {
74979
+ return false;
74980
+ }
74981
+ return settle(requestId, answer, false);
74982
+ },
74983
+ sessionOf: (requestId) => pending.get(requestId)?.sessionId,
74849
74984
  pendingCount: () => pending.size
74850
74985
  };
74851
74986
  }
@@ -74853,20 +74988,25 @@ function serveAskUserRespond(bridge, params) {
74853
74988
  if (!params || typeof params !== "object") {
74854
74989
  return { accepted: false, reason: "ask_user.respond requires an object params" };
74855
74990
  }
74856
- const { requestId, answer } = params;
74991
+ const { requestId, answer, sessionId: sessionId2 } = params;
74857
74992
  if (typeof requestId !== "string" || requestId.length === 0) {
74858
74993
  return { accepted: false, reason: "ask_user.respond requires a non-empty string requestId" };
74859
74994
  }
74860
74995
  if (answer !== null && typeof answer !== "string") {
74861
74996
  return { accepted: false, reason: "ask_user.respond answer must be a string or null" };
74862
74997
  }
74998
+ const scope = typeof sessionId2 === "string" && sessionId2.length > 0 ? sessionId2 : void 0;
74999
+ if (scope && bridge.sessionOf(requestId) && bridge.sessionOf(requestId) !== scope) {
75000
+ return { accepted: false, reason: "session_mismatch: requestId belongs to another session" };
75001
+ }
74863
75002
  const text = typeof answer === "string" ? answer.trim() : null;
74864
- return { accepted: bridge.respond(requestId, text && text.length > 0 ? text : null) };
75003
+ return { accepted: bridge.respond(requestId, text && text.length > 0 ? text : null, scope) };
74865
75004
  }
74866
75005
  var init_askUserBridge = __esm({
74867
75006
  "src/cli/serve/askUserBridge.ts"() {
74868
75007
  "use strict";
74869
75008
  init_askUserTimeout();
75009
+ init_sessionControl();
74870
75010
  }
74871
75011
  });
74872
75012
 
@@ -75030,17 +75170,23 @@ function createCliRunTurn(onPermissionAsk, onAskUser) {
75030
75170
  const opts = bindHarnessTurnOptions(input, deps.session.workspaceRoot);
75031
75171
  if (onPermissionAsk) opts.onPermissionAsk = onPermissionAsk;
75032
75172
  if (onAskUser) opts.onAskUser = onAskUser;
75173
+ const priorPreset = process.env[PRESET_ENV2];
75033
75174
  applyTurnPermissionPreset(input);
75034
- const lspProvider = resolveTurnLspProvider(deps.services);
75035
- const exitCode = await dispatchHeadlessTurn(
75036
- opts,
75037
- provider,
75038
- model,
75039
- stream,
75040
- void 0,
75041
- lspProvider ? { lspProvider } : void 0
75042
- );
75043
- return { exitCode };
75175
+ try {
75176
+ const lspProvider = resolveTurnLspProvider(deps.services);
75177
+ const exitCode = await dispatchHeadlessTurn(
75178
+ opts,
75179
+ provider,
75180
+ model,
75181
+ stream,
75182
+ void 0,
75183
+ lspProvider ? { lspProvider } : void 0
75184
+ );
75185
+ return { exitCode };
75186
+ } finally {
75187
+ if (priorPreset === void 0) delete process.env[PRESET_ENV2];
75188
+ else process.env[PRESET_ENV2] = priorPreset;
75189
+ }
75044
75190
  };
75045
75191
  }
75046
75192
  function unknownSessionEnvelope(id3, rawSessionId) {
@@ -75121,6 +75267,7 @@ function startHarnessServer(options = {}) {
75121
75267
  return { id: req.id ?? null, ok: false, error: { code: "unknown_session", message: `no session '${String(params.sessionId ?? "")}'` } };
75122
75268
  }
75123
75269
  await session.dispose();
75270
+ clearSessionPermissionGrants(session.id);
75124
75271
  return { id: req.id ?? null, ok: true, result: { disposed: true } };
75125
75272
  }
75126
75273
  // t32: session-scoped steer/cancel over the SAME protocol v2 — no new
@@ -75236,6 +75383,7 @@ async function runHarnessServer() {
75236
75383
  process.once("SIGTERM", finish2);
75237
75384
  });
75238
75385
  }
75386
+ var PRESET_ENV2;
75239
75387
  var init_harnessServer = __esm({
75240
75388
  "src/cli/serve/harnessServer.ts"() {
75241
75389
  "use strict";
@@ -75251,8 +75399,10 @@ var init_harnessServer = __esm({
75251
75399
  init_policyGate();
75252
75400
  init_policyLoadMode();
75253
75401
  init_permissionBridge();
75402
+ init_toolPermissions();
75254
75403
  init_askUserBridge();
75255
75404
  init_spineLockSweep();
75405
+ PRESET_ENV2 = "ZELARI_PERMISSION_PRESET";
75256
75406
  }
75257
75407
  });
75258
75408
 
@@ -75314,8 +75464,8 @@ var init_harnessClient = __esm({
75314
75464
  );
75315
75465
  }
75316
75466
  const id3 = this.nextId++;
75317
- return new Promise((resolve10, reject) => {
75318
- this.pending.set(id3, { resolve: resolve10, reject });
75467
+ return new Promise((resolve11, reject) => {
75468
+ this.pending.set(id3, { resolve: resolve11, reject });
75319
75469
  this.transport.write(JSON.stringify({ id: id3, method, params }));
75320
75470
  });
75321
75471
  }
@@ -75341,6 +75491,18 @@ var init_harnessClient = __esm({
75341
75491
  if (reason !== void 0) params["reason"] = reason;
75342
75492
  return await this.request("session.cancel", params);
75343
75493
  }
75494
+ /** t63: settle a permission.request (allow/deny/always-tool/always-category). */
75495
+ async permissionRespond(requestId, decision, sessionId2) {
75496
+ const params = { requestId, decision };
75497
+ if (sessionId2 !== void 0) params["sessionId"] = sessionId2;
75498
+ return await this.request("permission.respond", params);
75499
+ }
75500
+ /** t63: settle an ask_user.request (null = dismiss). */
75501
+ async askUserRespond(requestId, answer, sessionId2) {
75502
+ const params = { requestId, answer };
75503
+ if (sessionId2 !== void 0) params["sessionId"] = sessionId2;
75504
+ return await this.request("ask_user.respond", params);
75505
+ }
75344
75506
  async disposeSession(sessionId2) {
75345
75507
  await this.request("session.dispose", { sessionId: sessionId2 });
75346
75508
  }
@@ -75370,6 +75532,9 @@ import { tmpdir as tmpdir4 } from "node:os";
75370
75532
  function harnessServerMode() {
75371
75533
  return process.env[HARNESS_SERVER_ENV] === "1";
75372
75534
  }
75535
+ function isPermissionRequestEvent(ev) {
75536
+ return typeof ev === "object" && ev !== null && ev.type === "permission.request";
75537
+ }
75373
75538
  var HARNESS_SERVER_ENV, RunManager;
75374
75539
  var init_runManager = __esm({
75375
75540
  "src/cli/companion/runManager.ts"() {
@@ -75427,6 +75592,13 @@ var init_runManager = __esm({
75427
75592
  }
75428
75593
  emit(ev) {
75429
75594
  if (!this.active) return;
75595
+ if (this.active.permissionPreset === "yolo" && isPermissionRequestEvent(ev)) {
75596
+ const requestId = ev.requestId;
75597
+ if (typeof requestId === "string" && requestId.trim()) {
75598
+ void this.permissionRespond(this.active.run.id, requestId, "allow");
75599
+ return;
75600
+ }
75601
+ }
75430
75602
  this.active.run.events.push(ev);
75431
75603
  if (this.active.run.events.length > 5e3) {
75432
75604
  this.active.run.events.splice(0, this.active.run.events.length - 4e3);
@@ -75438,7 +75610,7 @@ var init_runManager = __esm({
75438
75610
  }
75439
75611
  }
75440
75612
  }
75441
- start(args) {
75613
+ start(args, gate) {
75442
75614
  if (this.active) {
75443
75615
  return {
75444
75616
  ok: false,
@@ -75460,9 +75632,79 @@ var init_runManager = __esm({
75460
75632
  createdAt: Date.now(),
75461
75633
  events: []
75462
75634
  };
75635
+ if (gate?.awaitingTrust) {
75636
+ run.status = "awaiting_trust";
75637
+ this.active = {
75638
+ run,
75639
+ listeners: /* @__PURE__ */ new Set(),
75640
+ trustArgs: args,
75641
+ trustMode: this.clientMode ? "harness" : "spawn",
75642
+ permissionPreset: args.permissionPreset
75643
+ };
75644
+ this.emit({
75645
+ type: "trust.pending",
75646
+ runId: id3,
75647
+ path: args.cwd,
75648
+ message: `[companion] run ${id3} awaiting desktop trust approval for ${args.cwd}`
75649
+ });
75650
+ return { ok: true, run, awaitingTrust: true };
75651
+ }
75463
75652
  if (this.clientMode) {
75464
75653
  return this.startViaHarness(run, args, mode, phase2);
75465
75654
  }
75655
+ return this.launchSpawn(run, args);
75656
+ }
75657
+ /**
75658
+ * t66: desktop approved the parked run — launch it now, preserving the
75659
+ * listeners (phone SSE) attached while it was awaiting trust.
75660
+ */
75661
+ releaseTrust() {
75662
+ const a = this.active;
75663
+ if (!a || a.run.status !== "awaiting_trust" || !a.trustArgs) {
75664
+ return { ok: false, error: "no run awaiting trust" };
75665
+ }
75666
+ const args = a.trustArgs;
75667
+ a.run.status = "running";
75668
+ this.emit({ type: "trust.settled", runId: a.run.id, approved: true });
75669
+ if (a.trustMode === "harness") {
75670
+ return this.startViaHarness(a.run, args, a.run.mode, a.run.phase);
75671
+ }
75672
+ return this.launchSpawn(a.run, args);
75673
+ }
75674
+ /** t66: desktop denied the parked run — fail it, fail-closed. */
75675
+ denyTrust(reason) {
75676
+ const a = this.active;
75677
+ if (!a || a.run.status !== "awaiting_trust") {
75678
+ return { ok: false, error: "no run awaiting trust" };
75679
+ }
75680
+ a.run.status = "error";
75681
+ a.run.error = reason;
75682
+ a.run.finishedAt = Date.now();
75683
+ this.emit({ type: "trust.settled", runId: a.run.id, approved: false });
75684
+ this.emit({
75685
+ type: "error",
75686
+ severity: "fatal",
75687
+ message: reason,
75688
+ code: "trust_denied"
75689
+ });
75690
+ this.emit({
75691
+ type: "run_finished",
75692
+ runId: a.run.id,
75693
+ status: "error",
75694
+ exitCode: null
75695
+ });
75696
+ this.finishActive();
75697
+ return { ok: true };
75698
+ }
75699
+ /** t66: current awaiting_trust run (if any) for GET /v1/trust/pending. */
75700
+ pendingTrust() {
75701
+ const a = this.active;
75702
+ if (!a || a.run.status !== "awaiting_trust") return null;
75703
+ return { runId: a.run.id, path: a.run.cwd };
75704
+ }
75705
+ /** t66: spawn-path launch, extracted from start() so releaseTrust can
75706
+ * launch a parked run. Preserves parked listeners (same run id). */
75707
+ launchSpawn(run, args) {
75466
75708
  const cliEntry = process.argv[1];
75467
75709
  if (!cliEntry) {
75468
75710
  return { ok: false, error: "Cannot resolve CLI entry (process.argv[1])" };
@@ -75471,13 +75713,13 @@ var init_runManager = __esm({
75471
75713
  cliEntry,
75472
75714
  "--headless",
75473
75715
  "--task",
75474
- prompt,
75716
+ run.prompt,
75475
75717
  "--output",
75476
75718
  "json",
75477
75719
  "--mode",
75478
- mode === "council" || mode === "zelari" ? mode : "kraken",
75720
+ run.mode === "council" || run.mode === "zelari" ? run.mode : "kraken",
75479
75721
  "--phase",
75480
- phase2 === "plan" ? "plan" : "build"
75722
+ run.phase === "plan" ? "plan" : "build"
75481
75723
  ];
75482
75724
  if (args.provider?.trim()) {
75483
75725
  argv.push("--provider", args.provider.trim());
@@ -75485,9 +75727,14 @@ var init_runManager = __esm({
75485
75727
  if (args.model?.trim()) {
75486
75728
  argv.push("--model", args.model.trim());
75487
75729
  }
75730
+ if (args.strictDone === true) argv.push("--strict-done");
75731
+ else if (args.strictDone === false) argv.push("--no-strict-done");
75732
+ if (args.permissionPreset) {
75733
+ argv.push("--permissions", args.permissionPreset);
75734
+ }
75488
75735
  let historyFile;
75489
75736
  if (args.history && Array.isArray(args.history) && args.history.length > 0) {
75490
- historyFile = join49(tmpdir4(), `zelari-companion-hist-${id3}.json`);
75737
+ historyFile = join49(tmpdir4(), `zelari-companion-hist-${run.id}.json`);
75491
75738
  try {
75492
75739
  writeFileSync27(historyFile, JSON.stringify(args.history), "utf8");
75493
75740
  argv.push("--history-file", historyFile);
@@ -75501,15 +75748,26 @@ var init_runManager = __esm({
75501
75748
  ...process.env,
75502
75749
  ZELARI_SKIP_PREFLIGHT: "1",
75503
75750
  ANATHEMA_DEV: "1",
75504
- FORCE_COLOR: "0"
75751
+ FORCE_COLOR: "0",
75752
+ // t65: belt-and-suspenders — the long-lived headless child reads the
75753
+ // preset straight from env even if it never re-parses --permissions;
75754
+ // mirrors main.ts's --permissions → ZELARI_PERMISSION_PRESET promotion.
75755
+ ...args.permissionPreset ? { ZELARI_PERMISSION_PRESET: args.permissionPreset } : {}
75505
75756
  },
75506
75757
  stdio: ["ignore", "pipe", "pipe"],
75507
75758
  windowsHide: true
75508
75759
  });
75509
- this.active = { run, child, listeners: /* @__PURE__ */ new Set(), historyFile };
75760
+ const parked = this.active?.run.id === run.id ? this.active : null;
75761
+ this.active = {
75762
+ run,
75763
+ child,
75764
+ listeners: parked?.listeners ?? /* @__PURE__ */ new Set(),
75765
+ historyFile,
75766
+ permissionPreset: args.permissionPreset
75767
+ };
75510
75768
  this.emit({
75511
75769
  type: "log",
75512
- message: `[companion] run ${id3} started mode=${mode} phase=${phase2} cwd=${args.cwd}`
75770
+ message: `[companion] run ${run.id} started mode=${run.mode} phase=${run.phase} cwd=${args.cwd}`
75513
75771
  });
75514
75772
  if (child.stdout) {
75515
75773
  const rl = createInterface4({ input: child.stdout });
@@ -75556,7 +75814,7 @@ var init_runManager = __esm({
75556
75814
  }
75557
75815
  this.emit({
75558
75816
  type: "run_finished",
75559
- runId: id3,
75817
+ runId: run.id,
75560
75818
  status: run.status,
75561
75819
  exitCode: code
75562
75820
  });
@@ -75571,7 +75829,12 @@ var init_runManager = __esm({
75571
75829
  * reports through the usual run status + emit path.
75572
75830
  */
75573
75831
  startViaHarness(run, args, mode, phase2) {
75574
- this.active = { run, listeners: /* @__PURE__ */ new Set() };
75832
+ const parked = this.active?.run.id === run.id ? this.active : null;
75833
+ this.active = {
75834
+ run,
75835
+ listeners: parked?.listeners ?? /* @__PURE__ */ new Set(),
75836
+ permissionPreset: args.permissionPreset
75837
+ };
75575
75838
  this.emit({
75576
75839
  type: "log",
75577
75840
  message: `[companion] run ${run.id} started mode=${mode} phase=${phase2} cwd=${args.cwd}`
@@ -75619,6 +75882,9 @@ var init_runManager = __esm({
75619
75882
  if (Array.isArray(args.history) && args.history.length > 0) {
75620
75883
  turnInput["history"] = args.history;
75621
75884
  }
75885
+ if (args.permissionPreset) turnInput["permissionPreset"] = args.permissionPreset;
75886
+ if (args.strictDone !== void 0) turnInput["strictDone"] = args.strictDone;
75887
+ if (args.verifyPack !== void 0) turnInput["verifyPack"] = args.verifyPack;
75622
75888
  const result = await client.runTurn(sessionId2, turnInput);
75623
75889
  const stillOwner = this.active?.run.id === run.id;
75624
75890
  if (stillOwner && run.status === "running") {
@@ -75719,6 +75985,51 @@ var init_runManager = __esm({
75719
75985
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
75720
75986
  }
75721
75987
  }
75988
+ /** t63: settle a permission.request on the active client-mode run. */
75989
+ async permissionRespond(runId, requestId, decision) {
75990
+ if (!this.active) return { ok: false, error: "No active run" };
75991
+ if (runId && this.active.run.id !== runId) {
75992
+ return { ok: false, error: `Run ${runId} is not active` };
75993
+ }
75994
+ const id3 = requestId?.trim();
75995
+ if (!id3) return { ok: false, error: "requestId is required" };
75996
+ if (!this.active.harnessSessionId || !this.harnessClient) {
75997
+ return {
75998
+ ok: false,
75999
+ error: `permission respond requires harness server mode (${HARNESS_SERVER_ENV}=1)`
76000
+ };
76001
+ }
76002
+ try {
76003
+ const result = await this.harnessClient.permissionRespond(id3, decision);
76004
+ return { ok: true, result };
76005
+ } catch (err) {
76006
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
76007
+ }
76008
+ }
76009
+ /** t63: settle an ask_user.request (answer null = dismiss). */
76010
+ async askUserRespond(runId, requestId, answer) {
76011
+ if (!this.active) return { ok: false, error: "No active run" };
76012
+ if (runId && this.active.run.id !== runId) {
76013
+ return { ok: false, error: `Run ${runId} is not active` };
76014
+ }
76015
+ const id3 = requestId?.trim();
76016
+ if (!id3) return { ok: false, error: "requestId is required" };
76017
+ if (answer != null && typeof answer !== "string") {
76018
+ return { ok: false, error: "answer must be a string or null" };
76019
+ }
76020
+ if (!this.active.harnessSessionId || !this.harnessClient) {
76021
+ return {
76022
+ ok: false,
76023
+ error: `ask respond requires harness server mode (${HARNESS_SERVER_ENV}=1)`
76024
+ };
76025
+ }
76026
+ try {
76027
+ const result = await this.harnessClient.askUserRespond(id3, answer);
76028
+ return { ok: true, result };
76029
+ } catch (err) {
76030
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
76031
+ }
76032
+ }
75722
76033
  cancel(runId) {
75723
76034
  if (!this.active) {
75724
76035
  return { ok: false, error: "No active run" };
@@ -75810,8 +76121,8 @@ __export(serve_exports, {
75810
76121
  runCompanionServe: () => runCompanionServe
75811
76122
  });
75812
76123
  import { createServer as createServer3 } from "node:http";
75813
- import { existsSync as existsSync62 } from "node:fs";
75814
- import { resolve as resolve9 } from "node:path";
76124
+ import { existsSync as existsSync62, readdirSync as readdirSync13 } from "node:fs";
76125
+ import { join as join50, resolve as resolve10 } from "node:path";
75815
76126
  function readBody(req, max = 2e6) {
75816
76127
  return new Promise((resolveBody, reject) => {
75817
76128
  const chunks = [];
@@ -75858,7 +76169,7 @@ async function runCompanionServe(opts = {}) {
75858
76169
  const { token, created } = loadOrCreateToken(opts.token);
75859
76170
  let projects = mergeProjects(fileCfg, opts.projects ?? []);
75860
76171
  projects = projects.filter((p3) => {
75861
- const abs = resolve9(p3.path);
76172
+ const abs = resolve10(p3.path);
75862
76173
  if (!existsSync62(abs)) {
75863
76174
  process.stderr.write(
75864
76175
  `[zelari-code serve] skip missing project path: ${p3.path}
@@ -75878,7 +76189,7 @@ async function runCompanionServe(opts = {}) {
75878
76189
  });
75879
76190
  }
75880
76191
  if (projects.length === 0) {
75881
- const cwd = resolve9(process.cwd());
76192
+ const cwd = resolve10(process.cwd());
75882
76193
  projects = [
75883
76194
  {
75884
76195
  id: "default",
@@ -75894,6 +76205,7 @@ async function runCompanionServe(opts = {}) {
75894
76205
  );
75895
76206
  }
75896
76207
  const runs = new RunManager();
76208
+ const fsFull = (opts.fsMode ?? "full") === "full";
75897
76209
  const allowedOrigins = [
75898
76210
  ...loopbackOrigins(port),
75899
76211
  ...parseAllowedOrigins(process.env[COMPANION_ALLOWED_ORIGINS_ENV])
@@ -75935,6 +76247,65 @@ async function runCompanionServe(opts = {}) {
75935
76247
  sendJson2(res, 200, { ok: true, ...snap });
75936
76248
  return;
75937
76249
  }
76250
+ if (req.method === "GET" && path104 === "/v1/fs") {
76251
+ const listFsRootsInline = () => {
76252
+ if (process.platform === "win32") {
76253
+ const roots = [];
76254
+ for (let code = 65; code <= 90; code++) {
76255
+ const letter = String.fromCharCode(code);
76256
+ const drive = `${letter}:\\`;
76257
+ if (existsSync62(drive)) {
76258
+ roots.push({
76259
+ id: `fs-${letter.toLowerCase()}`,
76260
+ name: drive,
76261
+ path: drive
76262
+ });
76263
+ }
76264
+ }
76265
+ return roots;
76266
+ }
76267
+ return [{ id: "fs-root", name: "/", path: "/" }];
76268
+ };
76269
+ const rawParam = url2.searchParams.get("path")?.trim() ?? "";
76270
+ if (!rawParam) {
76271
+ sendJson2(res, 200, {
76272
+ ok: true,
76273
+ roots: fsFull ? listFsRootsInline() : projects,
76274
+ entries: []
76275
+ });
76276
+ return;
76277
+ }
76278
+ const norm = rawParam.replace(/\\/g, "/").replace(/\/+$/, "");
76279
+ const absolute = /^([a-zA-Z]:\/|\/)/.test(norm);
76280
+ const allowed = fsFull && absolute && !norm.split("/").includes("..") ? true : isUnderRoots(rawParam, projects).ok;
76281
+ if (!allowed) {
76282
+ sendJson2(res, 403, { ok: false, error: "path outside allowed roots" });
76283
+ return;
76284
+ }
76285
+ if (!existsSync62(rawParam)) {
76286
+ sendJson2(res, 404, { ok: false, error: "path not found" });
76287
+ return;
76288
+ }
76289
+ const dirs = readdirSync13(rawParam, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).sort((a, b) => a.name.localeCompare(b.name));
76290
+ const slash = norm.lastIndexOf("/");
76291
+ let parent = slash > 0 ? norm.slice(0, slash) : null;
76292
+ const rootHit = isUnderRoots(rawParam, projects);
76293
+ if (rootHit.ok) {
76294
+ const rootNorm = rootHit.root.path.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
76295
+ if (rootHit.normalized === rootNorm) parent = null;
76296
+ }
76297
+ sendJson2(res, 200, {
76298
+ ok: true,
76299
+ path: rawParam,
76300
+ parent,
76301
+ entries: dirs.map((d) => ({
76302
+ name: d.name,
76303
+ path: join50(rawParam, d.name),
76304
+ dir: true
76305
+ }))
76306
+ });
76307
+ return;
76308
+ }
75938
76309
  if (req.method === "GET" && path104 === "/v1/projects") {
75939
76310
  sendJson2(res, 200, {
75940
76311
  ok: true,
@@ -75977,27 +76348,37 @@ async function runCompanionServe(opts = {}) {
75977
76348
  const mode = String(body.mode ?? "kraken");
75978
76349
  const phase2 = String(body.phase ?? "build");
75979
76350
  const cwdArg = body.cwd != null ? String(body.cwd) : body.projectId != null ? String(body.projectId) : null;
75980
- const resolved = resolveProjectPath(projects, cwdArg);
76351
+ const resolved = resolveProjectPath(projects, cwdArg, { fullFs: fsFull });
75981
76352
  if (!resolved.ok) {
75982
76353
  sendJson2(res, 400, { ok: false, error: resolved.error });
75983
76354
  return;
75984
76355
  }
75985
76356
  const history2 = Array.isArray(body.history) ? body.history : void 0;
75986
- const result = runs.start({
76357
+ const presetRaw = typeof body.permissionPreset === "string" ? body.permissionPreset.trim().toLowerCase() : "";
76358
+ const permissionPreset = ["standard", "strict", "yolo"].includes(presetRaw) ? presetRaw : void 0;
76359
+ const strictDone = typeof body.strictDone === "boolean" ? body.strictDone : void 0;
76360
+ const verifyPack = typeof body.verifyPack === "boolean" ? body.verifyPack : void 0;
76361
+ const startArgs = {
75987
76362
  prompt,
75988
76363
  mode,
75989
76364
  phase: phase2,
75990
76365
  cwd: resolved.project.path,
75991
76366
  provider: body.provider != null ? String(body.provider) : void 0,
75992
76367
  model: body.model != null ? String(body.model) : void 0,
75993
- history: history2
75994
- });
76368
+ history: history2,
76369
+ permissionPreset,
76370
+ strictDone,
76371
+ verifyPack
76372
+ };
76373
+ const gate = fsFull && !resolved.trusted ? { awaitingTrust: true } : void 0;
76374
+ const result = runs.start(startArgs, gate);
75995
76375
  if (!result.ok) {
75996
76376
  sendJson2(res, 409, { ok: false, error: result.error });
75997
76377
  return;
75998
76378
  }
75999
76379
  sendJson2(res, 201, {
76000
76380
  ok: true,
76381
+ awaitingTrust: gate ? true : void 0,
76001
76382
  run: {
76002
76383
  id: result.run.id,
76003
76384
  status: result.run.status,
@@ -76008,10 +76389,57 @@ async function runCompanionServe(opts = {}) {
76008
76389
  },
76009
76390
  eventsUrl: `/v1/runs/${result.run.id}/events`,
76010
76391
  cancelUrl: `/v1/runs/${result.run.id}/cancel`,
76011
- steerUrl: `/v1/runs/${result.run.id}/steer`
76392
+ steerUrl: `/v1/runs/${result.run.id}/steer`,
76393
+ permissionUrl: `/v1/runs/${result.run.id}/permission`,
76394
+ askUrl: `/v1/runs/${result.run.id}/ask`
76012
76395
  });
76013
76396
  return;
76014
76397
  }
76398
+ if (req.method === "POST" && path104 === "/v1/trust") {
76399
+ const raw = await readBody(req);
76400
+ let body = {};
76401
+ try {
76402
+ body = raw ? JSON.parse(raw) : {};
76403
+ } catch {
76404
+ sendJson2(res, 400, { ok: false, error: "invalid JSON body" });
76405
+ return;
76406
+ }
76407
+ const runId = typeof body.runId === "string" ? body.runId.trim() : "";
76408
+ if (!runId) {
76409
+ sendJson2(res, 400, { ok: false, error: "runId is required" });
76410
+ return;
76411
+ }
76412
+ const current = runs.pendingTrust();
76413
+ if (!current || current.runId !== runId) {
76414
+ sendJson2(res, 409, { ok: false, error: "no such awaiting_trust run" });
76415
+ return;
76416
+ }
76417
+ if (body.approve === true) {
76418
+ projects = mergeProjects({ ...fileCfg, projects }, [current.path]);
76419
+ try {
76420
+ saveCompanionConfig({ ...fileCfg, bind, port, projects });
76421
+ } catch {
76422
+ }
76423
+ const released = runs.releaseTrust();
76424
+ if (!released.ok) {
76425
+ sendJson2(res, 409, { ok: false, error: released.error });
76426
+ return;
76427
+ }
76428
+ sendJson2(res, 200, { ok: true, approved: runId, path: current.path });
76429
+ return;
76430
+ }
76431
+ const denied = runs.denyTrust("Trust negato sul desktop");
76432
+ if (!denied.ok) {
76433
+ sendJson2(res, 409, { ok: false, error: denied.error });
76434
+ return;
76435
+ }
76436
+ sendJson2(res, 200, { ok: true, denied: runId });
76437
+ return;
76438
+ }
76439
+ if (req.method === "GET" && path104 === "/v1/trust/pending") {
76440
+ sendJson2(res, 200, { ok: true, pending: runs.pendingTrust() });
76441
+ return;
76442
+ }
76015
76443
  const eventsMatch = /^\/v1\/runs\/([^/]+)\/events$/.exec(path104);
76016
76444
  if (req.method === "GET" && eventsMatch) {
76017
76445
  const runId = eventsMatch[1];
@@ -76039,7 +76467,7 @@ async function runCompanionServe(opts = {}) {
76039
76467
  }
76040
76468
  };
76041
76469
  const unsub = runs.subscribe(runId, writeEv);
76042
- if (run.status !== "running" && run.status !== "queued") {
76470
+ if (run.status !== "running" && run.status !== "queued" && run.status !== "awaiting_trust") {
76043
76471
  writeEv({
76044
76472
  type: "run_finished",
76045
76473
  runId,
@@ -76065,7 +76493,7 @@ async function runCompanionServe(opts = {}) {
76065
76493
  req.on("close", onClose);
76066
76494
  const check2 = setInterval(() => {
76067
76495
  const r = runs.getRun(runId);
76068
- if (!r || r.status !== "running" && r.status !== "queued") {
76496
+ if (!r || r.status !== "running" && r.status !== "queued" && r.status !== "awaiting_trust") {
76069
76497
  clearInterval(check2);
76070
76498
  clearInterval(heartbeat);
76071
76499
  unsub();
@@ -76112,6 +76540,53 @@ async function runCompanionServe(opts = {}) {
76112
76540
  sendJson2(res, 200, { ok: true, steered: runId, result: result.result });
76113
76541
  return;
76114
76542
  }
76543
+ const askMatch = /^\/v1\/runs\/([^/]+)\/(permission|ask)$/.exec(path104);
76544
+ if (req.method === "POST" && askMatch) {
76545
+ const runId = askMatch[1];
76546
+ const kind2 = askMatch[2];
76547
+ const raw = await readBody(req);
76548
+ let body = {};
76549
+ try {
76550
+ body = raw ? JSON.parse(raw) : {};
76551
+ } catch {
76552
+ sendJson2(res, 400, { ok: false, error: "invalid JSON body" });
76553
+ return;
76554
+ }
76555
+ const requestId = typeof body.requestId === "string" ? body.requestId.trim() : "";
76556
+ if (!requestId) {
76557
+ sendJson2(res, 400, { ok: false, error: "requestId is required" });
76558
+ return;
76559
+ }
76560
+ if (kind2 === "permission") {
76561
+ const decision = typeof body.decision === "string" ? body.decision.trim() : "";
76562
+ if (!["allow", "deny", "always-tool", "always-category"].includes(decision)) {
76563
+ sendJson2(res, 400, {
76564
+ ok: false,
76565
+ error: "decision must be allow|deny|always-tool|always-category"
76566
+ });
76567
+ return;
76568
+ }
76569
+ const result2 = await runs.permissionRespond(runId, requestId, decision);
76570
+ if (!result2.ok) {
76571
+ sendJson2(res, 404, { ok: false, error: result2.error });
76572
+ return;
76573
+ }
76574
+ sendJson2(res, 200, { ok: true, runId, requestId, result: result2.result });
76575
+ return;
76576
+ }
76577
+ const answer = body.answer == null ? null : typeof body.answer === "string" ? body.answer : void 0;
76578
+ if (answer === void 0) {
76579
+ sendJson2(res, 400, { ok: false, error: "answer must be a string or null" });
76580
+ return;
76581
+ }
76582
+ const result = await runs.askUserRespond(runId, requestId, answer);
76583
+ if (!result.ok) {
76584
+ sendJson2(res, 404, { ok: false, error: result.error });
76585
+ return;
76586
+ }
76587
+ sendJson2(res, 200, { ok: true, runId, requestId, result: result.result });
76588
+ return;
76589
+ }
76115
76590
  sendJson2(res, 404, { ok: false, error: "not found" });
76116
76591
  } catch (err) {
76117
76592
  sendJson2(res, 500, {
@@ -76187,12 +76662,14 @@ function parseServeFlags(argv) {
76187
76662
  }
76188
76663
  const portRaw = get("--port");
76189
76664
  const port = portRaw ? Number.parseInt(portRaw, 10) : void 0;
76665
+ const fsRaw = get("--fs");
76190
76666
  return {
76191
76667
  bind: get("--bind"),
76192
76668
  port: Number.isFinite(port) ? port : void 0,
76193
76669
  token: get("--token"),
76194
76670
  projects,
76195
- persistProjects: argv.includes("--save-projects")
76671
+ persistProjects: argv.includes("--save-projects"),
76672
+ fsMode: fsRaw === "allowlist" ? "allowlist" : fsRaw === "full" ? "full" : void 0
76196
76673
  };
76197
76674
  }
76198
76675
  var init_serve = __esm({
@@ -76253,8 +76730,8 @@ __export(doctor_exports, {
76253
76730
  firstBlockingRed: () => firstBlockingRed,
76254
76731
  runDoctor: () => runDoctor
76255
76732
  });
76256
- import { execSync as execSync2 } from "node:child_process";
76257
- import { existsSync as existsSync63, readFileSync as readFileSync46, readlinkSync, statSync as statSync10 } from "node:fs";
76733
+ import { execSync as execSync3 } from "node:child_process";
76734
+ import { existsSync as existsSync63, readFileSync as readFileSync46, readlinkSync, statSync as statSync11 } from "node:fs";
76258
76735
  import { createRequire as createRequire3 } from "node:module";
76259
76736
  import { fileURLToPath as fileURLToPath3 } from "node:url";
76260
76737
  import path100 from "node:path";
@@ -76277,7 +76754,7 @@ function findPackageRoot(start) {
76277
76754
  }
76278
76755
  function tryExec(cmd) {
76279
76756
  try {
76280
- return execSync2(cmd, {
76757
+ return execSync3(cmd, {
76281
76758
  encoding: "utf8",
76282
76759
  stdio: ["ignore", "pipe", "ignore"]
76283
76760
  }).trim();
@@ -76293,7 +76770,7 @@ function readPackageJson4() {
76293
76770
  return null;
76294
76771
  }
76295
76772
  }
76296
- function getGlobalPrefix() {
76773
+ function getGlobalPrefix2() {
76297
76774
  const fromNpm = tryExec("npm prefix -g");
76298
76775
  if (fromNpm) return fromNpm;
76299
76776
  return (process.env.npm_config_prefix || process.env.NPM_CONFIG_PREFIX || "").trim();
@@ -76302,7 +76779,7 @@ function isSourceCheckout() {
76302
76779
  return existsSync63(path100.join(packageRoot, "src", "cli", "main.ts")) && existsSync63(path100.join(packageRoot, "apps", "desktop", "package.json"));
76303
76780
  }
76304
76781
  function checkShim(pkgName) {
76305
- const prefix = getGlobalPrefix();
76782
+ const prefix = getGlobalPrefix2();
76306
76783
  if (!prefix) {
76307
76784
  return WARN("npm global prefix not detectable (run inside an npm context)");
76308
76785
  }
@@ -76318,13 +76795,21 @@ function checkShim(pkgName) {
76318
76795
  optional: npm install -g ${pkgName}@latest --force`
76319
76796
  );
76320
76797
  }
76798
+ const installKind = resolveInstallKind();
76799
+ if (installKind === "npx" || installKind === "local") {
76800
+ return WARN(
76801
+ `non-global install (${installKind}) \u2014 no global shim at ${shimPath}
76802
+ PATH/shim expectations do not apply to an npx or local run
76803
+ for a persistent install: npm install -g ${pkgName}@latest`
76804
+ );
76805
+ }
76321
76806
  return FAIL(
76322
76807
  `shim not found at ${shimPath}
76323
76808
  fix: npm install -g ${pkgName}@latest --force`
76324
76809
  );
76325
76810
  }
76326
76811
  try {
76327
- const st = statSync10(shimPath);
76812
+ const st = statSync11(shimPath);
76328
76813
  if (isWin) {
76329
76814
  const content = readFileSync46(shimPath, "utf8");
76330
76815
  if (content.includes(`${pkgName}\\bin\\`) || content.includes(`${pkgName}/bin/`)) {
@@ -76395,7 +76880,7 @@ function checkBundle() {
76395
76880
  );
76396
76881
  }
76397
76882
  try {
76398
- const st = statSync10(bundle);
76883
+ const st = statSync11(bundle);
76399
76884
  return OK(`bundle OK (${(st.size / 1024 / 1024).toFixed(2)} MB)`);
76400
76885
  } catch (err) {
76401
76886
  return FAIL(
@@ -76425,7 +76910,7 @@ function checkRuntimeDeps() {
76425
76910
  );
76426
76911
  }
76427
76912
  function checkPath() {
76428
- const prefix = getGlobalPrefix();
76913
+ const prefix = getGlobalPrefix2();
76429
76914
  if (!prefix) return WARN("npm global prefix not detectable");
76430
76915
  const pathSep = process.platform === "win32" ? ";" : ":";
76431
76916
  const pathDirs = (process.env.PATH || "").split(pathSep);
@@ -76643,7 +77128,7 @@ async function runDoctor() {
76643
77128
  console.log(`zelari-code doctor (v${pkg?.version ?? "unknown"})`);
76644
77129
  console.log("platform:", process.platform, process.arch);
76645
77130
  console.log("node: ", process.version);
76646
- console.log("prefix: ", getGlobalPrefix() || "(unknown)");
77131
+ console.log("prefix: ", getGlobalPrefix2() || "(unknown)");
76647
77132
  console.log("root: ", packageRoot);
76648
77133
  console.log("");
76649
77134
  let criticalFails = 0;
@@ -76684,6 +77169,7 @@ var init_doctor = __esm({
76684
77169
  init_prereqChecks();
76685
77170
  init_metrics3();
76686
77171
  init_contextGrowthSummary();
77172
+ init_updater();
76687
77173
  require3 = createRequire3(import.meta.url);
76688
77174
  __dirname3 = path100.dirname(fileURLToPath3(import.meta.url));
76689
77175
  packageRoot = findPackageRoot(__dirname3);
@@ -76711,7 +77197,7 @@ __export(fixPath_exports, {
76711
77197
  repairWindowsUserPath: () => repairWindowsUserPath
76712
77198
  });
76713
77199
  import { spawnSync as spawnSync3 } from "node:child_process";
76714
- function getGlobalPrefix2() {
77200
+ function getGlobalPrefix3() {
76715
77201
  try {
76716
77202
  const fromNpm = spawnSync3("npm", ["prefix", "-g"], {
76717
77203
  encoding: "utf8",
@@ -76736,7 +77222,7 @@ function powershell(script) {
76736
77222
  }
76737
77223
  }
76738
77224
  function repairWindowsUserPath() {
76739
- const prefix = getGlobalPrefix2();
77225
+ const prefix = getGlobalPrefix3();
76740
77226
  if (!prefix) {
76741
77227
  return {
76742
77228
  ok: false,
@@ -77146,7 +77632,7 @@ __export(inspect_exports, {
77146
77632
  runInspect: () => runInspect
77147
77633
  });
77148
77634
  import path103 from "node:path";
77149
- import { existsSync as existsSync66, readFileSync as readFileSync48, readdirSync as readdirSync13 } from "node:fs";
77635
+ import { existsSync as existsSync66, readFileSync as readFileSync48, readdirSync as readdirSync14 } from "node:fs";
77150
77636
  async function collectInspectReport(cwd = process.cwd()) {
77151
77637
  ensureBuiltinSkillsLoadedSync();
77152
77638
  const snap = listSkillsSnapshot(cwd);
@@ -77215,7 +77701,7 @@ async function collectInspectReport(cwd = process.cwd()) {
77215
77701
  }
77216
77702
  function listJsonFiles(dir) {
77217
77703
  try {
77218
- return readdirSync13(dir).filter((f) => f.endsWith(".json")).sort();
77704
+ return readdirSync14(dir).filter((f) => f.endsWith(".json")).sort();
77219
77705
  } catch {
77220
77706
  return [];
77221
77707
  }
@@ -77682,12 +78168,12 @@ function mergeChanges(unstaged, staged, untrackedPaths) {
77682
78168
  return [...byPath.values()].sort((a, b) => churn(b) - churn(a));
77683
78169
  }
77684
78170
  function runGit(args, cwd) {
77685
- return new Promise((resolve10, reject) => {
78171
+ return new Promise((resolve11, reject) => {
77686
78172
  execFile(
77687
78173
  "git",
77688
78174
  args,
77689
78175
  { cwd, timeout: 5e3, windowsHide: true, maxBuffer: 4 * 1024 * 1024 },
77690
- (err, stdout) => err ? reject(err) : resolve10(stdout)
78176
+ (err, stdout) => err ? reject(err) : resolve11(stdout)
77691
78177
  );
77692
78178
  });
77693
78179
  }
@@ -80266,7 +80752,7 @@ init_toolPermissions();
80266
80752
  init_askUserTimeout();
80267
80753
  function createPermissionAskHandler(opts) {
80268
80754
  const { setPicker: setPicker2, appendSystem: appendSystem2 } = opts;
80269
- return (req) => new Promise((resolve10) => {
80755
+ return (req) => new Promise((resolve11) => {
80270
80756
  const cats = req.categories;
80271
80757
  const catLabel = cats.length === 1 ? cats[0] : cats.join("+") || "action";
80272
80758
  const title = `Allow tool "${req.toolName}"?`;
@@ -80292,7 +80778,7 @@ ${detail}${note}${claimsBlock}
80292
80778
  cancelAskTimeout();
80293
80779
  setPicker2(null);
80294
80780
  if (note2) appendSystem2?.(note2, Date.now());
80295
- resolve10(ok);
80781
+ resolve11(ok);
80296
80782
  };
80297
80783
  cancelAskTimeout = armPickerTimeout(
80298
80784
  () => finish2(
@@ -80491,10 +80977,10 @@ function useChatTurn(params) {
80491
80977
  }
80492
80978
  } catch {
80493
80979
  }
80494
- const onAskUser = setPicker2 ? (req) => new Promise((resolve10) => {
80980
+ const onAskUser = setPicker2 ? (req) => new Promise((resolve11) => {
80495
80981
  const choices = req.choices ?? [];
80496
80982
  if (choices.length < 2) {
80497
- resolve10(null);
80983
+ resolve11(null);
80498
80984
  return;
80499
80985
  }
80500
80986
  setLastClarification({
@@ -80518,7 +81004,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il turno continua dopo
80518
81004
  settled = true;
80519
81005
  cancelAskTimeout();
80520
81006
  setPicker2(null);
80521
- resolve10(value);
81007
+ resolve11(value);
80522
81008
  };
80523
81009
  cancelAskTimeout = armPickerTimeout(
80524
81010
  () => {
@@ -81464,10 +81950,10 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
81464
81950
  const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
81465
81951
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
81466
81952
  const workPhase = getPhase();
81467
- const onAskUserCouncil = setPicker2 ? (req) => new Promise((resolve10) => {
81953
+ const onAskUserCouncil = setPicker2 ? (req) => new Promise((resolve11) => {
81468
81954
  const choices = req.choices ?? [];
81469
81955
  if (choices.length < 2) {
81470
- resolve10(null);
81956
+ resolve11(null);
81471
81957
  return;
81472
81958
  }
81473
81959
  setLastClarification({
@@ -81491,7 +81977,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
81491
81977
  settled = true;
81492
81978
  cancelAskTimeout();
81493
81979
  setPicker2(null);
81494
- resolve10(value);
81980
+ resolve11(value);
81495
81981
  };
81496
81982
  cancelAskTimeout = armPickerTimeout(
81497
81983
  () => {
@@ -81673,10 +82159,10 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
81673
82159
  appendSystem(setMessages, message, Date.now());
81674
82160
  },
81675
82161
  // v1.8.0: pause council when a member asks a structured question.
81676
- onClarification: setPicker2 ? (req) => new Promise((resolve10) => {
82162
+ onClarification: setPicker2 ? (req) => new Promise((resolve11) => {
81677
82163
  const choices = req.choices ?? [];
81678
82164
  if (choices.length < 2) {
81679
- resolve10(null);
82165
+ resolve11(null);
81680
82166
  return;
81681
82167
  }
81682
82168
  setLastClarification({
@@ -81697,7 +82183,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
81697
82183
  if (settled) return;
81698
82184
  settled = true;
81699
82185
  setPicker2(null);
81700
- resolve10(value);
82186
+ resolve11(value);
81701
82187
  };
81702
82188
  setPicker2({
81703
82189
  kind: "clarification",
@@ -82355,10 +82841,10 @@ function usePermissionBroker(opts) {
82355
82841
  setPicker: setPicker2,
82356
82842
  appendSystem: (msg, at) => appendSystem(setMessages, msg, at ?? Date.now())
82357
82843
  });
82358
- const onQuestion = (req) => new Promise((resolve10) => {
82844
+ const onQuestion = (req) => new Promise((resolve11) => {
82359
82845
  const choices = (req.choices ?? []).map((c) => String(c).trim()).filter((c) => c.length > 0);
82360
82846
  if (choices.length < 2) {
82361
- resolve10(null);
82847
+ resolve11(null);
82362
82848
  return;
82363
82849
  }
82364
82850
  let settled = false;
@@ -82368,7 +82854,7 @@ function usePermissionBroker(opts) {
82368
82854
  settled = true;
82369
82855
  cancelWaitTimeout?.();
82370
82856
  setPicker2(null);
82371
- resolve10(value);
82857
+ resolve11(value);
82372
82858
  };
82373
82859
  const waitTimeoutMs = askUserTimeoutMs();
82374
82860
  cancelWaitTimeout = armPickerTimeout(() => {
@@ -84622,7 +85108,8 @@ async function handleUpdateCheck(ctx) {
84622
85108
  appendSystem(
84623
85109
  ctx.setMessages,
84624
85110
  `[update] \u{1F195} zelari-code ${info.latestVersion} available (current: ${info.currentVersion})
84625
- Run \`/update --yes\` to install. You'll need to restart manually after.`
85111
+ Run \`/update --yes\` to install (global installs only).
85112
+ On npx/local installs, update with \`npx zelari-code@latest\` \u2014 you'll need to restart manually after.`
84626
85113
  );
84627
85114
  } else {
84628
85115
  appendSystem(
@@ -84639,7 +85126,18 @@ async function handleUpdateCheck(ctx) {
84639
85126
  }
84640
85127
  async function handleUpdatePerform(ctx) {
84641
85128
  try {
84642
- const { performUpdate: performUpdate2, distTagForVersion: distTagForVersion2, getCurrentVersion: getCurrentVersion2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
85129
+ const {
85130
+ performUpdate: performUpdate2,
85131
+ resolveInstallKind: resolveInstallKind2,
85132
+ nonGlobalUpdateAdvisory: nonGlobalUpdateAdvisory2,
85133
+ distTagForVersion: distTagForVersion2,
85134
+ getCurrentVersion: getCurrentVersion2
85135
+ } = await Promise.resolve().then(() => (init_updater(), updater_exports));
85136
+ const kind2 = resolveInstallKind2();
85137
+ if (kind2 === "npx" || kind2 === "local") {
85138
+ appendSystem(ctx.setMessages, nonGlobalUpdateAdvisory2(kind2));
85139
+ return;
85140
+ }
84643
85141
  const tag = distTagForVersion2(getCurrentVersion2());
84644
85142
  appendSystem(
84645
85143
  ctx.setMessages,
@@ -84746,7 +85244,7 @@ ${browsers.output}` : "") + (browsers.ok ? "" : `
84746
85244
  return result;
84747
85245
  }
84748
85246
  function runPlaywrightInstallChromium(cwd, executor) {
84749
- return new Promise((resolve10) => {
85247
+ return new Promise((resolve11) => {
84750
85248
  let stdout = "";
84751
85249
  let stderr = "";
84752
85250
  const stdio = ["ignore", "pipe", "pipe"];
@@ -84759,7 +85257,7 @@ function runPlaywrightInstallChromium(cwd, executor) {
84759
85257
  stderr += chunk.toString();
84760
85258
  });
84761
85259
  child.on("error", (err) => {
84762
- resolve10({
85260
+ resolve11({
84763
85261
  ok: false,
84764
85262
  output: stdout + stderr,
84765
85263
  error: err.message,
@@ -84768,7 +85266,7 @@ function runPlaywrightInstallChromium(cwd, executor) {
84768
85266
  });
84769
85267
  child.on("close", (code) => {
84770
85268
  const ok = code === 0;
84771
- resolve10({
85269
+ resolve11({
84772
85270
  ok,
84773
85271
  output: stdout + stderr,
84774
85272
  exitCode: code,
@@ -84778,7 +85276,7 @@ function runPlaywrightInstallChromium(cwd, executor) {
84778
85276
  });
84779
85277
  }
84780
85278
  function runNpm2(executor, args, cwd, mode, npmCliPath) {
84781
- return new Promise((resolve10) => {
85279
+ return new Promise((resolve11) => {
84782
85280
  let stdout = "";
84783
85281
  let stderr = "";
84784
85282
  const stdio = ["ignore", "pipe", "pipe"];
@@ -84790,7 +85288,7 @@ function runNpm2(executor, args, cwd, mode, npmCliPath) {
84790
85288
  stderr += chunk.toString();
84791
85289
  });
84792
85290
  child.on("error", (err) => {
84793
- resolve10({
85291
+ resolve11({
84794
85292
  ok: false,
84795
85293
  output: stdout + stderr,
84796
85294
  error: err.message,
@@ -84799,7 +85297,7 @@ function runNpm2(executor, args, cwd, mode, npmCliPath) {
84799
85297
  });
84800
85298
  child.on("close", (code) => {
84801
85299
  const ok = code === 0;
84802
- resolve10({
85300
+ resolve11({
84803
85301
  ok,
84804
85302
  output: stdout + stderr,
84805
85303
  exitCode: code,
@@ -87263,7 +87761,7 @@ function runPreflight() {
87263
87761
  }
87264
87762
  async function backgroundUpdateCheck() {
87265
87763
  if (process.env.ANATHEMA_DEV === "1") return;
87266
- await new Promise((resolve10) => setTimeout(resolve10, 3e3));
87764
+ await new Promise((resolve11) => setTimeout(resolve11, 3e3));
87267
87765
  try {
87268
87766
  const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_updater(), updater_exports));
87269
87767
  const info = await checkForUpdate2();
@@ -88064,7 +88562,7 @@ function main() {
88064
88562
  closeMcpClients2();
88065
88563
  } catch {
88066
88564
  }
88067
- await new Promise((resolve10) => setImmediate(resolve10));
88565
+ await new Promise((resolve11) => setImmediate(resolve11));
88068
88566
  process.exit(code);
88069
88567
  }).catch(async (err) => {
88070
88568
  console.error(