@livx.cc/agentx 0.99.63 → 0.99.65

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.
@@ -1,5 +1,5 @@
1
1
  import { IFilesystem } from '@livx.cc/wcli/core';
2
- import { M as Message, o as MessageContent, H as HostBridge, A as AgentTool, C as ChatLike, k as JobRegistry, U as UserQuestion } from './tools-HsbxgqjF.js';
2
+ import { M as Message, o as MessageContent, H as HostBridge, A as AgentTool, C as ChatLike, k as JobRegistry, U as UserQuestion } from './tools-DV4SkkQs.js';
3
3
 
4
4
  /**
5
5
  * `--model auto`: per-turn model routing. A classifier (Jev by default — injected, so a regex or
@@ -209,6 +209,8 @@ interface PermissionRule {
209
209
  /** glob on the call's `path` arg to match (omit = any/none). */
210
210
  pathGlob?: string;
211
211
  decision: Decision;
212
+ /** Set by the caller for per-run rules (CLI `--disallowedTools`/`--allowedTools`), vs rules loaded from config files. */
213
+ explicit?: boolean;
212
214
  }
213
215
  declare class PermissionOptions {
214
216
  rules: PermissionRule[];
@@ -229,9 +231,18 @@ declare class PermissionPolicy {
229
231
  constructor(options?: Partial<PermissionOptions>);
230
232
  /** Resolve the decision for a tool call (first matching rule, else default). */
231
233
  decide(call: ToolUse): Decision;
234
+ /** The policy's `deny` rules as rule strings (`Tool` / `Tool(glob)`) — for delegated runtimes that run
235
+ * their OWN tools (claude-code/cursor) and never pass through {@link hooks}. */
236
+ denyRules(explicitOnly?: boolean): string[];
232
237
  /** A preToolUse hook enforcing this policy (deny/ask → block; allow → proceed). */
233
238
  hooks(): Hooks;
234
239
  }
240
+ /**
241
+ * A policy's deny rules as Claude Code `disallowedTools` entries (deduped; agentx shell names mapped to
242
+ * `Bash`). The Claude Agent SDK removes these from the model's context and refuses them even under
243
+ * `bypassPermissions`. A bare `*` (deny everything) has no CC spelling, so it throws — fail closed.
244
+ */
245
+ declare function claudeCodeDisallowedTools(policy?: PermissionPolicy): string[];
235
246
  /** Tools the agent must not run before the plan is approved (sensible default). */
236
247
  declare const DEFAULT_MUTATING: string[];
237
248
  /**
@@ -732,4 +743,4 @@ declare class Agent {
732
743
  * stub made `/compact` chase a target it could never reach and drop-oldest the conversation away. */
733
744
  declare function estimateTokens(m: Message[], keepRecentImages?: number): number;
734
745
 
735
- export { Agent as A, type ChatFragment as C, DEFAULT_MUTATING as D, type Hooks as H, ModelRouter as M, PermissionOptions as P, type ReasoningEffort as R, type Tier as T, AgentOptions as a, type Classification as b, type Classifier as c, type CompactResult as d, type Decision as e, ModelRouterOptions as f, PermissionPolicy as g, type PermissionRule as h, type PreToolUseDecision as i, RecordingHooks as j, RecordingLifecycle as k, type RouteDecision as l, type RouterState as m, type RunResult as n, type ToolUse as o, type ToolUseMeta as p, composeHooks as q, estimateTokens as r, jevAvailable as s, jevClassifier as t, pickTier as u, planMode as v, reasoningToChatFragment as w };
746
+ export { Agent as A, type ChatFragment as C, DEFAULT_MUTATING as D, type Hooks as H, ModelRouter as M, PermissionOptions as P, type ReasoningEffort as R, type Tier as T, AgentOptions as a, type Classification as b, type Classifier as c, type CompactResult as d, type Decision as e, ModelRouterOptions as f, PermissionPolicy as g, type PermissionRule as h, type PreToolUseDecision as i, RecordingHooks as j, RecordingLifecycle as k, type RouteDecision as l, type RouterState as m, type RunResult as n, type ToolUse as o, type ToolUseMeta as p, claudeCodeDisallowedTools as q, composeHooks as r, estimateTokens as s, jevAvailable as t, jevClassifier as u, pickTier as v, planMode as w, reasoningToChatFragment as x };
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { m as RouterState, H as Hooks, n as RunResult, R as ReasoningEffort, A as Agent } from './Agent-DbqXQy5q.js';
2
+ import { m as RouterState, H as Hooks, n as RunResult, R as ReasoningEffort, A as Agent } from './Agent-BpCqDWFG.js';
3
3
  import { IFilesystem } from '@livx.cc/wcli/core';
4
- import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-HsbxgqjF.js';
4
+ import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-DV4SkkQs.js';
5
5
 
6
6
  /**
7
7
  * On-disk session store for the CLI: each conversation is one JSON file at
package/dist/cli.js CHANGED
@@ -3581,6 +3581,149 @@ function modelProvider(model) {
3581
3581
  return `?${model}`;
3582
3582
  }
3583
3583
 
3584
+ // src/permissions.ts
3585
+ init_tools_structured();
3586
+ function commandMatches(glob, cmd) {
3587
+ const pattern = glob.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
3588
+ return new RegExp("^" + pattern + "$").test(cmd);
3589
+ }
3590
+ function toolMatches(rule, name) {
3591
+ return rule.includes("*") ? commandMatches(rule, name) : rule === name;
3592
+ }
3593
+ var PermissionOptions = class {
3594
+ rules = [];
3595
+ /** decision when no rule matches. */
3596
+ default = "allow";
3597
+ /** host channel for `ask` (confirm). If absent, `ask` is treated as `deny` (fail-closed). */
3598
+ host;
3599
+ };
3600
+ var PermissionPolicy = class {
3601
+ options;
3602
+ constructor(options) {
3603
+ this.options = { ...new PermissionOptions(), ...options };
3604
+ }
3605
+ /** Resolve the decision for a tool call (first matching rule, else default). */
3606
+ decide(call) {
3607
+ const names = call.name === "McpCall" && typeof call.args?.name === "string" ? [call.name, call.args.name] : [call.name];
3608
+ for (const r of this.options.rules) {
3609
+ if (r.tool && !names.some((n) => toolMatches(r.tool, n))) continue;
3610
+ if (r.pathGlob) {
3611
+ const path = typeof call.args?.path === "string" ? call.args.path : null;
3612
+ const cmd = typeof call.args?.command === "string" ? call.args.command : null;
3613
+ if (path != null) {
3614
+ if (!globToRegExp(r.pathGlob).test(path.startsWith("/") ? path : `/${path}`)) continue;
3615
+ } else if (cmd != null) {
3616
+ if (!commandMatches(r.pathGlob, cmd)) continue;
3617
+ } else continue;
3618
+ }
3619
+ return r.decision;
3620
+ }
3621
+ return this.options.default;
3622
+ }
3623
+ /** The policy's `deny` rules as rule strings (`Tool` / `Tool(glob)`) — for delegated runtimes that run
3624
+ * their OWN tools (claude-code/cursor) and never pass through {@link hooks}. */
3625
+ denyRules(explicitOnly = false) {
3626
+ return this.options.rules.filter((r) => r.decision === "deny" && (!explicitOnly || r.explicit)).map((r) => `${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`);
3627
+ }
3628
+ /** A preToolUse hook enforcing this policy (deny/ask → block; allow → proceed). */
3629
+ hooks() {
3630
+ return {
3631
+ preToolUse: async (call) => {
3632
+ const d = this.decide(call);
3633
+ if (d === "allow") return;
3634
+ if (d === "deny") return { block: true, reason: `denied by permission policy (${call.name})` };
3635
+ if (this.options.ask) {
3636
+ const r = await this.options.ask(call);
3637
+ const decision = r?.decision ?? "deny";
3638
+ const rememberTool2 = call.name === "McpCall" && typeof call.args?.name === "string" ? call.args.name : call.name;
3639
+ if (r?.remember) this.options.rules.unshift({ tool: rememberTool2, decision });
3640
+ return decision === "allow" ? void 0 : { block: true, reason: `not approved (${call.name})` };
3641
+ }
3642
+ const ok = await this.options.host?.confirm?.(`Allow ${call.name}${call.args?.path ? " on " + call.args.path : ""}?`);
3643
+ return ok ? void 0 : { block: true, reason: `not approved (${call.name})` };
3644
+ }
3645
+ };
3646
+ }
3647
+ };
3648
+ var CC_TOOL_ALIAS = { Shell: "Bash", bash: "Bash" };
3649
+ function claudeCodeDisallowedTools(policy) {
3650
+ const out = /* @__PURE__ */ new Set();
3651
+ for (const rule of policy?.denyRules() ?? []) {
3652
+ const m = /^([^(]+)(\(.*\))?$/.exec(rule);
3653
+ if (m[1] === "*") throw new Error("permission policy denies every tool (`*`) \u2014 claude-code cannot express that; refusing to run.");
3654
+ out.add((CC_TOOL_ALIAS[m[1]] ?? m[1]) + (m[2] ?? ""));
3655
+ }
3656
+ return [...out];
3657
+ }
3658
+ var DEFAULT_MUTATING = ["Write", "Edit", "MultiEdit", "deleteFile", "bash"];
3659
+ function planMode(opts) {
3660
+ const mutating = new Set(opts?.mutating ?? DEFAULT_MUTATING);
3661
+ let approved = false;
3662
+ const tool = {
3663
+ name: "ExitPlanMode",
3664
+ description: "Call when your plan is ready and you want to start making changes. Provide the `plan`. Until this is approved, edits/writes are blocked.",
3665
+ parameters: { type: "object", required: ["plan"], properties: { plan: { type: "string", description: "the concrete steps you will take" } } },
3666
+ async run({ plan }, _ctx) {
3667
+ if (opts?.host?.confirm) {
3668
+ const confirm = opts.host.confirm(`Approve this plan?
3669
+
3670
+ ${String(plan ?? "")}`);
3671
+ const ok = await (_ctx?.parkHuman ? _ctx.parkHuman(confirm) : confirm);
3672
+ if (!ok) return "Plan not approved. Revise it and call ExitPlanMode again.";
3673
+ }
3674
+ approved = true;
3675
+ return "Plan approved \u2014 you may now make changes.";
3676
+ }
3677
+ };
3678
+ const hooks = {
3679
+ preToolUse: (call) => !approved && mutating.has(call.name) ? { block: true, reason: "plan mode: present a plan and call ExitPlanMode (approved) before editing." } : void 0
3680
+ };
3681
+ return { hooks, tool };
3682
+ }
3683
+ function composeHooks(...list) {
3684
+ const hooks = list.filter(Boolean);
3685
+ return {
3686
+ async preToolUse(call, meta) {
3687
+ for (const h of hooks) {
3688
+ const d = await h.preToolUse?.(call, meta);
3689
+ if (d?.block) return d;
3690
+ }
3691
+ },
3692
+ async postToolUse(call, result, meta) {
3693
+ for (const h of hooks) await h.postToolUse?.(call, result, meta);
3694
+ },
3695
+ onToolOutput(call, chunk, meta) {
3696
+ for (const h of hooks) h.onToolOutput?.(call, chunk, meta);
3697
+ },
3698
+ onStop(text) {
3699
+ for (const h of hooks) h.onStop?.(text);
3700
+ },
3701
+ // lifecycle: concatenate session-start context; chain prompt-submit transforms; fan out pre-compact.
3702
+ async onSessionStart() {
3703
+ let ctx = "";
3704
+ for (const h of hooks) {
3705
+ const r = await h.onSessionStart?.();
3706
+ if (r) ctx += (ctx ? "\n\n" : "") + r;
3707
+ }
3708
+ return ctx || void 0;
3709
+ },
3710
+ async onUserPromptSubmit(text) {
3711
+ let t = text;
3712
+ for (const h of hooks) {
3713
+ const r = await h.onUserPromptSubmit?.(t);
3714
+ if (typeof r === "string") t = r;
3715
+ }
3716
+ return t;
3717
+ },
3718
+ async onPreCompact(messages) {
3719
+ for (const h of hooks) await h.onPreCompact?.(messages);
3720
+ },
3721
+ async onSubagentStop(summary, info) {
3722
+ for (const h of hooks) await h.onSubagentStop?.(summary, info);
3723
+ }
3724
+ };
3725
+ }
3726
+
3584
3727
  // src/subagent.ts
3585
3728
  init_OverlayFilesystem();
3586
3729
  init_worktree();
@@ -3738,6 +3881,8 @@ function childOptionsFor(opts, fs, depth, maxDepth, agentType, model, root) {
3738
3881
  }
3739
3882
  const po = opts.providerOptionsFor?.(childModel);
3740
3883
  if (po) childOpts.providerOptions = po;
3884
+ const deny = opts.permissions?.options.rules.filter((r) => r.decision === "deny") ?? [];
3885
+ if (deny.length) childOpts.permissions = new PermissionPolicy({ rules: deny, default: "allow" });
3741
3886
  if (opts.hooks?.preToolUse) childOpts.hooks = { preToolUse: opts.hooks.preToolUse.bind(opts.hooks) };
3742
3887
  return childOpts;
3743
3888
  }
