codeden 0.6.3 → 0.7.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 (42) hide show
  1. package/README.md +12 -5
  2. package/dist/cli.js +1 -1
  3. package/dist/drizzle/20261113075317_transcript_search/migration.sql +29 -0
  4. package/dist/drizzle/20261113075317_transcript_search/snapshot.json +3635 -0
  5. package/dist/drizzle/20261113083655_runners/migration.sql +17 -0
  6. package/dist/drizzle/20261113083655_runners/snapshot.json +3792 -0
  7. package/dist/drizzle/20261113165404_search_newest_first/migration.sql +43 -0
  8. package/dist/drizzle/20261113165404_search_newest_first/snapshot.json +3785 -0
  9. package/dist/{main-Bx58CdV4.js → main-BITVD_Bx.js} +4781 -1305
  10. package/dist/main-BITVD_Bx.js.map +1 -0
  11. package/dist/{main-CMt4z1Z9.js → main-NN0iK2tB.js} +2 -2
  12. package/dist/{main-CMt4z1Z9.js.map → main-NN0iK2tB.js.map} +1 -1
  13. package/dist/{mediaType-BCz-hbZl.js → mediaType-N0hKQqG9.js} +2 -2
  14. package/dist/{mediaType-BCz-hbZl.js.map → mediaType-N0hKQqG9.js.map} +1 -1
  15. package/dist/{runner-CiaiUWOE.js → runner-CQYh087M.js} +1302 -304
  16. package/dist/runner-CQYh087M.js.map +1 -0
  17. package/dist/runner.js +1 -1
  18. package/dist/{server-test-b0hHPqXb.js → server-test-BwyCFssm.js} +3 -3
  19. package/dist/{server-test-b0hHPqXb.js.map → server-test-BwyCFssm.js.map} +1 -1
  20. package/dist/{tree-31WAOm5k.js → tree-Bjv-rXir.js} +457 -21
  21. package/dist/tree-Bjv-rXir.js.map +1 -0
  22. package/dist/web/assets/{core-FqX7NQzW.js → core-EsMgAasL.js} +1 -1
  23. package/dist/web/assets/engine-oniguruma-C2eJcn5h.js +1 -0
  24. package/dist/web/assets/{highlighted-body-JLMTFACH-BMKFJRL-.js → highlighted-body-JLMTFACH-Co1oXN1b.js} +1 -1
  25. package/dist/web/assets/{highlights-9yuGk1pg.js → highlights-DUjJZrPQ.js} +1 -1
  26. package/dist/web/assets/index-29XyPQXD.css +1 -0
  27. package/dist/web/assets/index-DK4GsIYY.js +943 -0
  28. package/dist/web/assets/langs-BduZAjBk.js +1 -0
  29. package/dist/web/assets/mermaid-WFAXDIZX-D00b7K53.js +1 -0
  30. package/dist/web/assets/{shiki-Qa-y3xHo.js → shiki-DP8JfDZC.js} +2 -2
  31. package/dist/web/assets/{stream-tokenizer-VL43-ily.js → stream-tokenizer-IR1cpBIQ.js} +1 -1
  32. package/dist/web/assets/{themes-Di4DO5zr.js → themes-D2aixyve.js} +2 -2
  33. package/dist/web/index.html +2 -2
  34. package/package.json +1 -1
  35. package/dist/main-Bx58CdV4.js.map +0 -1
  36. package/dist/runner-CiaiUWOE.js.map +0 -1
  37. package/dist/tree-31WAOm5k.js.map +0 -1
  38. package/dist/web/assets/engine-oniguruma-C5UP18m1.js +0 -1
  39. package/dist/web/assets/index-C3QkKvUn.js +0 -942
  40. package/dist/web/assets/index-CmllrYFx.css +0 -1
  41. package/dist/web/assets/langs-DfCgj44a.js +0 -1
  42. package/dist/web/assets/mermaid-WFAXDIZX-zTA5f8lD.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import { n as __require, r as __toESM, t as __commonJSMin } from "./rolldown-runtime-DegdyBIC.js";
2
- import { Ar as ReasoningEffort, Bn as BROWSER_RELAY_SECRET_HEADER, Hn as RuntimeResponse, Vn as ProviderCapabilities, Wn as ProjectConfigFile, a as childEnv, ar as winningCommands, d as RunnerLock, er as AgentEvent, i as listDescendants, ir as oneLineDescription, m as ServerToRunnerMessage, n as killTree$1, nr as mergeItemDeltas, o as CODEDEN_VERSION, or as itemDeltaText, sr as UserInput, t as isAlive$1, u as RunnerEventMessage, zn as SETUP_TAIL_LIMIT } from "./tree-31WAOm5k.js";
2
+ import { $n as ProjectConfigFile, Hn as SETUP_TAIL_LIMIT, Un as BROWSER_RELAY_SECRET_HEADER, Xn as ProviderCapabilities, Yn as CODEDEN_CREDENTIAL_FILES, Zn as RuntimeResponse, a as childEnv, d as RunnerEventMessage, dr as mergeItemDeltas, f as RunnerLock, gr as UserInput, h as ServerToRunnerMessage, hr as itemDeltaText, i as listDescendants, lr as AgentEvent, mr as winningCommands, n as killTree$1, o as CODEDEN_VERSION, pr as oneLineDescription, qn as MAX_FOCUS_TYPE_CHARS, t as isAlive$1, zr as ReasoningEffort } from "./tree-Bjv-rXir.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { accessSync, appendFileSync, chmodSync, closeSync, constants, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
5
5
  import { pathToFileURL } from "node:url";
