@livx.cc/agentx 0.99.122 → 0.99.123

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/README.md CHANGED
@@ -87,7 +87,7 @@ Beyond file tools, the runtime ships the higher-altitude pieces too — each an
87
87
  - **Skills + memory** — VFS-backed (`skillsDir`/`memoryDir`); persistence is just the backend choice.
88
88
  - **Subagents** (`subagents`; **typed agents** via `agentsDir` — `<dir>/<name>.md` defines a persona + model + scoped tools, selected with the `Task` `agentType`), **hooks** (`hooks`: preToolUse/postToolUse/onStop — block or audit any tool call), **slash-commands** (`commandsDir`), **TodoWrite**, **MCP** (`mcpToolsToAgentTools`).
89
89
  - **A delegated child is a full agent**: it inherits the parent's **tools** (incl. host-injected/MCP ones), **system prompt**, and **providerOptions** (only when it stays on the same provider — cursor's `cwd`/`cursorSession` never leak onto an anthropic child; override with `providerOptionsFor(model)`). `Task({ model })` (and a `TaskBatch` task's `model`) runs one sub-task on another model — alias or full id, precedence **explicit `model` > `agentType` def's `model` > parent's**. A def's frontmatter `tools:` allowlist narrows the inherited set and may name a host-injected tool by name; its body still overrides the persona. The host's `preToolUse` gate is inherited too, so delegation is not a way around it (the observer/lifecycle hooks stay parent-only). Inherited tools that escape the VFS are re-bound per child (`AgentTool.withCwd`): a `isolation:"worktree"` child's real `Shell` runs in the WORKTREE, keeping any host rename (`Shell` -> `Bash`) (background shell jobs are off there, and the parent-bound `ShellOutput`/`ShellStatus`/`ShellKill` are dropped rather than pointed at the parent's jobs); an overlay child's shell writes straight to the working tree — an overlay isolates the VFS, not a process.
90
- - **Two-model modes** (`src/modes.ts`) — **delegate** (`delegate: { worker }`): the agent's model PLANS and may not write (Write/Edit/MultiEdit/ApplyEdits/bash/Shell are refused at execution, keyed on `ToolContext.model`), `Task`/`TaskBatch` go on (depth 1, children default to the worker, and get the host's `postToolUse` so their writes are observed). **advisor** (`advisor: { model, limit?, snapshot? }`): the agent's model does the work and an `Advise` tool asks the stronger model (≤3 calls; it sees only the request, the host's optional `snapshot()` and the question); the first write waits for one consult. Both are off when the models are equal or either is `cursor/*`; the second model's usage comes back as `RunResult.workerUsage` / `workerModel` (not in `usage`). Prompt directives switch them: `[opus->haiku] task` (delegate), `[haiku<-opus] task` (advisor), `[off]`; keyed forms `delegate:x`, `advisor:x`, `mode:off`. A host passing its own `keys` to `parseDirectives` must list `delegate` / `advisor` / `mode`.
90
+ - **Two-model modes** (`src/modes.ts`) — **delegate** (`delegate: { worker }`): the agent's model PLANS and may not write (Write/Edit/MultiEdit/ApplyEdits/bash/Shell are refused at execution, keyed on `ToolContext.model`), `Task`/`TaskBatch` go on (depth 1, children default to the worker, and get the host's `postToolUse` so their writes are observed). **advisor** (`advisor: { model, limit?, snapshot? }`): the agent's model does the work and an `Advise` tool asks the stronger model (≤3 calls; it sees only the request, the host's optional `snapshot()` and the question); the first write waits for one consult. A `claude-code/*` agent gets the same modes as SDK options (`ccModeOptions`): a planner's native Write/Edit/MultiEdit/NotebookEdit/Bash/Task/Agent are `disallowedTools` and agentx's Task/TaskBatch reach it over the in-process bridge; an advised worker gets `Advise` bridged and a PreToolUse hook that denies native writes until one consult. Mixed pairs (anthropic ↔ claude-code) work. Both are off when the models are equal or either is `cursor/*`; the second model's usage comes back as `RunResult.workerUsage` / `workerModel` (not in `usage`). Prompt directives switch them: `[opus->haiku] task` (delegate), `[haiku<-opus] task` (advisor), `[off]`; keyed forms `delegate:x`, `advisor:x`, `mode:off`. A host passing its own `keys` to `parseDirectives` must list `delegate` / `advisor` / `mode`.
91
91
  - **Streaming** (`stream: true` → `text_delta` via `HostBridge.notify`) and **context compaction** (`compaction: { maxMessages }` → edge-safe summarize-and-boundary). Defaults preserve the original non-stream, drop-oldest behavior.
