@livx.cc/agentx 0.99.127 → 0.99.129

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-CYU9qqwr.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 ?? [];
@@ -5647,6 +5669,8 @@ var Agent = class _Agent {
5647
5669
  const claudeSteerPo = this.claudeCodePo();
5648
5670
  if (this.ccServer) this.ccExec = hostExec;
5649
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.");
5650
5674
  if (cursorDeny.length)
5651
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.`);
5652
5676
  const reasonOpts = {
@@ -5908,7 +5932,7 @@ var Agent = class _Agent {
5908
5932
  e.code = "stall";
5909
5933
  return e;
5910
5934
  };
5911
- const poke = () => {
5935
+ const poke = (quietMs) => {
5912
5936
  if (!stallMs) return;
5913
5937
  if (timer) clearTimeout(timer);
5914
5938
  timer = setTimeout(() => {
@@ -5923,7 +5947,7 @@ var Agent = class _Agent {
5923
5947
  log9.warn(`stall watchdog: no stream output for ${stallMs}ms and no tool holding the stream \u2014 aborting the request` + (inFlight.length ? ` (${inFlight.length} detached job(s) running, not awaited by the stream: ${inFlight.map((j) => `${j.id}/${j.kind}`).join(", ")})` : ""));
5924
5948
  fired = true;
5925
5949
  ac.abort(stallError());
5926
- }, stallMs);
5950
+ }, Math.max(stallMs, quietMs ?? 0));
5927
5951
  };
5928
5952
  this.stallPoke = poke;
5929
5953
  poke();
@@ -5965,7 +5989,7 @@ var Agent = class _Agent {
5965
5989
  let resolvedModel;
5966
5990
  for await (const chunk of stream) {
5967
5991
  if (chunk.model) resolvedModel = chunk.model;
5968
- poke?.();
5992
+ poke?.(chunk.quietMs);
5969
5993
  if (this.options.signal?.aborted) break;
5970
5994
  if (chunk.reasoningContent) {
5971
5995
  reasoning += chunk.reasoningContent;
@@ -19503,6 +19527,7 @@ function parseArgs(argv) {
19503
19527
  } else if (x === "--think-model") a.thinkModel = val(++i, x);
19504
19528
  else if (x === "--no-think") a.thinkModel = false;
19505
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);
19506
19531
  else if (x === "--disallowedTools" || x === "--disallowed-tools") a.disallowedTools = val(++i, x).split(",").map((s) => s.trim()).filter(Boolean);
19507
19532
  else if (x === "--append-system-prompt") a.appendSystemPrompt = val(++i, x);
19508
19533
  else if (x === "--voice-system-prompt") a.voiceSystemPrompt = val(++i, x);
@@ -19588,6 +19613,8 @@ Flags:
19588
19613
  --verbose, --debug verbose logs (sets DEBUG=* \u2014 tool args, hook decisions, retries)
19589
19614
  --allowedTools <l> comma-list of tools to allow w/o asking, e.g. "Edit,Shell(git *)"
19590
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
19591
19618
  --append-system-prompt <t> extra instructions appended to the system prompt for this run
19592
19619
  --call voice call (duplex v3): the main agent (-m, full tools) answers every turn; a fast model
19593
19620
  (--voice-model, default Claude Haiku) only fills the wait \u2014 backchannels, fillers, bridges,
@@ -20369,9 +20396,9 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
20369
20396
  const perm = resolvePermMode(args, canPrompt);
20370
20397
  if (perm.notice) err(dim(` \u26A0 ${perm.notice}
20371
20398
  `));
20372
- const configRules = parsePermRules(cfg.permissions);
20373
- const flagRules = [...parsePermRules({ deny: args.disallowedTools }, true), ...parsePermRules({ allow: args.allowedTools }, true)];
20374
- 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" })) : [];
20375
20402
  const rules = [...flagRules, ...configRules, ...askRules];
20376
20403
  if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
20377
20404
  `));
@@ -20397,7 +20424,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
20397
20424
  stream: args.stream,
20398
20425
  host: makeHost(args.outputFormat, { stream: true }),
20399
20426
  planMode: args.plan || cfg.planMode || false,
20400
- 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,
20401
20428
  subagents: args.subagents || cfg.subagents || false,
20402
20429
  maxSteps: args.maxSteps ?? cfg.maxSteps,
20403
20430
  reasoning: args.reasoning ?? cfg.reasoning,