@hicaru/pi-rlm 0.1.9 → 0.2.1
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/package.json +1 -1
- package/src/bridge/fallback-todo.ts +12 -1
- package/src/bridge/subcall-handlers.ts +336 -0
- package/src/commands/rlm-config.ts +8 -8
- package/src/commands/rlm.ts +48 -12
- package/src/config/defaults.ts +4 -1
- package/src/config/settings.ts +33 -3
- package/src/context/repomix-context.ts +5 -10
- package/src/core/answer.ts +4 -3
- package/src/core/artifacts.ts +4 -3
- package/src/core/engine.ts +101 -267
- package/src/core/gates.ts +3 -3
- package/src/core/limits.ts +19 -1
- package/src/core/pipeline-handlers.ts +319 -0
- package/src/core/pipeline.ts +2 -2
- package/src/core/types.ts +25 -27
- package/src/index.ts +63 -17
- package/src/mode/rlm-mode.ts +8 -11
- package/src/prompts/system.ts +164 -52
- package/src/prompts/user.ts +1 -5
- package/src/sandbox/protocol.ts +6 -7
- package/src/sandbox/sandbox-manager.ts +25 -11
- package/src/sandbox/sandbox.ts +93 -22
- package/src/sandbox/worker.py +798 -66
- package/src/state/paths.ts +1 -1
- package/src/state/reads.ts +12 -4
- package/src/state/resume.ts +5 -11
- package/src/text/parsing.ts +0 -6
- package/src/tool/background-tasks.ts +95 -0
- package/src/tool/repl-details.ts +2 -0
- package/src/tool/repl-tool.ts +223 -318
- package/src/tool/rlm-details.ts +0 -10
- package/src/tool/rlm-events.ts +10 -2
- package/src/tool/rlm-tool.ts +18 -31
- package/src/tool/subcall-render.ts +75 -11
- package/src/tool/subcall-store.ts +57 -1
- package/src/ui/config-panel.ts +41 -21
- package/src/ui/intro.ts +2 -1
- package/src/ui/status.ts +8 -5
- package/src/ui/theme-adapter.ts +36 -0
- package/src/ui/theme.ts +0 -25
- package/src/util/concurrency.ts +87 -13
- package/src/util/trace.ts +42 -0
- package/src/bridge/llm-query.ts +0 -133
- package/src/bridge/rlm-query.ts +0 -122
- package/src/mode/input-router.ts +0 -23
package/src/state/paths.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function generateRunId(
|
|
|
20
20
|
now: Date = new Date(),
|
|
21
21
|
suffix: string = randomBytes(RUN_ID_SUFFIX_BYTES).toString("hex"),
|
|
22
22
|
): string {
|
|
23
|
-
const pad = (n: number) => String(n).padStart(2, "0");
|
|
23
|
+
const pad = (n: number): string => String(n).padStart(2, "0");
|
|
24
24
|
const iso = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
25
25
|
return `${iso.slice(0, ISO_DATETIME_LENGTH).replaceAll(":", "-").replace("T", "_")}-${suffix}`;
|
|
26
26
|
}
|
package/src/state/reads.ts
CHANGED
|
@@ -12,8 +12,12 @@ import { runsDir, runDir, trailPath, contextPath } from "./paths.ts";
|
|
|
12
12
|
import { isHeader, isRow, type RunHeader, type Row } from "./rows.ts";
|
|
13
13
|
import { errorMessage, failSoft, listDirectoriesSorted, pathExists, warn } from "./internal.ts";
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Raw JSONL lines of a run's trail, in order. Missing or unreadable file → [].
|
|
17
|
+
* Shared by the fail-soft reader here and the hole-detecting reader in resume.ts, which
|
|
18
|
+
* differ only in how they treat a bad line.
|
|
19
|
+
*/
|
|
20
|
+
export async function readTrailLines(cwd: string, dir: string, runId: string): Promise<string[]> {
|
|
17
21
|
const path = trailPath(cwd, dir, runId);
|
|
18
22
|
if (!await pathExists(path)) return [];
|
|
19
23
|
const content = await failSoft(
|
|
@@ -21,10 +25,14 @@ export async function readRows(cwd: string, dir: string, runId: string): Promise
|
|
|
21
25
|
undefined as string | undefined,
|
|
22
26
|
);
|
|
23
27
|
const trimmed = content?.trim();
|
|
24
|
-
|
|
28
|
+
return trimmed ? trimmed.split("\n") : [];
|
|
29
|
+
}
|
|
25
30
|
|
|
31
|
+
/** Every well-formed row, in trail order. Malformed line → one warn, skipped. */
|
|
32
|
+
export async function readRows(cwd: string, dir: string, runId: string): Promise<Row[]> {
|
|
33
|
+
const lines = await readTrailLines(cwd, dir, runId);
|
|
26
34
|
const rows: Row[] = [];
|
|
27
|
-
for (const line of
|
|
35
|
+
for (const line of lines) {
|
|
28
36
|
try {
|
|
29
37
|
const row = JSON.parse(line) as unknown;
|
|
30
38
|
if (isRow(row)) rows.push(row);
|
package/src/state/resume.ts
CHANGED
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
* garbage from a crash is tolerated.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { readFile } from "node:fs/promises";
|
|
11
10
|
import { type ChatMsg } from "../bridge/model.ts";
|
|
12
11
|
import { appendUserMessage } from "../core/history.ts";
|
|
13
12
|
import { buildTurnPrompt } from "../prompts/user.ts";
|
|
14
|
-
import { readHeader } from "./reads.ts";
|
|
13
|
+
import { readHeader, readTrailLines } from "./reads.ts";
|
|
15
14
|
import {
|
|
16
15
|
isCompaction,
|
|
17
16
|
isHeader,
|
|
@@ -24,8 +23,8 @@ import {
|
|
|
24
23
|
type Row,
|
|
25
24
|
type RunHeader,
|
|
26
25
|
} from "./rows.ts";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
26
|
+
import { snapshotPath } from "./paths.ts";
|
|
27
|
+
import { pathExists } from "./internal.ts";
|
|
29
28
|
|
|
30
29
|
/** Artifact path + supersede flag reconstructed from phase rows. */
|
|
31
30
|
export interface PhaseReconArtifact {
|
|
@@ -63,15 +62,10 @@ export type ReconstructResult =
|
|
|
63
62
|
|
|
64
63
|
/** QB: single read + parse — detects mid-file holes without reading the trail twice. */
|
|
65
64
|
async function readRowsStrict(cwd: string, dir: string, runId: string): Promise<{ readonly rows: Row[]; readonly hole: boolean }> {
|
|
66
|
-
const
|
|
67
|
-
if (!await pathExists(path)) return { rows: [], hole: false };
|
|
68
|
-
const content = await failSoft(() => readFile(path, "utf-8"), undefined as string | undefined);
|
|
69
|
-
const trimmed = content?.trim();
|
|
70
|
-
if (!trimmed) return { rows: [], hole: false };
|
|
71
|
-
|
|
65
|
+
const lines = await readTrailLines(cwd, dir, runId);
|
|
72
66
|
const rows: Row[] = [];
|
|
73
67
|
let sawBad = false;
|
|
74
|
-
for (const line of
|
|
68
|
+
for (const line of lines) {
|
|
75
69
|
try {
|
|
76
70
|
const row = JSON.parse(line) as unknown;
|
|
77
71
|
if (!isRow(row)) {
|
package/src/text/parsing.ts
CHANGED
|
@@ -19,12 +19,6 @@ export function findReplBlocks(text: string): string[] {
|
|
|
19
19
|
return blocks;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
/** True if the response contains at least one runnable ```repl``` block. */
|
|
23
|
-
export function hasReplBlock(text: string): boolean {
|
|
24
|
-
FENCE.lastIndex = 0;
|
|
25
|
-
return FENCE.test(text);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
/** Truncate REPL stdout for the model's context window (head + tail, with an elision note). */
|
|
29
23
|
export function truncateOutput(text: string, limit = 20_000): string {
|
|
30
24
|
if (text.length <= limit) return text;
|
|
@@ -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
|
@@ -22,6 +22,8 @@ export interface ReplDetails {
|
|
|
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
|
}
|