@hicaru/pi-rlm 0.1.7 → 0.1.9

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 (43) hide show
  1. package/README.md +41 -4
  2. package/package.json +2 -1
  3. package/src/bridge/library.ts +155 -0
  4. package/src/bridge/llm-query.ts +1 -0
  5. package/src/bridge/rlm-query.ts +56 -12
  6. package/src/config/defaults.ts +2 -0
  7. package/src/config/settings.ts +4 -0
  8. package/src/context/library-context.ts +266 -0
  9. package/src/context/repomix-context.ts +2 -48
  10. package/src/core/answer.ts +1 -10
  11. package/src/core/artifacts.ts +88 -0
  12. package/src/core/critique.ts +92 -0
  13. package/src/core/engine.ts +446 -53
  14. package/src/core/gates.ts +301 -0
  15. package/src/core/iteration.ts +7 -2
  16. package/src/core/pipeline.ts +196 -28
  17. package/src/core/types.ts +5 -3
  18. package/src/index.ts +3 -6
  19. package/src/mode/native-guards.ts +2 -2
  20. package/src/prompts/phases.ts +104 -0
  21. package/src/prompts/system.ts +59 -16
  22. package/src/prompts/user.ts +12 -4
  23. package/src/sandbox/protocol.ts +29 -11
  24. package/src/sandbox/sandbox.ts +77 -2
  25. package/src/sandbox/worker.py +215 -46
  26. package/src/state/index.ts +2 -1
  27. package/src/state/paths.ts +4 -2
  28. package/src/state/reads.ts +31 -2
  29. package/src/state/resume.ts +31 -6
  30. package/src/state/rows.ts +8 -2
  31. package/src/state/writes.ts +5 -3
  32. package/src/text/tokens.ts +7 -1
  33. package/src/tool/repl-details.ts +2 -3
  34. package/src/tool/repl-tool.ts +52 -57
  35. package/src/tool/rlm-aggregator.ts +7 -7
  36. package/src/tool/rlm-details.ts +6 -3
  37. package/src/tool/rlm-events.ts +14 -11
  38. package/src/tool/rlm-tool.ts +2 -8
  39. package/src/tool/subcall-store.ts +2 -0
  40. package/src/ui/config-panel.ts +8 -1
  41. package/src/registry/edit-registry.ts +0 -22
  42. package/src/text/edits.ts +0 -16
  43. package/src/tool/apply-edits-tool.ts +0 -288
@@ -18,6 +18,7 @@ import type { Model, Usage, Api } from "@earendil-works/pi-ai";
18
18
  import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
19
19
  import { modelRef, resolveModelId } from "../config/settings.ts";
20
20
  import { buildInteractiveHandlers } from "../bridge/interactive.ts";
21
+ import { buildLibraryHandler } from "../bridge/library.ts";
21
22
  import { createPiInteractiveDeps } from "../bridge/pi-interactive.ts";
22
23
  import { type ChatMsg, modelComplete } from "../bridge/model.ts";
23
24
  import { previewText } from "../text/preview.ts";
@@ -27,14 +28,13 @@ import { checkResourceLimits } from "../core/resource-limits.ts";
27
28
  import type { InteractiveDeps, RlmConfig, Sampling } from "../core/types.ts";
28
29
  import { SandboxManager } from "../sandbox/sandbox-manager.ts";
29
30
  import type { SubLlmHandlers } from "../sandbox/sandbox.ts";
30
- import type { ProposedEdit, ReplResult } from "../sandbox/protocol.ts";
31
+ import type { ReplResult } from "../sandbox/protocol.ts";
31
32
  import { RlmEmitter } from "./rlm-events.ts";
32
33
  import { SubcallStore } from "./subcall-store.ts";
33
34
  import type { ReplDetails } from "./repl-details.ts";
34
35
  import type { RlmSubcall } from "./rlm-details.ts";
35
36
  import { createEngine } from "../core/engine.ts";
36
37
  import { formatCost, formatTokens, spinnerFrame } from "../ui/theme.ts";
37
- import type { EditRegistry } from "../registry/edit-registry.ts";
38
38
  import { errorMessage, formatError, isErrorText } from "../util/errors.ts";
