@livx.cc/agentx 0.99.64 → 0.99.66

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.
@@ -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,5 +1,5 @@
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-3eJ9N9hh.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
4
  import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-DV4SkkQs.js';
5
5
 
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 } : {},
@@ -13467,20 +13495,20 @@ var GitCheckpointsOptions = class {
13467
13495
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
13468
13496
  import { homedir as homedir7 } from "os";
13469
13497
  import { join as join10 } from "path";
13470
- var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
13498
+ var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
13471
13499
  function parseOne(raw, decision) {
13472
13500
  const m = RULE_RE.exec(raw.trim());
13473
13501
  if (!m) return null;
13474
13502
  const [, tool, pathGlob] = m;
13475
13503
  return pathGlob ? { tool, pathGlob, decision } : { tool, decision };
13476
13504
  }
13477
- function parsePermRules(perms) {
13505
+ function parsePermRules(perms, explicit = false) {
13478
13506
  if (!perms) return [];
13479
13507
  const out = [];
13480
13508
  for (const [decision, list] of [["deny", perms.deny], ["allow", perms.allow], ["ask", perms.ask]]) {
13481
13509
  for (const raw of list ?? []) {
13482
13510
  const r = parseOne(raw, decision);
13483
- if (r) out.push(r);
13511
+ if (r) out.push(explicit ? { ...r, explicit } : r);
13484
13512
  }
13485
13513
  }
13486
13514
  return out;
@@ -16518,7 +16546,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
16518
16546
  if (perm.notice) err(dim(` \u26A0 ${perm.notice}
16519
16547
  `));
16520
16548
  const configRules = parsePermRules(cfg.permissions);
16521
- const flagRules = [...parsePermRules({ deny: args.disallowedTools }), ...parsePermRules({ allow: args.allowedTools })];
16549
+ const flagRules = [...parsePermRules({ deny: args.disallowedTools }, true), ...parsePermRules({ allow: args.allowedTools }, true)];
16522
16550
  const askRules = perm.gate === "ask" ? ASK_MUTATING.map((t) => ({ tool: t, decision: "ask" })) : [];
16523
16551
  const rules = [...flagRules, ...configRules, ...askRules];
16524
16552
  if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
@@ -17237,8 +17265,8 @@ async function repl(args, ai, cfg, cwd) {
17237
17265
  const sendVia = dx ? (c, o) => dx.send(c, o) : void 0;
17238
17266
  const routeVia = autoRouteSend(args, agent, () => session, (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)));
17239
17267
  const baseRules = () => [
17240
- ...parsePermRules({ deny: args.disallowedTools }),
17241
- ...parsePermRules({ allow: args.allowedTools }),
17268
+ ...parsePermRules({ deny: args.disallowedTools }, true),
17269
+ ...parsePermRules({ allow: args.allowedTools }, true),
17242
17270
  ...parsePermRules(mergePerms(loadPersistedRules(cwd), cfg.permissions))
17243
17271
  ];
17244
17272
  const askFor = (tools) => tools.map((t) => ({ tool: t, decision: "ask" }));