@hicaru/pi-rlm 0.2.0 → 0.2.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.
- package/README.md +12 -35
- package/README.ru.md +18 -23
- package/README.zh-CN.md +17 -28
- package/package.json +1 -1
- package/src/bridge/library.ts +61 -26
- package/src/bridge/subcall-handlers.ts +382 -0
- package/src/commands/rlm-config.ts +47 -18
- package/src/commands/rlm.ts +3 -152
- package/src/config/defaults.ts +7 -15
- package/src/config/settings.ts +8 -32
- package/src/context/library-context.ts +90 -17
- package/src/core/engine.ts +115 -360
- package/src/core/history.ts +1 -1
- package/src/core/limits.ts +5 -12
- package/src/core/resource-limits.ts +0 -2
- package/src/core/types.ts +3 -36
- package/src/index.ts +49 -10
- package/src/mode/llm-model.ts +54 -0
- package/src/mode/rlm-mode.ts +26 -57
- package/src/prompts/glossary.ts +287 -0
- package/src/prompts/native.ts +127 -0
- package/src/prompts/system.ts +14 -386
- package/src/sandbox/context-file.ts +154 -0
- package/src/sandbox/interrupts.ts +145 -0
- package/src/sandbox/protocol.ts +14 -69
- package/src/sandbox/py/guards.py +150 -0
- package/src/sandbox/py/retrieval.py +265 -0
- package/src/sandbox/py/tasks.py +116 -0
- package/src/sandbox/py/worker.py +836 -0
- package/src/sandbox/sandbox-manager.ts +33 -6
- package/src/sandbox/sandbox.ts +153 -182
- package/src/text/tokens.ts +29 -3
- package/src/tool/background-tasks.ts +95 -0
- package/src/tool/repl-details.ts +4 -2
- package/src/tool/repl-render.ts +58 -0
- package/src/tool/repl-result.ts +70 -0
- package/src/tool/repl-tool.ts +178 -216
- package/src/tool/rlm-aggregator.ts +2 -10
- package/src/tool/rlm-details.ts +0 -2
- package/src/tool/rlm-events.ts +10 -16
- package/src/tool/rlm-tool.ts +1 -12
- package/src/tool/subcall-render.ts +15 -3
- package/src/tool/subcall-store.ts +57 -1
- package/src/ui/config-panel.ts +4 -16
- package/src/ui/intro.ts +1 -2
- package/src/ui/model-picker.ts +34 -10
- package/src/ui/status.ts +3 -7
- package/src/util/concurrency.ts +91 -13
- package/src/util/trace.ts +42 -0
- package/src/bridge/fallback-todo.ts +0 -137
- package/src/bridge/interactive.ts +0 -65
- package/src/bridge/llm-query.ts +0 -156
- package/src/bridge/pi-interactive.ts +0 -41
- package/src/bridge/rlm-query.ts +0 -108
- package/src/core/artifacts.ts +0 -89
- package/src/core/critique.ts +0 -92
- package/src/core/gates.ts +0 -301
- package/src/core/pipeline-handlers.ts +0 -319
- package/src/core/pipeline.ts +0 -268
- package/src/prompts/phases.ts +0 -104
- package/src/sandbox/worker.py +0 -1078
- package/src/state/index.ts +0 -24
- package/src/state/internal.ts +0 -46
- package/src/state/paths.ts +0 -44
- package/src/state/reads.ts +0 -133
- package/src/state/resume.ts +0 -173
- package/src/state/rows.ts +0 -123
- package/src/state/writes.ts +0 -58
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped home for detached `spawn()` work in native repl() mode.
|
|
3
|
+
*
|
|
4
|
+
* A spawned sub-call may settle after the repl() call that started it has returned, when
|
|
5
|
+
* that turn's RlmEmitter has been shut down and its LimitGuard discarded. Both live here for
|
|
6
|
+
* the whole session instead. The turn that awaits a task — or simply the next turn to run —
|
|
7
|
+
* adopts its settled subtree into its own ReplDetails, so background spend is reported even
|
|
8
|
+
* when the model never collects the result.
|
|
9
|
+
*
|
|
10
|
+
* The headless engine needs none of this: its emitter and guard already outlive every
|
|
11
|
+
* sub-call it services (see core/engine.ts).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { RlmEmitter } from "./rlm-events.ts";
|
|
15
|
+
import { SubcallStore, type SubcallTotals } from "./subcall-store.ts";
|
|
16
|
+
import type { RlmSubcall } from "./rlm-details.ts";
|
|
17
|
+
import { LimitGuard, type Limits } from "../core/limits.ts";
|
|
18
|
+
import type { Invocation } from "../bridge/subcall-handlers.ts";
|
|
19
|
+
import { trace, traceEnabled } from "../util/trace.ts";
|
|
20
|
+
|
|
21
|
+
/** What a drain hands to the turn that is reporting it. */
|
|
22
|
+
export interface BackgroundDrain {
|
|
23
|
+
readonly subcalls: readonly RlmSubcall[];
|
|
24
|
+
readonly totals: SubcallTotals;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class BackgroundTasks {
|
|
28
|
+
/** "bg" prefix so these IDs can never collide with a turn emitter's `s1, s2, …`. */
|
|
29
|
+
private readonly _emitter = new RlmEmitter("bg");
|
|
30
|
+
private readonly store = new SubcallStore(this._emitter);
|
|
31
|
+
private readonly limits: LimitGuard;
|
|
32
|
+
private active = 0;
|
|
33
|
+
|
|
34
|
+
constructor(limits: Limits) {
|
|
35
|
+
this.limits = new LimitGuard(limits);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Read-only access for the progressive tracer (scope "background"). */
|
|
39
|
+
get emitter(): RlmEmitter {
|
|
40
|
+
return this._emitter;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The Invocation detached sub-calls resolve to. Stable for the session. */
|
|
44
|
+
get invocation(): Invocation {
|
|
45
|
+
return {
|
|
46
|
+
emitter: this._emitter,
|
|
47
|
+
parentId: undefined,
|
|
48
|
+
depth: 0,
|
|
49
|
+
limits: this.limits,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Detached sub-calls still in flight. The single pending counter for the session. */
|
|
54
|
+
get pending(): number {
|
|
55
|
+
return this.active;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Live snapshot of detached sub-calls, settled or not — progressive rendering only. */
|
|
59
|
+
liveSubcalls(): readonly RlmSubcall[] {
|
|
60
|
+
return this.store.getSubcalls();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Live totals for that same snapshot. Accounting still flows through `drain()`. */
|
|
64
|
+
liveTotals(): SubcallTotals {
|
|
65
|
+
return this.store.getTotals();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Count `run` as in-flight detached work for its duration. */
|
|
69
|
+
async track<T>(run: () => Promise<T>): Promise<T> {
|
|
70
|
+
this.active += 1;
|
|
71
|
+
const startedAt = Date.now();
|
|
72
|
+
if (traceEnabled) trace("bg.start", { pending: this.active });
|
|
73
|
+
try {
|
|
74
|
+
return await run();
|
|
75
|
+
} finally {
|
|
76
|
+
this.active -= 1;
|
|
77
|
+
if (traceEnabled) trace("bg.settle", { pending: this.active, durationMs: Date.now() - startedAt });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Hand over every fully-settled subtree and forget it.
|
|
83
|
+
*
|
|
84
|
+
* Called at the end of every repl() call, not only when a task was awaited, so a spawn
|
|
85
|
+
* the model never collects still reaches the user's cost totals.
|
|
86
|
+
*/
|
|
87
|
+
drain(): BackgroundDrain {
|
|
88
|
+
return this.store.takeSettledSubtrees();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
dispose(): void {
|
|
92
|
+
this.store.dispose();
|
|
93
|
+
this._emitter.shutdown();
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/tool/repl-details.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ReplDetails — structured payload for the repl() tool's AgentToolResult<T>.
|
|
3
3
|
*
|
|
4
4
|
* Mirrors RlmDetails but scoped to a single code execution. Sub-calls (llm_query,
|
|
5
|
-
* rlm_query,
|
|
5
|
+
* rlm_query, load_library) triggered during sandbox execution are
|
|
6
6
|
* accumulated into the subcalls array for tree rendering.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -16,12 +16,14 @@ export interface ReplDetails {
|
|
|
16
16
|
readonly stderr: string;
|
|
17
17
|
/** Wall-clock execution time in milliseconds. */
|
|
18
18
|
readonly executionTimeMs: number;
|
|
19
|
-
/** Sub-calls triggered during this execution (llm_query, rlm_query,
|
|
19
|
+
/** Sub-calls triggered during this execution (llm_query, rlm_query, etc.). */
|
|
20
20
|
readonly subcalls: readonly RlmSubcall[];
|
|
21
21
|
/** Running totals for this repl() call (cost + tokens from sub-LLM calls). */
|
|
22
22
|
readonly totals: { readonly costUsd: number; readonly tokens: number };
|
|
23
23
|
/** Final answer submitted through answer["ready"] without echoing it to the model. */
|
|
24
24
|
readonly finalAnswer?: string;
|
|
25
|
+
/** Detached spawn() sub-calls still running when this call returned. Absent when none. */
|
|
26
|
+
readonly backgroundPending?: number;
|
|
25
27
|
/** Advisory diagnostics — surfaced to the user, never a failure. */
|
|
26
28
|
readonly warnings?: readonly string[];
|
|
27
29
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** repl() tool TUI views — collapsed one-liner card and the expanded output/sub-call tree. */
|
|
2
|
+
|
|
3
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import type { ReplDetails } from "./repl-details.ts";
|
|
6
|
+
import { cardHeader, cardStatsLine, renderCollapsedCard, renderExpandedSubcallTree } from "./subcall-render.ts";
|
|
7
|
+
|
|
8
|
+
/** Chars of stdout/stderr shown in the expanded view. */
|
|
9
|
+
const EXPANDED_STDOUT_CHARS = 2_000;
|
|
10
|
+
const EXPANDED_STDERR_CHARS = 500;
|
|
11
|
+
|
|
12
|
+
// ── Collapsed view ──
|
|
13
|
+
|
|
14
|
+
export function replStats(details: ReplDetails, theme: Theme): string {
|
|
15
|
+
const elapsed = details.executionTimeMs > 0 ? `${details.executionTimeMs}ms` : undefined;
|
|
16
|
+
return cardStatsLine(details.totals, theme, elapsed, details.backgroundPending);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function renderReplCollapsed(details: ReplDetails, theme: Theme): Text {
|
|
20
|
+
return renderCollapsedCard("REPL", details.status, replStats(details, theme), details.subcalls, theme);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Expanded view ──
|
|
24
|
+
|
|
25
|
+
export function renderReplExpanded(details: ReplDetails, theme: Theme): Container {
|
|
26
|
+
const container = new Container();
|
|
27
|
+
|
|
28
|
+
container.addChild(new Text(cardHeader("REPL", details.status, replStats(details, theme), theme), 0, 0));
|
|
29
|
+
|
|
30
|
+
// Output
|
|
31
|
+
if (details.output) {
|
|
32
|
+
container.addChild(new Spacer(1));
|
|
33
|
+
const out = details.output.length > EXPANDED_STDOUT_CHARS
|
|
34
|
+
? `${details.output.slice(0, EXPANDED_STDOUT_CHARS)}…`
|
|
35
|
+
: details.output;
|
|
36
|
+
container.addChild(new Text(out, 0, 0));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (details.warnings && details.warnings.length > 0) {
|
|
40
|
+
container.addChild(new Spacer(1));
|
|
41
|
+
container.addChild(new Text(theme.fg("muted", details.warnings.join("\n")), 0, 0));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Stderr
|
|
45
|
+
if (details.stderr) {
|
|
46
|
+
container.addChild(new Spacer(1));
|
|
47
|
+
container.addChild(new Text(theme.fg("error", details.stderr.slice(0, EXPANDED_STDERR_CHARS)), 0, 0));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Sub-call tree
|
|
51
|
+
if (details.subcalls.length > 0) {
|
|
52
|
+
container.addChild(new Spacer(1));
|
|
53
|
+
container.addChild(new Text(theme.fg("muted", "─── Sub-calls ───"), 0, 0));
|
|
54
|
+
container.addChild(renderExpandedSubcallTree(details.subcalls, theme));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return container;
|
|
58
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-visible text assembly for a repl() result, plus the advisory diagnostics derived from
|
|
3
|
+
* its sub-calls. Split out of repl-tool.ts: this is pure string/array work with no sandbox,
|
|
4
|
+
* emitter, or TUI dependency, and both halves are asserted directly by test/phase-guards.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RlmSubcall } from "./rlm-details.ts";
|
|
8
|
+
import { capReplResultText, replDelegationNudge } from "../mode/native-guards.ts";
|
|
9
|
+
|
|
10
|
+
/** Model-visible text assembled from a repl() result. */
|
|
11
|
+
export interface ReplResultText {
|
|
12
|
+
readonly text: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Assemble the model-visible text for a repl() result: cap stdout, append a zero-subcall
|
|
17
|
+
* delegation nudge when a bulk read went undelegated, and report tasks still running.
|
|
18
|
+
*
|
|
19
|
+
* The pending line is the model's only signal that `spawn()`ed work is outstanding — without
|
|
20
|
+
* it a model that spawned and moved on has no way to know it should still collect.
|
|
21
|
+
*
|
|
22
|
+
* `varNames` covers the opposite failure: a block that stores its results in `answers` and
|
|
23
|
+
* prints nothing reads as a bare "(no output)", so the model concludes the block did nothing
|
|
24
|
+
* and re-runs it — paying twice for the same sub-calls. The headless engine already answers
|
|
25
|
+
* this with the same hint (core/answer.ts); native mode was the only path missing it.
|
|
26
|
+
*/
|
|
27
|
+
export function buildReplResultText(
|
|
28
|
+
stdout: string,
|
|
29
|
+
finalAnswer: string | undefined,
|
|
30
|
+
subcalls: readonly RlmSubcall[],
|
|
31
|
+
backgroundPending = 0,
|
|
32
|
+
varNames: readonly string[] = [],
|
|
33
|
+
): ReplResultText {
|
|
34
|
+
const answerSubmitted = finalAnswer !== undefined;
|
|
35
|
+
const noOutput = !answerSubmitted && !stdout;
|
|
36
|
+
const varsHint = noOutput && varNames.length > 0
|
|
37
|
+
? ` — the block ran fine and these REPL vars are defined: ${varNames.join(", ")}. `
|
|
38
|
+
+ "Do NOT re-run it; read them in the next block."
|
|
39
|
+
: "";
|
|
40
|
+
const rawText = answerSubmitted
|
|
41
|
+
? `ANSWER_SUBMITTED (${finalAnswer.length} chars) — delivered to user. Do not restate it.`
|
|
42
|
+
: stdout || `(no output)${varsHint}`;
|
|
43
|
+
// Model-visible text is capped; the caller keeps full stdout in `details` for the TUI.
|
|
44
|
+
const cappedText = capReplResultText(rawText) ?? rawText;
|
|
45
|
+
const delegated = subcalls.some((s) => s.kind === "llm" || s.kind === "batch" || s.kind === "rlm");
|
|
46
|
+
const nudge = answerSubmitted ? undefined : replDelegationNudge(rawText.length, delegated);
|
|
47
|
+
const failedBg = subcalls.filter((s) => s.id.startsWith("bg") && s.status === "error").length;
|
|
48
|
+
const pendingLine = backgroundPending > 0
|
|
49
|
+
? `\n\n[rlm] ${backgroundPending} background task(s) still running — rlm_await_all(tasks) to collect.`
|
|
50
|
+
: "";
|
|
51
|
+
const failedLine = failedBg > 0
|
|
52
|
+
? `\n[rlm] ${failedBg} background sub-call(s) FAILED — their rlm_await value is an "Error: …" string, not data.`
|
|
53
|
+
: "";
|
|
54
|
+
return { text: cappedText + (nudge ?? "") + pendingLine + failedLine };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Advisory diagnostics derived from a completed invocation's sub-calls. */
|
|
58
|
+
export function collectReplWarnings(subcalls: readonly RlmSubcall[]): readonly string[] | undefined {
|
|
59
|
+
let failed = 0;
|
|
60
|
+
let total = 0;
|
|
61
|
+
for (let i = 0; i < subcalls.length; i++) {
|
|
62
|
+
const call = subcalls[i];
|
|
63
|
+
if (call.status !== "error") continue;
|
|
64
|
+
// A batch subcall stands for many prompts; a single call stands for one.
|
|
65
|
+
failed += call.failedCount ?? 1;
|
|
66
|
+
total += call.totalCount ?? 1;
|
|
67
|
+
}
|
|
68
|
+
if (failed === 0) return undefined;
|
|
69
|
+
return Object.freeze([`${failed}/${total} sub-call(s) failed — results may be incomplete`]);
|
|
70
|
+
}
|