@hicaru/pi-rlm 0.1.7 → 0.1.8
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 +35 -1
- package/package.json +1 -1
- package/src/bridge/library.ts +77 -0
- package/src/bridge/rlm-query.ts +58 -12
- package/src/config/defaults.ts +2 -0
- package/src/config/settings.ts +4 -0
- package/src/context/library-context.ts +79 -0
- package/src/core/artifacts.ts +88 -0
- package/src/core/engine.ts +432 -35
- package/src/core/gates.ts +272 -0
- package/src/core/iteration.ts +7 -2
- package/src/core/pipeline.ts +170 -27
- package/src/core/types.ts +4 -0
- package/src/index.ts +6 -1
- package/src/prompts/phases.ts +125 -0
- package/src/prompts/system.ts +38 -7
- package/src/prompts/user.ts +12 -4
- package/src/sandbox/protocol.ts +25 -2
- package/src/sandbox/sandbox.ts +42 -1
- package/src/sandbox/worker.py +42 -5
- package/src/state/index.ts +2 -1
- package/src/state/paths.ts +4 -2
- package/src/state/reads.ts +31 -2
- package/src/state/resume.ts +19 -1
- package/src/state/rows.ts +6 -0
- package/src/state/writes.ts +5 -3
- package/src/text/edits.ts +148 -0
- package/src/tool/apply-edits-tool.ts +36 -29
- package/src/tool/repl-tool.ts +23 -2
- package/src/tool/rlm-tool.ts +0 -1
- package/src/ui/config-panel.ts +8 -1
package/src/text/edits.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import type { ProposedEdit } from "../sandbox/protocol.ts";
|
|
5
|
+
import { errorMessage, formatError } from "../util/errors.ts";
|
|
6
|
+
|
|
1
7
|
export interface AnchorEdit {
|
|
2
8
|
readonly oldText: string;
|
|
3
9
|
readonly newText: string;
|
|
@@ -14,3 +20,145 @@ export function countOccurrences(haystack: string, needle: string): number {
|
|
|
14
20
|
offset = match + needle.length;
|
|
15
21
|
}
|
|
16
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Literal string replace of the first occurrence of `oldText` with `newText`.
|
|
26
|
+
* Splice-based so `$&` / `$$` / `$'` in newText are NOT treated as
|
|
27
|
+
* special replacement patterns (String.prototype.replace string-form hazard).
|
|
28
|
+
*/
|
|
29
|
+
export function replaceOnceLiteral(content: string, oldText: string, newText: string): string {
|
|
30
|
+
const idx = content.indexOf(oldText);
|
|
31
|
+
if (idx < 0) return content;
|
|
32
|
+
return content.slice(0, idx) + newText + content.slice(idx + oldText.length);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type PlanEditResult =
|
|
36
|
+
| {
|
|
37
|
+
readonly ok: true;
|
|
38
|
+
/** create = new file; replace = one-shot anchor swap; already-applied = idempotent skip */
|
|
39
|
+
readonly kind: "create" | "replace" | "already-applied";
|
|
40
|
+
readonly before: string;
|
|
41
|
+
readonly after: string;
|
|
42
|
+
}
|
|
43
|
+
| { readonly ok: false; readonly error: string };
|
|
44
|
+
|
|
45
|
+
async function pathExists(abs: string): Promise<boolean> {
|
|
46
|
+
try {
|
|
47
|
+
await access(abs, constants.F_OK);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validate and compute an edit without writing.
|
|
56
|
+
* Shared by headless apply and the native apply_edits tool (DRY).
|
|
57
|
+
*
|
|
58
|
+
* Idempotent retry: if a prior unit already landed the change
|
|
59
|
+
* (oldText absent + newText already present for replace; create with identical content),
|
|
60
|
+
* returns kind "already-applied" so a failed fanout can be retried without wedging.
|
|
61
|
+
*
|
|
62
|
+
* Create-file: refuses to clobber when the target exists with different content.
|
|
63
|
+
*/
|
|
64
|
+
export async function planEdit(
|
|
65
|
+
cwd: string,
|
|
66
|
+
path: string,
|
|
67
|
+
oldText: string,
|
|
68
|
+
newText: string,
|
|
69
|
+
): Promise<PlanEditResult> {
|
|
70
|
+
try {
|
|
71
|
+
const fullPath = resolve(cwd, path);
|
|
72
|
+
if (oldText.length === 0) {
|
|
73
|
+
if (await pathExists(fullPath)) {
|
|
74
|
+
const existing = await readFile(fullPath, "utf8");
|
|
75
|
+
if (existing === newText) {
|
|
76
|
+
return { ok: true, kind: "already-applied", before: existing, after: existing };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: formatError(
|
|
81
|
+
`${path}: file already exists with different content — refuse to clobber (create requires empty target or identical content)`,
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { ok: true, kind: "create", before: "", after: newText };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const content = await readFile(fullPath, "utf8");
|
|
89
|
+
const occurrences = countOccurrences(content, oldText);
|
|
90
|
+
if (occurrences === 0) {
|
|
91
|
+
// Idempotent skip: prior apply already removed the anchor and left newText.
|
|
92
|
+
// Deletions (newText === "") always "include" empty string — treat them as
|
|
93
|
+
// retry-unsafe so a typo'd anchor fails instead of silently skipping.
|
|
94
|
+
if (newText.length > 0 && content.includes(newText)) {
|
|
95
|
+
return { ok: true, kind: "already-applied", before: content, after: content };
|
|
96
|
+
}
|
|
97
|
+
return { ok: false, error: formatError(`anchor occurs 0 times in ${path}`) };
|
|
98
|
+
}
|
|
99
|
+
if (occurrences !== 1) {
|
|
100
|
+
return { ok: false, error: formatError(`anchor occurs ${occurrences} times in ${path}`) };
|
|
101
|
+
}
|
|
102
|
+
const after = replaceOnceLiteral(content, oldText, newText);
|
|
103
|
+
return { ok: true, kind: "replace", before: content, after };
|
|
104
|
+
} catch (err: unknown) {
|
|
105
|
+
return { ok: false, error: formatError(`${path}: ${errorMessage(err)}`) };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type ApplyOneEditResult =
|
|
110
|
+
| { readonly ok: true; readonly before: string; readonly after: string; readonly kind: "create" | "replace" | "already-applied" }
|
|
111
|
+
| { readonly ok: false; readonly error: string };
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Apply a single anchor edit to the working tree (direct disk write).
|
|
115
|
+
* Used by the implement fanout and any headless apply path.
|
|
116
|
+
*/
|
|
117
|
+
export async function applyOneEdit(
|
|
118
|
+
cwd: string,
|
|
119
|
+
path: string,
|
|
120
|
+
oldText: string,
|
|
121
|
+
newText: string,
|
|
122
|
+
): Promise<ApplyOneEditResult> {
|
|
123
|
+
const planned = await planEdit(cwd, path, oldText, newText);
|
|
124
|
+
if (!planned.ok) return planned;
|
|
125
|
+
if (planned.kind === "already-applied") {
|
|
126
|
+
return { ok: true, before: planned.before, after: planned.after, kind: "already-applied" };
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const fullPath = resolve(cwd, path);
|
|
130
|
+
if (planned.kind === "create") {
|
|
131
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
await writeFile(fullPath, planned.after, "utf8");
|
|
134
|
+
return { ok: true, before: planned.before, after: planned.after, kind: planned.kind };
|
|
135
|
+
} catch (err: unknown) {
|
|
136
|
+
return { ok: false, error: formatError(`${path}: ${errorMessage(err)}`) };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type ApplyProposedEditsResult =
|
|
141
|
+
| { readonly ok: true; readonly applied: number }
|
|
142
|
+
| { readonly ok: false; readonly error: string; readonly applied: number };
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Apply a series of proposed edits to the working tree (patch series, not a race).
|
|
146
|
+
* Shared by the implement fanout and any headless apply path.
|
|
147
|
+
* Safe to re-run: already-applied units succeed without re-writing.
|
|
148
|
+
*/
|
|
149
|
+
export async function applyProposedEdits(
|
|
150
|
+
edits: readonly ProposedEdit[],
|
|
151
|
+
cwd: string,
|
|
152
|
+
): Promise<ApplyProposedEditsResult> {
|
|
153
|
+
let applied = 0;
|
|
154
|
+
for (let i = 0; i < edits.length; i++) {
|
|
155
|
+
const edit = edits[i];
|
|
156
|
+
if (edit === undefined) continue;
|
|
157
|
+
const one = await applyOneEdit(cwd, edit.path, edit.oldText, edit.newText);
|
|
158
|
+
if (!one.ok) {
|
|
159
|
+
return { ok: false, error: one.error, applied };
|
|
160
|
+
}
|
|
161
|
+
applied++;
|
|
162
|
+
}
|
|
163
|
+
return { ok: true, applied };
|
|
164
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
1
|
import { createEditToolDefinition, type AgentToolResult, type Theme, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
4
2
|
import { Container, Text, type Component } from "@earendil-works/pi-tui";
|
|
5
3
|
import { Type } from "typebox";
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
6
|
import type { EditRegistry } from "../registry/edit-registry.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { planEdit } from "../text/edits.ts";
|
|
8
8
|
import { errorMessage, formatError } from "../util/errors.ts";
|
|
9
9
|
|
|
10
10
|
export const ApplyEditsToolParams = Object.freeze(Type.Object({
|
|
@@ -205,8 +205,10 @@ export function createApplyEditsTool(editRegistry: EditRegistry): ToolDefinition
|
|
|
205
205
|
const fileStatsByPath = new Map<string, ApplyEditsFileStat>();
|
|
206
206
|
let appliedCount = 0;
|
|
207
207
|
let failedCount = 0;
|
|
208
|
-
|
|
208
|
+
// Native mode: plan/validate with shared planEdit, then write through the host
|
|
209
|
+
// edit tool so file-watcher / undo hooks still fire (direct write for create only).
|
|
209
210
|
const editTool = createEditToolDefinition(ctx.cwd);
|
|
211
|
+
|
|
210
212
|
for (let i = 0; i < params.ids.length; i++) {
|
|
211
213
|
const id = params.ids[i];
|
|
212
214
|
const edit = editRegistry.get(id);
|
|
@@ -217,41 +219,46 @@ export function createApplyEditsTool(editRegistry: EditRegistry): ToolDefinition
|
|
|
217
219
|
continue;
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
const planned = await planEdit(ctx.cwd, edit.path, edit.oldText, edit.newText);
|
|
223
|
+
if (!planned.ok) {
|
|
224
|
+
errors[failedCount] = { id, path: edit.path, error: planned.error };
|
|
225
|
+
mergeFileStat(fileStatsByPath, edit.path, "failed", { added: 0, removed: 0 }, { oldText: edit.oldText, newText: edit.newText });
|
|
226
|
+
failedCount++;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
220
230
|
try {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
224
|
-
await writeFile(fullPath, edit.newText, "utf8");
|
|
225
|
-
mergeFileStat(fileStatsByPath, edit.path, "applied", diffStats("", edit.newText), { oldText: edit.oldText, newText: edit.newText });
|
|
231
|
+
if (planned.kind === "already-applied") {
|
|
232
|
+
mergeFileStat(fileStatsByPath, edit.path, "applied", { added: 0, removed: 0 }, { oldText: edit.oldText, newText: edit.newText });
|
|
226
233
|
editRegistry.delete(id);
|
|
227
234
|
appliedCount++;
|
|
228
235
|
continue;
|
|
229
236
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
if (planned.kind === "create") {
|
|
238
|
+
const fullPath = resolve(ctx.cwd, edit.path);
|
|
239
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
240
|
+
await writeFile(fullPath, planned.after, "utf8");
|
|
241
|
+
} else {
|
|
242
|
+
await editTool.execute(
|
|
243
|
+
toolCallId,
|
|
244
|
+
{ path: edit.path, edits: [{ oldText: edit.oldText, newText: edit.newText }] },
|
|
245
|
+
signal,
|
|
246
|
+
undefined,
|
|
247
|
+
ctx,
|
|
248
|
+
);
|
|
238
249
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
undefined,
|
|
246
|
-
ctx,
|
|
250
|
+
mergeFileStat(
|
|
251
|
+
fileStatsByPath,
|
|
252
|
+
edit.path,
|
|
253
|
+
"applied",
|
|
254
|
+
diffStats(planned.before, planned.after),
|
|
255
|
+
{ oldText: edit.oldText, newText: edit.newText },
|
|
247
256
|
);
|
|
248
|
-
mergeFileStat(fileStatsByPath, edit.path, "applied", diffStats(content, after), { oldText: edit.oldText, newText: edit.newText });
|
|
249
257
|
editRegistry.delete(id);
|
|
250
258
|
appliedCount++;
|
|
251
259
|
} catch (error: unknown) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
mergeFileStat(fileStatsByPath, path, "failed", { added: 0, removed: 0 }, { oldText: edit.oldText, newText: edit.newText });
|
|
260
|
+
errors[failedCount] = { id, path: edit.path, error: formatError(errorMessage(error)) };
|
|
261
|
+
mergeFileStat(fileStatsByPath, edit.path, "failed", { added: 0, removed: 0 }, { oldText: edit.oldText, newText: edit.newText });
|
|
255
262
|
failedCount++;
|
|
256
263
|
}
|
|
257
264
|
}
|
package/src/tool/repl-tool.ts
CHANGED
|
@@ -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";
|
|
@@ -341,13 +342,19 @@ export interface ReplToolDeps {
|
|
|
341
342
|
readonly signal?: AbortSignal;
|
|
342
343
|
readonly onUsage?: (usage: Usage, role: "sub") => void;
|
|
343
344
|
readonly ensureContext?: () => Promise<void>;
|
|
345
|
+
/** Register a reset hook for sandbox death/dispose (e.g. load_library slot counter). */
|
|
346
|
+
readonly registerDiscardHook?: (reset: () => void) => void;
|
|
344
347
|
}
|
|
345
348
|
|
|
346
349
|
export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplToolParams, ReplDetails> {
|
|
347
350
|
const { sandboxManager, workerModel, registry, editRegistry, config, signal, onUsage } = deps;
|
|
348
351
|
const bridgeState = new NativeBridgeState();
|
|
349
352
|
|
|
350
|
-
//
|
|
353
|
+
// Late-bound cwd — getOrCreate installs handlers only at spawn; never rebuild the closure.
|
|
354
|
+
let sessionCwd = process.cwd();
|
|
355
|
+
|
|
356
|
+
// Build handlers once — llm/rlm/library use late-bound deps so the same closures stay correct
|
|
357
|
+
// across repl() calls; counter resets when the sandbox is discarded and re-spawned.
|
|
351
358
|
const llmHandlers = bridgeState.buildLlmHandlers({
|
|
352
359
|
workerModel,
|
|
353
360
|
getWorkerModel: deps.getWorkerModel,
|
|
@@ -373,6 +380,17 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
373
380
|
llmHandlers,
|
|
374
381
|
});
|
|
375
382
|
|
|
383
|
+
const libraryBundle = config.libraryLoader
|
|
384
|
+
? buildLibraryHandler({
|
|
385
|
+
getCwd: () => sessionCwd,
|
|
386
|
+
getEmitter: () => bridgeState.currentEmitter,
|
|
387
|
+
parentId: undefined,
|
|
388
|
+
signal,
|
|
389
|
+
startIndex: 1,
|
|
390
|
+
})
|
|
391
|
+
: undefined;
|
|
392
|
+
if (libraryBundle) deps.registerDiscardHook?.(libraryBundle.reset);
|
|
393
|
+
|
|
376
394
|
return {
|
|
377
395
|
name: "repl",
|
|
378
396
|
label: "REPL",
|
|
@@ -382,7 +400,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
382
400
|
"chunk `context` and delegate semantic work to llm_query / llm_query_batched / " +
|
|
383
401
|
"llm_query_chunked / rlm_query — stdout returned to you is hard-capped at 4K chars, so " +
|
|
384
402
|
"printing file bodies is useless. Variables, imports, and state persist across calls. " +
|
|
385
|
-
"Also supports todo and
|
|
403
|
+
"Also supports todo, ask_user_question, and load_library inside the sandbox.",
|
|
386
404
|
parameters: ReplToolParams,
|
|
387
405
|
|
|
388
406
|
async execute(_toolCallId, rawParams, _execSignal, onUpdate, ctx) {
|
|
@@ -448,12 +466,15 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
448
466
|
parentId: undefined,
|
|
449
467
|
});
|
|
450
468
|
|
|
469
|
+
sessionCwd = ctx.cwd ?? process.cwd();
|
|
470
|
+
|
|
451
471
|
await deps.ensureContext?.();
|
|
452
472
|
await sandboxManager.getOrCreate({
|
|
453
473
|
...llmHandlers,
|
|
454
474
|
...rlmHandlers,
|
|
455
475
|
askUserQuestion: interactiveHandlers.askUserQuestion,
|
|
456
476
|
todo: interactiveHandlers.todo,
|
|
477
|
+
...(libraryBundle?.handlers ?? {}),
|
|
457
478
|
});
|
|
458
479
|
|
|
459
480
|
// Detect queue contention AFTER sandbox init (initPromise settled, isExecuting now accurate)
|
package/src/tool/rlm-tool.ts
CHANGED
|
@@ -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
|
*/
|
package/src/ui/config-panel.ts
CHANGED
|
@@ -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
|
|
44
|
+
item("pipeline", "Phase pipeline", config.pipeline ? "on" : "off", CHOICES.pipeline, "Enable artifact-gated phases: clarify→research→blueprint→implement fanout→validate (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 a new context_N slot."),
|
|
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
|
}
|