@hicaru/pi-rlm 0.3.18 → 0.3.20
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 -21
- package/package.json +1 -1
- package/src/bridge/handlers/completion.ts +3 -0
- package/src/bridge/handlers/emitting.ts +0 -4
- package/src/bridge/handlers/rlm-query.ts +3 -3
- package/src/bridge/handlers/task-registry.ts +46 -19
- package/src/bridge/handlers/types.ts +6 -3
- package/src/bridge/model.ts +4 -0
- package/src/config/defaults.ts +16 -4
- package/src/config/settings.ts +1 -3
- package/src/context/merge.ts +10 -3
- package/src/context/namespace.ts +6 -2
- package/src/context/refresh.ts +32 -11
- package/src/core/answer.ts +15 -0
- package/src/core/budget.ts +42 -21
- package/src/core/compaction.ts +101 -15
- package/src/core/engine.ts +40 -18
- package/src/core/iteration.ts +4 -0
- package/src/core/limits.ts +13 -8
- package/src/core/root-context.ts +13 -2
- package/src/core/root-state.ts +17 -8
- package/src/core/run-state.ts +7 -2
- package/src/core/types.ts +9 -3
- package/src/index.ts +12 -2
- package/src/prompts/glossary.ts +4 -2
- package/src/prompts/user.ts +2 -1
- package/src/sandbox/sandbox.ts +13 -1
- package/src/text/parsing.ts +88 -2
- package/src/tool/repl-details.ts +2 -2
- package/src/tool/repl-tool.ts +3 -5
- package/src/tool/rlm-aggregator.ts +1 -1
- package/src/tool/rlm-details.ts +1 -2
- package/src/tool/rlm-events.ts +3 -6
- package/src/tool/rlm-tool.ts +1 -1
- package/src/tool/subcall-store.ts +5 -18
- package/src/ui/config-panel.ts +3 -18
- package/src/ui/panel/run-registry.ts +2 -2
- package/src/ui/tree/tree-model.ts +14 -7
package/src/text/parsing.ts
CHANGED
|
@@ -49,10 +49,55 @@ export type StateFenceResult =
|
|
|
49
49
|
|
|
50
50
|
const STATE_FENCE = /(`{3,})[ \t]*state[ \t]*\r?\n([\s\S]*?)\1/g;
|
|
51
51
|
|
|
52
|
+
/** Tolerant fallback: a payload object whose FIRST key is the patch key, emitted without a
|
|
53
|
+
* (well-formed) fence — soak keeps catching `...report.state {"state_patch": …}}` blobs from
|
|
54
|
+
* small models that mangle the opening backticks. Matched literally so ordinary prose or
|
|
55
|
+
* example JSON never trips the scanner. */
|
|
56
|
+
const BARE_PATCH = /\{"state_patch"\s*:/g;
|
|
57
|
+
|
|
58
|
+
/** String-aware balanced-brace scan from `start` (an index of `{`). Honors string literals and
|
|
59
|
+
* backslash escapes so braces inside JSON strings cannot unbalance the count. Returns the
|
|
60
|
+
* complete object slice, or undefined when braces never balance before EOF. */
|
|
61
|
+
function balancedJsonObject(text: string, start: number): string | undefined {
|
|
62
|
+
let depth = 0;
|
|
63
|
+
let inStr = false;
|
|
64
|
+
let esc = false;
|
|
65
|
+
for (let i = start; i < text.length; i++) {
|
|
66
|
+
const ch = text.charAt(i);
|
|
67
|
+
if (inStr) {
|
|
68
|
+
if (esc) esc = false;
|
|
69
|
+
else if (ch === "\\") esc = true;
|
|
70
|
+
else if (ch === '"') inStr = false;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (ch === '"') inStr = true;
|
|
74
|
+
else if (ch === "{") depth += 1;
|
|
75
|
+
else if (ch === "}") {
|
|
76
|
+
depth -= 1;
|
|
77
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A dangling opener/closer pair after the fence body was mangled (e.g. `.state {…}}` followed
|
|
84
|
+
* by a lone ``` line) — cosmetic residue we scrub alongside the object itself. */
|
|
85
|
+
const ORPHAN_FENCE = /^ {0,3}`{3,}[ \t]*\r?$/gm;
|
|
86
|
+
|
|
87
|
+
/** Bookkeeping transport, never content: remove well-formed ```state fences AND the bare
|
|
88
|
+
* {"state_patch"…} objects models leak when they botch the fence syntax (both directions —
|
|
89
|
+
* parse for Σ harvest, strip for user-visible answers). Order: well-formed fences out first,
|
|
90
|
+
* then the bare-object scan over the remainder can never double-count the same payload. */
|
|
91
|
+
function sansFences(text: string): string {
|
|
92
|
+
return text.replace(STATE_FENCE, "");
|
|
93
|
+
}
|
|
94
|
+
|
|
52
95
|
/**
|
|
53
96
|
* Workstream A: extract ```state fences (model-proposed ΔΣ_t) from a response, in document
|
|
54
|
-
* order. ```repl parsing is untouched — the two fences coexist in one response.
|
|
55
|
-
*
|
|
97
|
+
* order. ```repl parsing is untouched — the two fences coexist in one response. Well-formed
|
|
98
|
+
* fences yield parsed payloads or a parse error (error-as-observation for the retry ladder);
|
|
99
|
+
* malformed-fence payloads are recovered by the tolerant bare-object scanner, so a mangled
|
|
100
|
+
* opening fence never orphans a valid delta.
|
|
56
101
|
*/
|
|
57
102
|
export function findStatePatches(text: string): readonly StateFenceResult[] {
|
|
58
103
|
const out: StateFenceResult[] = [];
|
|
@@ -67,9 +112,50 @@ export function findStatePatches(text: string): readonly StateFenceResult[] {
|
|
|
67
112
|
out.push({ ok: false, error: errorMessage(err) });
|
|
68
113
|
}
|
|
69
114
|
}
|
|
115
|
+
// Tolerant harvest over fence-free remainder (well-formed payloads already taken above).
|
|
116
|
+
const rest = sansFences(text);
|
|
117
|
+
BARE_PATCH.lastIndex = 0;
|
|
118
|
+
while ((m = BARE_PATCH.exec(rest)) !== null) {
|
|
119
|
+
const obj = balancedJsonObject(rest, m.index);
|
|
120
|
+
if (obj === undefined) continue;
|
|
121
|
+
try {
|
|
122
|
+
out.push({ ok: true, value: JSON.parse(obj) as unknown });
|
|
123
|
+
} catch (err: unknown) {
|
|
124
|
+
out.push({ ok: false, error: errorMessage(err) });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
70
127
|
return out;
|
|
71
128
|
}
|
|
72
129
|
|
|
130
|
+
/** Strip ```state fences from free text. A Σ fence is bookkeeping, never content — but models
|
|
131
|
+
* that finalize right after a Σ splice tend to echo the fence verbatim as their final output,
|
|
132
|
+
* which leaked raw state JSON into RlmResult.answer (bench graders scored JSON, reports showed
|
|
133
|
+
* bookkeeping). Deterministic scrub on the answer path; parse semantics stay in findStatePatches.
|
|
134
|
+
* Also removes bare {"state_patch"…} objects (mangled-fence leaks), a `state` token glued to
|
|
135
|
+
* preceding prose, and orphan ``` lines left behind by the mangled pair. */
|
|
136
|
+
export function stripStateFences(text: string): string {
|
|
137
|
+
let out = sansFences(text);
|
|
138
|
+
const parts: string[] = [];
|
|
139
|
+
let cursor = 0;
|
|
140
|
+
BARE_PATCH.lastIndex = 0;
|
|
141
|
+
let m: RegExpExecArray | null;
|
|
142
|
+
while ((m = BARE_PATCH.exec(out)) !== null) {
|
|
143
|
+
const obj = balancedJsonObject(out, m.index);
|
|
144
|
+
if (obj === undefined) continue;
|
|
145
|
+
// Glom any immediately-preceding bare `state`/`.state` token (prose like "...report.state {").
|
|
146
|
+
const before = out.slice(cursor, m.index).replace(/\s*(?:\.?state)\s*$/i, "");
|
|
147
|
+
parts.push(before);
|
|
148
|
+
cursor = m.index + obj.length;
|
|
149
|
+
BARE_PATCH.lastIndex = cursor;
|
|
150
|
+
}
|
|
151
|
+
// No bare objects → leave the text exactly as the well-formed pass left it (a lone ```
|
|
152
|
+
// line can be a legitimate unclosed code fence in real content; only scrub residue that
|
|
153
|
+
// our own removal created).
|
|
154
|
+
if (parts.length === 0) return out.trim();
|
|
155
|
+
parts.push(out.slice(cursor));
|
|
156
|
+
return parts.join("").replace(ORPHAN_FENCE, "").trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
73
159
|
/** Truncate REPL stdout for the model's context window (head + tail, with an elision note).
|
|
74
160
|
* `mark` lets callers specialize the wording (root elision cites the session log) while the
|
|
75
161
|
* head/tail math stays the one implementation. */
|
package/src/tool/repl-details.ts
CHANGED
|
@@ -18,8 +18,8 @@ export interface ReplDetails {
|
|
|
18
18
|
readonly executionTimeMs: number;
|
|
19
19
|
/** Sub-calls triggered during this execution (llm_query, rlm_query, etc.). */
|
|
20
20
|
readonly subcalls: readonly RlmSubcall[];
|
|
21
|
-
/** Running totals for this repl() call (
|
|
22
|
-
readonly totals: { readonly
|
|
21
|
+
/** Running totals for this repl() call (tokens from sub-LLM calls). */
|
|
22
|
+
readonly totals: { readonly tokens: number };
|
|
23
23
|
/** Final answer submitted through answer["ready"] without echoing it to the model. */
|
|
24
24
|
readonly finalAnswer?: string;
|
|
25
25
|
/** Detached spawn() sub-calls still running when this call returned. Absent when none. */
|
package/src/tool/repl-tool.ts
CHANGED
|
@@ -270,7 +270,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
270
270
|
stderr: errors,
|
|
271
271
|
executionTimeMs: 0,
|
|
272
272
|
subcalls: [],
|
|
273
|
-
totals: {
|
|
273
|
+
totals: { tokens: 0 },
|
|
274
274
|
}));
|
|
275
275
|
if (!validation.ok) return validation.error;
|
|
276
276
|
const params = validation.value;
|
|
@@ -318,7 +318,7 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
318
318
|
stderr: capturedStderr,
|
|
319
319
|
executionTimeMs: Date.now() - startedAt,
|
|
320
320
|
subcalls: live.length > 0 ? [...store.getSubcalls(), ...live] : store.getSubcalls(),
|
|
321
|
-
totals: {
|
|
321
|
+
totals: { tokens: own.tokens + bg.tokens },
|
|
322
322
|
backgroundPending: background.pending > 0 ? background.pending : undefined,
|
|
323
323
|
};
|
|
324
324
|
},
|
|
@@ -400,12 +400,11 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
400
400
|
? [...store.getSubcalls(), ...adopted.subcalls]
|
|
401
401
|
: store.getSubcalls();
|
|
402
402
|
const totals = {
|
|
403
|
-
costUsd: store.getTotals().costUsd + adopted.totals.costUsd,
|
|
404
403
|
tokens: store.getTotals().tokens + adopted.totals.tokens,
|
|
405
404
|
};
|
|
406
405
|
const subUsage: Usage = {
|
|
407
406
|
input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: totals.tokens,
|
|
408
|
-
cost: { total:
|
|
407
|
+
cost: { total: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
409
408
|
};
|
|
410
409
|
onUsage?.(subUsage, "sub");
|
|
411
410
|
|
|
@@ -453,7 +452,6 @@ export function createReplTool(deps: ReplToolDeps): ToolDefinition<typeof ReplTo
|
|
|
453
452
|
executionTimeMs: 0,
|
|
454
453
|
subcalls: [...store.getSubcalls(), ...adopted.subcalls],
|
|
455
454
|
totals: {
|
|
456
|
-
costUsd: store.getTotals().costUsd + adopted.totals.costUsd,
|
|
457
455
|
tokens: store.getTotals().tokens + adopted.totals.tokens,
|
|
458
456
|
},
|
|
459
457
|
backgroundPending: background.pending > 0 ? background.pending : undefined,
|
|
@@ -55,7 +55,7 @@ export class RlmEventAggregator extends EmitterListener {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
private handleRootUsage(event: RootUsageEvent): void {
|
|
58
|
-
this.store.addRootUsage(event.
|
|
58
|
+
this.store.addRootUsage(event.tokens, event.tokensIn, event.tokensOut);
|
|
59
59
|
this.notify();
|
|
60
60
|
}
|
|
61
61
|
|
package/src/tool/rlm-details.ts
CHANGED
|
@@ -31,7 +31,6 @@ export interface RlmSubcall {
|
|
|
31
31
|
readonly resultPreview?: string;
|
|
32
32
|
readonly startedAt: number;
|
|
33
33
|
readonly endedAt?: number;
|
|
34
|
-
readonly costUsd: number;
|
|
35
34
|
readonly tokens: number;
|
|
36
35
|
/** In/out split (input / output) — mirrors tokens. */
|
|
37
36
|
readonly tokensIn: number;
|
|
@@ -49,7 +48,7 @@ export interface RlmDetails {
|
|
|
49
48
|
readonly rootPrompt: string;
|
|
50
49
|
readonly turns: { readonly current: number; readonly max: number };
|
|
51
50
|
readonly subcalls: readonly RlmSubcall[];
|
|
52
|
-
readonly totals: { readonly
|
|
51
|
+
readonly totals: { readonly tokens: number; readonly tokensIn: number; readonly tokensOut: number };
|
|
53
52
|
readonly answer?: string;
|
|
54
53
|
}
|
|
55
54
|
|
package/src/tool/rlm-events.ts
CHANGED
|
@@ -38,8 +38,6 @@ export interface SubcallUpdatedEvent {
|
|
|
38
38
|
readonly args?: string;
|
|
39
39
|
readonly resultPreview?: string;
|
|
40
40
|
/** Delta — additive on both the subcall and running totals. */
|
|
41
|
-
readonly costUsd?: number;
|
|
42
|
-
/** Delta — additive on both the subcall and running totals. */
|
|
43
41
|
readonly tokens?: number;
|
|
44
42
|
/** Deltas for the in/out split shown in the tree (input / output). Additive like tokens. */
|
|
45
43
|
readonly tokensIn?: number;
|
|
@@ -56,7 +54,6 @@ export interface TurnEvent {
|
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
export interface RootUsageEvent {
|
|
59
|
-
readonly costUsd: number;
|
|
60
57
|
readonly tokens: number;
|
|
61
58
|
readonly tokensIn?: number;
|
|
62
59
|
readonly tokensOut?: number;
|
|
@@ -108,7 +105,7 @@ export class RlmEmitter {
|
|
|
108
105
|
return id;
|
|
109
106
|
}
|
|
110
107
|
|
|
111
|
-
/** Update an existing sub-call. All fields are partial.
|
|
108
|
+
/** Update an existing sub-call. All fields are partial. tokens are additive. */
|
|
112
109
|
emitSubcallUpdated(event: SubcallUpdatedEvent): void {
|
|
113
110
|
this.ee.emit("subcall:updated", event);
|
|
114
111
|
}
|
|
@@ -119,8 +116,8 @@ export class RlmEmitter {
|
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
/** Accumulate usage directly to root-level totals. */
|
|
122
|
-
emitRootUsage(
|
|
123
|
-
this.ee.emit("root-usage", {
|
|
119
|
+
emitRootUsage(tokens: number, tokensIn?: number, tokensOut?: number): void {
|
|
120
|
+
this.ee.emit("root-usage", { tokens, tokensIn, tokensOut } satisfies RootUsageEvent);
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
/** Set the final answer text (root-only). */
|
package/src/tool/rlm-tool.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function createRlmTool(controller: RlmController, runRegistry?: RunRegist
|
|
|
51
51
|
rootPrompt: "",
|
|
52
52
|
turns: { current: 0, max: 0 },
|
|
53
53
|
subcalls: [],
|
|
54
|
-
totals: {
|
|
54
|
+
totals: { tokens: 0, tokensIn: 0, tokensOut: 0 },
|
|
55
55
|
}));
|
|
56
56
|
if (!validation.ok) return validation.error;
|
|
57
57
|
const params = validation.value;
|
|
@@ -14,9 +14,8 @@ type MutableSubcall = {
|
|
|
14
14
|
-readonly [Key in keyof RlmSubcall]: RlmSubcall[Key];
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
/** Accumulated
|
|
17
|
+
/** Accumulated tokens, shared by getTotals() and takeSettledSubtrees(). */
|
|
18
18
|
export interface SubcallTotals {
|
|
19
|
-
readonly costUsd: number;
|
|
20
19
|
readonly tokens: number;
|
|
21
20
|
/** In/out split (input / output) — mirrors tokens, shown separately in the tree. */
|
|
22
21
|
readonly tokensIn: number;
|
|
@@ -26,11 +25,9 @@ export interface SubcallTotals {
|
|
|
26
25
|
export class SubcallStore extends EmitterListener {
|
|
27
26
|
private readonly subcalls = new Map<string, MutableSubcall>();
|
|
28
27
|
|
|
29
|
-
private totalCostUsd = 0;
|
|
30
28
|
private totalTokens = 0;
|
|
31
29
|
private totalTokensIn = 0;
|
|
32
30
|
private totalTokensOut = 0;
|
|
33
|
-
private rootCostUsd = 0;
|
|
34
31
|
private rootTokens = 0;
|
|
35
32
|
private rootTokensIn = 0;
|
|
36
33
|
private rootTokensOut = 0;
|
|
@@ -57,7 +54,6 @@ export class SubcallStore extends EmitterListener {
|
|
|
57
54
|
detail: event.detail,
|
|
58
55
|
args: event.args,
|
|
59
56
|
startedAt: Date.now(),
|
|
60
|
-
costUsd: 0,
|
|
61
57
|
tokens: 0,
|
|
62
58
|
tokensIn: 0,
|
|
63
59
|
tokensOut: 0,
|
|
@@ -76,10 +72,6 @@ export class SubcallStore extends EmitterListener {
|
|
|
76
72
|
if (event.detail !== undefined) sc.detail = event.detail;
|
|
77
73
|
if (event.args !== undefined) sc.args = event.args;
|
|
78
74
|
if (event.resultPreview !== undefined) sc.resultPreview = event.resultPreview;
|
|
79
|
-
if (event.costUsd !== undefined) {
|
|
80
|
-
sc.costUsd += event.costUsd;
|
|
81
|
-
this.totalCostUsd += event.costUsd;
|
|
82
|
-
}
|
|
83
75
|
if (event.tokens !== undefined) {
|
|
84
76
|
sc.tokens += event.tokens;
|
|
85
77
|
this.totalTokens += event.tokens;
|
|
@@ -105,7 +97,7 @@ export class SubcallStore extends EmitterListener {
|
|
|
105
97
|
|
|
106
98
|
/** Snapshot running totals. O(1). */
|
|
107
99
|
getTotals(): SubcallTotals {
|
|
108
|
-
return {
|
|
100
|
+
return { tokens: this.totalTokens, tokensIn: this.totalTokensIn, tokensOut: this.totalTokensOut };
|
|
109
101
|
}
|
|
110
102
|
|
|
111
103
|
/**
|
|
@@ -141,7 +133,6 @@ export class SubcallStore extends EmitterListener {
|
|
|
141
133
|
};
|
|
142
134
|
|
|
143
135
|
const taken: RlmSubcall[] = [];
|
|
144
|
-
let costUsd = 0;
|
|
145
136
|
let tokens = 0;
|
|
146
137
|
let tokensIn = 0;
|
|
147
138
|
let tokensOut = 0;
|
|
@@ -149,7 +140,6 @@ export class SubcallStore extends EmitterListener {
|
|
|
149
140
|
const subtree = settledSubtree(root);
|
|
150
141
|
if (subtree === undefined) continue;
|
|
151
142
|
for (const node of subtree) {
|
|
152
|
-
costUsd += node.costUsd;
|
|
153
143
|
tokens += node.tokens;
|
|
154
144
|
tokensIn += node.tokensIn;
|
|
155
145
|
tokensOut += node.tokensOut;
|
|
@@ -157,22 +147,19 @@ export class SubcallStore extends EmitterListener {
|
|
|
157
147
|
this.subcalls.delete(node.id);
|
|
158
148
|
}
|
|
159
149
|
}
|
|
160
|
-
this.totalCostUsd -= costUsd;
|
|
161
150
|
this.totalTokens -= tokens;
|
|
162
151
|
this.totalTokensIn -= tokensIn;
|
|
163
152
|
this.totalTokensOut -= tokensOut;
|
|
164
|
-
return { subcalls: taken, totals: {
|
|
153
|
+
return { subcalls: taken, totals: { tokens, tokensIn, tokensOut } };
|
|
165
154
|
}
|
|
166
155
|
|
|
167
156
|
// ── Root usage (delegated from RlmEventAggregator) ──
|
|
168
157
|
|
|
169
158
|
/** Accumulate root-level usage into shared totals. Called by aggregator. */
|
|
170
|
-
addRootUsage(
|
|
171
|
-
this.totalCostUsd += costUsd;
|
|
159
|
+
addRootUsage(tokens: number, tokensIn = 0, tokensOut = 0): void {
|
|
172
160
|
this.totalTokens += tokens;
|
|
173
161
|
this.totalTokensIn += tokensIn;
|
|
174
162
|
this.totalTokensOut += tokensOut;
|
|
175
|
-
this.rootCostUsd += costUsd;
|
|
176
163
|
this.rootTokens += tokens;
|
|
177
164
|
this.rootTokensIn += tokensIn;
|
|
178
165
|
this.rootTokensOut += tokensOut;
|
|
@@ -180,6 +167,6 @@ export class SubcallStore extends EmitterListener {
|
|
|
180
167
|
|
|
181
168
|
/** Root engine's OWN spend (driver-model turns only) — never blends sub-call models. */
|
|
182
169
|
getRootUsage(): SubcallTotals {
|
|
183
|
-
return {
|
|
170
|
+
return { tokens: this.rootTokens, tokensIn: this.rootTokensIn, tokensOut: this.rootTokensOut };
|
|
184
171
|
}
|
|
185
172
|
}
|
package/src/ui/config-panel.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { THINKING_LEVELS } from "../config/settings.ts";
|
|
|
9
9
|
|
|
10
10
|
const CHOICES = Object.freeze({
|
|
11
11
|
maxDepth: Object.freeze(["1", "2", "3", "4"]),
|
|
12
|
-
maxIterations: Object.freeze(["
|
|
12
|
+
maxIterations: Object.freeze(["100", "200", "500", "1000"]),
|
|
13
13
|
execTimeoutS: Object.freeze(["30", "60", "120", "300"]),
|
|
14
14
|
maxConcurrentSubcalls: Object.freeze(["2", "4", "8", "16", "32"]),
|
|
15
15
|
maxConcurrentChildren: Object.freeze(["1", "2", "3", "4", "6", "8"]),
|
|
@@ -20,7 +20,6 @@ const CHOICES = Object.freeze({
|
|
|
20
20
|
compaction: Object.freeze(["on", "off"]),
|
|
21
21
|
compactionThresholdPct: Object.freeze(["50", "65", "80", "90"]),
|
|
22
22
|
rootSamplingMaxTokens: Object.freeze(["4096", "8192", "16384", "32768"]),
|
|
23
|
-
rootSamplingTemperature: Object.freeze(["0", "0.3", "0.7", "1.0", "default"]),
|
|
24
23
|
smartReasoning: Object.freeze(["default", ...Object.keys(THINKING_LEVELS)]),
|
|
25
24
|
subSamplingMaxTokens: Object.freeze(["1024", "2048", "4096", "8192"]),
|
|
26
25
|
subSamplingTemperature: Object.freeze(["0", "0.3", "0.7", "1.0", "default"]),
|
|
@@ -43,7 +42,7 @@ export async function showConfigPanel(ctx: ExtensionContext, config: RlmConfig):
|
|
|
43
42
|
let edited = config;
|
|
44
43
|
const items: SettingItem[] = [
|
|
45
44
|
item("maxDepth", "Max recursion depth", String(config.maxDepth), CHOICES.maxDepth, "rlm_query past this depth degrades to plain llm_query (1 = no recursion)."),
|
|
46
|
-
item("maxIterations", "Max iterations", String(config.maxIterations), CHOICES.maxIterations, "Maximum root REPL turns before RLM asks the model for a final answer."),
|
|
45
|
+
item("maxIterations", "Max iterations", String(config.maxIterations), CHOICES.maxIterations, "Maximum root REPL turns before RLM asks the model for a final answer. Large by design — runs end on FINAL/errors/wall-clock first."),
|
|
47
46
|
item("execTimeoutS", "REPL block timeout (s)", String(config.execTimeoutS), CHOICES.execTimeoutS, "Wall-clock limit for one model-authored Python REPL block."),
|
|
48
47
|
item("maxConcurrentSubcalls", "Max concurrent sub-calls", String(config.maxConcurrentSubcalls), CHOICES.maxConcurrentSubcalls, "Concurrency pool size for llm_batch and rlm_batch."),
|
|
49
48
|
item("maxConcurrentChildren", "Max concurrent children", String(config.maxConcurrentChildren), CHOICES.maxConcurrentChildren, "Concurrent rlm_query child engines per depth. Each is a Python process holding its own copy of the inherited context."),
|
|
@@ -52,10 +51,8 @@ export async function showConfigPanel(ctx: ExtensionContext, config: RlmConfig):
|
|
|
52
51
|
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."),
|
|
53
52
|
item("orchestrator", "Orchestrator addendum", config.orchestrator ? "on" : "off", CHOICES.orchestrator, "Append extra divide-and-conquer guidance to the root model system prompt."),
|
|
54
53
|
item("compaction", "Trajectory compaction", config.compaction ? "on" : "off", CHOICES.compaction, "Summarize old turns when history approaches the model context window."),
|
|
55
|
-
item("compactionThresholdPct", "Compaction threshold (%)", String(Math.round(config.compactionThresholdPct * 100)), CHOICES.compactionThresholdPct, "
|
|
54
|
+
item("compactionThresholdPct", "Compaction threshold (%)", String(Math.round(config.compactionThresholdPct * 100)), CHOICES.compactionThresholdPct, "DEPRECATED — ignored: compaction uses the absolute 256k ceiling (COMPACTION_CEILING_TOKENS)."),
|
|
56
55
|
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."),
|
|
57
|
-
item("rootSamplingTemperature", "Root sampling temperature", config.rootSampling?.temperature === undefined ? "default" : String(config.rootSampling?.temperature), CHOICES.rootSamplingTemperature,
|
|
58
|
-
"Sampling temperature for RLM root turns, finalize included — 0 = deterministic (the r3 reproducibility setting); 'default' = provider default. Applies to RLM-mode runs, rlm() delegation and child recursion; the native Pi agent loop follows Pi's own session settings."),
|
|
59
56
|
item("smartReasoning", "Root reasoning effort", config.smartReasoning ?? "default", CHOICES.smartReasoning,
|
|
60
57
|
"Thinking effort for the root model ('default' = none). Only models whose registry entry supports reasoning will think; others silently run without it. Reasoning tokens share the output cap — raise the root output cap when thinking is on."),
|
|
61
58
|
item("subSamplingMaxTokens", "Worker output cap (tok)", String(config.subSampling?.maxTokens ?? 8192), CHOICES.subSamplingMaxTokens,
|
|
@@ -68,12 +65,6 @@ export async function showConfigPanel(ctx: ExtensionContext, config: RlmConfig):
|
|
|
68
65
|
"Allow add_context() to pull an external dir, file, document, or git repo into context."),
|
|
69
66
|
item("autoSeedCwd", "Auto-seed cwd", config.autoSeedCwd ? "on" : "off", CHOICES.autoSeedCwd,
|
|
70
67
|
"Seed the working directory into context on the first repl() call (otherwise starts empty)."),
|
|
71
|
-
// R0 (/tmp/ROOT_FULL_SKILLSTATE_PLAN.md): the SKILL.state / Root Σ paradigm flags are
|
|
72
|
-
// ENFORCED — rendered as a read-only badge so the truth is visible instead of hidden.
|
|
73
|
-
// No toggle exists: applySetting has no case for them and the validator forces true.
|
|
74
|
-
item("__sigma_enforced__", "SKILL.state / Root Σ", "enforced", ["enforced"],
|
|
75
|
-
"ENFORCED (no opt-out): run state, skill state + distill, root context transform, state fences, digest compaction. " +
|
|
76
|
-
"Override attempts in rlm.json are traced (skillstate.override-ignored) and ignored; RLM_BENCH_NO_ROOTCONTEXT=1 is the dev-only measurement hatch."),
|
|
77
68
|
// R5: the window calibrations are rlm.json-only knobs — shown read-only with live values.
|
|
78
69
|
item("__sigma_window__", "Root Σ window (calibration)",
|
|
79
70
|
`keepTurns=${config.rootContextKeepTurns} · elide=${config.rootContextElideChars} · snapshot=${config.rootContextSnapshot ? "on" : "off"}`,
|
|
@@ -138,12 +129,6 @@ export function applySetting(config: RlmConfig, id: string, value: string): RlmC
|
|
|
138
129
|
case "compactionThresholdPct": return Object.freeze({ ...config, compactionThresholdPct: Number(value) / 100 });
|
|
139
130
|
case "rootSamplingMaxTokens":
|
|
140
131
|
return Object.freeze({ ...config, rootSampling: Object.freeze({ ...config.rootSampling, maxTokens: Number(value) }) });
|
|
141
|
-
case "rootSamplingTemperature": {
|
|
142
|
-
const t = optionalTemperature(value);
|
|
143
|
-
// Reject invalid values (NaN / out of range) — keep the current setting.
|
|
144
|
-
if (t === undefined && value !== "default") return config;
|
|
145
|
-
return Object.freeze({ ...config, rootSampling: Object.freeze({ ...config.rootSampling, temperature: t }) });
|
|
146
|
-
}
|
|
147
132
|
case "smartReasoning":
|
|
148
133
|
if (value === "default") return Object.freeze({ ...config, smartReasoning: undefined });
|
|
149
134
|
return Object.hasOwn(THINKING_LEVELS, value)
|
|
@@ -20,7 +20,7 @@ interface RunRegistration {
|
|
|
20
20
|
readonly label: string;
|
|
21
21
|
readonly emitter: RlmEmitter;
|
|
22
22
|
readonly subcalls: () => readonly RlmSubcall[];
|
|
23
|
-
readonly totals: () => { readonly
|
|
23
|
+
readonly totals: () => { readonly tokens: number };
|
|
24
24
|
/** Live root state; defaults: running, no phase, no turns. */
|
|
25
25
|
readonly rootStatus?: () => RlmRunStatus;
|
|
26
26
|
readonly rootPhase?: () => SubcallPhase | undefined;
|
|
@@ -40,7 +40,7 @@ export interface RunEntry {
|
|
|
40
40
|
readonly label: string;
|
|
41
41
|
readonly timeline: TimelineStore;
|
|
42
42
|
readonly subcalls: () => readonly RlmSubcall[];
|
|
43
|
-
readonly totals: () => { readonly
|
|
43
|
+
readonly totals: () => { readonly tokens: number };
|
|
44
44
|
readonly rootStatus: () => RlmRunStatus;
|
|
45
45
|
readonly rootPhase: () => SubcallPhase | undefined;
|
|
46
46
|
readonly turns: () => { readonly current: number; readonly max: number };
|
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Nothing is ever hidden: every sub-call renders as its own row (parity with
|
|
8
8
|
* pi, which shows each concurrent tool call individually) — except runs of
|
|
9
|
-
* IDENTICAL sibling leaves (same label+model+status), which
|
|
10
|
-
* expandable "label ×N" group row so a 20-item llm_batch is
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* CONSECUTIVE IDENTICAL sibling leaves (same label+model+status), which
|
|
10
|
+
* collapse into one expandable "label ×N" group row so a 20-item llm_batch is
|
|
11
|
+
* one line, not 20 and a wholesale batch failure is one `✗ label ×N` line.
|
|
12
|
+
* Errors group exactly like successes; distinct failures keep their own rows
|
|
13
|
+
* and reasons, and singletons render as plain rows. Interleaved siblings (✗ ✓
|
|
14
|
+
* ✗ with different keys between) stay in encounter order — position is never
|
|
15
|
+
* rewritten. Collapsed subtrees are skipped at the user's explicit request
|
|
16
|
+
* (chevron flips). Token rows are own-spend only — a row never blends models.
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import type { RlmSubcall, RlmRunStatus, SubcallPhase, SubcallStatus } from "../../tool/rlm-details.ts";
|
|
@@ -81,9 +84,13 @@ type Entry =
|
|
|
81
84
|
| { readonly type: "node"; readonly sc: RlmSubcall }
|
|
82
85
|
| { readonly type: "group"; readonly key: string; readonly label: string; readonly model?: string; readonly status: SubcallStatus; readonly members: RlmSubcall[] };
|
|
83
86
|
|
|
84
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Any childless llm leaf may group — errors included. groupKey pins status, so
|
|
89
|
+
* only runs of identical failures merge; per-item reasons stay in the detail
|
|
90
|
+
* modal (expand the group).
|
|
91
|
+
*/
|
|
85
92
|
const groupable = (sc: RlmSubcall, byParent: ReadonlyMap<string | undefined, RlmSubcall[]>): boolean =>
|
|
86
|
-
sc.kind === "llm" &&
|
|
93
|
+
sc.kind === "llm" && (byParent.get(sc.id)?.length ?? 0) === 0;
|
|
87
94
|
|
|
88
95
|
const groupKey = (sc: RlmSubcall): string => `${sc.label}|${sc.model ?? ""}|${sc.status}`;
|
|
89
96
|
|