@@ -4521,18 +4521,29 @@ const reasonOf = (error) => error instanceof Error ? error.message : String(erro
4521
4521
  * Copy a Claude conversation with the SDK's `forkSession()`: the transcript is written to a new
4522
4522
  * session file at once (fresh message ids, the original untouched) and the new session id is
4523
4523
  * returned for resuming. With `upTo` (a turn's fork point: its last transcript message), the
4524
- * transcript is read first (`getSessionMessages`) to confirm it still holds that message, and
4525
- * the copy ends with it. A conversation that no longer holds it (replaced or deleted) throws
4526
- * `FORK_POINT_MISSING` before anything is copied, so it never becomes a whole copy instead.
4524
+ * copy ends with that message. A conversation that no longer holds it (replaced or deleted)
4525
+ * throws `FORK_POINT_MISSING` before anything is copied, so it never becomes a whole copy
4526
+ * instead: `confirmForkPoint` refuses it from the transcript read when it can, and otherwise
4527
+ * `forkSession()` does, as it slices the whole transcript file and refuses a message the file
4528
+ * doesn't hold before writing anything (SDK 0.3.318: `Message <uuid> not found in session <id>`).
4527
4529
  */
4528
4530
  async function forkClaudeConversation(sdk, resumeToken, upTo) {
4529
4531
  if (upTo) await confirmForkPoint(sdk, resumeToken, upTo);
4530
4532
  try {
4531
4533
  return (await sdk.forkSession(resumeToken, upTo ? { upToMessageId: upTo } : void 0)).sessionId;
4532
4534
  } catch (error) {
4535
+ if (upTo && refusedForkPoint(error, upTo)) throw forkPointMissing();
4533
4536
  throw new Error(`Could not fork the Claude conversation ${resumeToken}: ${reasonOf(error)}`);
4534
4537
  }
4535
4538
  }
4539
+ /**
4540
+ * Read the transcript (`getSessionMessages`) and refuse a fork point it doesn't hold. That read
4541
+ * follows `parentUuid` links back from the newest message, and a context compaction's boundary
4542
+ * has none, so in a compacted conversation it starts at the latest boundary (a `system`
4543
+ * message) and leaves out every earlier message, although the transcript file still holds
4544
+ * them. A fork point missing from such a read may be before the compaction, so `forkSession()`
4545
+ * decides then. An empty read (a conversation that was deleted) holds nothing.
4546
+ */
4536
4547
  async function confirmForkPoint(sdk, resumeToken, upTo) {
4537
4548
  let messages;
4538
4549
  try {
@@ -4540,7 +4551,15 @@ async function confirmForkPoint(sdk, resumeToken, upTo) {
4540
4551
  } catch (error) {
4541
4552
  throw new Error(`Could not read the Claude conversation ${resumeToken}: ${reasonOf(error)}`);
4542
4553
  }
4543
- if (!messages.some((m) => m.uuid === upTo)) throw new ProviderSessionError(FORK_POINT_MISSING, "The conversation no longer holds the end of that turn (it was replaced or deleted), so it can't be forked there");
4554
+ if (!(messages[0]?.type === "system") && !messages.some((m) => m.uuid === upTo)) throw forkPointMissing();
4555
+ }
4556
+ /** Whether `forkSession()` refused `upTo` as a message its transcript doesn't hold. */
4557
+ function refusedForkPoint(error, upTo) {
4558
+ const reason = reasonOf(error);
4559
+ return reason.includes(upTo) && /\bnot found\b/i.test(reason);
4560
+ }
4561
+ function forkPointMissing() {
4562
+ return new ProviderSessionError(FORK_POINT_MISSING, "The conversation no longer holds the end of that turn (it was replaced or deleted), so it can't be forked there");
4544
4563
  }
4545
4564
  // ../../packages/providers/src/util/model.ts
4546
4565
  /** The id the model pickers use for "the provider's own default model". */
@@ -4553,8 +4572,26 @@ function explicitModel(model) {
4553
4572
  const id = model?.trim();
4554
4573
  return id && id !== "default" ? id : void 0;
4555
4574
  }
4575
+ // ../../packages/providers/src/policy/assessment.ts
4576
+ const SAFE = { risky: false };
4577
+ const risky = (action, reason) => ({
4578
+ risky: true,
4579
+ action,
4580
+ reason
4581
+ });
4582
+ /** True for a verdict on CodeDen's own credential files. */
4583
+ const touchesCodeden = (risk) => risk.risky && risk.codeden === true;
4584
+ /**
4585
+ * The verdict for several checks of one action: CodeDen's own credentials first (a level may
4586
+ * allow an ordinary risk that comes before them), then the first risky one, else safe.
4587
+ */
4588
+ function mostSevere(risks) {
4589
+ return risks.find(touchesCodeden) ?? risks.find((r) => r.risky) ?? SAFE;
4590
+ }
4556
4591
  // ../../packages/providers/src/policy/paths.ts
4592
+ /** True when the context's paths are Windows paths. */
4557
4593
  const isWin = (ctx) => (ctx.platform ?? process.platform) === "win32";
4594
+ /** The path functions for the context's platform. */
4558
4595
  function pathApi(ctx) {
4559
4596
  return isWin(ctx) ? path.win32 : path.posix;
4560
4597
  }
@@ -4571,6 +4608,8 @@ const HOME_VARS = /^(\$HOME|\$\{HOME\}|\$env:USERPROFILE|%USERPROFILE%)(?=$|[\\/
4571
4608
  * printed) is not the temp directory; PowerShell's and cmd's names are not.
4572
4609
  */
4573
4610
  const TEMP_VARS = [/^(\$TMPDIR|\$\{TMPDIR\}|\$TEMP|\$\{TEMP\}|\$TMP|\$\{TMP\})(?=$|[\\/])/, /^(\$env:TEMP|\$env:TMP|%TEMP%|%TMP%)(?=$|[\\/])/i];
4611
+ /** CodeDen's home variable, which agents inherit: case-sensitive in POSIX shells only. */
4612
+ const CODEDEN_HOME_VARS = [/^(\$CODEDEN_HOME|\$\{CODEDEN_HOME\})(?=$|[\\/])/, /^(\$env:CODEDEN_HOME|%CODEDEN_HOME%)(?=$|[\\/])/i];
4574
4613
  /**
4575
4614
  * Git Bash paths on Windows: `/c/Users/x` is `C:\Users\x`, and `/tmp` is the user's temp
4576
4615
  * directory.
@@ -4582,9 +4621,9 @@ function fromGitBash(value, temp) {
4582
4621
  return value;
4583
4622
  }
4584
4623
  /**
4585
- * Resolve `target` (absolute, relative to `dir`, `~`-prefixed, or starting with a home or
4586
- * temp variable) to an absolute path. On Windows the directory it resolves from may be in Git
4587
- * Bash form too: the shell reports `/c/Users/x/repo` once a command has run in it.
4624
+ * Resolve `target` (absolute, relative to `dir`, `~`-prefixed, or starting with a home, temp or
4625
+ * CodeDen home variable) to an absolute path. On Windows the directory it resolves from may be
4626
+ * in Git Bash form too: the shell reports `/c/Users/x/repo` once a command has run in it.
4588
4627
  */
4589
4628
  function resolveAgainst(target, ctx) {
4590
4629
  const p = pathApi(ctx);
@@ -4592,6 +4631,8 @@ function resolveAgainst(target, ctx) {
4592
4631
  const home = homeOf(ctx);
4593
4632
  if (value === "~" || value.startsWith("~/") || value.startsWith("~\\")) value = home + value.slice(1);
4594
4633
  value = value.replace(HOME_VARS, home);
4634
+ const codeden = ctx.codedenHome;
4635
+ if (codeden) for (const vars of CODEDEN_HOME_VARS) value = value.replace(vars, codeden);
4595
4636
  const temp = tempRootsOf(ctx)[0];
4596
4637
  if (temp) for (const vars of TEMP_VARS) value = value.replace(vars, temp);
4597
4638
  let base = ctx.dir ?? ctx.cwd;
@@ -4611,6 +4652,10 @@ function relative$1(root, target, ctx) {
4611
4652
  const norm = (s) => isWin(ctx) ? p.resolve(s).toLowerCase() : p.resolve(s);
4612
4653
  return p.relative(norm(root), norm(target));
4613
4654
  }
4655
+ /** True when two absolute paths name the same file. Case-insensitive on Windows. */
4656
+ function samePath(a, b, ctx) {
4657
+ return relative$1(a, b, ctx) === "";
4658
+ }
4614
4659
  /** True when `target` is inside the worktree or one of the extra roots. */
4615
4660
  function isInWorkspace(target, ctx) {
4616
4661
  const abs = resolveAgainst(target, ctx);
@@ -4727,13 +4772,6 @@ function isSecretPath(target) {
4727
4772
  if (SECRET_EXTENSIONS.some((ext) => base.endsWith(ext))) return true;
4728
4773
  return /(^|\/)\.(ssh|aws|gnupg|docker)\//i.test(normalized) && !base.endsWith(".pub");
4729
4774
  }
4730
- // ../../packages/providers/src/policy/assessment.ts
4731
- const SAFE = { risky: false };
4732
- const risky = (action, reason) => ({
4733
- risky: true,
4734
- action,
4735
- reason
4736
- });
4737
4775
  // ../../packages/providers/src/policy/git.ts
4738
4776
  /**
4739
4777
  * Git rules of the risk classifier (§11.2): force pushes, deleting refs, global config. `args`
@@ -5684,6 +5722,590 @@ function unixToIso(value) {
5684
5722
  const ms = value < 0xe8d4a51000 ? value * 1e3 : value;
5685
5723
  return new Date(ms).toISOString();
5686
5724
  }
5725
+ // ../../packages/providers/src/policy/codeden-home.ts
5726
+ /**
5727
+ * CodeDen's own credential files (§15): the install token, the runner lock and the remote
5728
+ * access store in its data directory. An agent holding the install token or the runner's token
5729
+ * could drive CodeDen itself and approve its own requests, so touching one asks at every level,
5730
+ * Full included, and is refused for read-only sessions (§11.2). The data directory comes from
5731
+ * the session (`SessionOptions.codedenHome`); without it, this rule is off.
5732
+ */
5733
+ /** A risky verdict on CodeDen's own credentials, which every level asks about. */
5734
+ function credentialsRisk(reason) {
5735
+ return {
5736
+ risky: true,
5737
+ action: "secrets",
5738
+ reason,
5739
+ codeden: true
5740
+ };
5741
+ }
5742
+ /** The credential files in the session's CodeDen home, as absolute paths (none without one). */
5743
+ function codedenCredentialFiles(ctx) {
5744
+ const home = ctx.codedenHome;
5745
+ if (!home) return [];
5746
+ const p = pathApi(ctx);
5747
+ return CODEDEN_CREDENTIAL_FILES.map((file) => p.join(home, file));
5748
+ }
5749
+ /** The credential files, then the folders that hold them. */
5750
+ function guardedPaths(ctx) {
5751
+ const files = codedenCredentialFiles(ctx);
5752
+ const p = pathApi(ctx);
5753
+ return [...files, ...new Set(files.map((file) => p.dirname(file)))];
5754
+ }
5755
+ const WILDCARD = /[*?[]/;
5756
+ /** `c` matched as itself in a regular expression. */
5757
+ const literalChar = (c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5758
+ /**
5759
+ * A shell wildcard (`*`, `?`, `[...]`, and `**`, which before a slash stands for zero or more
5760
+ * folders) over a `/`-separated path as a regular expression. Its first `literal` characters
5761
+ * match as themselves. Undefined when malformed.
5762
+ */
5763
+ function wildcardPattern(pattern, ignoreCase, literal = 0) {
5764
+ let source = pattern.slice(0, literal).replace(/[\s\S]/g, literalChar);
5765
+ for (let i = literal; i < pattern.length; i += 1) {
5766
+ const c = pattern[i];
5767
+ if (c === "*" && pattern[i + 1] === "*") {
5768
+ const folders = pattern[i + 2] === "/";
5769
+ source += folders ? "(?:.*/)?" : ".*";
5770
+ i += folders ? 2 : 1;
5771
+ } else if (c === "*") source += "[^/]*";
5772
+ else if (c === "?") source += "[^/]";
5773
+ else if (c === "[" && pattern.indexOf("]", i + 2) > 0) {
5774
+ const end = pattern.indexOf("]", i + 2);
5775
+ const body = pattern.slice(i + 1, end).replace(/^!/, "^").replace(/\\/g, "\\\\");
5776
+ source += `[${body}]`;
5777
+ i = end;
5778
+ } else source += literalChar(c);
5779
+ }
5780
+ try {
5781
+ return new RegExp(`^${source}$`, ignoreCase ? "i" : "");
5782
+ } catch {
5783
+ return;
5784
+ }
5785
+ }
5786
+ /** `/`-separated, for wildcard matching; Windows paths match in any case. */
5787
+ function slashes(value, ctx) {
5788
+ return isWin(ctx) ? value.replace(/\\/g, "/") : value;
5789
+ }
5790
+ /**
5791
+ * Variables left in a resolved path (`$f`, `${f}`, `$env:F`, `%F%`) could be any name, so they
5792
+ * match like `*`. `$CODEDEN_HOME`, `$HOME` and the temp variables at the start resolve first.
5793
+ */
5794
+ const PATH_VARIABLE = /\$\{\w+\}|\$env:\w+|\$\w+|%\w+%/gi;
5795
+ /**
5796
+ * How many leading characters of `abs` are CodeDen's home or a folder above it, at a folder
5797
+ * boundary. That part comes from the session (`$CODEDEN_HOME`, `~`, the working directory), so it
5798
+ * is always literal: `[`, `*`, `?` or `$` in the home's own path (`CodeDen [dev]`) are never a
5799
+ * pattern. Compared like `samePath`, in any case on Windows.
5800
+ */
5801
+ function literalPrefix(abs, ctx) {
5802
+ const home = ctx.codedenHome;
5803
+ if (!home) return 0;
5804
+ const p = pathApi(ctx);
5805
+ const value = slashes(abs, ctx);
5806
+ const fold = (s) => isWin(ctx) ? s.toLowerCase() : s;
5807
+ for (let dir = p.resolve(home);; dir = p.dirname(dir)) {
5808
+ const prefix = slashes(dir, ctx).replace(/\/$/, "");
5809
+ const n = prefix.length;
5810
+ const atBoundary = value.length === n || value[n] === "/";
5811
+ if (n > 0 && atBoundary && fold(value.slice(0, n)) === fold(prefix)) return n;
5812
+ if (p.dirname(dir) === dir) return 0;
5813
+ }
5814
+ }
5815
+ /** `target` as an absolute path, the unknown variables it adds as wildcards. */
5816
+ function resolvePattern(target, ctx) {
5817
+ const abs = resolveAgainst(target, ctx);
5818
+ const literal = literalPrefix(abs, ctx);
5819
+ return abs.slice(0, literal) + abs.slice(literal).replace(PATH_VARIABLE, "*");
5820
+ }
5821
+ /**
5822
+ * The wildcards in `abs` (from `resolvePattern`) as a regular expression over `/`-separated
5823
+ * paths, or undefined when it has none (or is malformed). Only the agent's part can be a pattern:
5824
+ * CodeDen's home, and the folders above it, match literally (`literalPrefix`).
5825
+ */
5826
+ function agentPattern(abs, ctx) {
5827
+ const value = slashes(abs, ctx);
5828
+ const literal = literalPrefix(abs, ctx);
5829
+ if (!WILDCARD.test(value.slice(literal))) return void 0;
5830
+ return wildcardPattern(value, isWin(ctx), literal);
5831
+ }
5832
+ /**
5833
+ * True when `target` reaches one of CodeDen's credential files: the file itself, a folder that
5834
+ * holds one (`<home>`, `<home>/runner`: copying or searching it reads them), or a wildcard that
5835
+ * matches either. `~`, `$HOME`, `$CODEDEN_HOME` (and its PowerShell and cmd forms) and relative
5836
+ * paths resolve as in the shell. The home itself is always compared literally.
5837
+ */
5838
+ function touchesCodedenCredentials(target, ctx) {
5839
+ if (!ctx.codedenHome || !target.trim()) return false;
5840
+ const abs = resolvePattern(target, ctx);
5841
+ const guarded = guardedPaths(ctx);
5842
+ if (guarded.some((p) => samePath(p, abs, ctx))) return true;
5843
+ const pattern = agentPattern(abs, ctx);
5844
+ return pattern !== void 0 && guarded.some((p) => pattern.test(slashes(p, ctx)));
5845
+ }
5846
+ // ../../packages/providers/src/policy/search-words.ts
5847
+ /**
5848
+ * Search commands (`grep`, `rg`, `findstr`, `Select-String`, ...) read as the tool reads them, for
5849
+ * the risk classifier (§11.2). Each word gets one role, once: an option, an option's value or an
5850
+ * operand. The pattern is an option's pattern value, or else the first operand. Because roles are
5851
+ * fixed in one pass, a value that looks like an option (`rg -r-e`: replacement text `-e`) is never
5852
+ * read as one, and a word that looks like a pattern option is never skipped as a value.
5853
+ */
5854
+ /** Search commands, whose first operand is the pattern (`grep -rn "process.env" src`). */
5855
+ const SEARCHES = /* @__PURE__ */ new Set([
5856
+ "grep",
5857
+ "egrep",
5858
+ "fgrep",
5859
+ "zgrep",
5860
+ "rg",
5861
+ "ag",
5862
+ "ack",
5863
+ "findstr",
5864
+ "select-string",
5865
+ "sls"
5866
+ ]);
5867
+ /** Options that take the next word as their value, per search tool. */
5868
+ const GREP_VALUED = [
5869
+ "-A",
5870
+ "-B",
5871
+ "-C",
5872
+ "-m",
5873
+ "-d",
5874
+ "-D",
5875
+ "--after-context",
5876
+ "--before-context",
5877
+ "--context",
5878
+ "--max-count",
5879
+ "--include",
5880
+ "--exclude",
5881
+ "--exclude-dir",
5882
+ "--label",
5883
+ "--binary-files",
5884
+ "--devices",
5885
+ "--directories"
5886
+ ];
5887
+ const RG_VALUED = [
5888
+ ...GREP_VALUED,
5889
+ "-g",
5890
+ "--glob",
5891
+ "--iglob",
5892
+ "-t",
5893
+ "--type",
5894
+ "-T",
5895
+ "--type-not",
5896
+ "-j",
5897
+ "--threads",
5898
+ "--max-depth",
5899
+ "-M",
5900
+ "--max-columns",
5901
+ "--sort",
5902
+ "--sortr",
5903
+ "-r",
5904
+ "--replace",
5905
+ "--colors",
5906
+ "-E",
5907
+ "--encoding"
5908
+ ];
5909
+ const SELECT_STRING_VALUED = [
5910
+ "-path",
5911
+ "-literalpath",
5912
+ "-include",
5913
+ "-exclude",
5914
+ "-encoding",
5915
+ "-context"
5916
+ ];
5917
+ /** Tools whose option names are matched in any case (names are kept in lower case). */
5918
+ const ANY_CASE = /* @__PURE__ */ new Set([
5919
+ "findstr",
5920
+ "select-string",
5921
+ "sls"
5922
+ ]);
5923
+ /**
5924
+ * Tools whose short options combine in one word, the getopt way (`-ne.` is `-n -e .`), and whose
5925
+ * `-e` and `-f` take a value. `ag -f` and `ack -f` take none, so their words stay as written.
5926
+ */
5927
+ const CLUSTERING = /* @__PURE__ */ new Set([
5928
+ "grep",
5929
+ "egrep",
5930
+ "fgrep",
5931
+ "zgrep",
5932
+ "rg"
5933
+ ]);
5934
+ /** Options whose value is the search pattern. */
5935
+ const PATTERN_OPTIONS = [
5936
+ "-e",
5937
+ "--regexp",
5938
+ "-pattern",
5939
+ "/c"
5940
+ ];
5941
+ /** Options that give the pattern another way, a pattern file: then no operand is the pattern. */
5942
+ const isPatternFile = (name) => name === "-f" || name.startsWith("--file") || name === "/g";
5943
+ /** True when the option `name` takes the next word as its value. */
5944
+ function takesValue(bin, name) {
5945
+ if ([
5946
+ "-e",
5947
+ "--regexp",
5948
+ "-pattern",
5949
+ "-f",
5950
+ "--file"
5951
+ ].includes(name)) return true;
5952
+ if (bin === "rg") return RG_VALUED.includes(name);
5953
+ if (bin === "select-string" || bin === "sls") return SELECT_STRING_VALUED.includes(name);
5954
+ return GREP_VALUED.includes(name);
5955
+ }
5956
+ /** An option word's value bound with `=` or `:` (`--glob=x`, `-Path:x`, `/c:x`). */
5957
+ const BOUND = /^(--?[A-Za-z][\w-]*|\/[A-Za-z])[=:]/;
5958
+ /** Parse a search command's arguments (after the program name) into roles. */
5959
+ function parseSearch(bin, args) {
5960
+ const words = [];
5961
+ const add = (text, role, arg, from = 0, option) => words.push({
5962
+ text,
5963
+ role,
5964
+ option,
5965
+ arg,
5966
+ from,
5967
+ pattern: false
5968
+ });
5969
+ const fold = (name) => ANY_CASE.has(bin) ? name.toLowerCase() : name;
5970
+ const isOption = (a) => a.startsWith("-") || bin === "findstr" && a.startsWith("/");
5971
+ let operandsOnly = false;
5972
+ for (let i = 0; i < args.length; i += 1) {
5973
+ const a = args[i];
5974
+ if (operandsOnly || !isOption(a)) add(a, "operand", i);
5975
+ else if (a === "--") {
5976
+ operandsOnly = true;
5977
+ add(a, "option", i, 0, a);
5978
+ } else if (CLUSTERING.has(bin) && /^-[^-]./.test(a)) for (let j = 1; j < a.length; j += 1) {
5979
+ const flag = `-${a[j]}`;
5980
+ add(flag, "option", -1, 0, flag);
5981
+ if (!takesValue(bin, flag)) continue;
5982
+ if (j + 1 < a.length) add(a.slice(j + 1), "value", i, j + 1, flag);
5983
+ else if (i + 1 < args.length) add(args[++i], "value", i, 0, flag);
5984
+ break;
5985
+ }
5986
+ else {
5987
+ const bound = BOUND.exec(a);
5988
+ const name = fold(bound?.[1] ?? a);
5989
+ add(a, "option", i, 0, name);
5990
+ if (!bound && takesValue(bin, name) && i + 1 < args.length) add(args[++i], "value", i, 0, name);
5991
+ }
5992
+ }
5993
+ markPatterns(words);
5994
+ return words;
5995
+ }
5996
+ /**
5997
+ * Mark the pattern words: the values of `-e`, `--regexp`, `-Pattern` and `findstr /c:`, or, when
5998
+ * no option gives the pattern (nor a pattern file, `-f`), the first operand.
5999
+ */
6000
+ function markPatterns(words) {
6001
+ let explicit = false;
6002
+ for (const w of words) {
6003
+ const name = w.option ?? "";
6004
+ if (w.role === "value" && PATTERN_OPTIONS.includes(name)) {
6005
+ w.pattern = true;
6006
+ explicit = true;
6007
+ } else if (w.role === "option" && PATTERN_OPTIONS.includes(name)) {
6008
+ explicit = true;
6009
+ if (w.text.length > name.length) w.pattern = true;
6010
+ } else if (w.role === "option" && isPatternFile(name)) explicit = true;
6011
+ }
6012
+ if (explicit) return;
6013
+ const first = words.find((w) => w.role === "operand");
6014
+ if (first) first.pattern = true;
6015
+ }
6016
+ /** The file operands of a parsed search (`grep -r -e x src lib` -> `src`, `lib`). */
6017
+ function searchOperands(words) {
6018
+ return words.filter((w) => w.role === "operand" && !w.pattern);
6019
+ }
6020
+ /** The values given to the named options: `name X`, `name=X`, or in a cluster (`-gX`). */
6021
+ function searchOptionValues(words, names) {
6022
+ return words.flatMap((w) => {
6023
+ if (!w.option || !names.includes(w.option)) return [];
6024
+ if (w.role === "value") return [w.text];
6025
+ return w.role === "option" && w.text.length > w.option.length ? [w.text.slice(w.option.length + 1)] : [];
6026
+ });
6027
+ }
6028
+ // ../../packages/providers/src/policy/secrets.ts
6029
+ /**
6030
+ * Secrets rules of the risk classifier (§11.2): which words of a command read a credentials
6031
+ * file, and which commands dump the environment. Writing a secrets file inside the worktree is
6032
+ * not access (`cp .env.example .env`); reading one is.
6033
+ */
6034
+ /**
6035
+ * The argument as a path, or undefined when it can't be one: inline code, multi-line text
6036
+ * or anything with shell or code punctuation (`selected={row.key`) is not a file name. A
6037
+ * variable in braces (`${HOME}/.env`) is a path, and so is an option's value: `--file=x`, or
6038
+ * PowerShell's colon-bound `-Path:x` and `-LiteralPath:x`.
6039
+ */
6040
+ function asPath(arg) {
6041
+ const value = arg.replace(/^-+[a-z-]*=/i, "").replace(/^-[a-z][a-z-]*:(?=.)/i, "");
6042
+ if (!value || /[\n\r{}()<>|;=`]/.test(value.replace(/\$\{\w+\}/g, ""))) return void 0;
6043
+ return value;
6044
+ }
6045
+ /** Commands whose words are data or only checked for existence (`echo .env >> .gitignore`). */
6046
+ const DATA = /* @__PURE__ */ new Set([
6047
+ "echo",
6048
+ "printf",
6049
+ "write-output",
6050
+ "write-host",
6051
+ "test",
6052
+ "[",
6053
+ "[["
6054
+ ]);
6055
+ /** Commands that list or describe files without reading them (`ls certs/*.pem`). */
6056
+ const LISTS = /* @__PURE__ */ new Set([
6057
+ "ls",
6058
+ "dir",
6059
+ "get-childitem",
6060
+ "gci",
6061
+ "find",
6062
+ "fd",
6063
+ "tree",
6064
+ "stat",
6065
+ "du",
6066
+ "test-path"
6067
+ ]);
6068
+ /**
6069
+ * The first argument that reads a credentials file, if any. `written` are the paths the command
6070
+ * writes (a copy's destination), which classifyWrite judges instead. `isSecret` decides which
6071
+ * paths count (default: secrets files by name). `paths` gives what a word may stand for (each
6072
+ * element of a PowerShell list, `shell.wordPaths`). A search's patterns are skipped whole, list
6073
+ * elements included: the tool searches for them, not in them.
6074
+ */
6075
+ function secretArgument(bin, args, written = [], isSecret = isSecretPath, paths = (word, from) => [word.slice(from)]) {
6076
+ if (DATA.has(bin)) return void 0;
6077
+ const runs = (bin === "find" || bin === "fd") && args.some((a) => /^-(exec|execdir|ok|okdir|x|X|-exec|-exec-batch)$/.test(a));
6078
+ if (LISTS.has(bin) && !runs) return void 0;
6079
+ return (SEARCHES.has(bin) ? parseSearch(bin, args) : args.map((text, arg) => ({
6080
+ text,
6081
+ arg,
6082
+ from: 0,
6083
+ pattern: false
6084
+ }))).find((w) => {
6085
+ if (w.pattern || written.includes(w.text)) return false;
6086
+ return (w.arg >= 0 ? paths(args[w.arg], w.from) : [w.text]).some((word) => braceAlternatives(word).some((alt) => {
6087
+ const value = asPath(alt);
6088
+ return value !== void 0 && isSecret(value);
6089
+ }));
6090
+ })?.text;
6091
+ }
6092
+ /** The most words one argument's brace expansion is followed to. */
6093
+ const MAX_ALTERNATIVES = 64;
6094
+ /**
6095
+ * The words a shell's brace expansion makes of `word` (`~/.ssh/{id_rsa,x}` -> `~/.ssh/id_rsa`,
6096
+ * `~/.ssh/x`), capped; just `word` when it has no `{a,b}` group.
6097
+ */
6098
+ function braceAlternatives(word) {
6099
+ const group = /\{([^{}]*,[^{}]*)\}/.exec(word);
6100
+ if (!group?.[1]) return [word];
6101
+ const before = word.slice(0, group.index);
6102
+ const after = word.slice(group.index + group[0].length);
6103
+ const out = [];
6104
+ for (const part of group[1].split(",")) for (const alt of braceAlternatives(before + part + after)) {
6105
+ if (out.length >= MAX_ALTERNATIVES) return out;
6106
+ out.push(alt);
6107
+ }
6108
+ return out;
6109
+ }
6110
+ /** Files a tool writes through its own options (`openssl req -keyout key.pem -out cert.pem`). */
6111
+ function toolOutputs(bin, args) {
6112
+ if (bin !== "openssl" && bin !== "ssh-keygen") return [];
6113
+ const flags = bin === "openssl" ? ["-out", "-keyout"] : ["-f"];
6114
+ return args.flatMap((a, i) => flags.includes(a) && args[i + 1] ? [args[i + 1]] : []);
6115
+ }
6116
+ /** Variable names that may hold a credential. */
6117
+ const SECRET_VAR = /key|token|secret|passw|pwd|credential|auth|private|session|cookie/i;
6118
+ /**
6119
+ * True for `printenv` and `Get-ChildItem env:` listings. Printing named variables that don't
6120
+ * look like credentials (`printenv PATH`, `Get-ChildItem env:NODE_ENV`) is not a dump.
6121
+ */
6122
+ function dumpsEnvironment(bin, args) {
6123
+ const named = (names) => names.length > 0 && names.every((n) => /^\w+$/.test(n) && !SECRET_VAR.test(n));
6124
+ if (bin === "printenv") return !named(args.filter((a) => !a.startsWith("-")));
6125
+ if (!/^(get-childitem|gci|ls|dir)$/.test(bin)) return false;
6126
+ const drives = args.filter((a) => /^env:/i.test(a));
6127
+ return drives.length > 0 && !named(drives.map((a) => a.slice(4)));
6128
+ }
6129
+ // ../../packages/providers/src/policy/codeden-search.ts
6130
+ /** The values of an option, given as `name X`, `name=X` or `name:X`, each time it appears. */
6131
+ function optionValues(args, names, anyCase) {
6132
+ const out = [];
6133
+ for (let i = 0; i < args.length; i += 1) {
6134
+ const a = args[i];
6135
+ const word = anyCase ? a.toLowerCase() : a;
6136
+ if (names.includes(word)) {
6137
+ if (i + 1 < args.length) out.push(args[i + 1]);
6138
+ i += 1;
6139
+ continue;
6140
+ }
6141
+ const bound = names.find((n) => word.startsWith(`${n}=`) || word.startsWith(`${n}:`));
6142
+ if (bound) out.push(a.slice(bound.length + 1));
6143
+ }
6144
+ return out;
6145
+ }
6146
+ const GREPS = /* @__PURE__ */ new Set([
6147
+ "grep",
6148
+ "egrep",
6149
+ "fgrep",
6150
+ "zgrep"
6151
+ ]);
6152
+ /** `grep -r`, `-R`, `-rn`, `--recursive`, `-d recurse`: grep reads folders only then. */
6153
+ function grepRecursive(words) {
6154
+ return words.some((w) => w.role === "option" && /^(-r|-R|--recursive|--dereference-recursive)$/.test(w.text)) || searchOptionValues(words, ["-d", "--directories"]).includes("recurse");
6155
+ }
6156
+ /**
6157
+ * The folders a search reads recursively, with its name filters. Roles come from `parseSearch`
6158
+ * (`grep -re. ~` is `grep -r -e . ~`; in `rg -r-g x ~`, `-g` is replacement text, not a filter),
6159
+ * and each root may be a PowerShell list (`paths`).
6160
+ */
6161
+ function searchWalks(bin, args, ctx, paths) {
6162
+ const words = parseSearch(bin, args);
6163
+ const operands = searchOperands(words).flatMap((w) => paths(args[w.arg], w.from));
6164
+ if (bin === "rg" || bin === "ag" || bin === "ack") return [{
6165
+ roots: operands,
6166
+ recursive: true,
6167
+ names: bin === "rg" ? searchOptionValues(words, [
6168
+ "-g",
6169
+ "--glob",
6170
+ "--iglob"
6171
+ ]).filter((n) => !n.startsWith("!")) : []
6172
+ }];
6173
+ if (GREPS.has(bin)) {
6174
+ if (!grepRecursive(words)) return [];
6175
+ return [{
6176
+ roots: operands,
6177
+ recursive: true,
6178
+ names: searchOptionValues(words, ["--include"])
6179
+ }];
6180
+ }
6181
+ const sub = words.some((w) => w.role === "option" && /^\/[a-z]*s[a-z]*$/i.test(w.text));
6182
+ if (bin === "findstr" && sub) {
6183
+ const p = pathApi(ctx);
6184
+ return operands.map((operand) => ({
6185
+ roots: [p.dirname(operand)],
6186
+ recursive: true,
6187
+ names: [p.basename(operand)]
6188
+ }));
6189
+ }
6190
+ return [];
6191
+ }
6192
+ const LISTINGS = /* @__PURE__ */ new Set([
6193
+ "get-childitem",
6194
+ "gci",
6195
+ "dir",
6196
+ "ls"
6197
+ ]);
6198
+ const LISTING_VALUED = [
6199
+ "-filter",
6200
+ "-include",
6201
+ "-exclude",
6202
+ "-depth",
6203
+ "-attributes"
6204
+ ];
6205
+ /** `find -exec cat {} +` and `fd -x cat` hand what they find to a command. */
6206
+ const RUNS = /^-(exec|execdir|ok|okdir|x|X|-exec|-exec-batch)$/;
6207
+ /** The folders a listing goes through, with its name filters. */
6208
+ function listingWalk(bin, args) {
6209
+ if (bin === "find") {
6210
+ const end = args.findIndex((a) => /^[-(!)]/.test(a));
6211
+ const either = args.some((a) => [
6212
+ "-o",
6213
+ "-or",
6214
+ "!",
6215
+ "-not"
6216
+ ].includes(a));
6217
+ return {
6218
+ roots: end < 0 ? args : args.slice(0, end),
6219
+ recursive: true,
6220
+ names: either ? [] : optionValues(args, ["-name", "-iname"], false)
6221
+ };
6222
+ }
6223
+ if (bin === "fd") return {
6224
+ roots: args.filter((a) => !a.startsWith("-")).slice(1),
6225
+ recursive: true,
6226
+ names: []
6227
+ };
6228
+ if (!LISTINGS.has(bin)) return void 0;
6229
+ const recursive = args.some((a) => /^-rec/i.test(a) || a === "-r" || /^-[a-zA-Z]*R[a-zA-Z]*$/.test(a) || /^\/s$/i.test(a));
6230
+ const named = optionValues(args, ["-path", "-literalpath"], true);
6231
+ const positional = [];
6232
+ for (let i = 0; i < args.length; i += 1) {
6233
+ const a = args[i];
6234
+ const lower = a.toLowerCase();
6235
+ if (LISTING_VALUED.includes(lower) || lower === "-path" || lower === "-literalpath") i += 1;
6236
+ else if (!a.startsWith("-") && !/^\/[a-z](:\S*)?$/i.test(a)) positional.push(a);
6237
+ }
6238
+ return {
6239
+ roots: [...named, ...positional].flatMap((root) => root.split(",")),
6240
+ recursive,
6241
+ names: optionValues(args, ["-filter", "-include"], true).flatMap((n) => n.split(","))
6242
+ };
6243
+ }
6244
+ /**
6245
+ * True when listing `dir` (and, recursively, its subfolders) reaches `folder`: by its path, or
6246
+ * through the wildcards the agent wrote in it (never ones in CodeDen's home path).
6247
+ */
6248
+ function holds(dir, folder, recursive, ctx) {
6249
+ if (recursive ? isInside$1(folder, dir, ctx) : samePath(dir, folder, ctx)) return true;
6250
+ const pattern = agentPattern(dir, ctx);
6251
+ if (!pattern) return false;
6252
+ const p = pathApi(ctx);
6253
+ for (let at = folder;; at = p.dirname(at)) {
6254
+ if (pattern.test(slashes(at, ctx))) return true;
6255
+ if (!recursive || p.dirname(at) === at) return false;
6256
+ }
6257
+ }
6258
+ /** True when `file`, found under `root`, passes one of the name filters (or there are none). */
6259
+ function keeps(names, file, root, ctx) {
6260
+ if (names.length === 0) return true;
6261
+ const p = pathApi(ctx);
6262
+ const base = p.basename(file);
6263
+ const below = slashes(p.relative(root, file), ctx);
6264
+ return names.flatMap((name) => braceAlternatives(name.trim().replace(/^\.[\\/]/, ""))).some((name) => {
6265
+ const pattern = wildcardPattern(slashes(name, ctx), true);
6266
+ if (!pattern) return true;
6267
+ return /[\\/]/.test(name) ? pattern.test(below) : pattern.test(base);
6268
+ });
6269
+ }
6270
+ /** The root of `walk` from which it reaches a credential file, if any. */
6271
+ function walkReaches(walk, ctx) {
6272
+ const files = codedenCredentialFiles(ctx);
6273
+ const p = pathApi(ctx);
6274
+ for (const root of walk.roots.length ? walk.roots : ["."]) for (const alt of braceAlternatives(root)) {
6275
+ const dir = resolvePattern(alt, ctx);
6276
+ if (files.some((file) => holds(dir, p.dirname(file), walk.recursive, ctx) && keeps(walk.names, file, dir, ctx))) return root;
6277
+ }
6278
+ }
6279
+ /**
6280
+ * A recursive search from `roots` (Claude's Grep), keeping files whose names match `names`:
6281
+ * the root it reaches a credential file from, if any.
6282
+ */
6283
+ function searchReaches(roots, names, ctx) {
6284
+ return ctx.codedenHome ? walkReaches({
6285
+ roots,
6286
+ recursive: true,
6287
+ names
6288
+ }, ctx) : void 0;
6289
+ }
6290
+ /**
6291
+ * How a command reaches CodeDen's credential files other than by naming one: a recursive search
6292
+ * from a folder above them, or a listing (or `echo`ed path) whose output `feeds` another command.
6293
+ * `paths` gives what a word may stand for (a PowerShell list's elements). Returns the word it
6294
+ * reaches them through.
6295
+ */
6296
+ function reachesThroughFolder(bin, args, ctx, feeds, paths = (word, from) => [word.slice(from)]) {
6297
+ if (!ctx.codedenHome) return void 0;
6298
+ if (SEARCHES.has(bin)) for (const walk of searchWalks(bin, args, ctx, paths)) {
6299
+ const root = walkReaches(walk, ctx);
6300
+ if (root) return root;
6301
+ }
6302
+ if (feeds || (bin === "find" || bin === "fd") && args.some((a) => RUNS.test(a))) {
6303
+ const walk = listingWalk(bin, args);
6304
+ const root = walk && walkReaches(walk, ctx);
6305
+ if (root) return root;
6306
+ }
6307
+ if (feeds && DATA.has(bin)) return args.find((a) => paths(a, 0).some((path) => touchesCodedenCredentials(path, ctx)));
6308
+ }
5687
6309
  // ../../packages/providers/src/util/git-head.ts
5688
6310
  /**
5689
6311
  * The branch checked out in the repository at `dir` (its `.git` folder, or the `gitdir:` a
@@ -5914,212 +6536,58 @@ const INTERPRETERS = new Map([
5914
6536
  "mksh",
5915
6537
  "ash",
5916
6538
  "fish"
5917
- ].map((b) => [b, SHELL]),
5918
- ["node", NODE],
5919
- ["nodejs", NODE],
5920
- ["perl", PERL],
5921
- ["ruby", RUBY],
5922
- ["pwsh", POWERSHELL],
5923
- ["powershell", POWERSHELL],
5924
- ["source", SOURCE],
5925
- [".", SOURCE]
5926
- ]);
5927
- const interpreter = (bin) => INTERPRETERS.get(bin) ?? (/^(python[\d.]*|py|pypy3?)$/.test(bin) ? PYTHON : void 0);
5928
- /** A program argument that means "read the program from stdin". */
5929
- const isStdin = (arg) => arg === void 0 || arg === "-" || arg === "/dev/stdin" || arg === "/proc/self/fd/0";
5930
- /**
5931
- * True when the command runs what it reads on stdin as code: an interpreter given no program
5932
- * (no `-c`/`-e` code, module or script file), or told to read it from stdin (`-`, `sh -s`).
5933
- */
5934
- function runsStdin(cmd) {
5935
- const { tokens } = unwrap(cmd.words);
5936
- const bin = binName(tokens[0]);
5937
- if (bin === "iex" || bin === "invoke-expression") return true;
5938
- const spec = interpreter(bin);
5939
- if (!spec) return false;
5940
- const args = tokens.slice(1);
5941
- for (let i = 0; i < args.length; i += 1) {
5942
- const arg = args[i];
5943
- if (arg === "--") return isStdin(args[i + 1]);
5944
- if (!/^[-+]./.test(arg)) return isStdin(arg);
5945
- if (spec.stdin?.(arg)) return true;
5946
- if (spec.program(arg)) return !arg.includes("=") && isStdin(args[i + 1]);
5947
- if (spec.valued(arg)) i += 1;
5948
- }
5949
- return true;
5950
- }
5951
- // ../../packages/providers/src/policy/secrets.ts
5952
- /**
5953
- * Secrets rules of the risk classifier (§11.2): which words of a command read a credentials
5954
- * file, and which commands dump the environment. Writing a secrets file inside the worktree is
5955
- * not access (`cp .env.example .env`); reading one is.
5956
- */
5957
- /**
5958
- * The argument as a path, or undefined when it can't be one: inline code, multi-line text
5959
- * or anything with shell or code punctuation (`selected={row.key`) is not a file name.
5960
- */
5961
- function asPath(arg) {
5962
- const value = arg.replace(/^-+[a-z-]*=/i, "");
5963
- if (!value || /[\n\r{}()<>|;=`]/.test(value)) return void 0;
5964
- return value;
5965
- }
5966
- /** Commands whose words are data or only checked for existence (`echo .env >> .gitignore`). */
5967
- const DATA = /* @__PURE__ */ new Set([
5968
- "echo",
5969
- "printf",
5970
- "write-output",
5971
- "write-host",
5972
- "test",
5973
- "[",
5974
- "[["
5975
- ]);
5976
- /** Commands that list or describe files without reading them (`ls certs/*.pem`). */
5977
- const LISTS = /* @__PURE__ */ new Set([
5978
- "ls",
5979
- "dir",
5980
- "get-childitem",
5981
- "gci",
5982
- "find",
5983
- "fd",
5984
- "tree",
5985
- "stat",
5986
- "du",
5987
- "test-path"
5988
- ]);
5989
- /** Search commands, whose first operand is the pattern (`grep -rn "process.env" src`). */
5990
- const SEARCHES = /* @__PURE__ */ new Set([
5991
- "grep",
5992
- "egrep",
5993
- "fgrep",
5994
- "zgrep",
5995
- "rg",
5996
- "ag",
5997
- "ack",
5998
- "findstr",
5999
- "select-string",
6000
- "sls"
6001
- ]);
6002
- /** Options that take the next word as their value, per search tool. */
6003
- const GREP_VALUED = [
6004
- "-A",
6005
- "-B",
6006
- "-C",
6007
- "-m",
6008
- "-d",
6009
- "-D",
6010
- "--after-context",
6011
- "--before-context",
6012
- "--context",
6013
- "--max-count",
6014
- "--include",
6015
- "--exclude",
6016
- "--exclude-dir",
6017
- "--label",
6018
- "--binary-files",
6019
- "--devices",
6020
- "--directories"
6021
- ];
6022
- const RG_VALUED = [
6023
- ...GREP_VALUED,
6024
- "-g",
6025
- "--glob",
6026
- "--iglob",
6027
- "-t",
6028
- "--type",
6029
- "-T",
6030
- "--type-not",
6031
- "-j",
6032
- "--threads",
6033
- "--max-depth",
6034
- "-M",
6035
- "--max-columns",
6036
- "--sort",
6037
- "--sortr",
6038
- "-r",
6039
- "--replace",
6040
- "--colors",
6041
- "-E",
6042
- "--encoding"
6043
- ];
6044
- const SELECT_STRING_VALUED = [
6045
- "-path",
6046
- "-literalpath",
6047
- "-include",
6048
- "-exclude",
6049
- "-encoding",
6050
- "-context"
6051
- ];
6052
- function searchValued(bin, arg) {
6053
- if (bin === "rg") return RG_VALUED.includes(arg);
6054
- if (bin === "select-string" || bin === "sls") return SELECT_STRING_VALUED.includes(arg.toLowerCase());
6055
- return GREP_VALUED.includes(arg);
6056
- }
6539
+ ].map((b) => [b, SHELL]),
6540
+ ["node", NODE],
6541
+ ["nodejs", NODE],
6542
+ ["perl", PERL],
6543
+ ["ruby", RUBY],
6544
+ ["pwsh", POWERSHELL],
6545
+ ["powershell", POWERSHELL],
6546
+ ["source", SOURCE],
6547
+ [".", SOURCE]
6548
+ ]);
6549
+ const interpreter = (bin) => INTERPRETERS.get(bin) ?? (/^(python[\d.]*|py|pypy3?)$/.test(bin) ? PYTHON : void 0);
6550
+ /** A program argument that means "read the program from stdin". */
6551
+ const isStdin = (arg) => arg === void 0 || arg === "-" || arg === "/dev/stdin" || arg === "/proc/self/fd/0";
6057
6552
  /**
6058
- * The search pattern words of a search command: `-e`/`--regexp`/`-Pattern` values, or the first
6059
- * operand when the pattern isn't given by an option (or a pattern file, `-f`). `findstr`'s
6060
- * `/c:"text"` is a pattern too.
6553
+ * True when the command runs what it reads on stdin as code: an interpreter given no program
6554
+ * (no `-c`/`-e` code, module or script file), or told to read it from stdin (`-`, `sh -s`).
6061
6555
  */
6062
- function searchPatterns(bin, args) {
6063
- const patterns = /* @__PURE__ */ new Set();
6064
- let explicit = false;
6065
- for (let i = 0; i < args.length; i += 1) {
6066
- const a = args[i];
6067
- if (a === "-e" || a === "--regexp" || /^-pattern$/i.test(a)) {
6068
- explicit = true;
6069
- const value = args[i + 1];
6070
- if (value !== void 0) patterns.add(value);
6071
- i += 1;
6072
- } else if (a.startsWith("--regexp=") || /^\/c:/i.test(a)) {
6073
- explicit = true;
6074
- patterns.add(a);
6075
- } else if (a === "-f" || a.startsWith("--file") || /^\/g:/i.test(a)) explicit = true;
6076
- }
6077
- if (explicit) return patterns;
6556
+ function runsStdin(cmd) {
6557
+ const { tokens } = unwrap(cmd.words);
6558
+ const bin = binName(tokens[0]);
6559
+ if (bin === "iex" || bin === "invoke-expression") return true;
6560
+ const spec = interpreter(bin);
6561
+ if (!spec) return false;
6562
+ const args = tokens.slice(1);
6078
6563
  for (let i = 0; i < args.length; i += 1) {
6079
- const a = args[i];
6080
- if (searchValued(bin, a)) i += 1;
6081
- else if (!a.startsWith("-") && !(bin === "findstr" && a.startsWith("/"))) {
6082
- patterns.add(a);
6083
- break;
6084
- }
6564
+ const arg = args[i];
6565
+ if (arg === "--") return isStdin(args[i + 1]);
6566
+ if (!/^[-+]./.test(arg)) return isStdin(arg);
6567
+ if (spec.stdin?.(arg)) return true;
6568
+ if (spec.program(arg)) return !arg.includes("=") && isStdin(args[i + 1]);
6569
+ if (spec.valued(arg)) i += 1;
6085
6570
  }
6086
- return patterns;
6087
- }
6088
- /**
6089
- * The first argument that reads a credentials file, if any. `written` are the paths the command
6090
- * writes (a copy's destination), which classifyWrite judges instead.
6091
- */
6092
- function secretArgument(bin, args, written = []) {
6093
- if (DATA.has(bin)) return void 0;
6094
- const runs = (bin === "find" || bin === "fd") && args.some((a) => /^-(exec|execdir|ok|okdir|x|X|-exec|-exec-batch)$/.test(a));
6095
- if (LISTS.has(bin) && !runs) return void 0;
6096
- const patterns = SEARCHES.has(bin) ? searchPatterns(bin, args) : /* @__PURE__ */ new Set();
6097
- return args.find((a) => {
6098
- if (patterns.has(a) || written.includes(a)) return false;
6099
- const value = asPath(a);
6100
- return value !== void 0 && isSecretPath(value);
6101
- });
6102
- }
6103
- /** Files a tool writes through its own options (`openssl req -keyout key.pem -out cert.pem`). */
6104
- function toolOutputs(bin, args) {
6105
- if (bin !== "openssl" && bin !== "ssh-keygen") return [];
6106
- const flags = bin === "openssl" ? ["-out", "-keyout"] : ["-f"];
6107
- return args.flatMap((a, i) => flags.includes(a) && args[i + 1] ? [args[i + 1]] : []);
6571
+ return true;
6108
6572
  }
6109
- /** Variable names that may hold a credential. */
6110
- const SECRET_VAR = /key|token|secret|passw|pwd|credential|auth|private|session|cookie/i;
6573
+ // ../../packages/providers/src/policy/shell.ts
6111
6574
  /**
6112
- * True for `printenv` and `Get-ChildItem env:` listings. Printing named variables that don't
6113
- * look like credentials (`printenv PATH`, `Get-ChildItem env:NODE_ENV`) is not a dump.
6114
- */
6115
- function dumpsEnvironment(bin, args) {
6116
- const named = (names) => names.length > 0 && names.every((n) => /^\w+$/.test(n) && !SECRET_VAR.test(n));
6117
- if (bin === "printenv") return !named(args.filter((a) => !a.startsWith("-")));
6118
- if (!/^(get-childitem|gci|ls|dir)$/.test(bin)) return false;
6119
- const drives = args.filter((a) => /^env:/i.test(a));
6120
- return drives.length > 0 && !named(drives.map((a) => a.slice(4)));
6575
+ * The paths a word of `cmd` may stand for, from `from` on: its text and, for a PowerShell list
6576
+ * (`a,b`), each element. The whole text stays: when the shell is unknown it may be one name, or a
6577
+ * brace expansion (`{a,b}`). Callers apply it after reading the word's role, so a list given as a
6578
+ * search pattern stays data.
6579
+ */
6580
+ function wordPaths(cmd, word, from = 0) {
6581
+ const text = word.slice(from);
6582
+ const commas = cmd.lists?.get(word)?.filter((at) => at >= from) ?? [];
6583
+ if (!commas.length) return [text];
6584
+ const bounds = [
6585
+ from - 1,
6586
+ ...commas,
6587
+ word.length
6588
+ ];
6589
+ return [text, ...bounds.slice(1).map((end, k) => word.slice(bounds[k] + 1, end)).filter(Boolean)];
6121
6590
  }
6122
- // ../../packages/providers/src/policy/shell.ts
6123
6591
  /** Parse a command line into its simple commands. */
6124
6592
  function parseShell(src, dialect = "auto") {
6125
6593
  return new Lexer(src, dialect).list(null);
@@ -6140,6 +6608,8 @@ var Builder = class {
6140
6608
  target = "word";
6141
6609
  heredocStrip = false;
6142
6610
  heredocs = [];
6611
+ /** Where the word's unquoted commas are: a PowerShell list's element boundaries. */
6612
+ commas = [];
6143
6613
  constructor(out) {
6144
6614
  this.out = out;
6145
6615
  }
@@ -6147,6 +6617,11 @@ var Builder = class {
6147
6617
  this.word += text;
6148
6618
  this.inWord = true;
6149
6619
  }
6620
+ /** An unquoted comma, which separates a PowerShell list's elements (`a,b`). */
6621
+ addComma() {
6622
+ this.commas.push(this.word.length);
6623
+ this.add(",");
6624
+ }
6150
6625
  endWord() {
6151
6626
  if (!this.inWord) return;
6152
6627
  if (this.target === "write") this.cmd.writes.push(this.word);
@@ -6154,10 +6629,24 @@ var Builder = class {
6154
6629
  delim: this.word.replace(/\\/g, ""),
6155
6630
  strip: this.heredocStrip
6156
6631
  });
6157
- else if (this.target === "word") this.cmd.words.push(this.word);
6632
+ else if (this.target === "word") {
6633
+ this.cmd.words.push(this.word);
6634
+ if (this.commas.length) this.addList();
6635
+ } else if (this.target === "skip") this.cmd.input = [...this.cmd.input ?? [], this.word];
6158
6636
  this.target = "word";
6159
6637
  this.word = "";
6160
6638
  this.inWord = false;
6639
+ this.commas = [];
6640
+ }
6641
+ /**
6642
+ * Note where the current word's unquoted commas are. The same text written twice with different
6643
+ * quoting keeps every comma either one had, so its list is never missed.
6644
+ */
6645
+ addList() {
6646
+ this.cmd.lists ??= /* @__PURE__ */ new Map();
6647
+ const seen = this.cmd.lists.get(this.word) ?? [];
6648
+ const commas = [.../* @__PURE__ */ new Set([...seen, ...this.commas])].sort((a, b) => a - b);
6649
+ this.cmd.lists.set(this.word, commas);
6161
6650
  }
6162
6651
  endCommand() {
6163
6652
  this.endWord();
@@ -6234,6 +6723,9 @@ var Lexer = class {
6234
6723
  this.i += 1;
6235
6724
  b.cmd.nested.push(this.list(")"));
6236
6725
  b.add(src.slice(start, this.i));
6726
+ } else if (c === "," && this.dialect !== "posix") {
6727
+ b.addComma();
6728
+ this.i += 1;
6237
6729
  } else {
6238
6730
  b.add(c);
6239
6731
  this.i += 1;
@@ -6376,12 +6868,62 @@ const MAX_DEPTH = 4;
6376
6868
  * Classify a shell command (any shell: POSIX, PowerShell or cmd). Downloads piped into an
6377
6869
  * interpreter (`curl … | sh`, `irm … | iex`) are found on the parsed pipeline in
6378
6870
  * `classifyList`; here, a download run through a substitution (`iex (irm …)`,
6379
- * `bash -c "$(curl …)"`, `bash <(curl …)`).
6871
+ * `bash -c "$(curl …)"`, `bash <(curl …)`). CodeDen's own credentials are looked for first,
6872
+ * across the whole command, so an earlier ordinary risk (`cat .env; …`) can't hide them.
6380
6873
  */
6381
6874
  function classifyCommand(command, ctx) {
6875
+ const list = parseShell(command, ctx.shell ?? "auto");
6876
+ const credentials = credentialAccess(list, ctx, 0);
6877
+ if (credentials) return credentials;
6382
6878
  if (/\b(iex|invoke-expression)\b[^|]*\b(irm|iwr|invoke-restmethod|invoke-webrequest|downloadstring)\b/i.test(command)) return risky("network", "Executes a downloaded script");
6383
6879
  if (/\b(bash|sh|zsh)\s+(-c\s+)?["']?\$\(\s*(curl|wget)\b/i.test(command) || /<\(\s*(curl|wget)\b/i.test(command)) return risky("network", "Executes a downloaded script");
6384
- return classifyList(parseShell(command, ctx.shell ?? "auto"), ctx, 0);
6880
+ return classifyList(list, ctx, 0);
6881
+ }
6882
+ /**
6883
+ * The first access to CodeDen's own credential files anywhere in `list`: substitutions,
6884
+ * subshells and inline scripts (`bash -c "…"`) included, following `cd` like `classifyList`.
6885
+ * `feeds`: the list's output is used by another command (it sits in a substitution or a pipe).
6886
+ */
6887
+ function credentialAccess(list, ctx, depth, feeds = false) {
6888
+ let here = ctx;
6889
+ for (const [i, cmd] of list.entries()) {
6890
+ for (const nested of cmd.nested) {
6891
+ const found = credentialAccess(nested, here, depth, true);
6892
+ if (found) return found;
6893
+ }
6894
+ const piped = list[i + 1]?.piped === true;
6895
+ const found = credentialCommand(cmd, here, depth, feeds || piped);
6896
+ if (found) return found;
6897
+ here = afterCd(cmd, here);
6898
+ }
6899
+ }
6900
+ /** The paths each word of `cmd` may stand for: itself, and a PowerShell list's elements. */
6901
+ const pathsOf = (cmd) => (word, from) => wordPaths(cmd, word, from);
6902
+ /**
6903
+ * One simple command's access to CodeDen's credentials: a redirect onto one, any argument (or
6904
+ * here-string) that reaches one (read, written, copied or deleted), or a search or listing that
6905
+ * reaches one from a folder above it (`./codeden-search`). Moving into CodeDen's home with `cd`
6906
+ * reads nothing; the command that then reads is caught.
6907
+ */
6908
+ function credentialCommand(cmd, ctx, depth, feeds) {
6909
+ const { tokens } = unwrap(cmd.words);
6910
+ const bin = binName(tokens[0]);
6911
+ const args = tokens.slice(1);
6912
+ const script = inlineScript(bin, args);
6913
+ if (script && depth < MAX_DEPTH) {
6914
+ const inner = credentialAccess(parseShell(script.text, script.dialect), ctx, depth + 1, feeds);
6915
+ if (inner) return inner;
6916
+ }
6917
+ for (const target of cmd.writes) if (!DISCARD.test(target) && touchesCodedenCredentials(target, ctx)) return credentialsRisk(`Writes CodeDen's own credentials: ${target}`);
6918
+ const given = (cmd.input ?? []).find((word) => touchesCodedenCredentials(word, ctx));
6919
+ if (given) return credentialsRisk(`Passes CodeDen's own credentials: ${given}`);
6920
+ if (CD.has(bin)) return void 0;
6921
+ const paths = pathsOf(cmd);
6922
+ const reads = (a) => touchesCodedenCredentials(a, ctx);
6923
+ const touched = secretArgument(bin, args, [], reads, paths);
6924
+ if (touched) return credentialsRisk(`Accesses CodeDen's own credentials: ${touched}`);
6925
+ const through = reachesThroughFolder(bin, args, ctx, feeds, paths);
6926
+ return through ? credentialsRisk(`Reaches CodeDen's own credentials from ${through}`) : void 0;
6385
6927
  }
6386
6928
  /**
6387
6929
  * Classify commands in order. A `cd` moves where later relative paths resolve; commands in
@@ -6421,10 +6963,17 @@ function afterCd(cmd, ctx) {
6421
6963
  dir: resolveAgainst(target, ctx)
6422
6964
  };
6423
6965
  }
6424
- /** The value after a PowerShell-style named parameter (`-Path x`), if present. */
6966
+ /**
6967
+ * The value of a PowerShell-style named parameter, if present: `-Path x`, or bound with a
6968
+ * colon, `-Path:x`.
6969
+ */
6425
6970
  function namedArg(args, names) {
6426
- const i = args.findIndex((a) => names.includes(a.toLowerCase()));
6427
- return i >= 0 ? args[i + 1] : void 0;
6971
+ for (let i = 0; i < args.length; i += 1) {
6972
+ const arg = args[i].toLowerCase();
6973
+ if (names.includes(arg)) return args[i + 1];
6974
+ const bound = names.find((name) => arg.startsWith(`${name}:`));
6975
+ if (bound) return args[i].slice(bound.length + 1);
6976
+ }
6428
6977
  }
6429
6978
  /** The script of an inline shell invocation (`bash -c "…"`, `pwsh -Command …`, `cmd /c …`). */
6430
6979
  function inlineScript(bin, args) {
@@ -6507,7 +7056,7 @@ function classifySimple(cmd, ctx, depth) {
6507
7056
  const write = classifyWrite(target, ctx);
6508
7057
  if (write.risky) return write;
6509
7058
  }
6510
- const secret = secretArgument(bin, args, written);
7059
+ const secret = secretArgument(bin, args, written, isSecretPath, pathsOf(cmd));
6511
7060
  if (secret) return risky("secrets", `Accesses the secrets file ${secret}`);
6512
7061
  if (dumpsEnvironment(bin, args)) return risky("secrets", "Dumps environment variables");
6513
7062
  return SAFE;
@@ -6564,18 +7113,36 @@ function copyTargets(bin, args, dest, ctx) {
6564
7113
  * Classify a file write/edit. The worktree, extra roots and OS temp are fine, secrets files in
6565
7114
  * the worktree included: writing one (`cp .env.example .env`) reads nothing. A secrets file in
6566
7115
  * temp counts as outside the worktree, so Full autonomy (which only asks for writes outside the
6567
- * worktree) still asks for it.
7116
+ * worktree) still asks for it. CodeDen's own credential files ask at every level.
6568
7117
  */
6569
7118
  function classifyWrite(target, ctx) {
7119
+ if (touchesCodedenCredentials(target, ctx)) return credentialsRisk(`Writes CodeDen's own credentials: ${target}`);
6570
7120
  if (!isWritable(target, ctx)) return risky("outside_worktree", `Writes ${target}, outside the worktree`);
6571
7121
  if (isSecretPath(target) && !isInWorkspace(target, ctx)) return risky("outside_worktree", `Writes the secrets file ${target}, outside the worktree`);
6572
7122
  return SAFE;
6573
7123
  }
6574
- /** Classify a file read or search. Reads anywhere are fine except credential files. */
6575
- function classifyRead(target, _ctx) {
7124
+ /**
7125
+ * Classify a file read or search. Reads anywhere are fine except credential files; CodeDen's own
7126
+ * (or a folder holding them) ask at every level.
7127
+ */
7128
+ function classifyRead(target, ctx) {
7129
+ if (touchesCodedenCredentials(target, ctx)) return credentialsRisk(`Reads CodeDen's own credentials: ${target}`);
6576
7130
  if (isSecretPath(target)) return risky("secrets", `Reads the secrets file ${target}`);
6577
7131
  return SAFE;
6578
7132
  }
7133
+ /**
7134
+ * Classify a file-name search: `pattern` (Claude's Glob, or a search's `--glob` filter) from
7135
+ * `base` (default: the current directory). Listing names reads nothing, so only a pattern that
7136
+ * reaches CodeDen's own credential files asks; `{a,b}` alternatives are followed.
7137
+ */
7138
+ function classifyGlob(pattern, base, ctx) {
7139
+ if (!pattern.trim()) return SAFE;
7140
+ const dir = base ? resolveAgainst(base, ctx) : ctx.dir ?? ctx.cwd;
7141
+ return braceAlternatives(pattern).some((alt) => touchesCodedenCredentials(alt, {
7142
+ ...ctx,
7143
+ dir
7144
+ })) ? credentialsRisk(`Lists CodeDen's own credential files: ${pattern}`) : SAFE;
7145
+ }
6579
7146
  /** Classify a web fetch. Only flagged when the context has an allowlist of hosts. */
6580
7147
  function classifyFetch(url, ctx) {
6581
7148
  if (!ctx.allowedHosts) return SAFE;
@@ -6801,9 +7368,11 @@ function assessClaudeTool(name, input, ctx) {
6801
7368
  if (name === "Read") return classifyRead(str(input, "file_path") ?? "", ctx);
6802
7369
  if (name === "Grep") {
6803
7370
  const target = str(input, "path");
6804
- return target ? classifyRead(target, ctx) : { risky: false };
7371
+ const filter = str(input, "glob");
7372
+ const root = searchReaches(target ? [target] : [], filter ? [filter] : [], ctx);
7373
+ return mostSevere([target ? classifyRead(target, ctx) : SAFE, root ? credentialsRisk(`Reaches CodeDen's own credentials from ${root}`) : SAFE]);
6805
7374
  }
6806
- if (name === "Glob") return { risky: false };
7375
+ if (name === "Glob") return classifyGlob(str(input, "pattern") ?? "", str(input, "path"), ctx);
6807
7376
  if (name === "WebFetch") return classifyFetch(str(input, "url") ?? "", ctx);
6808
7377
  return { risky: false };
6809
7378
  }
@@ -6835,7 +7404,8 @@ const FINISH_PLAN = "Finish the plan and present it with ExitPlanMode; once the
6835
7404
  * - auto -> `acceptEdits`, plus the risky-action classifier
6836
7405
  * - full -> `bypassPermissions`, asking only for writes outside the worktree (the OS
6837
7406
  * temp directory counts as inside, except for secrets files there)
6838
- * Plan mode overrides all of them with `plan`.
7407
+ * Plan mode overrides all of them with `plan`. CodeDen's own credential files ask at every
7408
+ * level and are refused for read-only sessions (`../policy/codeden-home`).
6839
7409
  */
6840
7410
  function permissionModeFor(autonomy, planMode) {
6841
7411
  if (planMode) return "plan";
@@ -6853,6 +7423,8 @@ var ClaudePolicy = class {
6853
7423
  ctx;
6854
7424
  readOnly;
6855
7425
  sessionAllow = /* @__PURE__ */ new Set();
7426
+ /** `allow_session` decisions on calls that touched CodeDen's own credentials (`credentialKey`). */
7427
+ credentialAllow = /* @__PURE__ */ new Set();
6856
7428
  /** Plan mode: edits outside Claude's notes and writing commands are refused. */
6857
7429
  planning = false;
6858
7430
  constructor(autonomy, ctx, readOnly = false) {
@@ -6881,8 +7453,12 @@ var ClaudePolicy = class {
6881
7453
  setPlanning(on) {
6882
7454
  this.planning = on;
6883
7455
  }
6884
- /** Why a call is refused outright (read-only sessions, plan mode), or undefined. */
6885
- denial(toolName, input) {
7456
+ /**
7457
+ * Why a call is refused outright (read-only sessions, plan mode), or undefined. `dir` is the
7458
+ * shell's current directory, as for `gate`.
7459
+ */
7460
+ denial(toolName, input, dir) {
7461
+ if (this.isReadOnly && touchesCodeden(this.assess(toolName, input, dir))) return this.readOnly ? "This session is read-only: CodeDen's own credential files are off limits. Act through CodeDen tools instead." : "This session has read-only permissions: CodeDen's own credential files are off limits. Report what you found instead.";
6886
7462
  if (this.planning && !this.readOnly && this.isNotesEdit(toolName, input)) return void 0;
6887
7463
  if (!this.isReadOnly) return this.planning ? this.planDenial(toolName, input) : void 0;
6888
7464
  if (EDIT_TOOLS$1.has(toolName)) return this.readOnly ? "This session is read-only: it cannot edit files. Act through CodeDen tools instead." : "This session has read-only permissions: it cannot edit files, commit or push. Report what you found instead.";
@@ -6923,11 +7499,8 @@ var ClaudePolicy = class {
6923
7499
  action: "plan",
6924
7500
  reason: "The agent finished planning"
6925
7501
  };
6926
- if (this.sessionAllow.has(this.key(toolName, input))) return { ask: false };
6927
- const risk = assessClaudeTool(toolName, input, dir ? {
6928
- ...this.ctx,
6929
- dir
6930
- } : this.ctx);
7502
+ const risk = this.assess(toolName, input, dir);
7503
+ if (touchesCodeden(risk) ? this.credentialAllow.has(this.credentialKey(toolName, input, dir)) : this.sessionAllow.has(this.key(toolName, input))) return { ask: false };
6931
7504
  if (this.autonomy === "supervised") {
6932
7505
  if (risk.risky) return {
6933
7506
  ask: true,
@@ -6942,13 +7515,19 @@ var ClaudePolicy = class {
6942
7515
  return { ask: false };
6943
7516
  }
6944
7517
  if (!risk.risky) return { ask: false };
6945
- if (this.autonomy === "full" && risk.action !== "outside_worktree") return { ask: false };
7518
+ if (this.autonomy === "full" && risk.action !== "outside_worktree" && !risk.codeden) return { ask: false };
6946
7519
  return {
6947
7520
  ask: true,
6948
7521
  action: risk.action,
6949
7522
  reason: risk.reason
6950
7523
  };
6951
7524
  }
7525
+ assess(toolName, input, dir) {
7526
+ return assessClaudeTool(toolName, input, dir ? {
7527
+ ...this.ctx,
7528
+ dir
7529
+ } : this.ctx);
7530
+ }
6952
7531
  /**
6953
7532
  * Whether a `canUseTool` call (Claude's own permission prompt) needs the user. In supervised
6954
7533
  * mode everything Claude asks about goes to the user; otherwise only gated calls do.
@@ -6963,9 +7542,17 @@ var ClaudePolicy = class {
6963
7542
  reason: "Claude asked for permission"
6964
7543
  };
6965
7544
  }
6966
- /** Remember an `allow_session` decision. Commands and edits match exactly; others by name. */
6967
- allowForSession(toolName, input) {
7545
+ /**
7546
+ * Remember an `allow_session` decision. Commands and edits match exactly; others by name. A
7547
+ * call that touches CodeDen's own credentials is remembered only as itself, in `dir`.
7548
+ */
7549
+ allowForSession(toolName, input, dir) {
6968
7550
  this.sessionAllow.add(this.key(toolName, input));
7551
+ if (touchesCodeden(this.assess(toolName, input, dir))) this.credentialAllow.add(this.credentialKey(toolName, input, dir));
7552
+ }
7553
+ /** The exact call, in the directory its relative paths resolve from. */
7554
+ credentialKey(toolName, input, dir) {
7555
+ return `${toolName}\n${dir ?? this.ctx.cwd}\n${JSON.stringify(input)}`;
6969
7556
  }
6970
7557
  key(toolName, input) {
6971
7558
  if (COMMAND_TOOLS.has(toolName)) return `${toolName}:${str(input, "command") ?? ""}`;
@@ -7724,7 +8311,7 @@ function policyHook(policy, shell) {
7724
8311
  const toolInput = hookInput.tool_input ?? {};
7725
8312
  const cwd = str(hookInput, "cwd");
7726
8313
  if (cwd && (path.isAbsolute(cwd) || path.win32.isAbsolute(cwd))) shell.dir = cwd;
7727
- const denied = policy.denial(name, toolInput);
8314
+ const denied = policy.denial(name, toolInput, shell.dir);
7728
8315
  if (denied) return { hookSpecificOutput: {
7729
8316
  hookEventName: "PreToolUse",
7730
8317
  permissionDecision: "deny",
@@ -8053,23 +8640,130 @@ function statsFromUsageTrailer(text) {
8053
8640
  };
8054
8641
  return stats(field("total_tokens"), field("tool_uses"), field("duration_ms"));
8055
8642
  }
8643
+ // ../../packages/providers/src/claude/subagent-usage.ts
8644
+ /** Token counts of one call from a Messages API `usage` object; undefined when it has none. */
8645
+ function callTokens(usage) {
8646
+ if (!usage || typeof usage !== "object") return void 0;
8647
+ const u = usage;
8648
+ const details = u.output_tokens_details;
8649
+ const tokens = {
8650
+ input: count(u.input_tokens) ?? 0,
8651
+ output: count(u.output_tokens) ?? 0,
8652
+ cacheRead: count(u.cache_read_input_tokens) ?? 0,
8653
+ cacheWrite: count(u.cache_creation_input_tokens) ?? 0,
8654
+ reasoning: count(details?.thinking_tokens) ?? 0
8655
+ };
8656
+ return tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite > 0 ? tokens : void 0;
8657
+ }
8658
+ /**
8659
+ * Tokens per model across one subagent's own API calls, for its card's cost (§7.3). Claude
8660
+ * streams a call as several `assistant` frames sharing its message id (one per content block),
8661
+ * each with the call's usage as it stood when the call started: input and cache counts are
8662
+ * final, output is not. So calls are keyed by message id, frames of one call merge (the larger
8663
+ * figure wins), and the subagent's transcript, read once it has finished, supplies the final
8664
+ * figures (`applyTranscript`).
8665
+ */
8666
+ var SubagentUsage = class {
8667
+ calls = /* @__PURE__ */ new Map();
8668
+ /** Fold in one streamed frame (`message` of an `assistant` message). */
8669
+ addFrame(message) {
8670
+ const id = str(message, "id");
8671
+ const tokens = callTokens(message.usage);
8672
+ if (!id || !tokens) return;
8673
+ const model = modelOf(message);
8674
+ const prev = this.calls.get(id);
8675
+ this.calls.set(id, {
8676
+ ...model || prev?.model ? { model: model ?? prev?.model } : {},
8677
+ input: Math.max(tokens.input, prev?.input ?? 0),
8678
+ output: Math.max(tokens.output, prev?.output ?? 0),
8679
+ cacheRead: Math.max(tokens.cacheRead, prev?.cacheRead ?? 0),
8680
+ cacheWrite: Math.max(tokens.cacheWrite, prev?.cacheWrite ?? 0),
8681
+ reasoning: Math.max(tokens.reasoning, prev?.reasoning ?? 0)
8682
+ });
8683
+ }
8684
+ /**
8685
+ * Take the final figures of the calls the frames showed from the subagent's transcript
8686
+ * (`getSubagentMessages`): per message id, the transcript wins. Calls only the transcript
8687
+ * holds are left out (an earlier run of a resumed subagent); calls it doesn't hold yet keep
8688
+ * their streamed figures. Returns whether anything changed.
8689
+ */
8690
+ applyTranscript(messages) {
8691
+ let changed = false;
8692
+ for (const entry of messages) {
8693
+ const raw = str(entry, "type") === "assistant" ? entry.message : void 0;
8694
+ if (!raw || typeof raw !== "object") continue;
8695
+ const message = raw;
8696
+ const id = str(message, "id");
8697
+ const prev = id ? this.calls.get(id) : void 0;
8698
+ const tokens = callTokens(message.usage);
8699
+ if (!id || !prev || !tokens) continue;
8700
+ const model = modelOf(message) ?? prev.model;
8701
+ const next = {
8702
+ ...model ? { model } : {},
8703
+ ...tokens
8704
+ };
8705
+ if (!sameCall(prev, next)) {
8706
+ this.calls.set(id, next);
8707
+ changed = true;
8708
+ }
8709
+ }
8710
+ return changed;
8711
+ }
8712
+ /** Tokens per model in the order the models were first used; undefined with no calls. */
8713
+ summary() {
8714
+ const byModel = /* @__PURE__ */ new Map();
8715
+ for (const call of this.calls.values()) {
8716
+ const key = call.model ?? "";
8717
+ const sum = byModel.get(key);
8718
+ byModel.set(key, {
8719
+ ...call.model ? { model: call.model } : {},
8720
+ input: (sum?.input ?? 0) + call.input,
8721
+ output: (sum?.output ?? 0) + call.output,
8722
+ cacheRead: (sum?.cacheRead ?? 0) + call.cacheRead,
8723
+ cacheWrite: (sum?.cacheWrite ?? 0) + call.cacheWrite,
8724
+ reasoning: (sum?.reasoning ?? 0) + call.reasoning
8725
+ });
8726
+ }
8727
+ if (byModel.size === 0) return void 0;
8728
+ return [...byModel.values()].map((c) => ({
8729
+ ...c.model ? { model: c.model } : {},
8730
+ inputTokens: c.input,
8731
+ outputTokens: c.output,
8732
+ cacheReadTokens: c.cacheRead,
8733
+ cacheWriteTokens: c.cacheWrite,
8734
+ ...c.reasoning > 0 ? { reasoningTokens: c.reasoning } : {}
8735
+ }));
8736
+ }
8737
+ };
8738
+ /** The model that served a call; Claude Code marks its own placeholder replies `<synthetic>`. */
8739
+ function modelOf(message) {
8740
+ const model = str(message, "model")?.trim();
8741
+ return model && model !== "<synthetic>" ? model : void 0;
8742
+ }
8743
+ function sameCall(a, b) {
8744
+ return a.model === b.model && a.input === b.input && a.output === b.output && a.cacheRead === b.cacheRead && a.cacheWrite === b.cacheWrite && a.reasoning === b.reasoning;
8745
+ }
8056
8746
  // ../../packages/providers/src/claude/subagents.ts
8057
8747
  /**
8058
8748
  * Subagent (Agent/Task tool) runs as child threads (§7.3). The parent gets a `subagent` item;
8059
8749
  * messages tagged with `parent_tool_use_id` go to the child thread. Background subagents
8060
- * finish on `task_notification`, foreground ones on their `tool_result`.
8750
+ * finish on `task_notification`, foreground ones on their `tool_result`. The card's figures
8751
+ * include the run's tokens per model, from its streamed messages; `onFinished` lets the
8752
+ * session read the final figures from the run's transcript (`correctUsage`).
8061
8753
  */
8062
8754
  var SubagentTracker = class {
8063
8755
  sessionId;
8064
8756
  emit;
8065
8757
  newId;
8066
8758
  onThreadEnd;
8759
+ onFinished;
8067
8760
  runs = /* @__PURE__ */ new Map();
8068
- constructor(sessionId, emit, newId, onThreadEnd = () => {}) {
8761
+ constructor(sessionId, emit, newId, onThreadEnd = () => {}, onFinished = () => {}) {
8069
8762
  this.sessionId = sessionId;
8070
8763
  this.emit = emit;
8071
8764
  this.newId = newId;
8072
8765
  this.onThreadEnd = onThreadEnd;
8766
+ this.onFinished = onFinished;
8073
8767
  }
8074
8768
  ctx(toolUseId) {
8075
8769
  return this.runs.get(toolUseId)?.child;
@@ -8097,7 +8791,8 @@ var SubagentTracker = class {
8097
8791
  child,
8098
8792
  item,
8099
8793
  background: input.run_in_background === true,
8100
- done: false
8794
+ done: false,
8795
+ usage: new SubagentUsage()
8101
8796
  });
8102
8797
  this.emit({
8103
8798
  type: "thread.started",
@@ -8121,11 +8816,43 @@ var SubagentTracker = class {
8121
8816
  }
8122
8817
  onTaskStarted(msg) {
8123
8818
  const run = this.runs.get(str(msg, "tool_use_id") ?? "");
8124
- if (run && msg.is_backgrounded === true) run.background = true;
8819
+ if (!run) return;
8820
+ if (msg.is_backgrounded === true) run.background = true;
8821
+ run.agentId ??= str(msg, "task_id");
8822
+ }
8823
+ /** One of the run's own `assistant` messages (`parent_tool_use_id` is its tool use). */
8824
+ onAssistant(toolUseId, msg) {
8825
+ const run = this.runs.get(toolUseId);
8826
+ if (!run || run.done) return;
8827
+ run.agentId ??= str(msg, "agent_id");
8828
+ if (msg.message && typeof msg.message === "object") run.usage.addFrame(msg.message);
8829
+ }
8830
+ /**
8831
+ * The final figures of a finished run's calls, from its transcript: the card is sent again
8832
+ * when they change what it shows (§7.3).
8833
+ */
8834
+ correctUsage(toolUseId, transcript) {
8835
+ const run = this.runs.get(toolUseId);
8836
+ if (!run?.done || !run.usage.applyTranscript(transcript)) return;
8837
+ const usage = run.usage.summary();
8838
+ if (!usage) return;
8839
+ run.item = {
8840
+ ...run.item,
8841
+ stats: {
8842
+ ...run.item.stats,
8843
+ usage
8844
+ }
8845
+ };
8846
+ this.emit({
8847
+ type: "item.completed",
8848
+ ...run.parent,
8849
+ item: run.item
8850
+ });
8125
8851
  }
8126
8852
  onToolResult(toolUseId, content, isError, toolUseResult) {
8127
8853
  const run = this.runs.get(toolUseId);
8128
8854
  if (!run || run.done) return;
8855
+ run.agentId ??= str(toolUseResult, "agentId");
8129
8856
  const status = str(toolUseResult, "status");
8130
8857
  if (!isError && (status === "async_launched" || toolUseResult?.isAsync === true)) {
8131
8858
  run.background = true;
@@ -8138,6 +8865,8 @@ var SubagentTracker = class {
8138
8865
  onTaskNotification(msg) {
8139
8866
  const toolUseId = str(msg, "tool_use_id");
8140
8867
  if (!toolUseId) return;
8868
+ const run = this.runs.get(toolUseId);
8869
+ if (run) run.agentId ??= str(msg, "task_id");
8141
8870
  const status = str(msg, "status");
8142
8871
  const summary = str(msg, "summary");
8143
8872
  this.finish(toolUseId, status === "completed" ? "completed" : status === "failed" ? "failed" : "interrupted", summary, statsFromTaskNotification(msg));
@@ -8170,16 +8899,23 @@ var SubagentTracker = class {
8170
8899
  reason: "user"
8171
8900
  });
8172
8901
  const status = outcome === "completed" ? "completed" : outcome === "failed" ? "failed" : "cancelled";
8902
+ const usage = run.usage.summary();
8903
+ const figures = usage ? {
8904
+ ...stats,
8905
+ usage
8906
+ } : stats;
8907
+ run.item = {
8908
+ ...run.item,
8909
+ status,
8910
+ ...summary ? { summary } : {},
8911
+ ...figures ? { stats: figures } : {}
8912
+ };
8173
8913
  this.emit({
8174
8914
  type: "item.completed",
8175
8915
  ...run.parent,
8176
- item: {
8177
- ...run.item,
8178
- status,
8179
- ...summary ? { summary } : {},
8180
- ...stats ? { stats } : {}
8181
- }
8916
+ item: run.item
8182
8917
  });
8918
+ if (usage && run.agentId) this.onFinished(toolUseId, run.agentId);
8183
8919
  }
8184
8920
  };
8185
8921
  /** Drop the CLI's internal trailer (agent ids, usage) from a subagent result. */
@@ -8475,7 +9211,7 @@ var ClaudeEventMapper = class {
8475
9211
  this.rootThreadId = `${opts.sessionId}:thread-root`;
8476
9212
  this.emit = opts.emit;
8477
9213
  this.newId = opts.newId ?? randomUUID;
8478
- this.subagents = new SubagentTracker(opts.sessionId, this.emit, this.newId, (ctx, ok) => this.tools.finishTurn(ctx, ok));
9214
+ this.subagents = new SubagentTracker(opts.sessionId, this.emit, this.newId, (ctx, ok) => this.tools.finishTurn(ctx, ok), opts.onSubagentFinished);
8479
9215
  this.tools = new ToolTracker(opts.cwd, (ctx, phase, item) => this.item(ctx, phase, item), this.subagents, this.newId);
8480
9216
  this.stream = new StreamAssembler({
8481
9217
  rootCtx: () => this.rootCtx(),
@@ -8553,6 +9289,13 @@ var ClaudeEventMapper = class {
8553
9289
  ctxForTool(toolUseId) {
8554
9290
  return this.tools.ctx(toolUseId) ?? this.rootCtx();
8555
9291
  }
9292
+ /**
9293
+ * A finished subagent's transcript (`getSubagentMessages`): its calls' final token counts
9294
+ * replace the streamed ones, and the card is sent again when that changes it.
9295
+ */
9296
+ correctSubagentUsage(toolUseId, transcript) {
9297
+ this.subagents.correctUsage(toolUseId, transcript);
9298
+ }
8556
9299
  /** Emit an item event in a context. */
8557
9300
  item(ctx, phase, item) {
8558
9301
  this.emit({
@@ -8614,6 +9357,7 @@ var ClaudeEventMapper = class {
8614
9357
  const isRoot = !msg.parent_tool_use_id;
8615
9358
  const messageId = str(message, "id") ?? this.newId();
8616
9359
  if (isRoot) this.contextTokens = contextTokensFromMessageUsage(message.usage) ?? this.contextTokens;
9360
+ else if (typeof msg.parent_tool_use_id === "string") this.subagents.onAssistant(msg.parent_tool_use_id, msg);
8617
9361
  const content = Array.isArray(message.content) ? message.content : [];
8618
9362
  for (const block of content) if (block.type === "text" || block.type === "thinking" || block.type === "redacted_thinking") {
8619
9363
  const type = block.type === "text" ? "text" : "thinking";
@@ -9020,7 +9764,10 @@ var ClaudeSession = class {
9020
9764
  this.mapper = new ClaudeEventMapper({
9021
9765
  sessionId: opts.sessionId,
9022
9766
  cwd: opts.cwd,
9023
- emit
9767
+ emit,
9768
+ onSubagentFinished: (toolUseId, agentId) => {
9769
+ this.correctSubagentUsage(toolUseId, agentId);
9770
+ }
9024
9771
  });
9025
9772
  this.broker = new ApprovalBroker(emit);
9026
9773
  this.autonomy = opts.autonomy;
@@ -9028,6 +9775,7 @@ var ClaudeSession = class {
9028
9775
  cwd: opts.cwd,
9029
9776
  extraRoots: opts.additionalDirectories,
9030
9777
  ...opts.ownBranch ? { ownBranch: opts.ownBranch } : {},
9778
+ ...opts.codedenHome ? { codedenHome: opts.codedenHome } : {},
9031
9779
  notesRoots: config.notesRoots ?? claudeNotesDirs({
9032
9780
  cwd: opts.cwd,
9033
9781
  ...claudeHomes(opts.env)
@@ -9042,6 +9790,20 @@ var ClaudeSession = class {
9042
9790
  return this.mapper.resumeToken ?? this.opts.resumeToken;
9043
9791
  }
9044
9792
  /**
9793
+ * A subagent's streamed messages don't carry their calls' final output counts; its
9794
+ * transcript does. Read it once the run finished and let the card take the final figures.
9795
+ * If it can't be read (another config folder, not written yet), the streamed ones stay.
9796
+ */
9797
+ async correctSubagentUsage(toolUseId, agentId) {
9798
+ const read = this.config.getSubagentMessages;
9799
+ const claudeSessionId = this.resumeToken();
9800
+ if (!read || !claudeSessionId || this.closed) return;
9801
+ try {
9802
+ const transcript = await read(claudeSessionId, agentId, { dir: this.opts.cwd });
9803
+ if (!this.closed) this.mapper.correctSubagentUsage(toolUseId, transcript);
9804
+ } catch {}
9805
+ }
9806
+ /**
9045
9807
  * Switch autonomy. The policy changes at once, so the next tool call (even in a running
9046
9808
  * turn) follows the new level; approvals already waiting stay waiting. The CLI's own
9047
9809
  * permission mode follows (`applyPermissionMode`), except while planning: the CLI stays in
@@ -9264,7 +10026,7 @@ var ClaudeSession = class {
9264
10026
  mapper: this.mapper
9265
10027
  }, input, options.toolUseID, options.signal);
9266
10028
  if (toolName === "ExitPlanMode") return this.reviewPlan(input, options);
9267
- const denied = this.policy.denial(toolName, input);
10029
+ const denied = this.policy.denial(toolName, input, this.shell.dir);
9268
10030
  if (denied) return {
9269
10031
  behavior: "deny",
9270
10032
  message: denied
@@ -9302,7 +10064,7 @@ var ClaudeSession = class {
9302
10064
  };
9303
10065
  toPermissionResult(toolName, input, response) {
9304
10066
  if (response.kind === "approval" && response.decision !== "deny") {
9305
- if (response.decision === "allow_session") this.policy.allowForSession(toolName, input);
10067
+ if (response.decision === "allow_session") this.policy.allowForSession(toolName, input, this.shell.dir);
9306
10068
  return {
9307
10069
  behavior: "allow",
9308
10070
  updatedInput: input
@@ -9446,10 +10208,11 @@ var ClaudeAdapter = class {
9446
10208
  async openSession(opts) {
9447
10209
  const executable = this.executable();
9448
10210
  if (!executable) throw new Error("Claude Code is not installed (no `claude` on PATH)");
9449
- const { query } = await (this.options.loadSdk ?? loadClaudeSdk)();
10211
+ const { query, getSubagentMessages } = await (this.options.loadSdk ?? loadClaudeSdk)();
9450
10212
  return new ClaudeSession(await withFork(this, opts), {
9451
10213
  executable,
9452
10214
  query,
10215
+ ...getSubagentMessages ? { getSubagentMessages } : {},
9453
10216
  onModels: (models) => {
9454
10217
  if (!models.length) return;
9455
10218
  this.freshness.confirm(executableFingerprint(executable));
@@ -9986,22 +10749,23 @@ var CodexUtilityModel = class {
9986
10749
  * same risk classifier as Claude's tool calls: what the level allows is approved by policy
9987
10750
  * (recorded with `decidedBy: "policy"`), and only the rest reaches the user.
9988
10751
  */
9989
- /** True when the permission level allows an action the classifier assessed as `risk`. */
10752
+ /**
10753
+ * True when the permission level allows an action the classifier assessed as `risk`. CodeDen's
10754
+ * own credential files are never allowed by policy. Commands Codex runs inside its sandbox
10755
+ * without asking (reads included) never reach this policy.
10756
+ */
9990
10757
  function levelAllows(autonomy, risk) {
9991
10758
  if (autonomy === "auto") return !risk.risky;
9992
- if (autonomy === "full") return !risk.risky || risk.action !== "outside_worktree";
10759
+ if (autonomy === "full") return !risk.risky || risk.action !== "outside_worktree" && !risk.codeden;
9993
10760
  return false;
9994
10761
  }
9995
- /** The first risky assessment of `items`, or safe. */
9996
- function first(items) {
9997
- return items.find((r) => r.risky) ?? { risky: false };
9998
- }
9999
10762
  /**
10000
10763
  * Risk of a command Codex asks to run: the command line as sent and each parsed action, so a
10001
- * redirect or path the parsed form drops still counts.
10764
+ * redirect or path the parsed form drops still counts. Here and below, CodeDen's own
10765
+ * credentials win over an earlier ordinary risk (`mostSevere`).
10002
10766
  */
10003
10767
  function commandRisk(texts, ctx) {
10004
- return first(texts.filter(Boolean).map((text) => classifyCommand(text, ctx)));
10768
+ return mostSevere(texts.filter(Boolean).map((text) => classifyCommand(text, ctx)));
10005
10769
  }
10006
10770
  /**
10007
10771
  * Risk of a patch Codex asks to apply: each file it changes, and the root it asks to write
@@ -10014,7 +10778,7 @@ function patchRisk(paths, grantRoot, ctx) {
10014
10778
  action: "outside_worktree",
10015
10779
  reason: "Changes files Codex did not name"
10016
10780
  };
10017
- return first(targets.map((target) => classifyWrite(target, ctx)));
10781
+ return mostSevere(targets.map((target) => classifyWrite(target, ctx)));
10018
10782
  }
10019
10783
  /**
10020
10784
  * Risk of extra permissions Codex asks for: network access (not restricted, as for Claude,
@@ -10029,7 +10793,7 @@ function permissionsRisk(permissions, ctx) {
10029
10793
  action: "outside_worktree",
10030
10794
  reason: "Asks for access to folders by pattern"
10031
10795
  };
10032
- return first([
10796
+ return mostSevere([
10033
10797
  ...(fs.write ?? []).map((p) => classifyWrite(p, ctx)),
10034
10798
  ...(fs.read ?? []).map((p) => classifyRead(p, ctx)),
10035
10799
  ...(fs.entries ?? []).map((entry) => {
@@ -11256,6 +12020,21 @@ function tokenUsage(u) {
11256
12020
  if (u.modelContextWindow && u.modelContextWindow > 0) usage.contextWindow = Math.round(u.modelContextWindow);
11257
12021
  return usage;
11258
12022
  }
12023
+ /**
12024
+ * A sub-agent's tokens for its card (§7.3), from its thread's running `total`, in the same
12025
+ * terms as `tokenUsage`. `model` is the one its spawn asked for; absent, it runs the session's.
12026
+ */
12027
+ function subagentUsage(u, model) {
12028
+ const t = tokenUsage(u);
12029
+ return {
12030
+ ...model ? { model } : {},
12031
+ inputTokens: t.inputTokens ?? 0,
12032
+ outputTokens: t.outputTokens ?? 0,
12033
+ cacheReadTokens: t.cachedInputTokens ?? 0,
12034
+ cacheWriteTokens: t.cacheWriteTokens ?? 0,
12035
+ ...t.reasoningTokens ? { reasoningTokens: t.reasoningTokens } : {}
12036
+ };
12037
+ }
11259
12038
  function windowId(minutes, fallback) {
11260
12039
  if (minutes === 300) return "5h";
11261
12040
  if (minutes === 10080) return "weekly";
@@ -11384,7 +12163,7 @@ var CodexThreadMapper = class {
11384
12163
  this.updatePlan(n.params.turnId, n.params.plan);
11385
12164
  return;
11386
12165
  case "thread/tokenUsage/updated": {
11387
- this.o.onTokens?.(n.params.tokenUsage.total.totalTokens);
12166
+ this.o.onTokens?.(n.params.tokenUsage);
11388
12167
  const usage = this.turnUsage.add(n.params.turnId, n.params.tokenUsage);
11389
12168
  if (!this.o.root) {
11390
12169
  delete usage.contextTokens;
@@ -11637,6 +12416,7 @@ var CodexSubagents = class {
11637
12416
  return false;
11638
12417
  }
11639
12418
  const run = this.claim(from, turnId, item.id, spawnDetails(item));
12419
+ if (item.model) run.model ??= item.model;
11640
12420
  const child = item.receiverThreadIds[0];
11641
12421
  if (child) this.bind(run, child);
11642
12422
  if (phase !== "completed") return true;
@@ -11775,8 +12555,8 @@ var CodexSubagents = class {
11775
12555
  prompt: item.input.text
11776
12556
  };
11777
12557
  },
11778
- onTokens: (total) => {
11779
- run.totalTokens = total;
12558
+ onTokens: (usage) => {
12559
+ run.tokens = usage;
11780
12560
  }
11781
12561
  });
11782
12562
  run.thread = thread;
@@ -11877,9 +12657,10 @@ var CodexSubagents = class {
11877
12657
  stats(run) {
11878
12658
  const end = run.endedAt ?? this.o.now();
11879
12659
  return {
11880
- ...run.totalTokens !== void 0 ? { totalTokens: Math.max(0, Math.round(run.totalTokens)) } : {},
12660
+ ...run.tokens ? { totalTokens: Math.max(0, Math.round(run.tokens.total.totalTokens)) } : {},
11881
12661
  toolUses: run.toolCalls.size,
11882
- durationMs: Math.max(0, Math.round(end - run.startedAt))
12662
+ durationMs: Math.max(0, Math.round(end - run.startedAt)),
12663
+ ...run.tokens ? { usage: [subagentUsage(run.tokens, run.model)] } : {}
11883
12664
  };
11884
12665
  }
11885
12666
  };
@@ -12226,7 +13007,8 @@ var CodexSession = class {
12226
13007
  risk: {
12227
13008
  cwd: this.opts.cwd,
12228
13009
  extraRoots: this.opts.additionalDirectories,
12229
- ...this.opts.ownBranch ? { ownBranch: this.opts.ownBranch } : {}
13010
+ ...this.opts.ownBranch ? { ownBranch: this.opts.ownBranch } : {},
13011
+ ...this.opts.codedenHome ? { codedenHome: this.opts.codedenHome } : {}
12230
13012
  },
12231
13013
  autonomy: () => this.opts.readOnly ? "read_only" : this.autonomy,
12232
13014
  ctx: (threadId, turnId) => this.mapper.requestCtx(threadId, turnId),
@@ -14927,25 +15709,32 @@ var OpenCodePolicy = class {
14927
15709
  };
14928
15710
  }
14929
15711
  }
14930
- /** Remember an `allow_session` decision: commands and paths match exactly. */
15712
+ /** Remember an `allow_session` decision: commands (in their directory) and paths match exactly. */
14931
15713
  allowForSession(call) {
14932
15714
  this.sessionAllow.add(this.key(call));
14933
15715
  }
14934
15716
  shell(call) {
14935
15717
  const command = commandOf(call);
15718
+ const risk = classifyCommand(command, {
15719
+ ...this.ctx,
15720
+ dir: this.workdir(call)
15721
+ });
14936
15722
  if (this.isReadOnly) {
15723
+ if (touchesCodeden(risk)) return this.credentialsDenial();
14937
15724
  const reason = readOnlyCommandDenial(command, { allowChecks: !this.readOnly });
14938
15725
  return reason ? {
14939
15726
  kind: "deny",
14940
15727
  message: `This session is read-only: ${reason}.`
14941
15728
  } : ALLOW;
14942
15729
  }
14943
- const workdir = str(call.toolInput, "workdir");
14944
- const dir = workdir ? path.resolve(this.ctx.cwd, workdir) : void 0;
14945
- return this.gate(classifyCommand(command, dir ? {
14946
- ...this.ctx,
14947
- dir
14948
- } : this.ctx), "command");
15730
+ return this.gate(risk, "command");
15731
+ }
15732
+ /** Read-only sessions never touch CodeDen's own credential files. */
15733
+ credentialsDenial() {
15734
+ return {
15735
+ kind: "deny",
15736
+ message: this.readOnly ? "This session is read-only: CodeDen's own credential files are off limits. Act through CodeDen tools instead." : "This session has read-only permissions: CodeDen's own credential files are off limits. Report what you found instead."
15737
+ };
14949
15738
  }
14950
15739
  edit(paths) {
14951
15740
  if (this.isReadOnly) return {
@@ -14954,19 +15743,19 @@ var OpenCodePolicy = class {
14954
15743
  };
14955
15744
  return this.gate(this.firstRisk(paths, classifyWrite), "file_change");
14956
15745
  }
15746
+ /** The verdict for every target: CodeDen's own credentials first, then the first risky one. */
14957
15747
  firstRisk(targets, classify) {
14958
- for (const target of targets) {
14959
- const risk = classify(target, this.ctx);
14960
- if (risk.risky) return risk;
14961
- }
14962
- return { risky: false };
15748
+ return mostSevere(targets.map((target) => classify(target, this.ctx)));
14963
15749
  }
14964
15750
  /**
14965
15751
  * The level's gate (the same as Claude's): edits and commands (`supervisedAction`) always
14966
15752
  * ask in supervised mode; `alwaysRisky` asks for flagged calls even at full autonomy
14967
- * (secrets reads).
15753
+ * (secrets reads). CodeDen's own credential files ask at every level and are refused for
15754
+ * read-only sessions.
14968
15755
  */
14969
15756
  gate(risk, supervisedAction, alwaysRisky = false) {
15757
+ const codeden = touchesCodeden(risk);
15758
+ if (codeden && this.isReadOnly) return this.credentialsDenial();
14970
15759
  if (this.autonomy === "supervised") {
14971
15760
  if (risk.risky) return {
14972
15761
  kind: "ask",
@@ -14981,18 +15770,28 @@ var OpenCodePolicy = class {
14981
15770
  return ALLOW;
14982
15771
  }
14983
15772
  if (!risk.risky) return ALLOW;
14984
- if (this.autonomy === "full" && risk.action !== "outside_worktree" && !alwaysRisky) return ALLOW;
15773
+ const always = alwaysRisky || codeden;
15774
+ if (this.autonomy === "full" && risk.action !== "outside_worktree" && !always) return ALLOW;
14985
15775
  return {
14986
15776
  kind: "ask",
14987
15777
  action: risk.action,
14988
15778
  reason: risk.reason
14989
15779
  };
14990
15780
  }
15781
+ /**
15782
+ * What an approval for the session covers: a command in its working directory (the same
15783
+ * relative path elsewhere is another file), or the exact paths.
15784
+ */
14991
15785
  key(call) {
14992
15786
  const { action, resources } = call.request;
14993
- if (action === "shell") return `shell:${commandOf(call)}`;
15787
+ if (action === "shell") return `shell:${this.workdir(call)}\n${commandOf(call)}`;
14994
15788
  return `${action}:${[...resources].sort().join("\n")}`;
14995
15789
  }
15790
+ /** Where a shell call runs: its `workdir`, else the worktree. */
15791
+ workdir(call) {
15792
+ const workdir = str(call.toolInput, "workdir");
15793
+ return workdir ? resolveAgainst(workdir, this.ctx) : this.ctx.cwd;
15794
+ }
14996
15795
  };
14997
15796
  /** The command an OpenCode `shell` ask is about: the tool input, else its parsed parts. */
14998
15797
  function commandOf(call) {
@@ -16079,7 +16878,8 @@ var OpenCodeSession = class {
16079
16878
  this.broker = new ApprovalBroker(emit);
16080
16879
  this.policy = new OpenCodePolicy(opts.autonomy, {
16081
16880
  cwd: opts.cwd,
16082
- extraRoots: opts.additionalDirectories
16881
+ extraRoots: opts.additionalDirectories,
16882
+ ...opts.codedenHome ? { codedenHome: opts.codedenHome } : {}
16083
16883
  }, opts.readOnly ?? false);
16084
16884
  this.model = explicitModel(opts.model);
16085
16885
  this.effort = opts.effort;
@@ -22234,6 +23034,7 @@ async function executeCommand(core, command) {
22234
23034
  if (added.length > 0) core.log?.(`PATH: picked up ${added.join(", ")}`);
22235
23035
  const adapters = command.providerId ? registry.list().filter((a) => a.id === command.providerId) : registry.list();
22236
23036
  const statuses = await Promise.all(adapters.map((a) => a.detect()));
23037
+ if (command.refreshModels === false) return { statuses };
22237
23038
  const refreshed = registry.refreshModels(adapters.map((a) => a.id));
22238
23039
  if (!command.waitForModels) return { statuses };
22239
23040
  await refreshed;
@@ -22348,7 +23149,7 @@ async function executeCommand(core, command) {
22348
23149
  case "checkpoint.restoreFiles": return restoreFiles(command.cwd, command.sessionId, command.index, command.paths);
22349
23150
  case "ports.list": return { ports: await listPorts(command.probe) };
22350
23151
  case "mcp.test": {
22351
- const { testMcpServer } = await import("./server-test-b0hHPqXb.js");
23152
+ const { testMcpServer } = await import("./server-test-BwyCFssm.js");
22352
23153
  return testMcpServer({
22353
23154
  spec: command.spec,
22354
23155
  cwd: command.cwd,
@@ -23077,9 +23878,24 @@ async function click(session, ref, opts = {}) {
23077
23878
  const options = opts.button ? { button: opts.button } : {};
23078
23879
  await withRef(session.page(opts.tabId), ref, (locator) => opts.doubleClick ? locator.dblclick(options) : locator.click(options));
23079
23880
  }
23080
- /** Replace the value of an editable element; `submit` presses Enter afterwards. */
23881
+ /**
23882
+ * Replace the value of an editable element; `submit` presses Enter afterwards. Without a ref,
23883
+ * type the text as key presses into whatever has focus (a canvas editor, a game, a field
23884
+ * clicked at coordinates).
23885
+ */
23081
23886
  async function type(session, ref, text, opts = {}) {
23082
- await withRef(session.page(opts.tabId), ref, async (locator) => {
23887
+ const page = session.page(opts.tabId);
23888
+ if (ref === void 0) {
23889
+ if (text.length > 2e3) throw new CommandError("invalid_text", `Without a ref, type at most ${MAX_FOCUS_TYPE_CHARS} characters`);
23890
+ try {
23891
+ await page.keyboard.type(text);
23892
+ if (opts.submit) await page.keyboard.press("Enter");
23893
+ } catch (error) {
23894
+ throw new CommandError("action_failed", `Typing at the focused element failed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`);
23895
+ }
23896
+ return;
23897
+ }
23898
+ await withRef(page, ref, async (locator) => {
23083
23899
  await locator.fill(text);
23084
23900
  if (opts.submit) await locator.press("Enter");
23085
23901
  });
@@ -23174,6 +23990,150 @@ async function wait(session, opts) {
23174
23990
  throw new CommandError("action_failed", error instanceof Error ? error.message : String(error));
23175
23991
  }
23176
23992
  }
23993
+ // ../runner/src/browser/pointer.ts
23994
+ /** Drags move in about one step per this many CSS pixels, so drag thresholds see motion. */
23995
+ const DRAG_STEP_PX = 20;
23996
+ const MIN_DRAG_STEPS = 5;
23997
+ const MAX_DRAG_STEPS = 50;
23998
+ /** A scroll returns once no scroll event came for this long (smooth scrolling has ended). */
23999
+ const SCROLL_QUIET_MS = 100;
24000
+ /** ... or after this long in the page, whatever happens. */
24001
+ const SCROLL_SETTLE_MAX_MS = 1e3;
24002
+ /** The runner stops waiting for the page itself after this (a busy or hidden page). */
24003
+ const SCROLL_SETTLE_TIMEOUT_MS = 1500;
24004
+ /**
24005
+ * Resolves once the page has had no `scroll` event (on any scroller: captured on `window`) for
24006
+ * `SCROLL_QUIET_MS`, at most `SCROLL_SETTLE_MAX_MS` (a timer too: hidden tabs get no animation
24007
+ * frames). A string, so it runs in the page's realm.
24008
+ */
24009
+ const SCROLL_SETTLED = `new Promise((resolve) => {
24010
+ const start = performance.now();
24011
+ let last = start;
24012
+ const onScroll = () => { last = performance.now(); };
24013
+ const done = () => {
24014
+ removeEventListener("scroll", onScroll, { capture: true });
24015
+ clearTimeout(timer);
24016
+ resolve(true);
24017
+ };
24018
+ const timer = setTimeout(done, ${SCROLL_SETTLE_MAX_MS});
24019
+ addEventListener("scroll", onScroll, { capture: true, passive: true });
24020
+ const tick = () => {
24021
+ const now = performance.now();
24022
+ if (now - last < ${SCROLL_QUIET_MS} && now - start < ${SCROLL_SETTLE_MAX_MS}) {
24023
+ requestAnimationFrame(tick);
24024
+ } else done();
24025
+ };
24026
+ requestAnimationFrame(tick);
24027
+ })`;
24028
+ /** Intermediate moves for a drag: about one per `DRAG_STEP_PX`, within 5 to 50. */
24029
+ function dragSteps(from, to) {
24030
+ const distance = Math.hypot(to.x - from.x, to.y - from.y);
24031
+ return Math.min(Math.max(Math.round(distance / DRAG_STEP_PX), MIN_DRAG_STEPS), MAX_DRAG_STEPS);
24032
+ }
24033
+ const fmt = (n) => String(Math.round(n * 100) / 100);
24034
+ /** Refuse a point outside the viewport: it would hit nothing, or the wrong thing. */
24035
+ function assertInViewport(point, viewport) {
24036
+ if (point.x >= 0 && point.y >= 0 && point.x < viewport.width && point.y < viewport.height) return;
24037
+ throw new CommandError("out_of_bounds", `Point (${fmt(point.x)}, ${fmt(point.y)}) is outside the viewport (${fmt(viewport.width)}x${fmt(viewport.height)} CSS pixels). Take a new browser_screenshot.`);
24038
+ }
24039
+ /** The points an action touches: its point, and a drag's end. */
24040
+ function pointsOf(command) {
24041
+ const points = [{
24042
+ x: command.x,
24043
+ y: command.y
24044
+ }];
24045
+ if (command.action === "drag") points.push({
24046
+ x: command.toX ?? NaN,
24047
+ y: command.toY ?? NaN
24048
+ });
24049
+ return points;
24050
+ }
24051
+ /**
24052
+ * Run `action` with `modifiers` held, releasing every key it tried to press, also on failure.
24053
+ * A key counts as held before its press resolves: Playwright marks it down before dispatching,
24054
+ * so a press that fails can still leave it held for the next action in the shared browser.
24055
+ */
24056
+ async function withModifiers(page, modifiers, action) {
24057
+ const held = [];
24058
+ try {
24059
+ for (const key of new Set(modifiers)) {
24060
+ held.push(key);
24061
+ await page.keyboard.down(key);
24062
+ }
24063
+ await action();
24064
+ } finally {
24065
+ for (const key of held.reverse()) await page.keyboard.up(key).catch(() => void 0);
24066
+ }
24067
+ }
24068
+ /**
24069
+ * Run steps that press `button`, releasing it if any of them fails, the press included (like
24070
+ * keys, Playwright marks a button held before the press is dispatched). The error is kept.
24071
+ */
24072
+ async function releasingOnFailure(page, button, steps) {
24073
+ try {
24074
+ await steps();
24075
+ } catch (error) {
24076
+ await page.mouse.up({ button }).catch(() => void 0);
24077
+ throw error;
24078
+ }
24079
+ }
24080
+ async function drag(page, from, to, button) {
24081
+ await page.mouse.move(from.x, from.y);
24082
+ await releasingOnFailure(page, button, async () => {
24083
+ await page.mouse.down({ button });
24084
+ await page.mouse.move(to.x, to.y, { steps: dragSteps(from, to) });
24085
+ });
24086
+ await page.mouse.up({ button });
24087
+ }
24088
+ /** Wait until a scroll has been rendered, bounded; a page that never answers is not an error. */
24089
+ async function scrollSettled(page) {
24090
+ let timer;
24091
+ const timeout = new Promise((resolve) => {
24092
+ timer = setTimeout(resolve, SCROLL_SETTLE_TIMEOUT_MS);
24093
+ });
24094
+ try {
24095
+ await Promise.race([page.evaluate(SCROLL_SETTLED).catch(() => void 0), timeout]);
24096
+ } finally {
24097
+ clearTimeout(timer);
24098
+ }
24099
+ }
24100
+ /** Run one pointer action on a page whose viewport is `viewport` (CSS pixels). */
24101
+ async function mouseAction(page, command, viewport) {
24102
+ const points = pointsOf(command);
24103
+ for (const point of points) assertInViewport(point, viewport);
24104
+ const [at, end] = points;
24105
+ try {
24106
+ await withModifiers(page, command.modifiers ?? [], async () => {
24107
+ switch (command.action) {
24108
+ case "click":
24109
+ await releasingOnFailure(page, command.button, () => page.mouse.click(at.x, at.y, {
24110
+ button: command.button,
24111
+ clickCount: command.clickCount ?? 1
24112
+ }));
24113
+ return;
24114
+ case "hover":
24115
+ await page.mouse.move(at.x, at.y);
24116
+ return;
24117
+ case "drag":
24118
+ await drag(page, at, end ?? at, command.button);
24119
+ return;
24120
+ case "scroll":
24121
+ await page.mouse.move(at.x, at.y);
24122
+ await page.mouse.wheel(command.deltaX ?? 0, command.deltaY ?? 0);
24123
+ return;
24124
+ }
24125
+ });
24126
+ } catch (error) {
24127
+ if (error instanceof CommandError) throw error;
24128
+ const message = error instanceof Error ? error.message.split("\n")[0] : String(error);
24129
+ throw new CommandError("action_failed", `Mouse ${command.action} at (${fmt(at.x)}, ${fmt(at.y)}) failed: ${message}`);
24130
+ }
24131
+ if (command.action === "scroll") await scrollSettled(page);
24132
+ }
24133
+ /** `browser.mouse`: act at coordinates in a session's tab, checked against its current viewport. */
24134
+ async function mouse(session, command) {
24135
+ await mouseAction(session.page(command.tabId), command, await session.currentViewport(command.tabId));
24136
+ }
23177
24137
  // ../runner/src/browser/handlers.ts
23178
24138
  /**
23179
24139
  * Handlers for the `browser.*` runner commands (§4.3, §12.2). Wire `execute` into the
@@ -23264,6 +24224,10 @@ function createBrowserCommandHandlers(deps) {
23264
24224
  await press(manager.get(c.sessionId), c.key, c.tabId);
23265
24225
  return {};
23266
24226
  },
24227
+ "browser.mouse": async ({ name: _name, sessionId, ...command }) => {
24228
+ await mouse(manager.get(sessionId), command);
24229
+ return {};
24230
+ },
23267
24231
  "browser.screenshot": (c) => screenshot(manager.get(c.sessionId), c.path, {
23268
24232
  tabId: c.tabId,
23269
24233
  fullPage: c.fullPage,
@@ -24127,21 +25091,34 @@ var BrowserSession = class {
24127
25091
  await (await this.cdp(state)).send("Emulation.setDeviceMetricsOverride", deviceMetricsParams(this.viewport));
24128
25092
  } catch {}
24129
25093
  }
24130
- /** A native view's actual size (CSS pixels and scale), as the desktop panel laid it out. */
25094
+ /**
25095
+ * A view's own size (CSS pixels and scale); undefined while it has none. Throws when the page
25096
+ * doesn't answer in time.
25097
+ */
25098
+ async viewSize(page) {
25099
+ const [width, height, scale] = await withTimeout(page.evaluate("[window.innerWidth, window.innerHeight, window.devicePixelRatio]"), NATIVE_MEASURE_TIMEOUT_MS);
25100
+ return width && height ? {
25101
+ width,
25102
+ height,
25103
+ deviceScaleFactor: scale || 1
25104
+ } : void 0;
25105
+ }
25106
+ /** The shown native view's size, just measured: the session's viewport follows it. */
25107
+ adopt(next) {
25108
+ const changed = !sameViewport(next, this.viewport);
25109
+ this.viewport = next;
25110
+ this.measuredAt = Date.now();
25111
+ if (changed) this.screencast.setFrameSize(nativeFrame(), next);
25112
+ }
25113
+ /**
25114
+ * The shown native view's actual size, as the desktop panel laid it out. Tolerant, for viewer
25115
+ * input and viewport answers: a view that doesn't answer keeps its last size.
25116
+ */
24131
25117
  async measure() {
24132
25118
  try {
24133
- const [width, height, scale] = await withTimeout(this.page().evaluate("[window.innerWidth, window.innerHeight, window.devicePixelRatio]"), NATIVE_MEASURE_TIMEOUT_MS);
24134
- if (width && height) {
24135
- const next = {
24136
- width,
24137
- height,
24138
- deviceScaleFactor: scale || 1
24139
- };
24140
- const changed = !sameViewport(next, this.viewport);
24141
- this.viewport = next;
24142
- if (changed) this.screencast.setFrameSize(nativeFrame(), next);
24143
- }
24144
- this.measuredAt = Date.now();
25119
+ const next = await this.viewSize(this.page());
25120
+ if (next) this.adopt(next);
25121
+ else this.measuredAt = Date.now();
24145
25122
  } catch {}
24146
25123
  return { ...this.viewport };
24147
25124
  }
@@ -24152,6 +25129,22 @@ var BrowserSession = class {
24152
25129
  async syncViewport() {
24153
25130
  if (this.native) await this.measure();
24154
25131
  }
25132
+ /**
25133
+ * The viewport of `tabId` (default: the active tab) that an agent's coordinates refer to, now:
25134
+ * the emulated size every tab shares, or a native view's own size measured fresh (agent actions
25135
+ * are few, and the panel may have just resized). A native view in the background can differ
25136
+ * from the shown one, so only the shown view's size updates the session's viewport. Strict,
25137
+ * unlike `measure`: a view that can't be measured (busy in a script, navigating) fails with a
25138
+ * retryable `viewport_unavailable` instead of borrowing another size, so no action is sent.
25139
+ */
25140
+ async currentViewport(tabId) {
25141
+ const state = this.tabState(tabId);
25142
+ if (!this.native) return { ...this.viewport };
25143
+ const size = await this.viewSize(state.page).catch(() => void 0);
25144
+ if (!size) throw new CommandError("viewport_unavailable", `Could not measure the size of tab ${state.tabId} (the page may be busy or loading), so nothing was done. Try again in a moment.`);
25145
+ if (state.tabId === this.activeTabId) this.adopt(size);
25146
+ return { ...size };
25147
+ }
24155
25148
  /** The native view's size for input, measured at most once a second (pointer moves are many). */
24156
25149
  async nativeViewport() {
24157
25150
  if (Date.now() - this.measuredAt > NATIVE_MEASURE_MS) return this.measure();
@@ -24692,7 +25685,12 @@ var SessionHost = class {
24692
25685
  async send(cmd) {
24693
25686
  const hosted = this.get(cmd.sessionId);
24694
25687
  await hosted.checkpointChain;
24695
- await hosted.session.send(cmd.input);
25688
+ try {
25689
+ await hosted.session.send(cmd.input);
25690
+ } catch (error) {
25691
+ if (error instanceof ProviderSessionError && error.code === "turn_running") throw new CommandError("turn_running", error.message);
25692
+ throw error;
25693
+ }
24696
25694
  }
24697
25695
  async steer(cmd) {
24698
25696
  const { session } = this.get(cmd.sessionId);
@@ -25059,6 +26057,6 @@ async function startRunner(opts) {
25059
26057
  close
25060
26058
  };
25061
26059
  }
25062
- export { isElectronRuntime as A, installClaudeSdk as C, acquireInstallLock as D, resolveNpm as E, import_websocket_server as M, wrapper_default as N, brokenInstallMessage as O, importClaudeSdkFrom as S, runNpm as T, claudeMemoryDir as _, bufferToFormData as a, claudeSdkSource as b, refreshProcessPath as c, ImageEngineRegistry as d, createImageEngineRegistry as f, ImageGenerationError as g, resolveImageOutput as h, tryDecodeURIComponent as i, ownNodeEnv as j, findExecutable as k, defaultShell as l, imageInfo as m, readLock as n, createAdaptorServer as o, IMAGE_ASPECT_RATIOS as p, Hono as r, upgradeWebSocket as s, startRunner as t, shellCommandLine as u, findGitRoot as v, isClaudeSdkInstalled as w, CLAUDE_SDK_VERSION as x, mainRepoRoot as y };
26060
+ export { findExecutable as A, installClaudeSdk as C, acquireInstallLock as D, resolveNpm as E, ownNodeEnv as M, import_websocket_server as N, isSecretPath as O, wrapper_default as P, importClaudeSdkFrom as S, runNpm as T, claudeMemoryDir as _, bufferToFormData as a, claudeSdkSource as b, refreshProcessPath as c, ImageEngineRegistry as d, createImageEngineRegistry as f, ImageGenerationError as g, resolveImageOutput as h, tryDecodeURIComponent as i, isElectronRuntime as j, brokenInstallMessage as k, defaultShell as l, imageInfo as m, readLock as n, createAdaptorServer as o, IMAGE_ASPECT_RATIOS as p, Hono as r, upgradeWebSocket as s, startRunner as t, shellCommandLine as u, findGitRoot as v, isClaudeSdkInstalled as w, CLAUDE_SDK_VERSION as x, mainRepoRoot as y };
25063
26061
 
25064
- //# sourceMappingURL=runner-CiaiUWOE.js.map
26062
+ //# sourceMappingURL=runner-CQYh087M.js.map