@@ -3957,133 +4102,6 @@ async function loadAgents(fs, dir) {
3957
4102
 
3958
4103
  // src/Agent.ts
3959
4104
  init_OverlayFilesystem();
3960
-
3961
- // src/permissions.ts
3962
- init_tools_structured();
3963
- function commandMatches(glob, cmd) {
3964
- const pattern = glob.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
3965
- return new RegExp("^" + pattern + "$").test(cmd);
3966
- }
3967
- var PermissionOptions = class {
3968
- rules = [];
3969
- /** decision when no rule matches. */
3970
- default = "allow";
3971
- /** host channel for `ask` (confirm). If absent, `ask` is treated as `deny` (fail-closed). */
3972
- host;
3973
- };
3974
- var PermissionPolicy = class {
3975
- options;
3976
- constructor(options) {
3977
- this.options = { ...new PermissionOptions(), ...options };
3978
- }
3979
- /** Resolve the decision for a tool call (first matching rule, else default). */
3980
- decide(call) {
3981
- const names = call.name === "McpCall" && typeof call.args?.name === "string" ? [call.name, call.args.name] : [call.name];
3982
- for (const r of this.options.rules) {
3983
- if (r.tool && !names.includes(r.tool)) continue;
3984
- if (r.pathGlob) {
3985
- const path = typeof call.args?.path === "string" ? call.args.path : null;
3986
- const cmd = typeof call.args?.command === "string" ? call.args.command : null;
3987
- if (path != null) {
3988
- if (!globToRegExp(r.pathGlob).test(path.startsWith("/") ? path : `/${path}`)) continue;
3989
- } else if (cmd != null) {
3990
- if (!commandMatches(r.pathGlob, cmd)) continue;
3991
- } else continue;
3992
- }
3993
- return r.decision;
3994
- }
3995
- return this.options.default;
3996
- }
3997
- /** A preToolUse hook enforcing this policy (deny/ask → block; allow → proceed). */
3998
- hooks() {
3999
- return {
4000
- preToolUse: async (call) => {
4001
- const d = this.decide(call);
4002
- if (d === "allow") return;
4003
- if (d === "deny") return { block: true, reason: `denied by permission policy (${call.name})` };
4004
- if (this.options.ask) {
4005
- const r = await this.options.ask(call);
4006
- const decision = r?.decision ?? "deny";
4007
- const rememberTool2 = call.name === "McpCall" && typeof call.args?.name === "string" ? call.args.name : call.name;
4008
- if (r?.remember) this.options.rules.unshift({ tool: rememberTool2, decision });
4009
- return decision === "allow" ? void 0 : { block: true, reason: `not approved (${call.name})` };
4010
- }
4011
- const ok = await this.options.host?.confirm?.(`Allow ${call.name}${call.args?.path ? " on " + call.args.path : ""}?`);
4012
- return ok ? void 0 : { block: true, reason: `not approved (${call.name})` };
4013
- }
4014
- };
4015
- }
4016
- };
4017
- var DEFAULT_MUTATING = ["Write", "Edit", "MultiEdit", "deleteFile", "bash"];
4018
- function planMode(opts) {
4019
- const mutating = new Set(opts?.mutating ?? DEFAULT_MUTATING);
4020
- let approved = false;
4021
- const tool = {
4022
- name: "ExitPlanMode",
4023
- description: "Call when your plan is ready and you want to start making changes. Provide the `plan`. Until this is approved, edits/writes are blocked.",
4024
- parameters: { type: "object", required: ["plan"], properties: { plan: { type: "string", description: "the concrete steps you will take" } } },
4025
- async run({ plan }, _ctx) {
4026
- if (opts?.host?.confirm) {
4027
- const confirm = opts.host.confirm(`Approve this plan?
4028
-
4029
- ${String(plan ?? "")}`);
4030
- const ok = await (_ctx?.parkHuman ? _ctx.parkHuman(confirm) : confirm);
4031
- if (!ok) return "Plan not approved. Revise it and call ExitPlanMode again.";
4032
- }
4033
- approved = true;
4034
- return "Plan approved \u2014 you may now make changes.";
4035
- }
4036
- };
4037
- const hooks = {
4038
- preToolUse: (call) => !approved && mutating.has(call.name) ? { block: true, reason: "plan mode: present a plan and call ExitPlanMode (approved) before editing." } : void 0
4039
- };
4040
- return { hooks, tool };
4041
- }
4042
- function composeHooks(...list) {
4043
- const hooks = list.filter(Boolean);
4044
- return {
4045
- async preToolUse(call, meta) {
4046
- for (const h of hooks) {
4047
- const d = await h.preToolUse?.(call, meta);
4048
- if (d?.block) return d;
4049
- }
4050
- },
4051
- async postToolUse(call, result, meta) {
4052
- for (const h of hooks) await h.postToolUse?.(call, result, meta);
4053
- },
4054
- onToolOutput(call, chunk, meta) {
4055
- for (const h of hooks) h.onToolOutput?.(call, chunk, meta);
4056
- },
4057
- onStop(text) {
4058
- for (const h of hooks) h.onStop?.(text);
4059
- },
4060
- // lifecycle: concatenate session-start context; chain prompt-submit transforms; fan out pre-compact.
4061
- async onSessionStart() {
4062
- let ctx = "";
4063
- for (const h of hooks) {
4064
- const r = await h.onSessionStart?.();
4065
- if (r) ctx += (ctx ? "\n\n" : "") + r;
4066
- }
4067
- return ctx || void 0;
4068
- },
4069
- async onUserPromptSubmit(text) {
4070
- let t = text;
4071
- for (const h of hooks) {
4072
- const r = await h.onUserPromptSubmit?.(t);
4073
- if (typeof r === "string") t = r;
4074
- }
4075
- return t;
4076
- },
4077
- async onPreCompact(messages) {
4078
- for (const h of hooks) await h.onPreCompact?.(messages);
4079
- },
4080
- async onSubagentStop(summary, info) {
4081
- for (const h of hooks) await h.onSubagentStop?.(summary, info);
4082
- }
4083
- };
4084
- }
4085
-
4086
- // src/Agent.ts
4087
4105
  init_logging();
4088
4106
 
4089
4107
  // src/lint.ts
@@ -4603,6 +4621,8 @@ var Agent = class _Agent {
4603
4621
  agents,
4604
4622
  hooks: o.hooks,
4605
4623
  traceDir: o.traceDir,
4624
+ permissions: o.permissions,
4625
+ // a child obeys the parent's allow/deny rules (else Task is a one-call bypass)
4606
4626
  tools: [...o.tools, ...this.injectedTools],
4607
4627
  systemPrompt: o.systemPrompt,
4608
4628
  providerOptionsFor: o.providerOptionsFor ?? ((m) => modelProvider(m) === modelProvider(o.model) ? o.providerOptions : void 0)
@@ -4775,6 +4795,14 @@ var Agent = class _Agent {
4775
4795
  }
4776
4796
  } : void 0;
4777
4797
  const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
4798
+ const denyTools = o.model.startsWith("claude-code/") ? claudeCodeDisallowedTools(o.permissions) : [];
4799
+ if (denyTools.length) {
4800
+ const prior = o.providerOptions?.disallowedTools ?? [];
4801
+ claudeSteerPo.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
4802
+ }
4803
+ const cursorDeny = o.model.startsWith("cursor/") ? o.permissions?.denyRules(true) ?? [] : [];
4804
+ if (cursorDeny.length)
4805
+ throw new Error(`cursor/* cannot enforce --disallowedTools (${cursorDeny.join(", ")}): its native tools bypass them. Refusing to run \u2014 use a claude-code/* or API model, or drop --disallowedTools.`);
4778
4806
  const reasonOpts = {
4779
4807
  ...frag,
4780
4808
  ...o.promptCache ? { promptCache: true } : {},
@@ -4891,6 +4919,9 @@ var Agent = class _Agent {
4891
4919
  this.transcript.push({
4892
4920
  role: "assistant",
4893
4921
  content: res.content ?? "",
4922
+ // Record the model the adapter actually ran: aliases (`claude-code/opus`) resolve to a concrete
4923
+ // id (e.g. `claude-opus-5-5`); fall back to the requested model if the adapter didn't report one.
4924
+ model: res.model ?? o.model,
4894
4925
  ...toolCalls.length ? { tool_calls: toolCalls } : {},
4895
4926
  ...res.reasoningContent && passesBackReasoning(o.model) ? { reasoning_content: res.reasoningContent } : {}
4896
4927
  });
@@ -5053,7 +5084,9 @@ var Agent = class _Agent {
5053
5084
  let sawTool = false;
5054
5085
  let trailing = "";
5055
5086
  let reasoning = "";
5087
+ let resolvedModel;
5056
5088
  for await (const chunk of stream) {
5089
+ if (chunk.model) resolvedModel = chunk.model;
5057
5090
  poke?.();
5058
5091
  if (this.options.signal?.aborted) break;
5059
5092
  if (chunk.reasoningContent) {
@@ -5102,7 +5135,7 @@ var Agent = class _Agent {
5102
5135
  }
5103
5136
  const delegatedTools = delegatedOrder.map((k) => delegated.get(k));
5104
5137
  const endedWithoutClosing = sawTool && (!trailing.trim() || endsOnAnnouncedAction(trailing));
5105
- return { content, ...reasoning ? { reasoningContent: reasoning } : {}, ...finishReason ? { finishReason } : {}, ...toolCalls ? { toolCalls } : {}, ...usage ? { usage } : {}, ...delegatedTools.length ? { delegatedTools } : {}, ...endedWithoutClosing ? { endedWithoutClosing: true } : {} };
5138
+ return { content, ...resolvedModel ? { model: resolvedModel } : {}, ...reasoning ? { reasoningContent: reasoning } : {}, ...finishReason ? { finishReason } : {}, ...toolCalls ? { toolCalls } : {}, ...usage ? { usage } : {}, ...delegatedTools.length ? { delegatedTools } : {}, ...endedWithoutClosing ? { endedWithoutClosing: true } : {} };
5106
5139
  }
5107
5140
  /**
5108
5141
  * FABRICATED TOOL USE — the failure no call-reactive layer can see, because there IS no call.
@@ -13462,20 +13495,20 @@ var GitCheckpointsOptions = class {
13462
13495
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
13463
13496
  import { homedir as homedir7 } from "os";
13464
13497
  import { join as join10 } from "path";
13465
- var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
13498
+ var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
13466
13499
  function parseOne(raw, decision) {
13467
13500
  const m = RULE_RE.exec(raw.trim());
13468
13501
  if (!m) return null;
13469
13502
  const [, tool, pathGlob] = m;
13470
13503
  return pathGlob ? { tool, pathGlob, decision } : { tool, decision };
13471
13504
  }
13472
- function parsePermRules(perms) {
13505
+ function parsePermRules(perms, explicit = false) {
13473
13506
  if (!perms) return [];
13474
13507
  const out = [];
13475
13508
  for (const [decision, list] of [["deny", perms.deny], ["allow", perms.allow], ["ask", perms.ask]]) {
13476
13509
  for (const raw of list ?? []) {
13477
13510
  const r = parseOne(raw, decision);
13478
- if (r) out.push(r);
13511
+ if (r) out.push(explicit ? { ...r, explicit } : r);
13479
13512
  }
13480
13513
  }
13481
13514
  return out;
@@ -16513,7 +16546,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
16513
16546
  if (perm.notice) err(dim(` \u26A0 ${perm.notice}
16514
16547
  `));
16515
16548
  const configRules = parsePermRules(cfg.permissions);
16516
- const flagRules = [...parsePermRules({ deny: args.disallowedTools }), ...parsePermRules({ allow: args.allowedTools })];
16549
+ const flagRules = [...parsePermRules({ deny: args.disallowedTools }, true), ...parsePermRules({ allow: args.allowedTools }, true)];
16517
16550
  const askRules = perm.gate === "ask" ? ASK_MUTATING.map((t) => ({ tool: t, decision: "ask" })) : [];
16518
16551
  const rules = [...flagRules, ...configRules, ...askRules];
16519
16552
  if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
@@ -17232,8 +17265,8 @@ async function repl(args, ai, cfg, cwd) {
17232
17265
  const sendVia = dx ? (c, o) => dx.send(c, o) : void 0;
17233
17266
  const routeVia = autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)));
17234
17267
  const baseRules = () => [
17235
- ...parsePermRules({ deny: args.disallowedTools }),
17236
- ...parsePermRules({ allow: args.allowedTools }),
17268
+ ...parsePermRules({ deny: args.disallowedTools }, true),
17269
+ ...parsePermRules({ allow: args.allowedTools }, true),
17237
17270
  ...parsePermRules(mergePerms(loadPersistedRules(cwd), cfg.permissions))
17238
17271
  ];
17239
17272
  const askFor = (tools) => tools.map((t) => ({ tool: t, decision: "ask" }));