@hicaru/pi-rlm 0.1.8 → 0.2.0
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 +22 -19
- package/package.json +2 -1
- package/src/bridge/library.ts +93 -15
- package/src/bridge/llm-query.ts +60 -36
- package/src/bridge/rlm-query.ts +63 -79
- package/src/commands/rlm-config.ts +8 -8
- package/src/commands/rlm.ts +48 -12
- package/src/config/settings.ts +33 -3
- package/src/context/library-context.ts +209 -22
- package/src/context/repomix-context.ts +7 -58
- package/src/core/answer.ts +5 -13
- package/src/core/artifacts.ts +4 -3
- package/src/core/critique.ts +92 -0
- package/src/core/engine.ts +94 -299
- package/src/core/gates.ts +33 -4
- package/src/core/limits.ts +19 -1
- package/src/core/pipeline-handlers.ts +319 -0
- package/src/core/pipeline.ts +40 -15
- package/src/core/types.ts +26 -30
- package/src/index.ts +36 -26
- package/src/mode/native-guards.ts +2 -2
- package/src/mode/rlm-mode.ts +8 -11
- package/src/prompts/phases.ts +18 -39
- package/src/prompts/system.ts +167 -64
- package/src/prompts/user.ts +1 -5
- package/src/sandbox/protocol.ts +5 -17
- package/src/sandbox/sandbox-manager.ts +5 -5
- package/src/sandbox/sandbox.ts +67 -27
- package/src/sandbox/worker.py +534 -48
- package/src/state/paths.ts +1 -1
- package/src/state/reads.ts +12 -4
- package/src/state/resume.ts +26 -25
- package/src/state/rows.ts +2 -2
- package/src/text/parsing.ts +0 -6
- package/src/text/tokens.ts +7 -1
- package/src/tool/repl-details.ts +2 -3
- package/src/tool/repl-tool.ts +132 -337
- package/src/tool/rlm-aggregator.ts +7 -7
- package/src/tool/rlm-details.ts +6 -13
- package/src/tool/rlm-events.ts +14 -11
- package/src/tool/rlm-tool.ts +20 -38
- package/src/tool/subcall-render.ts +61 -9
- package/src/tool/subcall-store.ts +4 -2
- package/src/ui/config-panel.ts +43 -23
- 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/mode/input-router.ts +0 -23
- package/src/registry/edit-registry.ts +0 -22
- package/src/text/edits.ts +0 -164
- package/src/tool/apply-edits-tool.ts +0 -295
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,12 +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
|
|
15
|
-
import { readHeader } from "./reads.ts";
|
|
13
|
+
import { readHeader, readTrailLines } from "./reads.ts";
|
|
16
14
|
import {
|
|
17
15
|
isCompaction,
|
|
18
16
|
isHeader,
|
|
@@ -25,15 +23,21 @@ import {
|
|
|
25
23
|
type Row,
|
|
26
24
|
type RunHeader,
|
|
27
25
|
} from "./rows.ts";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
26
|
+
import { snapshotPath } from "./paths.ts";
|
|
27
|
+
import { pathExists } from "./internal.ts";
|
|
28
|
+
|
|
29
|
+
/** Artifact path + supersede flag reconstructed from phase rows. */
|
|
30
|
+
export interface PhaseReconArtifact {
|
|
31
|
+
readonly path: string;
|
|
32
|
+
readonly superseded: boolean;
|
|
33
|
+
}
|
|
30
34
|
|
|
31
35
|
export interface PhaseRecon {
|
|
32
36
|
readonly current: string;
|
|
33
37
|
readonly advancedAt: number;
|
|
34
38
|
readonly summary?: string;
|
|
35
|
-
/** Repo-relative
|
|
36
|
-
readonly artifacts?: Readonly<Partial<Record<string,
|
|
39
|
+
/** Repo-relative artifacts keyed by the phase that produced them. */
|
|
40
|
+
readonly artifacts?: Readonly<Partial<Record<string, PhaseReconArtifact>>>;
|
|
37
41
|
readonly backwardJumps?: number;
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -45,7 +49,6 @@ export type ReconstructResult =
|
|
|
45
49
|
readonly pendingReplOutputs?: string;
|
|
46
50
|
readonly usageSeed: { readonly costUsd: number; readonly inputTokens: number; readonly outputTokens: number; readonly durationMs: number };
|
|
47
51
|
readonly best: string;
|
|
48
|
-
readonly editsAcc: ProposedEdit[];
|
|
49
52
|
readonly completedTurns: number;
|
|
50
53
|
readonly compactions: number;
|
|
51
54
|
/** R-C1: the latest turn whose per-turn snapshot file exists on disk (undefined ⇒ no restore). */
|
|
@@ -59,15 +62,10 @@ export type ReconstructResult =
|
|
|
59
62
|
|
|
60
63
|
/** QB: single read + parse — detects mid-file holes without reading the trail twice. */
|
|
61
64
|
async function readRowsStrict(cwd: string, dir: string, runId: string): Promise<{ readonly rows: Row[]; readonly hole: boolean }> {
|
|
62
|
-
const
|
|
63
|
-
if (!await pathExists(path)) return { rows: [], hole: false };
|
|
64
|
-
const content = await failSoft(() => readFile(path, "utf-8"), undefined as string | undefined);
|
|
65
|
-
const trimmed = content?.trim();
|
|
66
|
-
if (!trimmed) return { rows: [], hole: false };
|
|
67
|
-
|
|
65
|
+
const lines = await readTrailLines(cwd, dir, runId);
|
|
68
66
|
const rows: Row[] = [];
|
|
69
67
|
let sawBad = false;
|
|
70
|
-
for (const line of
|
|
68
|
+
for (const line of lines) {
|
|
71
69
|
try {
|
|
72
70
|
const row = JSON.parse(line) as unknown;
|
|
73
71
|
if (!isRow(row)) {
|
|
@@ -102,7 +100,6 @@ export async function reconstructRlmState(
|
|
|
102
100
|
let history: ChatMsg[] = [{ role: "system", content: systemPrompt }];
|
|
103
101
|
const usageSeed = { costUsd: 0, inputTokens: 0, outputTokens: 0, durationMs: 0 };
|
|
104
102
|
let best = "";
|
|
105
|
-
let editsAcc: ProposedEdit[] = [];
|
|
106
103
|
let completedTurns = 0;
|
|
107
104
|
let compactions = 0;
|
|
108
105
|
let snapshotTurn: number | undefined; // R-C1: latest turn with an existing snapshot file
|
|
@@ -110,8 +107,8 @@ export async function reconstructRlmState(
|
|
|
110
107
|
const todoRows: { action: string; params: Record<string, unknown>; result: string }[] = [];
|
|
111
108
|
let terminated = false;
|
|
112
109
|
let phase: PhaseRecon | undefined;
|
|
113
|
-
//
|
|
114
|
-
const artifactsAcc
|
|
110
|
+
// Append-only journal: paths stay; supersededPath flips the blueprint slot.
|
|
111
|
+
const artifactsAcc = new Map<string, PhaseReconArtifact>();
|
|
115
112
|
|
|
116
113
|
for (const row of rows) {
|
|
117
114
|
if (isHeader(row)) continue;
|
|
@@ -134,7 +131,6 @@ export async function reconstructRlmState(
|
|
|
134
131
|
usageSeed.outputTokens += row.usage.outputTokens;
|
|
135
132
|
if (row.answerContent) best = row.answerContent;
|
|
136
133
|
else if (!best && row.response.trim()) best = row.response; // C3: mirror engine fallback
|
|
137
|
-
if (row.edits && row.edits.length > 0) editsAcc = [...row.edits];
|
|
138
134
|
completedTurns = row.turn;
|
|
139
135
|
// R-C1: verify the per-turn snapshot file exists — a crashed finalize leaves the row claiming snapshotOk:true with no pkl.
|
|
140
136
|
if (row.snapshotOk && await pathExists(snapshotPath(cwd, dir, runId, row.turn)))
|
|
@@ -145,17 +141,22 @@ export async function reconstructRlmState(
|
|
|
145
141
|
}
|
|
146
142
|
if (isPhase(row)) {
|
|
147
143
|
if (row.artifactPath !== undefined && row.artifactPhase !== undefined) {
|
|
148
|
-
artifactsAcc
|
|
144
|
+
artifactsAcc.set(row.artifactPhase, { path: row.artifactPath, superseded: false });
|
|
149
145
|
}
|
|
150
|
-
//
|
|
151
|
-
if (row.
|
|
152
|
-
|
|
146
|
+
// Append-only: a superseded artifact keeps its slot, flipped to superseded.
|
|
147
|
+
if (row.supersededPath !== undefined) {
|
|
148
|
+
const prior = artifactsAcc.get("blueprint");
|
|
149
|
+
if (prior !== undefined) {
|
|
150
|
+
artifactsAcc.set("blueprint", { path: prior.path, superseded: true });
|
|
151
|
+
}
|
|
153
152
|
}
|
|
153
|
+
const artifactsObj: Record<string, PhaseReconArtifact> = {};
|
|
154
|
+
for (const [k, v] of artifactsAcc) artifactsObj[k] = v;
|
|
154
155
|
phase = {
|
|
155
156
|
current: row.phase,
|
|
156
157
|
advancedAt: row.turn - 1,
|
|
157
158
|
summary: row.summary,
|
|
158
|
-
artifacts:
|
|
159
|
+
artifacts: artifactsAcc.size > 0 ? artifactsObj : undefined,
|
|
159
160
|
backwardJumps: row.backwardJumps,
|
|
160
161
|
};
|
|
161
162
|
continue;
|
|
@@ -168,5 +169,5 @@ export async function reconstructRlmState(
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
if (completedTurns === 0 && !terminated) return { ok: false, reason: "no-turns", detail: runId };
|
|
171
|
-
return { ok: true, header, history, pendingReplOutputs, usageSeed, best,
|
|
172
|
+
return { ok: true, header, history, pendingReplOutputs, usageSeed, best, completedTurns, compactions, snapshotTurn, todoRows, terminated, phase };
|
|
172
173
|
}
|
package/src/state/rows.ts
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { ChatMsg } from "../bridge/model.ts";
|
|
12
|
-
import type { ProposedEdit } from "../sandbox/protocol.ts";
|
|
13
12
|
|
|
14
13
|
/** Bump when a row shape changes such that the resume fold cannot replay older files. */
|
|
15
14
|
export const STATE_SCHEMA_VERSION = 5;
|
|
@@ -47,7 +46,6 @@ export interface TurnRow {
|
|
|
47
46
|
readonly response: string; // assistant message
|
|
48
47
|
readonly replOutputs?: string; // formatReplOutputs(results) → next user message
|
|
49
48
|
readonly answerContent?: string; // restores `best`
|
|
50
|
-
readonly edits?: readonly ProposedEdit[]; // restores editsAcc (latest wins)
|
|
51
49
|
readonly error: boolean; // turnHadError → limits.observe on resume
|
|
52
50
|
readonly usage: UsageRow;
|
|
53
51
|
readonly cumulativeDurationMs: number; // limits.usage().durationMs at turn-write time
|
|
@@ -94,6 +92,8 @@ export interface PhaseRow {
|
|
|
94
92
|
readonly artifactPhase?: string;
|
|
95
93
|
readonly blockersCount?: number;
|
|
96
94
|
readonly backwardJumps?: number;
|
|
95
|
+
/** Path of the artifact this transition superseded (validate loop-back). */
|
|
96
|
+
readonly supersededPath?: string;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
export type Row = RunHeader | TurnRow | CompactionRow | TodoRow | TerminalRow | PhaseRow;
|
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;
|
package/src/text/tokens.ts
CHANGED
|
@@ -8,11 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
const CHARS_PER_TOKEN = 4;
|
|
10
10
|
|
|
11
|
+
/** Rough token count for a character length (≈4 chars/token). Always ≥ 1 for non-empty text. */
|
|
12
|
+
export function estimateTokens(charCount: number): number {
|
|
13
|
+
if (charCount <= 0) return 0;
|
|
14
|
+
return Math.ceil(charCount / CHARS_PER_TOKEN);
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
/** Rough token count for a list of role/content messages. */
|
|
12
18
|
export function estimateMessageTokens(messages: { content: string }[]): number {
|
|
13
19
|
let chars = 0;
|
|
14
20
|
for (const m of messages) chars += m.content.length + 8; // small per-message overhead
|
|
15
|
-
return
|
|
21
|
+
return estimateTokens(chars);
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
/** Total character length of a context payload (string or list of strings). */
|
package/src/tool/repl-details.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* accumulated into the subcalls array for tree rendering.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { ProposedEdit } from "../sandbox/protocol.ts";
|
|
10
9
|
import type { RlmSubcall } from "./rlm-details.ts";
|
|
11
10
|
|
|
12
11
|
export interface ReplDetails {
|
|
@@ -23,6 +22,6 @@ export interface ReplDetails {
|
|
|
23
22
|
readonly totals: { readonly costUsd: number; readonly tokens: number };
|
|
24
23
|
/** Final answer submitted through answer["ready"] without echoing it to the model. */
|
|
25
24
|
readonly finalAnswer?: string;
|
|
26
|
-
/**
|
|
27
|
-
readonly
|
|
25
|
+
/** Advisory diagnostics — surfaced to the user, never a failure. */
|
|
26
|
+
readonly warnings?: readonly string[];
|
|
28
27
|
}
|