39
39
  import {
40
40
  headlineStatusGlyph,
@@ -50,59 +50,44 @@ export const ReplToolParams = Object.freeze(Type.Object({
50
50
  code: Type.String({ description: "Python code to execute in the persistent REPL sandbox" }),
51
51
  }));
52
52
 
53
- export function surfaceReplEdits(edits: readonly ProposedEdit[], raised: boolean): readonly ProposedEdit[] | undefined {
54
- return edits.length > 0 && !raised ? edits : undefined;
55
- }
56
-
57
- /** Model-visible text assembled from a repl() result, plus the surfaced edits for `details`. */
53
+ /** Model-visible text assembled from a repl() result. */
58
54
  export interface ReplResultText {
59
55
  readonly text: string;
60
- readonly surfacedEdits: readonly ProposedEdit[] | undefined;
61
- }
62
-
63
- function countLines(text: string): number {
64
- if (text.length === 0) return 0;
65
- let count = 1;
66
- for (const ch of text) if (ch === "\n") count++;
67
- return count;
68
- }
69
-
70
- function stagedEditSummary(edits: readonly ProposedEdit[]): string {
71
- const rows = new Array<string>(edits.length);
72
- for (let i = 0; i < edits.length; i++) {
73
- const edit = edits[i];
74
- rows[i] = ` ${edit.id} ${edit.path} (-${countLines(edit.oldText)}/+${countLines(edit.newText)} lines)`;
75
- }
76
- return [
77
- "STAGED_EDITS (apply by id with apply_edits; do NOT re-type content):",
78
- ...rows,
79
- ].join("\n");
80
56
  }
81
57
 
82
58
  /**
83
- * Assemble the model-visible text for a repl() result: cap stdout, append a zero-subcall
84
- * delegation nudge (suppressed when edits were staged), and summarize staged edits by ID
85
- * without exposing oldText/newText bodies to the root model.
59
+ * Assemble the model-visible text for a repl() result: cap stdout and append a
60
+ * zero-subcall delegation nudge when a bulk read went undelegated.
86
61
  */
87
62
  export function buildReplResultText(
88
63
  stdout: string,
89
64
  finalAnswer: string | undefined,
90
- edits: readonly ProposedEdit[],
91
- raised: boolean,
92
65
  subcalls: readonly RlmSubcall[],
93
66
  ): ReplResultText {
94
67
  const answerSubmitted = finalAnswer !== undefined;
95
68
  const rawText = answerSubmitted
96
69
  ? `ANSWER_SUBMITTED (${finalAnswer.length} chars) — delivered to user. Do not restate it.`
97
70
  : stdout || "(no output)";
98
- const surfacedEdits = surfaceReplEdits(edits, raised);
99
- const editsBlock = surfacedEdits ? `\n\n${stagedEditSummary(surfacedEdits)}` : "";
100
- const modelText = rawText + editsBlock;
101
- // Model-visible text is capped; the caller keeps full stdout/final answer in `details` for the TUI.
102
- const cappedText = capReplResultText(modelText) ?? modelText;
71
+ // Model-visible text is capped; the caller keeps full stdout in `details` for the TUI.
72
+ const cappedText = capReplResultText(rawText) ?? rawText;
103
73
  const delegated = subcalls.some((s) => s.kind === "llm" || s.kind === "batch" || s.kind === "rlm");
104
- const nudge = surfacedEdits || answerSubmitted ? undefined : replDelegationNudge(rawText.length, delegated);
105
- return { text: cappedText + (nudge ?? ""), surfacedEdits };
74
+ const nudge = answerSubmitted ? undefined : replDelegationNudge(rawText.length, delegated);
75
+ return { text: cappedText + (nudge ?? "") };
76
+ }
77
+
78
+ /** Advisory diagnostics derived from a completed invocation's sub-calls. */
79
+ export function collectReplWarnings(subcalls: readonly RlmSubcall[]): readonly string[] | undefined {
80
+ let failed = 0;
81
+ let total = 0;
82
+ for (let i = 0; i < subcalls.length; i++) {
83
+ const call = subcalls[i];
84
+ if (call.status !== "error") continue;
85
+ // A batch subcall stands for many prompts; a single call stands for one.
86
+ failed += call.failedCount ?? 1;
87
+ total += call.totalCount ?? 1;
88
+ }
89
+ if (failed === 0) return undefined;
90
+ return Object.freeze([`${failed}/${total} sub-call(s) failed — results may be incomplete`]);
106
91
  }
107
92
 
108
93
  // ── Mutable bridge state (handler indirection) ──
@@ -213,6 +198,7 @@ class NativeBridgeState {
213
198
  if (id) state.currentEmitter?.emitSubcallUpdated({ id,
214
199
  status: error ? "error" : "done", costUsd: cost, tokens,
215
200
  resultPreview: previewText(out[0] ?? ""), detail: error,
201
+ failedCount: failed, totalCount: out.length,
216
202
  });
217
203
  return out;
218
204
  },
@@ -336,18 +322,23 @@ export interface ReplToolDeps {
336
322
  readonly getModel?: () => Model<Api> | undefined;
337
323
  readonly getWorkerModel?: () => Model<Api> | undefined;
338
324
  readonly registry: ModelRegistry;
339
- readonly editRegistry?: EditRegistry;
340
325
  readonly config: RlmConfig;
341
326
  readonly signal?: AbortSignal;
342
327
  readonly onUsage?: (usage: Usage, role: "sub") => void;
343
328
  readonly ensureContext?: () => Promise<void>;
329
+ /** Register a reset hook for sandbox death/dispose (e.g. load_library slot counter). */
330
+ readonly registerDiscardHook?: (reset: () => void) => void;
344
331
  }
345
332
 
346
333
  export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplToolParams, ReplDetails> {
347
- const { sandboxManager, workerModel, registry, editRegistry, config, signal, onUsage } = deps;
334
+ const { sandboxManager, workerModel, registry, config, signal, onUsage } = deps;
348
335
  const bridgeState = new NativeBridgeState();
349
336
 
350
- // Build handlers once llm/rlm use mutable refs, interactive is session-stable
337
+ // Late-bound cwdgetOrCreate installs handlers only at spawn; never rebuild the closure.
338
+ let sessionCwd = process.cwd();
339
+
340
+ // Build handlers once — llm/rlm/library use late-bound deps so the same closures stay correct
341
+ // across repl() calls; counter resets when the sandbox is discarded and re-spawned.
351
342
  const llmHandlers = bridgeState.buildLlmHandlers({
352
343
  workerModel,
353
344
  getWorkerModel: deps.getWorkerModel,
@@ -373,6 +364,17 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
373
364
  llmHandlers,
374
365
  });
375
366
 
367
+ const libraryBundle = config.libraryLoader
368
+ ? buildLibraryHandler({
369
+ getCwd: () => sessionCwd,
370
+ getEmitter: () => bridgeState.currentEmitter,
371
+ parentId: undefined,
372
+ signal,
373
+ startIndex: 1,
374
+ })
375
+ : undefined;
376
+ if (libraryBundle) deps.registerDiscardHook?.(libraryBundle.reset);
377
+
376
378
  return {
377
379
  name: "repl",
378
380
  label: "REPL",
@@ -382,7 +384,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
382
384
  "chunk `context` and delegate semantic work to llm_query / llm_query_batched / " +
383
385
  "llm_query_chunked / rlm_query — stdout returned to you is hard-capped at 4K chars, so " +
384
386
  "printing file bodies is useless. Variables, imports, and state persist across calls. " +
385
- "Also supports todo and ask_user_question inside the sandbox.",
387
+ "Also supports todo, ask_user_question, and load_library inside the sandbox.",
386
388
  parameters: ReplToolParams,
387
389
 
388
390
  async execute(_toolCallId, rawParams, _execSignal, onUpdate, ctx) {
@@ -448,12 +450,15 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
448
450
  parentId: undefined,
449
451
  });
450
452
 
453
+ sessionCwd = ctx.cwd ?? process.cwd();
454
+
451
455
  await deps.ensureContext?.();
452
456
  await sandboxManager.getOrCreate({
453
457
  ...llmHandlers,
454
458
  ...rlmHandlers,
455
459
  askUserQuestion: interactiveHandlers.askUserQuestion,
456
460
  todo: interactiveHandlers.todo,
461
+ ...(libraryBundle?.handlers ?? {}),
457
462
  });
458
463
 
459
464
  // Detect queue contention AFTER sandbox init (initPromise settled, isExecuting now accurate)
@@ -487,14 +492,11 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
487
492
  if (queuedId) emitter.emitSubcallUpdated({ id: queuedId, status: "done" });
488
493
 
489
494
  const finalAnswer = result.finalAnswer ?? undefined;
490
- const { text: resultText, surfacedEdits } = buildReplResultText(
495
+ const { text: resultText } = buildReplResultText(
491
496
  result.stdout,
492
497
  finalAnswer,
493
- result.edits,
494
- result.raised,
495
498
  store.getSubcalls(),
496
499
  );
497
- editRegistry?.registerAll(surfacedEdits);
498
500
 
499
501
  const details: ReplDetails = {
500
502
  status: "done",
@@ -504,7 +506,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
504
506
  subcalls: store.getSubcalls(),
505
507
  totals: store.getTotals(),
506
508
  finalAnswer,
507
- edits: surfacedEdits,
509
+ warnings: collectReplWarnings(store.getSubcalls()),
508
510
  };
509
511
  const progressText = finalAnswer !== undefined
510
512
  ? `ANSWER_SUBMITTED (${finalAnswer.length} chars)`
@@ -567,9 +569,6 @@ function renderReplCollapsed(details: ReplDetails, theme: Theme): Text {
567
569
  parts.push(formatCost(details.totals.costUsd));
568
570
  if (details.totals.tokens > 0) parts.push(`${formatTokens(details.totals.tokens)} tok`);
569
571
  if (details.executionTimeMs > 0) parts.push(`${details.executionTimeMs}ms`);
570
- if (details.edits && details.edits.length > 0) {
571
- parts.push(theme.fg("success", `${details.edits.length} staged`));
572
- }
573
572
  const stats = parts.length > 0 ? ` ${theme.fg("dim", parts.join(" · "))}` : "";
574
573
 
575
574
  const header = `${glyph} ${theme.fg("toolTitle", theme.bold("REPL"))}${stats}`;
@@ -604,13 +603,9 @@ function renderReplExpanded(details: ReplDetails, theme: Theme): Container {
604
603
  container.addChild(new Text(out, 0, 0));
605
604
  }
606
605
 
607
- if (details.edits && details.edits.length > 0) {
608
- const editFiles = new Set<string>();
609
- for (const edit of details.edits) editFiles.add(edit.path);
606
+ if (details.warnings && details.warnings.length > 0) {
610
607
  container.addChild(new Spacer(1));
611
- container.addChild(new Text(theme.fg("success",
612
- `${details.edits.length} edit${details.edits.length > 1 ? "s" : ""} staged across ${editFiles.size} file${editFiles.size > 1 ? "s" : ""}`,
613
- ), 0, 0));
608
+ container.addChild(new Text(theme.fg("muted", details.warnings.join("\n")), 0, 0));
614
609
  }
615
610
 
616
611
  // Stderr
@@ -6,14 +6,14 @@
6
6
  * getState(): RlmDetails for direct access (spinner loop, final return).
7
7
  *
8
8
  * Subcall storage and totals are delegated to SubcallStore. Root-level state
9
- * (status, prompt, turns, answer, edits) is kept in the aggregator.
9
+ * (status, prompt, turns, answer, warnings) is kept in the aggregator.
10
10
  *
11
11
  * Replaces RlmToolBridge's internal state accumulation. The emitter is pure
12
12
  * dispatch; the aggregator is pure state. Separated for independent testing.
13
13
  */
14
14
 
15
15
  import type { AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
16
- import type { RlmEmitter, TurnEvent, RootUsageEvent, AnswerEvent, EditsEvent, StatusEvent, RootPromptEvent } from "./rlm-events.ts";
16
+ import type { RlmEmitter, TurnEvent, RootUsageEvent, AnswerEvent, StatusEvent, RootPromptEvent, WarningsEvent } from "./rlm-events.ts";
17
17
  import type { RlmDetails, RlmRunStatus } from "./rlm-details.ts";
18
18
  import { EmitterListener } from "./emitter-listener.ts";
19
19
  import { SubcallStore } from "./subcall-store.ts";
@@ -27,7 +27,7 @@ export class RlmEventAggregator extends EmitterListener {
27
27
  private turnCurrent = 0;
28
28
  private turnMax = 0;
29
29
  private answer?: string;
30
- private edits: RlmDetails["edits"] = [];
30
+ private warnings?: readonly string[];
31
31
 
32
32
  constructor(
33
33
  emitter: RlmEmitter,
@@ -40,7 +40,7 @@ export class RlmEventAggregator extends EmitterListener {
40
40
  emitter.onTurn((e) => this.handleTurn(e)),
41
41
  emitter.onRootUsage((e) => this.handleRootUsage(e)),
42
42
  emitter.onAnswer((e) => this.handleAnswer(e)),
43
- emitter.onEdits((e) => this.handleEdits(e)),
43
+ emitter.onWarnings((e) => this.handleWarnings(e)),
44
44
  emitter.onStatus((e) => this.handleStatus(e)),
45
45
  emitter.onRootPrompt((e) => this.handleRootPrompt(e)),
46
46
  ]);
@@ -64,8 +64,8 @@ export class RlmEventAggregator extends EmitterListener {
64
64
  this.notify();
65
65
  }
66
66
 
67
- private handleEdits(event: EditsEvent): void {
68
- this.edits = event.edits;
67
+ private handleWarnings(event: WarningsEvent): void {
68
+ this.warnings = event.warnings;
69
69
  this.notify();
70
70
  }
71
71
 
@@ -90,7 +90,7 @@ export class RlmEventAggregator extends EmitterListener {
90
90
  subcalls: this.store.getSubcalls(),
91
91
  totals: this.store.getTotals(),
92
92
  answer: this.answer,
93
- edits: this.edits,
93
+ warnings: this.warnings,
94
94
  };
95
95
  }
96
96
 
@@ -6,8 +6,6 @@
6
6
  * after every mutation, enabling Pi's built-in progressive TUI re-render.
7
7
  */
8
8
 
9
- import type { ProposedEdit } from "../sandbox/protocol.ts";
10
-
11
9
  export type SubcallKind = "root" | "rlm" | "llm" | "batch" | "tool";
12
10
  export type SubcallStatus = "running" | "done" | "error";
13
11
  export type RlmRunStatus = "running" | "done" | "error" | "aborted";
@@ -29,6 +27,10 @@ export interface RlmSubcall {
29
27
  readonly endedAt?: number;
30
28
  readonly costUsd: number;
31
29
  readonly tokens: number;
30
+ /** For batch subcalls: failed prompt count (partial failure). */
31
+ readonly failedCount?: number;
32
+ /** For batch subcalls: total prompt count. */
33
+ readonly totalCount?: number;
32
34
  }
33
35
 
34
36
  export interface RlmDetails {
@@ -38,7 +40,8 @@ export interface RlmDetails {
38
40
  readonly subcalls: readonly RlmSubcall[];
39
41
  readonly totals: { readonly costUsd: number; readonly tokens: number };
40
42
  readonly answer?: string;
41
- readonly edits?: readonly ProposedEdit[];
43
+ /** Advisory diagnostics — surfaced to the user, never a failure. */
44
+ readonly warnings?: readonly string[];
42
45
  }
43
46
 
44
47
  export interface SubcallInit {
@@ -12,7 +12,6 @@
12
12
 
13
13
  import { EventEmitter } from "node:events";
14
14
  import type { SubcallKind, SubcallStatus, RlmRunStatus } from "./rlm-details.ts";
15
- import type { ProposedEdit } from "../sandbox/protocol.ts";
16
15
 
17
16
  // ── Event payloads ──
18
17
 
@@ -40,6 +39,14 @@ export interface SubcallUpdatedEvent {
40
39
  readonly costUsd?: number;
41
40
  /** Delta — additive on both the subcall and running totals. */
42
41
  readonly tokens?: number;
42
+ /** For batch subcalls: failed prompt count. */
43
+ readonly failedCount?: number;
44
+ /** For batch subcalls: total prompt count. */
45
+ readonly totalCount?: number;
46
+ }
47
+
48
+ export interface WarningsEvent {
49
+ readonly warnings: readonly string[];
43
50
  }
44
51
 
45
52
  export interface TurnEvent {
@@ -56,10 +63,6 @@ export interface AnswerEvent {
56
63
  readonly text: string;
57
64
  }
58
65
 
59
- export interface EditsEvent {
60
- readonly edits: readonly ProposedEdit[];
61
- }
62
-
63
66
  export interface StatusEvent {
64
67
  readonly status: RlmRunStatus;
65
68
  }
@@ -110,9 +113,9 @@ export class RlmEmitter {
110
113
  this.ee.emit("answer", { text } satisfies AnswerEvent);
111
114
  }
112
115
 
113
- /** Set proposed edits (root-only). */
114
- emitEdits(edits: readonly ProposedEdit[]): void {
115
- this.ee.emit("edits", { edits } satisfies EditsEvent);
116
+ /** Set advisory warnings (root-only; never a failure). */
117
+ emitWarnings(warnings: readonly string[]): void {
118
+ this.ee.emit("warnings", { warnings } satisfies WarningsEvent);
116
119
  }
117
120
 
118
121
  /** Set the root run status (done/error/aborted). */
@@ -152,9 +155,9 @@ export class RlmEmitter {
152
155
  return () => { this.ee.off("answer", handler); };
153
156
  }
154
157
 
155
- onEdits(handler: (event: EditsEvent) => void): () => void {
156
- this.ee.on("edits", handler);
157
- return () => { this.ee.off("edits", handler); };
158
+ onWarnings(handler: (event: WarningsEvent) => void): () => void {
159
+ this.ee.on("warnings", handler);
160
+ return () => { this.ee.off("warnings", handler); };
158
161
  }
159
162
 
160
163
  onStatus(handler: (event: StatusEvent) => void): () => void {
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * RLM tool — registers the RLM engine as a Pi tool with inline rendering.
3
3
  *
4
- * Modeled after rpiv-mono's subagent tool.
5
4
  * The tool's execute() wraps createEngine() with an RlmEmitter + RlmEventAggregator that feeds
6
5
  * onUpdate(partialResult) for progressive TUI re-rendering.
7
6
  */
@@ -157,14 +156,9 @@ function renderExpanded(details: RlmDetails, theme: Theme): Component {
157
156
  container.addChild(new Markdown(details.answer, 0, 0, getMarkdownTheme()));
158
157
  }
159
158
 
160
- if (details.edits && details.edits.length > 0) {
159
+ if (details.warnings && details.warnings.length > 0) {
161
160
  container.addChild(new Spacer(1));
162
- const editFiles = new Set(details.edits.map(e => e.path));
163
- container.addChild(new Text(
164
- theme.fg("muted", "─── Edits ───") +
165
- `\n ${theme.fg("dim", `${details.edits.length} edit${details.edits.length > 1 ? "s" : ""} proposed across ${editFiles.size} file${editFiles.size > 1 ? "s" : ""}`)}`,
166
- 0, 0,
167
- ));
161
+ container.addChild(new Text(theme.fg("muted", details.warnings.join("\n")), 0, 0));
168
162
  }
169
163
 
170
164
  return container;
@@ -66,6 +66,8 @@ export class SubcallStore extends EmitterListener {
66
66
  sc.tokens += event.tokens;
67
67
  this.totalTokens += event.tokens;
68
68
  }
69
+ if (event.failedCount !== undefined) sc.failedCount = event.failedCount;
70
+ if (event.totalCount !== undefined) sc.totalCount = event.totalCount;
69
71
  }
70
72
 
71
73
  // ── Read ──
@@ -16,11 +16,13 @@ const CHOICES = Object.freeze({
16
16
  maxErrors: Object.freeze(["3", "5", "10", "none"]),
17
17
  orchestrator: Object.freeze(["on", "off"]),
18
18
  pipeline: Object.freeze(["on", "off"]),
19
+ maxBackwardJumps: Object.freeze(["0", "1", "2", "3"]),
19
20
  compaction: Object.freeze(["on", "off"]),
20
21
  rootSamplingMaxTokens: Object.freeze(["4096", "8192", "16384", "32768"]),
21
22
  sandboxInitTimeoutMs: Object.freeze(["10000", "30000", "60000", "120000"]),
22
23
  askUserQuestion: Object.freeze(["on", "off"]),
23
24
  todo: Object.freeze(["on", "off"]),
25
+ libraryLoader: Object.freeze(["on", "off"]),
24
26
  });
25
27
 
26
28
  function item(id: string, label: string, currentValue: string, values: readonly string[], description: string): SettingItem {
@@ -39,12 +41,15 @@ export async function showConfigPanel(ctx: ExtensionContext, config: RlmConfig):
39
41
  item("maxTokens", "Token ceiling", config.maxTokens != null ? String(config.maxTokens) : "none", CHOICES.maxTokens, "Total input+output token cap for the whole recursive tree."),
40
42
  item("maxErrors", "Max consecutive errors", config.maxErrors != null ? String(config.maxErrors) : "none", CHOICES.maxErrors, "Stop after this many consecutive failing turns; none disables the guard."),
41
43
  item("orchestrator", "Orchestrator addendum", config.orchestrator ? "on" : "off", CHOICES.orchestrator, "Append extra divide-and-conquer guidance to the root model system prompt."),
42
- item("pipeline", "Phase pipeline", config.pipeline ? "on" : "off", CHOICES.pipeline, "Enable advance_phase plus phase-stall reminders at root depth."),
44
+ item("pipeline", "Phase pipeline", config.pipeline ? "on" : "off", CHOICES.pipeline, "Enable artifact-gated phases: clarify→research→blueprint→validate (read-only plan pipeline; clarify needs Ask user on)."),
45
+ item("maxBackwardJumps", "Max validate→blueprint loops", String(config.maxBackwardJumps), CHOICES.maxBackwardJumps, "Bounded corrective re-entries when validation reports blockers_count > 0."),
43
46
  item("compaction", "Trajectory compaction", config.compaction ? "on" : "off", CHOICES.compaction, "Summarize old turns when history approaches the model context window."),
44
47
  item("rootSamplingMaxTokens", "Root model output cap (tok)", String(config.rootSampling?.maxTokens ?? 16384), CHOICES.rootSamplingMaxTokens, "Max output tokens per root-model turn. Lower values keep each turn lean."),
45
48
  item("sandboxInitTimeoutMs", "Sandbox init timeout", String(config.sandboxInitTimeoutMs), CHOICES.sandboxInitTimeoutMs, "How long to wait for the Python worker to start."),
46
49
  item("askUserQuestion", "[Interactive] Ask user", config.askUserQuestion ? "on" : "off", CHOICES.askUserQuestion, "Allow root REPL code to present structured ask_user_question dialogs."),
47
50
  item("todo", "[Interactive] Todo", config.todo ? "on" : "off", CHOICES.todo, "Allow REPL code to manage a visible todo task list."),
51
+ item("libraryLoader", "Library loader", config.libraryLoader ? "on" : "off", CHOICES.libraryLoader,
52
+ "Allow load_library() to pull an external dir, file, or git repo into the shared context list."),
48
53
  item("__save__", "Save & close", "↵", ["↵"], "Save these settings and close (Esc also saves)."),
49
54
  ];
50
55
 
@@ -86,10 +91,12 @@ function applySetting(config: RlmConfig, id: string, value: string): void {
86
91
  case "maxErrors": config.maxErrors = value === "none" ? undefined : Number(value); break;
87
92
  case "orchestrator": config.orchestrator = value === "on"; break;
88
93
  case "pipeline": config.pipeline = value === "on"; break;
94
+ case "maxBackwardJumps": config.maxBackwardJumps = Number(value); break;
89
95
  case "compaction": config.compaction = value === "on"; break;
90
96
  case "rootSamplingMaxTokens": config.rootSampling = Object.freeze({ ...config.rootSampling, maxTokens: Number(value) }); break;
91
97
  case "sandboxInitTimeoutMs": config.sandboxInitTimeoutMs = Number(value); break;
92
98
  case "askUserQuestion": config.askUserQuestion = value === "on"; break;
93
99
  case "todo": config.todo = value === "on"; break;
100
+ case "libraryLoader": config.libraryLoader = value === "on"; break;
94
101
  }
95
102
  }
@@ -1,22 +0,0 @@
1
- import type { ProposedEdit } from "../sandbox/protocol.ts";
2
-
3
- export class EditRegistry {
4
- private readonly edits = new Map<string, ProposedEdit>();
5
-
6
- registerAll(edits: readonly ProposedEdit[] | undefined): void {
7
- if (edits === undefined) return;
8
- for (const edit of edits) this.edits.set(edit.id, edit);
9
- }
10
-
11
- get(id: string): ProposedEdit | undefined {
12
- return this.edits.get(id);
13
- }
14
-
15
- delete(id: string): boolean {
16
- return this.edits.delete(id);
17
- }
18
-
19
- clear(): void {
20
- this.edits.clear();
21
- }
22
- }
package/src/text/edits.ts DELETED
@@ -1,16 +0,0 @@
1
- export interface AnchorEdit {
2
- readonly oldText: string;
3
- readonly newText: string;
4
- }
5
-
6
- export function countOccurrences(haystack: string, needle: string): number {
7
- if (needle.length === 0) return 0;
8
- let count = 0;
9
- let offset = 0;
10
- for (;;) {
11
- const match = haystack.indexOf(needle, offset);
12
- if (match < 0) return count;
13
- count++;
14
- offset = match + needle.length;
15
- }
16
- }