@hicaru/pi-rlm 0.3.1 → 0.3.2

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.
Files changed (44) hide show
  1. package/README.md +34 -5
  2. package/README.ru.md +5 -5
  3. package/README.zh-CN.md +5 -5
  4. package/package.json +1 -1
  5. package/src/bridge/handlers/await.ts +148 -0
  6. package/src/bridge/handlers/completion.ts +72 -0
  7. package/src/bridge/handlers/emitting.ts +104 -0
  8. package/src/bridge/handlers/finish.ts +45 -0
  9. package/src/bridge/handlers/index.ts +48 -0
  10. package/src/bridge/handlers/llm-query.ts +130 -0
  11. package/src/bridge/handlers/rlm-query.ts +227 -0
  12. package/src/bridge/handlers/task-registry.ts +202 -0
  13. package/src/bridge/handlers/types.ts +136 -0
  14. package/src/commands/rlm-config.ts +33 -14
  15. package/src/context/listing.ts +2 -2
  16. package/src/context/refresh.ts +141 -0
  17. package/src/core/engine.ts +16 -18
  18. package/src/core/types.ts +1 -3
  19. package/src/index.ts +54 -42
  20. package/src/mode/native-guards.ts +4 -4
  21. package/src/mode/subagent.ts +1 -1
  22. package/src/prompts/glossary.ts +71 -74
  23. package/src/prompts/native.ts +127 -85
  24. package/src/prompts/system.ts +29 -15
  25. package/src/sandbox/interrupts.ts +258 -68
  26. package/src/sandbox/protocol.ts +53 -30
  27. package/src/sandbox/py/__pycache__/guards.cpython-314.pyc +0 -0
  28. package/src/sandbox/py/__pycache__/hostio.cpython-314.pyc +0 -0
  29. package/src/sandbox/py/__pycache__/retrieval.cpython-314.pyc +0 -0
  30. package/src/sandbox/py/__pycache__/tasks.cpython-314.pyc +0 -0
  31. package/src/sandbox/py/guards.py +8 -5
  32. package/src/sandbox/py/retrieval.py +17 -8
  33. package/src/sandbox/py/tasks.py +1 -1
  34. package/src/sandbox/py/worker.py +106 -79
  35. package/src/sandbox/sandbox-manager.ts +26 -1
  36. package/src/sandbox/sandbox.ts +1 -1
  37. package/src/tool/background-tasks.ts +1 -1
  38. package/src/tool/repl-result.ts +2 -2
  39. package/src/tool/repl-tool.ts +13 -14
  40. package/src/ui/config-panel.ts +1 -1
  41. package/src/ui/intro.ts +1 -4
  42. package/src/ui/model-picker.ts +28 -2
  43. package/src/util/concurrency.ts +1 -1
  44. package/src/bridge/subcall-handlers.ts +0 -382
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Keep RLM `context` in sync with the disk after native edit/write.
3
+ *
4
+ * Seed packs file bodies once; without this, search/map_files/llm still see pre-edit text.
5
+ */
6
+
7
+ import { readFile } from "node:fs/promises";
8
+ import { isAbsolute, relative, resolve } from "node:path";
9
+ import { estimateTokens } from "../text/tokens.ts";
10
+ import type { ContextFile } from "./types.ts";
11
+
12
+ /** Paths that look like tool file targets. */
13
+ export function extractEditPaths(input: unknown): readonly string[] {
14
+ if (typeof input !== "object" || input === null) return Object.freeze([]);
15
+ const o = input as Record<string, unknown>;
16
+ const keys = ["path", "file_path", "filePath", "filename", "file"] as const;
17
+ const out: string[] = [];
18
+ for (const k of keys) {
19
+ const v = o[k];
20
+ if (typeof v === "string" && v.trim() !== "") out.push(v.trim());
21
+ }
22
+ // Some tools pass { path, oldText, newText } only — already covered.
23
+ return Object.freeze(out);
24
+ }
25
+
26
+ /**
27
+ * Normalize disk path to how cwd-seed entries usually appear (relative to cwd when under cwd).
28
+ */
29
+ export function normalizeContextPath(filePath: string, cwd: string): string {
30
+ const abs = isAbsolute(filePath) ? resolve(filePath) : resolve(cwd, filePath);
31
+ const rel = relative(cwd, abs);
32
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return abs;
33
+ return rel.split("\\").join("/");
34
+ }
35
+
36
+ function pathMatches(entryPath: string, target: string, cwd: string): boolean {
37
+ if (entryPath === target) return true;
38
+ const a = normalizeContextPath(entryPath, cwd);
39
+ const b = normalizeContextPath(target, cwd);
40
+ if (a === b) return true;
41
+ // suffix match for namespaced entries
42
+ return entryPath.endsWith("/" + target) || entryPath.endsWith(target);
43
+ }
44
+
45
+ /**
46
+ * Upsert one file into a context payload list. Returns a **new** array (identity change
47
+ * so BM25 stamp invalidates when the worker rebinds `context`).
48
+ */
49
+ export function upsertContextFile(
50
+ payload: unknown,
51
+ filePath: string,
52
+ content: string,
53
+ cwd: string,
54
+ ): ContextFile[] {
55
+ const path = normalizeContextPath(filePath, cwd);
56
+ const tokens = estimateTokens(content.length);
57
+ const entry: ContextFile = Object.freeze({ path, content, tokens });
58
+
59
+ if (!Array.isArray(payload)) {
60
+ return [entry];
61
+ }
62
+
63
+ const next = new Array<ContextFile>(payload.length + 1);
64
+ let n = 0;
65
+ let replaced = false;
66
+ for (let i = 0; i < payload.length; i++) {
67
+ const item: unknown = payload[i];
68
+ if (
69
+ item !== null &&
70
+ typeof item === "object" &&
71
+ "path" in item &&
72
+ typeof (item as { path: unknown }).path === "string" &&
73
+ pathMatches((item as { path: string }).path, path, cwd)
74
+ ) {
75
+ next[n++] = entry;
76
+ replaced = true;
77
+ } else if (
78
+ item !== null &&
79
+ typeof item === "object" &&
80
+ "path" in item &&
81
+ "content" in item &&
82
+ typeof (item as { path: unknown }).path === "string" &&
83
+ typeof (item as { content: unknown }).content === "string"
84
+ ) {
85
+ const e = item as { path: string; content: string; tokens?: number };
86
+ next[n++] = Object.freeze({
87
+ path: e.path,
88
+ content: e.content,
89
+ tokens: typeof e.tokens === "number" ? e.tokens : estimateTokens(e.content.length),
90
+ });
91
+ }
92
+ // drop non-file entries silently (shouldn't appear in file bundles)
93
+ }
94
+ if (!replaced) next[n++] = entry;
95
+ next.length = n;
96
+ return next;
97
+ }
98
+
99
+ /** Read file from disk; return null if missing/unreadable. */
100
+ export async function readDiskFile(filePath: string, cwd: string): Promise<string | null> {
101
+ const abs = isAbsolute(filePath) ? resolve(filePath) : resolve(cwd, filePath);
102
+ try {
103
+ return await readFile(abs, "utf8");
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Python snippet that rebinds `context` with updated path content and forces a new list id
111
+ * so BM25 rebuilds on next search.
112
+ */
113
+ export function patchContextExecCode(filePath: string, content: string, cwd: string): string {
114
+ const path = normalizeContextPath(filePath, cwd);
115
+ // JSON for safe embedding in Python string literals
116
+ const pathLit = JSON.stringify(path);
117
+ const contentLit = JSON.stringify(content);
118
+ const tokens = estimateTokens(content.length);
119
+ return `
120
+ _path = ${pathLit}
121
+ _content = ${contentLit}
122
+ _tokens = ${tokens}
123
+ _old = context if isinstance(context, list) else []
124
+ _next = []
125
+ _found = False
126
+ for _e in _old:
127
+ if isinstance(_e, dict) and str(_e.get("path", "")) in (_path, _path.replace("\\\\", "/")):
128
+ _next.append({"path": _path, "content": _content, "tokens": _tokens})
129
+ _found = True
130
+ elif isinstance(_e, dict) and (
131
+ str(_e.get("path", "")).endswith("/" + _path) or str(_e.get("path", "")).endswith(_path)
132
+ ):
133
+ _next.append({"path": str(_e.get("path")), "content": _content, "tokens": _tokens})
134
+ _found = True
135
+ else:
136
+ _next.append(_e)
137
+ if not _found:
138
+ _next.append({"path": _path, "content": _content, "tokens": _tokens})
139
+ context = _next
140
+ `.trim();
141
+ }
@@ -13,10 +13,10 @@ import { buildAddContextHandler } from "../bridge/add-context.ts";
13
13
  import { mergeIntoContext } from "../context/merge.ts";
14
14
  import {
15
15
  createSubcallHandlers,
16
+ createTaskRegistry,
16
17
  type Invocation,
17
- } from "../bridge/subcall-handlers.ts";
18
+ } from "../bridge/handlers/index.ts";
18
19
  import { type ChatMsg, modelComplete } from "../bridge/model.ts";
19
- import { resolveModelId } from "../config/settings.ts";
20
20
  import { buildRlmSystemPrompt } from "../prompts/system.ts";
21
21
  import { buildTurnPrompt, FINALIZE_PROMPT } from "../prompts/user.ts";
22
22
  import type { RlmEmitter } from "../tool/rlm-events.ts";
@@ -30,7 +30,6 @@ import { appendUserMessage } from "./history.ts";
30
30
  import { runTurn } from "./iteration.ts";
31
31
  import { type Limits, LimitError, LimitGuard } from "./limits.ts";
32
32
  import type { RlmConfig, RlmInput, RlmResult, RunRlm, Sampling } from "./types.ts";
33
- import { formatError } from "../util/errors.ts";
34
33
  import { createSubcallGates, type SubcallGates } from "../util/concurrency.ts";
35
34
 
36
35
  /**
@@ -71,20 +70,7 @@ export function createEngine(deps: EngineDeps): RunRlm {
71
70
  emitter.emitTurn(0, deps.config.maxIterations);
72
71
  }
73
72
 
74
- const overrideModel = input.modelOverride ? resolveModelId(deps.registry, input.modelOverride) : undefined;
75
- if (input.modelOverride && !overrideModel) {
76
- if (selfReportId) emitter.emitSubcallUpdated({ id: selfReportId, status: "error", detail: "unknown model override" });
77
- else emitter.emitStatus("error");
78
- return {
79
- answer: formatError(`unknown model override '${input.modelOverride}'`),
80
- iterations: 0,
81
- costUsd: 0,
82
- inputTokens: 0,
83
- outputTokens: 0,
84
- durationMs: 0,
85
- };
86
- }
87
- const model = overrideModel ?? deps.model;
73
+ const model = deps.model;
88
74
 
89
75
  // Create LimitGuard BEFORE the bridge so sub-LLM usage feeds into it.
90
76
  // Children inherit the parent's remaining timeout (propagated as remaining amount, not
@@ -117,6 +103,8 @@ export function createEngine(deps: EngineDeps): RunRlm {
117
103
  // run can settle or abort it first (a child engine left running would keep spending).
118
104
  let detachedInFlight = 0;
119
105
  let detachedIdle: (() => void) | undefined;
106
+ // One registry per run — unawaited task reminders share the same map as await handlers.
107
+ const taskRegistry = createTaskRegistry();
120
108
  const subcalls = createSubcallHandlers({
121
109
  resolve: () => invocation,
122
110
  gates: deps.gates
@@ -140,7 +128,7 @@ export function createEngine(deps: EngineDeps): RunRlm {
140
128
  if (detachedInFlight === 0) detachedIdle?.();
141
129
  }
142
130
  },
143
- });
131
+ }, taskRegistry);
144
132
  /** Wait (bounded) for detached work before the sandbox goes away. */
145
133
  const settleDetached = async (): Promise<void> => {
146
134
  if (detachedInFlight === 0) return;
@@ -189,6 +177,7 @@ export function createEngine(deps: EngineDeps): RunRlm {
189
177
  maxPromptChars: deps.config.maxPromptChars,
190
178
  contextLoader: deps.config.contextLoader,
191
179
  child: input.depth > 0,
180
+ depth: input.depth,
192
181
  });
193
182
 
194
183
  const contextHandlers = deps.config.contextLoader
@@ -251,6 +240,15 @@ export function createEngine(deps: EngineDeps): RunRlm {
251
240
  pendingReplOutputs = undefined;
252
241
  }
253
242
 
243
+ // Soft runtime nudge (rlm_test parity): remind the model to await pending host tasks.
244
+ const pendingIds = taskRegistry.awaitDeps.unawaitedIds();
245
+ if (pendingIds.length > 0) {
246
+ appendUserMessage(
247
+ history,
248
+ `[runtime] Unawaited task_ids: ${pendingIds.join(", ")} — call await before finish.`,
249
+ );
250
+ }
251
+
254
252
  appendUserMessage(history, buildTurnPrompt(i, deps.config.maxIterations));
255
253
 
256
254
  // rootSampling fields win; smartReasoning is the default reasoning when not overridden.
package/src/core/types.ts CHANGED
@@ -55,7 +55,7 @@ export interface RlmConfig {
55
55
  * Keeps each turn short so the next turn's input stays manageable.
56
56
  * `reasoning` is read from `smartReasoning` if omitted here. */
57
57
  readonly rootSampling?: Readonly<Sampling>;
58
- /** System prompt injected into every llm_query / llm_query_batched sub-call.
58
+ /** System prompt injected into every llm_query / llm_batch sub-call.
59
59
  * Instructs the worker model to respond concisely.
60
60
  * undefined = no system prompt (raw completion). */
61
61
  readonly subSystemPrompt?: string;
@@ -73,8 +73,6 @@ export interface RlmInput {
73
73
  readonly depth: number;
74
74
  /** AgentTree node to attach this run's node under (set when recursing). */
75
75
  readonly parentNodeId?: string;
76
- /** "provider/id" — overrides the root model for this run (set by recursive rlm_query). */
77
- readonly modelOverride?: string;
78
76
  /** Remaining timeout for this subtree (set by parent from its LimitGuard). */
79
77
  readonly remainingTimeoutMs?: number;
80
78
  }
package/src/index.ts CHANGED
@@ -18,9 +18,10 @@ import { BackgroundTasks } from "./tool/background-tasks.ts";
18
18
  import { resolve } from "node:path";
19
19
  import { resolveSource } from "./context/resolve.ts";
20
20
  import { formatContextListing } from "./context/listing.ts";
21
+ import { extractEditPaths, readDiskFile } from "./context/refresh.ts";
21
22
  import type { AddContextHandlerBundle } from "./bridge/add-context.ts";
22
- import { buildNativeSystemPrompt, NATIVE_TURN_REMINDER } from "./prompts/native.ts";
23
- import { bashCommandFromInput, isFileReadingCommand, capToolResultText, BASH_BLOCK_REASON } from "./mode/native-guards.ts";
23
+ import { buildNativeSystemPrompt } from "./prompts/native.ts";
24
+ import { capToolResultText } from "./mode/native-guards.ts";
24
25
  import {
25
26
  isSubagentChildBypass,
26
27
  commitSubagentForceActivation,
@@ -37,14 +38,13 @@ export {
37
38
  processRlmDepth,
38
39
  } from "./mode/subagent.ts";
39
40
 
40
- const BLOCKED_NATIVE_TOOLS = Object.freeze(new Set(["read", "grep"]));
41
41
  /** How often to keep the parent sandbox's request watchdog alive during detached work. */
42
42
  const WATCHDOG_HEARTBEAT_MS = 30_000;
43
- const CAPPED_RESULT_TOOLS = Object.freeze(new Set(["bash", "find", "ls"]));
43
+ /** Soft token guard — cap bulk tool stdout; do NOT hard-block read/grep/bash readers. */
44
+ const CAPPED_RESULT_TOOLS = Object.freeze(new Set(["bash", "find", "ls", "read", "grep"]));
44
45
 
45
46
  export default function rlmExtension(pi: ExtensionAPI): void {
46
- // Subagent children run a native tool flow; RLM's contract is the opposite
47
- // (block read/grep, route through repl). Env fast path: full bypass when
47
+ // Subagent children may bypass full RLM registration. Env fast path when
48
48
  // PI_SUBAGENT_CHILD=1 (unless force-in under the depth cap). See mode/subagent.ts.
49
49
  if (isSubagentChildBypass()) {
50
50
  if (traceEnabled) {
@@ -182,7 +182,23 @@ export default function rlmExtension(pi: ExtensionAPI): void {
182
182
 
183
183
  if (controller.savedLlmRef) {
184
184
  const resolved = resolveModelId(ctx.modelRegistry, controller.savedLlmRef);
185
- if (resolved) controller.llmModel = resolved;
185
+ if (resolved) {
186
+ controller.llmModel = resolved;
187
+ } else {
188
+ // Keep the pin on disk/controller — do not fall back permanently. Runtime uses
189
+ // cheapest until the catalog has the model again; surface that once per session.
190
+ console.warn(
191
+ `[rlm] pinned sub-LLM ${controller.savedLlmRef} not in registry; using cheapest until it reappears`,
192
+ );
193
+ try {
194
+ ctx.ui.notify(
195
+ `RLM: pinned llm=${controller.savedLlmRef} unavailable — using cheapest until it is`,
196
+ "warning",
197
+ );
198
+ } catch {
199
+ // Some hosts have no UI at session_start.
200
+ }
201
+ }
186
202
  }
187
203
 
188
204
  // Re-register repl tool each session to pick up model provider changes
@@ -254,7 +270,7 @@ export default function rlmExtension(pi: ExtensionAPI): void {
254
270
  const filtered = event.messages.filter(
255
271
  (message) =>
256
272
  !(message.role === "custom" && message.customType === "rlm-intro")
257
- && !(message.role === "user" && typeof message.content === "string" && message.content === NATIVE_TURN_REMINDER),
273
+
258
274
  );
259
275
  if (!nativeTradeHolds()) return { messages: filtered };
260
276
 
@@ -266,11 +282,11 @@ export default function rlmExtension(pi: ExtensionAPI): void {
266
282
  listingPayloadRef = payload;
267
283
  const listing = formatContextListing(payload);
268
284
  const instruction = [
269
- "ANALYZE with repl({code}) read/grep are DISABLED.",
270
- "Files you have loaded live in the Python REPL `context` variable (starts empty; cwd seeds on first repl()).",
271
- "Locate with search()/grep_context()/outline() (free), then delegate bulk reading to",
272
- "map_files()/llm_query_batched(). Use add_context(path) for external dirs/files/docs/git URLs.",
273
- "If credits exhausted → report and stop.",
285
+ "Prefer repl({code}) for bulk analysis: free search/grep/outline, then fan-out Tasks.",
286
+ "Multi-module work rlm_batch (or rlm_query); one-shot extracts map_files/llm_batch.",
287
+ "Always-spawn returns Task (↯bg); only await_task has content — fire-all then await.",
288
+ "Large tool/repl outputs are capped. Files live in REPL `context` (cwd seeds first repl()).",
289
+ "add_context(path) for external dirs/files/docs/git. Credits exhausted → report and stop.",
274
290
  "",
275
291
  ].join("\n");
276
292
  filtered.unshift({
@@ -280,42 +296,38 @@ export default function rlmExtension(pi: ExtensionAPI): void {
280
296
  } as PiMessage);
281
297
  }
282
298
 
283
- // Per-turn last-position reminder (not persisted — context hook rebuilds every request)
284
- filtered.push({
285
- role: "user" as const,
286
- content: NATIVE_TURN_REMINDER,
287
- timestamp: 0,
288
- } as PiMessage);
289
299
 
290
300
  return { messages: filtered };
291
301
  });
292
302
 
293
- // ── Native mode restrictions: keep bulk file content out of root-model context ──
294
- // `edit`/`write` stay unblocked so the agent modifies files through Pi's native
295
- // tool flow (visible to all plugins, +/- diff preview). File reading/searching
296
- // belongs in the REPL, and bash output is capped as a backstop.
297
- // Fail-open when repl is not active (e.g. --tools allowlist without repl): never
298
- // confiscate readers without a working substitute (RLM paper §2 trade).
299
- pi.on("tool_call", async (event) => {
300
- if (!nativeTradeHolds()) {
301
- if (traceEnabled && controller.enabled) {
302
- trace("native.block_skip", { toolName: event.toolName, reason: "no_active_repl" });
303
+ // Soft token guard only never hard-block read/grep/bash. Large tool results are capped.
304
+ // After edit/write, re-read disk into RLM context so search/llm see fresh content.
305
+ const MUTATING_FILE_TOOLS = Object.freeze(new Set(["edit", "write"]));
306
+
307
+ pi.on("tool_result", async (event, ctx) => {
308
+ // ── Keep RLM context fresh after native file mutations ──
309
+ if (
310
+ nativeTradeHolds()
311
+ && MUTATING_FILE_TOOLS.has(event.toolName)
312
+ && event.isError !== true
313
+ ) {
314
+ const cwd = resolve(ctx?.cwd ?? process.cwd());
315
+ const paths = extractEditPaths(event.input);
316
+ for (const p of paths) {
317
+ const body = await readDiskFile(p, cwd);
318
+ if (body === null) continue;
319
+ try {
320
+ await sandboxManager.refreshFileFromDisk(p, body, cwd);
321
+ // Listing must re-inject if we rewrote payload identity
322
+ listingPayloadRef = undefined;
323
+ } catch (err) {
324
+ if (traceEnabled) {
325
+ trace("context.refresh_fail", { path: p, error: errorMessage(err) });
326
+ }
327
+ }
303
328
  }
304
- return;
305
- }
306
- if (BLOCKED_NATIVE_TOOLS.has(event.toolName)) {
307
- return {
308
- block: true,
309
- reason: "RLM mode active. Use repl({code}) to read files and search the repository — loaded files live in the REPL `context` variable (cwd seeds on first call). Use `edit`/`write` for file changes. If sub-LLM credits are exhausted, report to the user.",
310
- };
311
329
  }
312
- const bashCommand = event.toolName === "bash" ? bashCommandFromInput(event.input) : undefined;
313
- if (bashCommand !== undefined && isFileReadingCommand(bashCommand)) {
314
- return { block: true, reason: BASH_BLOCK_REASON };
315
- }
316
- });
317
330
 
318
- pi.on("tool_result", async (event) => {
319
331
  if (!nativeTradeHolds() || !CAPPED_RESULT_TOOLS.has(event.toolName)) return;
320
332
  let changed = false;
321
333
  const content = event.content.map((c) => {
@@ -43,7 +43,7 @@ export function isFileReadingCommand(command: string): boolean {
43
43
  export const BASH_BLOCK_REASON =
44
44
  "RLM mode: reading files via bash is blocked — that dumps file content into the root model's " +
45
45
  "context. All files are pre-loaded in the REPL `context` variable: use repl({code}) with Python " +
46
- "string/regex search, and delegate bulk analysis to llm_query / llm_query_batched / " +
46
+ "string/regex search, and delegate bulk analysis to llm_query / llm_batch / " +
47
47
  "llm_query_chunked. bash is for RUNNING things (tests, builds, git).";
48
48
 
49
49
  /** Max chars of tool output forwarded to the root model (≈1K tokens). */
@@ -51,12 +51,12 @@ export const TOOL_RESULT_CAP = 4_000;
51
51
 
52
52
  const CAP_NOTE =
53
53
  `\n[RLM: tool output capped at ${TOOL_RESULT_CAP.toLocaleString()} chars to protect the root ` +
54
- "model's context — route bulk text through repl() + llm_query_chunked / llm_query_batched.]";
54
+ "model's context — route bulk text through repl() + llm_query_chunked / llm_batch.]";
55
55
 
56
56
  const REPL_CAP_NOTE =
57
57
  `\n[RLM: repl() stdout capped at ${TOOL_RESULT_CAP.toLocaleString()} chars — printing bulk text ` +
58
58
  "is useless. Keep results in REPL variables and delegate semantic reading to llm_query / " +
59
- "llm_query_batched / llm_query_chunked.]";
59
+ "llm_batch / llm_query_chunked.]";
60
60
 
61
61
  /** Shared truncation core. Returns undefined when under the cap (leave the text untouched). */
62
62
  function capText(text: string, note: string): string | undefined {
@@ -86,7 +86,7 @@ export function replDelegationNudge(stdoutChars: number, delegated: boolean): st
86
86
  if (delegated || stdoutChars <= NUDGE_STDOUT_CHARS) return undefined;
87
87
  return (
88
88
  `\n[RLM: this repl() printed ${stdoutChars.toLocaleString()} chars with 0 sub-LLM calls — ` +
89
- "if you were READING, delegate via llm_query / llm_query_batched / llm_query_chunked. " +
89
+ "if you were READING, delegate via llm_query / llm_batch / llm_query_chunked. " +
90
90
  "Authoring an edit body yourself is correct and needs no delegation.]"
91
91
  );
92
92
  }
@@ -6,7 +6,7 @@
6
6
  * 2. Capability gate — never confiscate native readers unless `repl` is in the
7
7
  * active tool set (paper trade: scaffold only if the REPL substitute exists).
8
8
  *
9
- * In-process rlm_query depth is handled by subcall-handlers.childRun; this module
9
+ * In-process rlm_query depth is handled by bridge/handlers childRun; this module
10
10
  * only covers OS-process children (pi subagents), which restart at depth 0.
11
11
  */
12
12
  import { DEFAULT_CONFIG } from "../config/defaults.ts";