@livx.cc/agentx 0.99.126 → 0.99.128

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.
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { w as RouterState, H as Hooks, c as AgentOptions, x as RunResult, f as ChatUsage, R as ReasoningEffort, d as ChannelMode, b as Agent } from './secondChannel-B76KLrf7.js';
2
+ import { w as RouterState, H as Hooks, c as AgentOptions, x as RunResult, f as ChatUsage, R as ReasoningEffort, d as ChannelMode, b as Agent } from './secondChannel-D-8AGmAT.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-DwfeF8VQ.js';
5
5
 
@@ -170,6 +170,7 @@ interface Args {
170
170
  inputFormat?: 'stream-json';
171
171
  allowedTools?: string[];
172
172
  disallowedTools?: string[];
173
+ onlyTools?: string[];
173
174
  appendSystemPrompt?: string;
174
175
  voiceSystemPrompt?: string;
175
176
  addDirs?: string[];
package/dist/cli.js CHANGED
@@ -4160,16 +4160,19 @@ import { join as join4 } from "path";
4160
4160
 
4161
4161
  // src/permissions.ts
4162
4162
  init_tools_structured();
4163
+ import { posix } from "path";
4163
4164
  function commandMatches(glob, cmd) {
4164
4165
  const pattern = glob.split("*").map((s) => s.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
4165
4166
  return new RegExp("^" + pattern + "$").test(cmd);
4166
4167
  }
4168
+ var SHELL_CONTROL = /[;&|`$<>\n\r]/;
4167
4169
  function toolMatches(rule, name) {
4168
4170
  return rule.includes("*") ? commandMatches(rule, name) : rule === name;
4169
4171
  }
4170
4172
  var PermissionOptions = class {
4171
4173
  rules = [];
4172
- /** decision when no rule matches. */
4174
+ /** decision when no rule matches. 'deny' = an allow-list run (`--onlyTools`): only the allow rules run, an allowed
4175
+ * command glob never matches shell control syntax, and claude-code enforces it by a PreToolUse hook (ccHooks). */
4173
4176
  default = "allow";
4174
4177
  /** host channel for `ask` (confirm). If absent, `ask` is treated as `deny` (fail-closed). */
4175
4178
  host;
@@ -4182,15 +4185,17 @@ var PermissionPolicy = class {
4182
4185
  /** Resolve the decision for a tool call (first matching rule, else default). */
4183
4186
  decide(call) {
4184
4187
  const names = call.name === "McpCall" && typeof call.args?.name === "string" ? [call.name, call.args.name] : [call.name];
4188
+ if (CC_TOOL_ALIAS[call.name]) names.push(CC_TOOL_ALIAS[call.name]);
4189
+ const strict = this.options.default === "deny";
4185
4190
  for (const r of this.options.rules) {
4186
4191
  if (r.tool && !names.some((n) => toolMatches(r.tool, n))) continue;
4187
4192
  if (r.pathGlob) {
4188
4193
  const path = typeof call.args?.path === "string" ? call.args.path : null;
4189
4194
  const cmd = typeof call.args?.command === "string" ? call.args.command : null;
4190
4195
  if (path != null) {
4191
- if (!globToRegExp(r.pathGlob).test(path.startsWith("/") ? path : `/${path}`)) continue;
4196
+ if (!globToRegExp(r.pathGlob).test(posix.normalize(path.startsWith("/") ? path : `/${path}`))) continue;
4192
4197
  } else if (cmd != null) {
4193
- if (!commandMatches(r.pathGlob, cmd)) continue;
4198
+ if (!commandMatches(r.pathGlob, cmd) || strict && r.decision === "allow" && SHELL_CONTROL.test(cmd)) continue;
4194
4199
  } else continue;
4195
4200
  }
4196
4201
  return r.decision;
@@ -4202,6 +4207,18 @@ var PermissionPolicy = class {
4202
4207
  denyRules(explicitOnly = false) {
4203
4208
  return this.options.rules.filter((r) => r.decision === "deny" && (!explicitOnly || r.explicit)).map((r) => `${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`);
4204
4209
  }
4210
+ /** An allow-list policy (default 'deny') as Claude Code SDK hooks: claude-code runs its own tools, which never reach
4211
+ * {@link hooks}, and `disallowedTools` can't say "everything else". Every tool call (native and MCP) goes through
4212
+ * decide(); `ask` has no host there → deny. undefined for a default-allow policy (its deny rules travel as disallowedTools). */
4213
+ ccHooks() {
4214
+ if (this.options.default !== "deny") return void 0;
4215
+ const gate = async (input) => {
4216
+ const t = input?.tool_input ?? {}, path = t.file_path ?? t.notebook_path ?? t.path;
4217
+ const d = this.decide({ name: String(input?.tool_name ?? ""), args: { ...t, ...typeof path === "string" ? { path } : {} } });
4218
+ return d === "allow" ? {} : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: `not on this run's tool allow-list (${input?.tool_name})` } };
4219
+ };
4220
+ return { PreToolUse: [{ hooks: [gate] }] };
4221
+ }
4205
4222
  /** A preToolUse hook enforcing this policy (deny/ask → block; allow → proceed). */
4206
4223
  hooks() {
4207
4224
  return {
@@ -5290,6 +5307,11 @@ var Agent = class _Agent {
5290
5307
  }
5291
5308
  const denyTools = [...claudeCodeDisallowedTools(o.permissions), ...mode.disallow];
5292
5309
  if (mode.hooks) po.hooks = mode.hooks;
5310
+ const allowOnly = o.permissions?.ccHooks();
5311
+ if (allowOnly) {
5312
+ const h = po.hooks ?? o.providerOptions?.hooks ?? {};
5313
+ po.hooks = { ...h, PreToolUse: [...h.PreToolUse ?? [], ...allowOnly.PreToolUse] };
5314
+ }
5293
5315
  if (this.modeKind && o.providerOptions?.claudeCodeSession) po.claudeCodeSession = `${o.providerOptions.claudeCodeSession}:${this.modeKind}`;
5294
5316
  if (denyTools.length) {
5295
5317
  const prior = o.providerOptions?.disallowedTools ?? [];
@@ -5326,6 +5348,7 @@ var Agent = class _Agent {
5326
5348
  return texts;
5327
5349
  }
5328
5350
  /** Fold any host-injected messages into the transcript as user turns. Returns true if any landed. */
5351
+ reopen = () => this.drainInjections();
5329
5352
  drainInjections() {
5330
5353
  if (!this.pendingInjections.length) return false;
5331
5354
  for (const content of this.pendingInjections.splice(0)) this.transcript.push({ role: "user", content });
@@ -5596,6 +5619,14 @@ var Agent = class _Agent {
5596
5619
  };
5597
5620
  const overBudget = () => !!o.maxTokens && spent >= o.maxTokens;
5598
5621
  const canNudge = () => steps < o.maxSteps && !overBudget();
5622
+ this.reopen = () => {
5623
+ if (!this.pendingInjections.length) return false;
5624
+ if (steps >= o.maxSteps || overBudget()) {
5625
+ log9.warn(`answer finished with ${this.pendingInjections.length} injection(s) pending and no step/budget left \u2014 they wait for the next run`);
5626
+ return false;
5627
+ }
5628
+ return this.drainInjections();
5629
+ };
5599
5630
  while (true) {
5600
5631
  if (o.signal?.aborted) return kill("aborted");
5601
5632
  if (steps >= o.maxSteps) return kill("max_steps");
@@ -5638,6 +5669,8 @@ var Agent = class _Agent {
5638
5669
  const claudeSteerPo = this.claudeCodePo();
5639
5670
  if (this.ccServer) this.ccExec = hostExec;
5640
5671
  const cursorDeny = o.model.startsWith("cursor/") ? o.permissions?.denyRules(true) ?? [] : [];
5672
+ if (o.model.startsWith("cursor/") && o.permissions?.options.default === "deny")
5673
+ throw new Error("cursor/* cannot enforce --onlyTools: its native tools bypass it. Refusing to run \u2014 use a claude-code/* or API model.");
5641
5674
  if (cursorDeny.length)
5642
5675
  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.`);
5643
5676
  const reasonOpts = {
@@ -5740,6 +5773,7 @@ var Agent = class _Agent {
5740
5773
  usage.cacheCreationTokens += res.usage.cacheCreationTokens ?? 0;
5741
5774
  usage.cacheReadTokens += res.usage.cacheReadTokens ?? 0;
5742
5775
  const prompt = res.usage.promptTokens ?? 0, cacheRead = res.usage.cacheReadTokens ?? 0;
5776
+ const ctxTokens = res.usage.contextTokens ?? prompt;
5743
5777
  usage.contextTokens = res.usage.contextTokens ?? prompt;
5744
5778
  usage.contextWindow = res.usage.contextWindow || _Agent.contextCap(o);
5745
5779
  if (prevPrompt < 0) {
@@ -5748,7 +5782,7 @@ var Agent = class _Agent {
5748
5782
  }
5749
5783
  const reused = Math.min(prompt, Math.max(cacheRead, prevPrompt));
5750
5784
  spent += (res.usage.totalTokens ?? 0) - 0.9 * reused;
5751
- prevPrompt = stepPrompt = prompt;
5785
+ prevPrompt = stepPrompt = ctxTokens;
5752
5786
  }
5753
5787
  const delegatedTools = res.delegatedTools ?? [];
5754
5788
  if (delegatedTools.length) {
@@ -5788,7 +5822,7 @@ var Agent = class _Agent {
5788
5822
  });
5789
5823
  }
5790
5824
  if (toolCalls.length === 0) {
5791
- if (this.drainInjections()) continue;
5825
+ if (this.reopen()) continue;
5792
5826
  if (emptyTurn && o.failEmptyTurns) {
5793
5827
  if (!emptyNudged && canNudge()) {
5794
5828
  emptyNudged = true;
@@ -5826,7 +5860,7 @@ var Agent = class _Agent {
5826
5860
  const drained = await this.jobs.drain(o.signal);
5827
5861
  if (o.signal?.aborted) return kill("aborted");
5828
5862
  if (drained.length) lastProgressAt = Date.now();
5829
- if (this.drainInjections()) continue;
5863
+ if (this.reopen()) continue;
5830
5864
  this.releaseStaleImages();
5831
5865
  await this.activeHooks?.onStop?.(res.content ?? "");
5832
5866
  const claims = this.survivingClaims();
@@ -19493,6 +19527,7 @@ function parseArgs(argv) {
19493
19527
  } else if (x === "--think-model") a.thinkModel = val(++i, x);
19494
19528
  else if (x === "--no-think") a.thinkModel = false;
19495
19529
  else if (x === "--allowedTools" || x === "--allowed-tools") a.allowedTools = val(++i, x).split(",").map((s) => s.trim()).filter(Boolean);
19530
+ else if (x === "--onlyTools" || x === "--only-tools") a.onlyTools = val(++i, x).split(",").map((s) => s.trim()).filter(Boolean);
19496
19531
  else if (x === "--disallowedTools" || x === "--disallowed-tools") a.disallowedTools = val(++i, x).split(",").map((s) => s.trim()).filter(Boolean);
19497
19532
  else if (x === "--append-system-prompt") a.appendSystemPrompt = val(++i, x);
19498
19533
  else if (x === "--voice-system-prompt") a.voiceSystemPrompt = val(++i, x);
@@ -19578,6 +19613,8 @@ Flags:
19578
19613
  --verbose, --debug verbose logs (sets DEBUG=* \u2014 tool args, hook decisions, retries)
19579
19614
  --allowedTools <l> comma-list of tools to allow w/o asking, e.g. "Edit,Shell(git *)"
19580
19615
  --disallowedTools <l> comma-list of tools to deny outright (wins over allow), e.g. "Shell(rm *)"
19616
+ --onlyTools <l> allow-list: ONLY these run, all else is denied (a command glob never matches shell chaining, pipes, $ or redirects),
19617
+ e.g. "Read(/job/**),Write(/job/**),Bash(bun /skill/cli.ts *)" \u2014 cursor/* refuses it
19581
19618
  --append-system-prompt <t> extra instructions appended to the system prompt for this run
19582
19619
  --call voice call (duplex v3): the main agent (-m, full tools) answers every turn; a fast model
19583
19620
  (--voice-model, default Claude Haiku) only fills the wait \u2014 backchannels, fillers, bridges,
@@ -20359,9 +20396,9 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
20359
20396
  const perm = resolvePermMode(args, canPrompt);
20360
20397
  if (perm.notice) err(dim(` \u26A0 ${perm.notice}
20361
20398
  `));
20362
- const configRules = parsePermRules(cfg.permissions);
20363
- const flagRules = [...parsePermRules({ deny: args.disallowedTools }, true), ...parsePermRules({ allow: args.allowedTools }, true)];
20364
- const askRules = perm.gate === "ask" ? ASK_MUTATING.map((t) => ({ tool: t, decision: "ask" })) : [];
20399
+ const configRules = parsePermRules(cfg.permissions).filter((r) => !args.onlyTools || r.decision !== "allow");
20400
+ const flagRules = [...parsePermRules({ deny: args.disallowedTools }, true), ...parsePermRules({ allow: args.onlyTools ?? args.allowedTools }, true)];
20401
+ const askRules = perm.gate === "ask" && !args.onlyTools ? ASK_MUTATING.map((t) => ({ tool: t, decision: "ask" })) : [];
20365
20402
  const rules = [...flagRules, ...configRules, ...askRules];
20366
20403
  if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
20367
20404
  `));
@@ -20387,7 +20424,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
20387
20424
  stream: args.stream,
20388
20425
  host: makeHost(args.outputFormat, { stream: true }),
20389
20426
  planMode: args.plan || cfg.planMode || false,
20390
- permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve5(args.cwd ?? process.cwd())) }) : void 0,
20427
+ permissions: rules.length || args.onlyTools ? new PermissionPolicy({ rules, default: args.onlyTools ? "deny" : "allow", host: makeHost(), ask: makeAskResolver(resolve5(args.cwd ?? process.cwd())) }) : void 0,
20391
20428
  subagents: args.subagents || cfg.subagents || false,
20392
20429
  maxSteps: args.maxSteps ?? cfg.maxSteps,
20393
20430
  reasoning: args.reasoning ?? cfg.reasoning,