92
92
  - **Multi-turn + project context** — `Agent.send()` continues a conversation across turns (vs `run()`, which starts fresh); **project instructions** (`instructionFiles`: `AGENTS.md`/`CLAUDE.md` at the FS root) inject into the system prompt.
93
93
  - **DuplexAgent** (`src/duplex.ts`) — voice-optimized three-tier engine (reflex/act/think): a fast reflex agent streams instant replies and self-selects escalation — `Act` for standard tool work (Sonnet-class), `Think` for deep reasoning (Opus-class, configurable, default on). Results are pushed back and re-voiced by the reflex (turn mutex, coalesced completions, `TaskStatus`/`CancelTask`). See [`mind/10`](./mind/10-duplex.md).
package/dist/cli.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { u as RouterState, H as Hooks, c as AgentOptions, v as RunResult, e as ChatUsage, R as ReasoningEffort, C as ChannelMode, b as Agent } from './secondChannel-BddfVzTp.js';
2
+ import { v as RouterState, H as Hooks, c as AgentOptions, w as RunResult, f as ChatUsage, R as ReasoningEffort, d as ChannelMode, b as Agent } from './secondChannel-DES_VIeN.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-DZHZIyxh.js';
4
+ import { M as Message, U as UserQuestion, H as HostBridge, c as ContentPart, o as MessageContent } from './tools-DwfeF8VQ.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
@@ -825,6 +825,7 @@ var init_tools_structured = __esm({
825
825
  };
826
826
  writeTool = {
827
827
  name: "Write",
828
+ writes: true,
828
829
  description: "Create or overwrite a file with the given contents, creating parent directories as needed. Use for new files instead of `bash echo >`. To replace an EXISTING file you must Read it first \u2014 overwriting an un-Read file is refused (it would destroy unseen content).",
829
830
  parameters: {
830
831
  type: "object",
@@ -850,6 +851,7 @@ var init_tools_structured = __esm({
850
851
  };
851
852
  multiEditTool = {
852
853
  name: "MultiEdit",
854
+ writes: true,
853
855
  description: "Apply several exact-substring replacements to one file in order, in a single call. Requires a prior Read. Each `old_string` must be unique at the time it is applied. All-or-nothing: if any edit fails, none are written.",
854
856
  parameters: {
855
857
  type: "object",
@@ -891,6 +893,7 @@ var init_tools_structured = __esm({
891
893
  };
892
894
  applyEditsTool = {
893
895
  name: "ApplyEdits",
896
+ writes: true,
894
897
  description: "Apply edits across one or MORE files in a single call \u2014 for cross-file refactors (rename/extract/move). edits=[{path, old_string?, new_string}]. WITH old_string: replace that exact substring (must be UNIQUE in the file \u2014 add surrounding context; read fresh + verified, no prior Read needed). WITHOUT old_string: write new_string as the whole file (creates it + parent dirs; but to OVERWRITE a file that already exists you must Read it first, else use old_string to edit part of it). Locate sites first with Grep (its output shows the exact text). Atomic: validated across all files before any write.",
895
898
  parameters: {
896
899
  type: "object",
@@ -1584,6 +1587,7 @@ var init_tools = __esm({
1584
1587
  };
1585
1588
  bashTool = {
1586
1589
  name: "bash",
1590
+ writes: true,
1587
1591
  description: "Run a shell command. Supports ls, cat, grep, find, head, tail, echo, mkdir, rm, mv, cp, wc, pipes (|), redirects (>, >>), and chaining (&&, ||, ;). Best for: running tests/builds, file operations (mkdir/mv/rm), and piped workflows. For searching file contents, prefer `Grep` (structured results, no re-parse). For finding files by name, prefer `Glob`.",
1588
1592
  parameters: {
1589
1593
  type: "object",
@@ -1661,6 +1665,7 @@ var init_tools = __esm({
1661
1665
  };
1662
1666
  editTool = {
1663
1667
  name: "Edit",
1668
+ writes: true,
1664
1669
  description: "Replace an exact substring in a file. Requires a prior Read of the same file. `old_string` must occur exactly once \u2014 include surrounding context to disambiguate.",
1665
1670
  parameters: {
1666
1671
  type: "object",
@@ -2992,6 +2997,7 @@ function makeRealShellTool(options) {
2992
2997
  };
2993
2998
  return {
2994
2999
  name: "Shell",
3000
+ writes: true,
2995
3001
  // Rebind for an isolated child agent (git worktree): same policy/env/timeouts, new cwd. The
2996
3002
  // `registry` is deliberately dropped — it is bound to the PARENT's cwd (and, in hosts like
2997
3003
  // shraga-ee, to the parent's session), so a background job started from the child would run
@@ -3967,8 +3973,8 @@ function memorySearchTool(fs, dirs) {
3967
3973
  return `Error: invalid regex: ${e}`;
3968
3974
  }
3969
3975
  } else {
3970
- const lower = q2.toLowerCase();
3971
- matcher = (l) => l.toLowerCase().includes(lower);
3976
+ const lower2 = q2.toLowerCase();
3977
+ matcher = (l) => l.toLowerCase().includes(lower2);
3972
3978
  }
3973
3979
  const loaded = [];
3974
3980
  for (const slug2 of slugs) {
@@ -4618,6 +4624,11 @@ init_logging();
4618
4624
  var log8 = forComponent("modes");
4619
4625
  var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "ApplyEdits"]);
4620
4626
  var PLANNER_BLOCKED = /* @__PURE__ */ new Set([...WRITE_TOOLS, "bash", "Shell"]);
4627
+ var lower = (xs) => new Set([...xs].map((x) => x.toLowerCase()));
4628
+ var PLANNER_BLOCKED_LC = lower(PLANNER_BLOCKED);
4629
+ var WRITE_TOOLS_LC = lower(WRITE_TOOLS);
4630
+ var plannerBlocks = (t) => !!t.writes || PLANNER_BLOCKED_LC.has(t.name.toLowerCase());
4631
+ var isFileWrite = (t) => WRITE_TOOLS_LC.has(t.name.toLowerCase());
4621
4632
  var PLANNER_REFUSAL = "Refused: you are the planner and do not write files. Delegate the change with Task/TaskBatch (model set to the worker) and give the worker a full spec.";
4622
4633
  var PLAN_FIRST = "Refused: check your plan with `Advise` before the first write (files, data shape, the hard parts). Then write.";
4623
4634
  var ADVISE_LIMIT = 3;
@@ -4640,7 +4651,7 @@ function advisorFor(worker, advisor) {
4640
4651
  return advisor && pairOk("advisor", worker, advisor) ? advisor : void 0;
4641
4652
  }
4642
4653
  function delegateTools(tools, planner) {
4643
- return tools.map((t) => !PLANNER_BLOCKED.has(t.name) ? t : {
4654
+ return tools.map((t) => !plannerBlocks(t) ? t : {
4644
4655
  ...t,
4645
4656
  run: async (...a) => a[1]?.model === planner ? PLANNER_REFUSAL : t.run(...a)
4646
4657
  });
@@ -4710,7 +4721,7 @@ ${question}` }
4710
4721
  }
4711
4722
  }
4712
4723
  };
4713
- const gate = (tools) => tools.map((t) => !WRITE_TOOLS.has(t.name) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
4724
+ const gate = (tools) => tools.map((t) => !isFileWrite(t) ? t : { ...t, run: async (...a) => used ? t.run(...a) : PLAN_FIRST });
4714
4725
  return { tool, gate, used: () => used };
4715
4726
  }
4716
4727
  function meterWorkers(ai, main2) {
@@ -4753,6 +4764,13 @@ function meterWorkers(ai, main2) {
4753
4764
  resolved = void 0;
4754
4765
  } };
4755
4766
  }
4767
+ var CC_WRITE_TOOLS = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
4768
+ function ccModeOptions(kind, hostHooks, used) {
4769
+ if (kind === "delegate") return { bridge: /* @__PURE__ */ new Set(["Task", "TaskBatch"]), disallow: [...CC_WRITE_TOOLS, "Bash", "Task", "Agent"] };
4770
+ if (kind !== "advisor") return { bridge: /* @__PURE__ */ new Set(), disallow: [] };
4771
+ const gate = { matcher: CC_WRITE_TOOLS.join("|"), hooks: [async () => used() ? {} : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: PLAN_FIRST.replace("`Advise`", "`mcp__agentx__Advise`") } }] };
4772
+ return { bridge: /* @__PURE__ */ new Set(["Advise"]), disallow: [], hooks: { ...hostHooks, PreToolUse: [...hostHooks?.PreToolUse ?? [], gate] } };
4773
+ }
4756
4774
 
4757
4775
  // src/lint.ts
4758
4776
  var CODE_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
@@ -5080,6 +5098,8 @@ var Agent = class _Agent {
5080
5098
  meter;
5081
5099
  // delegate/advisor: meters the second model's calls
5082
5100
  modeModel;
5101
+ modeKind;
5102
+ adv;
5083
5103
  lastTask = "";
5084
5104
  // the current turn's text — what the advisor reads as the request
5085
5105
  /** claude-code bridge for agentx-only tools (ccBridge.ts) + the current step's executor it calls. */
@@ -5184,12 +5204,15 @@ var Agent = class _Agent {
5184
5204
  const o = this.options;
5185
5205
  if (!o.model.startsWith("claude-code/")) return void 0;
5186
5206
  const po = { steer: () => this.drainPendingSteers() };
5187
- const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
5207
+ const mode = ccModeOptions(this.modeKind, o.providerOptions?.hooks, () => this.adv?.used() ?? 0);
5208
+ const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name) || mode.bridge.has(t.name);
5188
5209
  if (o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
5189
5210
  this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
5190
5211
  po.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
5191
5212
  }
5192
- const denyTools = claudeCodeDisallowedTools(o.permissions);
5213
+ const denyTools = [...claudeCodeDisallowedTools(o.permissions), ...mode.disallow];
5214
+ if (mode.hooks) po.hooks = mode.hooks;
5215
+ if (this.modeKind && o.providerOptions?.claudeCodeSession) po.claudeCodeSession = `${o.providerOptions.claudeCodeSession}:${this.modeKind}`;
5193
5216
  if (denyTools.length) {
5194
5217
  const prior = o.providerOptions?.disallowedTools ?? [];
5195
5218
  po.disallowedTools = [.../* @__PURE__ */ new Set([...prior, ...denyTools])];
@@ -5287,7 +5310,8 @@ var Agent = class _Agent {
5287
5310
  const advisor = !worker && o.advisor ? advisorFor(o.model, o.advisor.model) : void 0;
5288
5311
  this.meter = worker || advisor ? meterWorkers(o.ai, o.model) : void 0;
5289
5312
  const ai = this.meter?.ai ?? o.ai;
5290
- const adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
5313
+ this.modeKind = worker ? "delegate" : advisor ? "advisor" : void 0;
5314
+ const adv = this.adv = advisor ? makeAdvisor({ ai, advisor, request: () => this.lastTask, snapshot: o.advisor.snapshot, limit: o.advisor.limit }) : void 0;
5291
5315
  if (o.instructionFiles !== false) {
5292
5316
  const names = Array.isArray(o.instructionFiles) ? o.instructionFiles : void 0;
5293
5317
  const ins = await loadInstructions(fs, names);
@@ -7389,8 +7413,8 @@ function isTranscriptComplete(text) {
7389
7413
  const lastWord = words4[words4.length - 1];
7390
7414
  if (COMPLETE_ENDINGS.has(lastWord)) return true;
7391
7415
  if (INCOMPLETE_ENDINGS.has(lastWord)) return false;
7392
- const lower = trimmed.toLowerCase();
7393
- for (const marker of CONTINUATION_MARKERS) if (lower.endsWith(marker)) return false;
7416
+ const lower2 = trimmed.toLowerCase();
7417
+ for (const marker of CONTINUATION_MARKERS) if (lower2.endsWith(marker)) return false;
7394
7418
  if (words4.length <= 2) return false;
7395
7419
  return true;
7396
7420
  }
@@ -14986,6 +15010,7 @@ function readJsonFile(path, fallback) {
14986
15010
  }
14987
15011
 
14988
15012
  // cli/core.ts
15013
+ init_models();
14989
15014
  init_hostPath();
14990
15015
  init_platform();
14991
15016
  var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
@@ -15182,6 +15207,14 @@ Reference files in them by their mount path (the left side).`;
15182
15207
  const po = providerOptionsFor(o.model, cwd, o.mcpServers, o.hostMcpServers);
15183
15208
  return po ? { providerOptions: o.lean && isClaudeCode ? { ...po, settingSources: [] } : po } : {};
15184
15209
  })(),
15210
+ // A child on ANOTHER provider (delegate mode's anthropic planner → claude-code/* worker, or back) gets that
15211
+ // runtime's own options (cwd, env, MCP) — the Agent default inherits only same-provider options, so a claude-code
15212
+ // worker under an anthropic planner ran with none. Same provider: the Agent default (parent's, minus its resume).
15213
+ providerOptionsFor: (m) => {
15214
+ const a = agentRef.agent, po = a?.options.providerOptions;
15215
+ if (modelProvider(m) === modelProvider(a?.options.model ?? "")) return po ? { ...po, resume: void 0, onClaudeCodeSession: void 0 } : void 0;
15216
+ return providerOptionsFor(m, cwd, o.mcpServers, o.hostMcpServers);
15217
+ },
15185
15218
  ...(() => {
15186
15219
  const now4 = /* @__PURE__ */ new Date();
15187
15220
  const platformNames = { darwin: "macOS", linux: "Linux", win32: "Windows" };