@hicaru/pi-rlm 0.1.2 → 0.1.5
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 +2 -1
- package/README.ru.md +2 -1
- package/README.zh-CN.md +2 -1
- package/package.json +4 -4
- package/src/bridge/llm-query.ts +8 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/settings.ts +2 -0
- package/src/context/repomix-context.ts +4 -2
- package/src/core/answer.ts +16 -5
- package/src/core/engine.ts +21 -16
- package/src/core/iteration.ts +7 -1
- package/src/core/pipeline.ts +1 -1
- package/src/core/types.ts +2 -0
- package/src/index.ts +41 -11
- package/src/mode/native-guards.ts +92 -0
- package/src/prompts/system.ts +149 -62
- package/src/sandbox/sandbox-manager.ts +2 -0
- package/src/sandbox/sandbox.ts +11 -1
- package/src/sandbox/worker.py +106 -12
- package/src/text/parsing.ts +2 -2
- package/src/tool/repl-tool.ts +54 -9
- package/src/ui/config-panel.ts +3 -0
package/src/tool/repl-tool.ts
CHANGED
|
@@ -31,6 +31,7 @@ import type { ProposedEdit, ReplResult } from "../sandbox/protocol.ts";
|
|
|
31
31
|
import { RlmEmitter } from "./rlm-events.ts";
|
|
32
32
|
import { SubcallStore } from "./subcall-store.ts";
|
|
33
33
|
import type { ReplDetails } from "./repl-details.ts";
|
|
34
|
+
import type { RlmSubcall } from "./rlm-details.ts";
|
|
34
35
|
import { createEngine } from "../core/engine.ts";
|
|
35
36
|
import { formatCost, formatTokens, spinnerFrame } from "../ui/theme.ts";
|
|
36
37
|
import { errorMessage, formatError, isErrorText } from "../util/errors.ts";
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
renderExpandedSubcallTree,
|
|
41
42
|
} from "./subcall-render.ts";
|
|
42
43
|
import { createProgressNotifier, validateToolParams } from "./tool-utils.ts";
|
|
44
|
+
import { capReplResultText, replDelegationNudge } from "../mode/native-guards.ts";
|
|
43
45
|
|
|
44
46
|
// ── Parameter schema ──
|
|
45
47
|
|
|
@@ -51,6 +53,37 @@ export function surfaceReplEdits(edits: readonly ProposedEdit[], raised: boolean
|
|
|
51
53
|
return edits.length > 0 && !raised ? edits : undefined;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
/** Model-visible text assembled from a repl() result, plus the surfaced edits for `details`. */
|
|
57
|
+
export interface ReplResultText {
|
|
58
|
+
readonly text: string;
|
|
59
|
+
readonly surfacedEdits: readonly ProposedEdit[] | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Assemble the model-visible text for a repl() result: cap stdout, append a zero-subcall
|
|
64
|
+
* delegation nudge (suppressed when edits were staged), and append the STAGED_EDITS block
|
|
65
|
+
* AFTER capping so edit JSON is never truncated. Extracted as a pure function so the
|
|
66
|
+
* capping/ordering invariants are testable independently of the sandbox.
|
|
67
|
+
*/
|
|
68
|
+
export function buildReplResultText(
|
|
69
|
+
stdout: string,
|
|
70
|
+
answerContent: string | undefined,
|
|
71
|
+
edits: readonly ProposedEdit[],
|
|
72
|
+
raised: boolean,
|
|
73
|
+
subcalls: readonly RlmSubcall[],
|
|
74
|
+
): ReplResultText {
|
|
75
|
+
const rawText = stdout || answerContent || "(no output)";
|
|
76
|
+
const surfacedEdits = surfaceReplEdits(edits, raised);
|
|
77
|
+
const editsBlock = surfacedEdits
|
|
78
|
+
? `\n\nSTAGED_EDITS:\n${JSON.stringify(surfacedEdits)}`
|
|
79
|
+
: "";
|
|
80
|
+
// Model-visible text is capped; the caller keeps full stdout in `details.output` for the TUI.
|
|
81
|
+
const cappedText = capReplResultText(rawText) ?? rawText;
|
|
82
|
+
const delegated = subcalls.some((s) => s.kind === "llm" || s.kind === "batch" || s.kind === "rlm");
|
|
83
|
+
const nudge = surfacedEdits ? undefined : replDelegationNudge(rawText.length, delegated);
|
|
84
|
+
return { text: cappedText + (nudge ?? "") + editsBlock, surfacedEdits };
|
|
85
|
+
}
|
|
86
|
+
|
|
54
87
|
// ── Mutable bridge state (handler indirection) ──
|
|
55
88
|
|
|
56
89
|
/**
|
|
@@ -91,6 +124,11 @@ class NativeBridgeState {
|
|
|
91
124
|
modelRef(model ? (resolveModelId(deps.registry, model) ?? workerModel()) : workerModel()) ?? workerModel().id;
|
|
92
125
|
|
|
93
126
|
async function complete1(prompt: string, model: string | null, track: (u: Usage) => void): Promise<string> {
|
|
127
|
+
const limits = state.currentLimits;
|
|
128
|
+
if (limits) {
|
|
129
|
+
const limitError = checkResourceLimits({ budgetUsd: limits.remainingBudgetUsd(), timeoutMs: limits.remainingTimeoutMs() });
|
|
130
|
+
if (limitError !== undefined) return limitError;
|
|
131
|
+
}
|
|
94
132
|
if (prompt.length > deps.maxPromptChars) {
|
|
95
133
|
return formatError(`sub-LLM prompt exceeded size limit (${prompt.length.toLocaleString()} chars > ${deps.maxPromptChars.toLocaleString()})`);
|
|
96
134
|
}
|
|
@@ -107,6 +145,7 @@ class NativeBridgeState {
|
|
|
107
145
|
reasoning: deps.sampling?.reasoning,
|
|
108
146
|
signal: deps.signal,
|
|
109
147
|
});
|
|
148
|
+
limits?.addUsage(res.usage);
|
|
110
149
|
track(res.usage);
|
|
111
150
|
return res.text;
|
|
112
151
|
} catch (err) {
|
|
@@ -132,7 +171,6 @@ class NativeBridgeState {
|
|
|
132
171
|
costUsd: cost, tokens, resultPreview: previewText(out),
|
|
133
172
|
detail: isErrorText(out) ? out : undefined,
|
|
134
173
|
});
|
|
135
|
-
state.currentLimits?.addRaw(cost, 0, tokens);
|
|
136
174
|
return out;
|
|
137
175
|
},
|
|
138
176
|
|
|
@@ -155,7 +193,6 @@ class NativeBridgeState {
|
|
|
155
193
|
status: error ? "error" : "done", costUsd: cost, tokens,
|
|
156
194
|
resultPreview: previewText(out[0] ?? ""), detail: error,
|
|
157
195
|
});
|
|
158
|
-
state.currentLimits?.addRaw(cost, 0, tokens);
|
|
159
196
|
return out;
|
|
160
197
|
},
|
|
161
198
|
};
|
|
@@ -317,7 +354,13 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
317
354
|
return {
|
|
318
355
|
name: "repl",
|
|
319
356
|
label: "REPL",
|
|
320
|
-
description:
|
|
357
|
+
description:
|
|
358
|
+
"PRIMARY tool for ALL repository reading and analysis (read/grep are disabled in RLM mode). " +
|
|
359
|
+
"Persistent Python sandbox with every file pre-loaded in `context`. You are an orchestrator: " +
|
|
360
|
+
"chunk `context` and delegate semantic work to llm_query / llm_query_batched / " +
|
|
361
|
+
"llm_query_chunked / rlm_query — stdout returned to you is hard-capped at 4K chars, so " +
|
|
362
|
+
"printing file bodies is useless. Variables, imports, and state persist across calls. " +
|
|
363
|
+
"Also supports todo and ask_user_question inside the sandbox.",
|
|
321
364
|
parameters: ReplToolParams,
|
|
322
365
|
|
|
323
366
|
async execute(_toolCallId, rawParams, _execSignal, onUpdate, ctx) {
|
|
@@ -421,11 +464,13 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
421
464
|
|
|
422
465
|
if (queuedId) emitter.emitSubcallUpdated({ id: queuedId, status: "done" });
|
|
423
466
|
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
467
|
+
const { text: resultText, surfacedEdits } = buildReplResultText(
|
|
468
|
+
result.stdout,
|
|
469
|
+
result.answerContent,
|
|
470
|
+
result.edits,
|
|
471
|
+
result.raised,
|
|
472
|
+
store.getSubcalls(),
|
|
473
|
+
);
|
|
429
474
|
|
|
430
475
|
const details: ReplDetails = {
|
|
431
476
|
status: "done",
|
|
@@ -438,7 +483,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
438
483
|
};
|
|
439
484
|
// Final progressive update
|
|
440
485
|
onUpdate?.({ content: [{ type: "text", text: result.stdout.slice(0, 500) || "(no output)" }], details });
|
|
441
|
-
return { content: [{ type: "text", text:
|
|
486
|
+
return { content: [{ type: "text", text: resultText }], details };
|
|
442
487
|
} catch (e) {
|
|
443
488
|
progressStatus = "error";
|
|
444
489
|
const msg = errorMessage(e);
|
package/src/ui/config-panel.ts
CHANGED
|
@@ -15,6 +15,7 @@ const CHOICES = Object.freeze({
|
|
|
15
15
|
maxTokens: Object.freeze(["none", "10000", "50000", "100000"]),
|
|
16
16
|
maxErrors: Object.freeze(["3", "5", "10", "none"]),
|
|
17
17
|
orchestrator: Object.freeze(["on", "off"]),
|
|
18
|
+
pipeline: Object.freeze(["on", "off"]),
|
|
18
19
|
compaction: Object.freeze(["on", "off"]),
|
|
19
20
|
rootSamplingMaxTokens: Object.freeze(["4096", "8192", "16384", "32768"]),
|
|
20
21
|
sandboxInitTimeoutMs: Object.freeze(["10000", "30000", "60000", "120000"]),
|
|
@@ -38,6 +39,7 @@ export async function showConfigPanel(ctx: ExtensionContext, config: RlmConfig):
|
|
|
38
39
|
item("maxTokens", "Token ceiling", config.maxTokens != null ? String(config.maxTokens) : "none", CHOICES.maxTokens, "Total input+output token cap for the whole recursive tree."),
|
|
39
40
|
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."),
|
|
40
41
|
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."),
|
|
41
43
|
item("compaction", "Trajectory compaction", config.compaction ? "on" : "off", CHOICES.compaction, "Summarize old turns when history approaches the model context window."),
|
|
42
44
|
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."),
|
|
43
45
|
item("sandboxInitTimeoutMs", "Sandbox init timeout", String(config.sandboxInitTimeoutMs), CHOICES.sandboxInitTimeoutMs, "How long to wait for the Python worker to start."),
|
|
@@ -83,6 +85,7 @@ function applySetting(config: RlmConfig, id: string, value: string): void {
|
|
|
83
85
|
case "maxTokens": config.maxTokens = value === "none" ? undefined : Number(value); break;
|
|
84
86
|
case "maxErrors": config.maxErrors = value === "none" ? undefined : Number(value); break;
|
|
85
87
|
case "orchestrator": config.orchestrator = value === "on"; break;
|
|
88
|
+
case "pipeline": config.pipeline = value === "on"; break;
|
|
86
89
|
case "compaction": config.compaction = value === "on"; break;
|
|
87
90
|
case "rootSamplingMaxTokens": config.rootSampling = Object.freeze({ ...config.rootSampling, maxTokens: Number(value) }); break;
|
|
88
91
|
case "sandboxInitTimeoutMs": config.sandboxInitTimeoutMs = Number(value); break;
|