@possumtech/rummy 0.5.0 → 2.0.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/.env.example +21 -5
- package/PLUGINS.md +389 -194
- package/README.md +25 -8
- package/SPEC.md +850 -373
- package/bin/demo.js +166 -0
- package/bin/rummy.js +9 -3
- package/biome/no-fallbacks.grit +50 -0
- package/lang/en.json +2 -2
- package/migrations/001_initial_schema.sql +88 -37
- package/package.json +6 -4
- package/service.js +50 -9
- package/src/agent/AgentLoop.js +460 -330
- package/src/agent/ContextAssembler.js +4 -4
- package/src/agent/Entries.js +655 -0
- package/src/agent/ProjectAgent.js +30 -18
- package/src/agent/TurnExecutor.js +229 -421
- package/src/agent/XmlParser.js +99 -33
- package/src/agent/budget.js +56 -0
- package/src/agent/errors.js +22 -0
- package/src/agent/httpStatus.js +39 -0
- package/src/agent/known_checks.sql +8 -4
- package/src/agent/known_queries.sql +9 -13
- package/src/agent/known_store.sql +275 -125
- package/src/agent/materializeContext.js +102 -0
- package/src/agent/runs.sql +10 -7
- package/src/agent/schemes.sql +14 -3
- package/src/agent/turns.sql +9 -9
- package/src/hooks/HookRegistry.js +6 -5
- package/src/hooks/Hooks.js +44 -3
- package/src/hooks/PluginContext.js +29 -21
- package/src/{server → hooks}/RpcRegistry.js +2 -1
- package/src/hooks/RummyContext.js +135 -35
- package/src/hooks/ToolRegistry.js +21 -16
- package/src/llm/LlmProvider.js +64 -90
- package/src/llm/errors.js +21 -0
- package/src/plugins/ask_user/README.md +1 -1
- package/src/plugins/ask_user/ask_user.js +37 -12
- package/src/plugins/ask_user/ask_userDoc.js +2 -25
- package/src/plugins/ask_user/ask_userDoc.md +10 -0
- package/src/plugins/budget/README.md +27 -25
- package/src/plugins/budget/budget.js +260 -88
- package/src/plugins/cp/README.md +2 -2
- package/src/plugins/cp/cp.js +29 -11
- package/src/plugins/cp/cpDoc.js +2 -15
- package/src/plugins/cp/cpDoc.md +7 -0
- package/src/plugins/engine/README.md +2 -2
- package/src/plugins/engine/engine.sql +4 -4
- package/src/plugins/engine/turn_context.sql +10 -10
- package/src/plugins/env/README.md +20 -5
- package/src/plugins/env/env.js +45 -6
- package/src/plugins/env/envDoc.js +2 -23
- package/src/plugins/env/envDoc.md +13 -0
- package/src/plugins/error/README.md +16 -0
- package/src/plugins/error/error.js +151 -0
- package/src/plugins/file/README.md +6 -6
- package/src/plugins/file/file.js +15 -2
- package/src/plugins/get/README.md +1 -1
- package/src/plugins/get/get.js +103 -48
- package/src/plugins/get/getDoc.js +2 -32
- package/src/plugins/get/getDoc.md +36 -0
- package/src/plugins/hedberg/README.md +1 -2
- package/src/plugins/hedberg/hedberg.js +8 -4
- package/src/plugins/hedberg/matcher.js +16 -17
- package/src/plugins/hedberg/normalize.js +0 -48
- package/src/plugins/helpers.js +42 -2
- package/src/plugins/index.js +146 -123
- package/src/plugins/instructions/README.md +35 -9
- package/src/plugins/instructions/instructions.js +122 -9
- package/src/plugins/instructions/instructions.md +25 -0
- package/src/plugins/instructions/instructions_104.md +7 -0
- package/src/plugins/instructions/instructions_105.md +46 -0
- package/src/plugins/instructions/instructions_106.md +0 -0
- package/src/plugins/instructions/instructions_107.md +0 -0
- package/src/plugins/instructions/instructions_108.md +8 -0
- package/src/plugins/instructions/protocol.js +12 -0
- package/src/plugins/known/README.md +2 -2
- package/src/plugins/known/known.js +67 -36
- package/src/plugins/known/knownDoc.js +2 -17
- package/src/plugins/known/knownDoc.md +8 -0
- package/src/plugins/log/README.md +48 -0
- package/src/plugins/log/log.js +109 -0
- package/src/plugins/mv/README.md +2 -2
- package/src/plugins/mv/mv.js +55 -22
- package/src/plugins/mv/mvDoc.js +2 -18
- package/src/plugins/mv/mvDoc.md +10 -0
- package/src/plugins/ollama/README.md +15 -0
- package/src/{llm/OllamaClient.js → plugins/ollama/ollama.js} +40 -18
- package/src/plugins/openai/README.md +17 -0
- package/src/plugins/openai/openai.js +120 -0
- package/src/plugins/openrouter/README.md +27 -0
- package/src/plugins/openrouter/openrouter.js +121 -0
- package/src/plugins/persona/README.md +20 -0
- package/src/plugins/persona/persona.js +9 -16
- package/src/plugins/policy/README.md +21 -0
- package/src/plugins/policy/policy.js +29 -14
- package/src/plugins/prompt/README.md +1 -1
- package/src/plugins/prompt/prompt.js +58 -16
- package/src/plugins/rm/README.md +1 -1
- package/src/plugins/rm/rm.js +56 -12
- package/src/plugins/rm/rmDoc.js +2 -20
- package/src/plugins/rm/rmDoc.md +13 -0
- package/src/plugins/rpc/README.md +2 -2
- package/src/plugins/rpc/rpc.js +515 -296
- package/src/plugins/set/README.md +1 -1
- package/src/plugins/set/set.js +318 -75
- package/src/plugins/set/setDoc.js +2 -35
- package/src/plugins/set/setDoc.md +22 -0
- package/src/plugins/sh/README.md +28 -5
- package/src/plugins/sh/sh.js +50 -6
- package/src/plugins/sh/shDoc.js +2 -23
- package/src/plugins/sh/shDoc.md +13 -0
- package/src/plugins/skill/README.md +23 -0
- package/src/plugins/skill/skill.js +14 -18
- package/src/plugins/stream/README.md +101 -0
- package/src/plugins/stream/stream.js +290 -0
- package/src/plugins/telemetry/README.md +1 -1
- package/src/plugins/telemetry/telemetry.js +129 -80
- package/src/plugins/think/README.md +1 -1
- package/src/plugins/think/think.js +12 -0
- package/src/plugins/think/thinkDoc.js +2 -15
- package/src/plugins/think/thinkDoc.md +7 -0
- package/src/plugins/unknown/README.md +3 -3
- package/src/plugins/unknown/unknown.js +47 -19
- package/src/plugins/unknown/unknownDoc.js +2 -21
- package/src/plugins/unknown/unknownDoc.md +11 -0
- package/src/plugins/update/README.md +1 -1
- package/src/plugins/update/update.js +67 -5
- package/src/plugins/update/updateDoc.js +2 -30
- package/src/plugins/update/updateDoc.md +8 -0
- package/src/plugins/xai/README.md +23 -0
- package/src/{llm/XaiClient.js → plugins/xai/xai.js} +58 -37
- package/src/server/ClientConnection.js +64 -37
- package/src/server/SocketServer.js +23 -10
- package/src/server/protocol.js +11 -0
- package/src/sql/v_model_context.sql +27 -31
- package/src/sql/v_run_log.sql +9 -14
- package/EXCEPTIONS.md +0 -46
- package/FIDELITY_CONTRACT.md +0 -172
- package/src/agent/KnownStore.js +0 -337
- package/src/agent/ResponseHealer.js +0 -241
- package/src/llm/OpenAiClient.js +0 -100
- package/src/llm/OpenRouterClient.js +0 -100
- package/src/plugins/budget/recovery.js +0 -47
- package/src/plugins/instructions/preamble.md +0 -45
- package/src/plugins/performed/README.md +0 -15
- package/src/plugins/performed/performed.js +0 -45
- package/src/plugins/previous/README.md +0 -16
- package/src/plugins/previous/previous.js +0 -56
- package/src/plugins/progress/README.md +0 -16
- package/src/plugins/progress/progress.js +0 -43
- package/src/plugins/summarize/README.md +0 -19
- package/src/plugins/summarize/summarize.js +0 -32
- package/src/plugins/summarize/summarizeDoc.js +0 -27
package/src/agent/XmlParser.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { Parser } from "htmlparser2";
|
|
2
2
|
import { parseEditContent } from "../plugins/hedberg/edits.js";
|
|
3
|
-
import {
|
|
3
|
+
import { parseJsonEdit } from "../plugins/hedberg/normalize.js";
|
|
4
4
|
import { parseSed } from "../plugins/hedberg/sed.js";
|
|
5
5
|
|
|
6
6
|
const STORE_TOOLS = new Set(["get", "rm", "set", "mv", "cp", "search"]);
|
|
7
7
|
export const ALL_TOOLS = new Set([
|
|
8
8
|
...STORE_TOOLS,
|
|
9
|
-
"known",
|
|
10
9
|
"sh",
|
|
11
10
|
"env",
|
|
12
11
|
"ask_user",
|
|
13
|
-
"summarize",
|
|
14
12
|
"update",
|
|
15
|
-
"unknown",
|
|
16
13
|
"think",
|
|
17
14
|
]);
|
|
18
15
|
|
|
@@ -20,8 +17,7 @@ export const ALL_TOOLS = new Set([
|
|
|
20
17
|
* Resolve the competing attr-vs-body philosophies per tool.
|
|
21
18
|
* If the canonical attribute is missing, the body fills it. Silent.
|
|
22
19
|
*/
|
|
23
|
-
function resolveCommand(name,
|
|
24
|
-
const a = normalizeAttrs(attrs);
|
|
20
|
+
function resolveCommand(name, a, rawBody) {
|
|
25
21
|
const trimmed = rawBody.trim();
|
|
26
22
|
|
|
27
23
|
if (name === "set") {
|
|
@@ -88,47 +84,48 @@ function resolveCommand(name, attrs, rawBody) {
|
|
|
88
84
|
preview: a.preview,
|
|
89
85
|
};
|
|
90
86
|
}
|
|
91
|
-
// Plain write or
|
|
87
|
+
// Plain write or visibility change
|
|
92
88
|
const body = trimmed || a.body || "";
|
|
93
89
|
return { name, ...a, body };
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
if (name === "
|
|
92
|
+
if (name === "update") {
|
|
97
93
|
const body = trimmed || a.body || "";
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (name === "known") {
|
|
102
|
-
const body = trimmed || a.body || "";
|
|
103
|
-
const path = a.path || null;
|
|
104
|
-
return { name, ...a, path, body };
|
|
94
|
+
const status = a.status ? Number(a.status) : 102;
|
|
95
|
+
return { name, ...a, body, status };
|
|
105
96
|
}
|
|
106
97
|
|
|
107
98
|
if (name === "get" || name === "rm") {
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
// Spread `a` so `line`, `limit`, `visibility`, and future attrs
|
|
100
|
+
// reach the handler. Earlier narrow extraction silently dropped
|
|
101
|
+
// `line=/limit=` and stranded the partial-read path advertised
|
|
102
|
+
// in getDoc.
|
|
103
|
+
return { name, ...a, path: a.path || trimmed || null };
|
|
110
104
|
}
|
|
111
105
|
|
|
112
106
|
if (name === "search") {
|
|
113
107
|
const path = a.path || trimmed || null;
|
|
114
108
|
const results = a.results ? Number(a.results) : null;
|
|
115
|
-
return { name, path, results };
|
|
109
|
+
return { name, ...a, path, results };
|
|
116
110
|
}
|
|
117
111
|
|
|
118
112
|
if (name === "mv" || name === "cp") {
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
// Spread `a` so `visibility` reaches the handler. mvDoc
|
|
114
|
+
// advertises `<mv path="known://..." visibility="summarized"/>`
|
|
115
|
+
// for batch visibility changes and was silently stripping that
|
|
116
|
+
// attr before.
|
|
117
|
+
return { name, ...a, path: a.path, to: a.to || trimmed || null };
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
if (name === "sh" || name === "env") {
|
|
124
121
|
const command = a.command || trimmed || null;
|
|
125
|
-
return { name, command };
|
|
122
|
+
return { name, ...a, command };
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
if (name === "ask_user") {
|
|
129
126
|
const question = a.question || null;
|
|
130
127
|
const options = a.options || trimmed || null;
|
|
131
|
-
return { name, question, options };
|
|
128
|
+
return { name, ...a, question, options };
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
return { name, ...a, body: trimmed || a.body };
|
|
@@ -143,7 +140,7 @@ export default class XmlParser {
|
|
|
143
140
|
* @param {string} content - Raw model response text
|
|
144
141
|
* @returns {{ commands: Array, warnings: string[], unparsed: string }}
|
|
145
142
|
*/
|
|
146
|
-
static MAX_COMMANDS = Number(process.env.RUMMY_MAX_COMMANDS)
|
|
143
|
+
static MAX_COMMANDS = Number(process.env.RUMMY_MAX_COMMANDS);
|
|
147
144
|
|
|
148
145
|
static parse(content) {
|
|
149
146
|
if (!content) return { commands: [], warnings: [], unparsed: "" };
|
|
@@ -155,10 +152,24 @@ export default class XmlParser {
|
|
|
155
152
|
const warnings = [];
|
|
156
153
|
const textChunks = [];
|
|
157
154
|
|
|
155
|
+
// Pre-flight: neutralize tool tags inside markdown code spans.
|
|
156
|
+
// Models quote instructions containing `<get/>` etc. — the parser
|
|
157
|
+
// would treat them as real tool calls. Replace the angle brackets
|
|
158
|
+
// inside backtick spans so htmlparser2 ignores them.
|
|
159
|
+
const codeNeutralized = XmlParser.#neutralizeCodeSpans(normalized);
|
|
160
|
+
|
|
161
|
+
// Pre-flight: fix mismatched close tags that htmlparser2 silently
|
|
162
|
+
// drops (making our onclosetag recovery code unreachable). Must run
|
|
163
|
+
// before balanceAttrQuotes since the mismatch scan needs clean tags.
|
|
164
|
+
const mismatchFixed = XmlParser.#correctMismatchedCloses(
|
|
165
|
+
codeNeutralized,
|
|
166
|
+
warnings,
|
|
167
|
+
);
|
|
168
|
+
|
|
158
169
|
// Pre-flight: balance unclosed attribute quotes that would otherwise
|
|
159
170
|
// cause htmlparser2 to consume the rest of input as a single attribute
|
|
160
171
|
// value, silently dropping every subsequent tool call.
|
|
161
|
-
const balanced = XmlParser.#balanceAttrQuotes(
|
|
172
|
+
const balanced = XmlParser.#balanceAttrQuotes(mismatchFixed, warnings);
|
|
162
173
|
let current = null;
|
|
163
174
|
let ended = false;
|
|
164
175
|
let capped = false;
|
|
@@ -194,9 +205,7 @@ export default class XmlParser {
|
|
|
194
205
|
const attrStr = Object.entries(attrs)
|
|
195
206
|
.map(([k, v]) => (v === "" ? k : `${k}="${v}"`))
|
|
196
207
|
.join(" ");
|
|
197
|
-
current.rawBody += attrStr
|
|
198
|
-
? `<${name} ${attrStr}>`
|
|
199
|
-
: `<${name}>`;
|
|
208
|
+
current.rawBody += attrStr ? `<${name} ${attrStr}>` : `<${name}>`;
|
|
200
209
|
current.nested ||= [];
|
|
201
210
|
current.nested.push(name);
|
|
202
211
|
return;
|
|
@@ -228,10 +237,7 @@ export default class XmlParser {
|
|
|
228
237
|
if (current) {
|
|
229
238
|
// Matching nested close — pop stack, keep as text.
|
|
230
239
|
const nested = current.nested;
|
|
231
|
-
if (
|
|
232
|
-
nested.length > 0 &&
|
|
233
|
-
nested[nested.length - 1] === name
|
|
234
|
-
) {
|
|
240
|
+
if (nested.length > 0 && nested[nested.length - 1] === name) {
|
|
235
241
|
nested.pop();
|
|
236
242
|
current.rawBody += `</${name}>`;
|
|
237
243
|
return;
|
|
@@ -337,6 +343,58 @@ export default class XmlParser {
|
|
|
337
343
|
return repaired;
|
|
338
344
|
}
|
|
339
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Correct mismatched close tags before htmlparser2 sees them.
|
|
348
|
+
*
|
|
349
|
+
* htmlparser2 silently drops close tags that don't match the currently
|
|
350
|
+
* open element (e.g. `<set>body</known>` — `</known>` vanishes). This
|
|
351
|
+
* makes the explicit mismatch recovery in onclosetag unreachable and
|
|
352
|
+
* causes all subsequent sibling commands to be absorbed as body text.
|
|
353
|
+
*
|
|
354
|
+
* Conservative: only corrects when the mismatch is at the outermost
|
|
355
|
+
* tool depth (stack.length === 1). Nested mismatches inside body text
|
|
356
|
+
* are left for htmlparser2 + body opacity to handle normally.
|
|
357
|
+
*/
|
|
358
|
+
/**
|
|
359
|
+
* Neutralize XML tags inside markdown code spans so the parser
|
|
360
|
+
* doesn't treat quoted tool names as real commands.
|
|
361
|
+
* `<get/>` → `<get/>` (htmlparser2 ignores entities)
|
|
362
|
+
*/
|
|
363
|
+
static #neutralizeCodeSpans(content) {
|
|
364
|
+
return content.replace(/`([^`]*)`/g, (match, inner) => {
|
|
365
|
+
if (!/<\/?[\w]/.test(inner)) return match;
|
|
366
|
+
return `\`${inner.replace(/</g, "<").replace(/>/g, ">")}\``;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
static #correctMismatchedCloses(content, warnings) {
|
|
371
|
+
const stack = [];
|
|
372
|
+
return content.replace(
|
|
373
|
+
/<(\/?)(\w+)([^>]*?)(\/?)>/g,
|
|
374
|
+
(match, slash, tag, _attrs, selfClose) => {
|
|
375
|
+
if (!ALL_TOOLS.has(tag)) return match;
|
|
376
|
+
if (selfClose === "/") return match;
|
|
377
|
+
if (slash === "/") {
|
|
378
|
+
if (stack.length === 0) return match;
|
|
379
|
+
if (stack[stack.length - 1] === tag) {
|
|
380
|
+
stack.pop();
|
|
381
|
+
return match;
|
|
382
|
+
}
|
|
383
|
+
if (stack.length === 1) {
|
|
384
|
+
const top = stack.pop();
|
|
385
|
+
warnings.push(
|
|
386
|
+
`Mismatched </${tag}> closing <${top}> — corrected to </${top}>`,
|
|
387
|
+
);
|
|
388
|
+
return `</${top}>`;
|
|
389
|
+
}
|
|
390
|
+
return match;
|
|
391
|
+
}
|
|
392
|
+
stack.push(tag);
|
|
393
|
+
return match;
|
|
394
|
+
},
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
340
398
|
/**
|
|
341
399
|
* Normalize native tool call formats to rummy XML.
|
|
342
400
|
* Models sometimes emit their training-format tool calls instead of
|
|
@@ -359,7 +417,7 @@ export default class XmlParser {
|
|
|
359
417
|
(match, qualifiedName, params) => {
|
|
360
418
|
const name = qualifiedName.match(/\w+$/)?.[0] ?? qualifiedName;
|
|
361
419
|
if (!ALL_TOOLS.has(name)) {
|
|
362
|
-
return `<error>Unknown
|
|
420
|
+
return `<error>Unknown command '${qualifiedName}' in <|tool_call> format. Use XML commands listed above.</error>`;
|
|
363
421
|
}
|
|
364
422
|
const valueMatch = params.match(
|
|
365
423
|
/[=:]\s*(?:<\|"\|>([^<]*?)<\|"\|>|"([^"]*)"|'([^']*)'|([^,}]+))/,
|
|
@@ -422,12 +480,20 @@ export default class XmlParser {
|
|
|
422
480
|
result = result.replace(
|
|
423
481
|
/<\|tool_call>[\s\S]*?(?:<\|?tool_call\|?>|<\/\w+>|$)/g,
|
|
424
482
|
() =>
|
|
425
|
-
"<error>Native tool call format not supported. Use the XML
|
|
483
|
+
"<error>Native tool call format not supported. Use the XML commands listed above (e.g. a get tag with a path attribute, or a set tag with path and body).</error>",
|
|
426
484
|
);
|
|
427
485
|
|
|
428
486
|
// Strip any orphan chat-format quote tokens left after replacement.
|
|
429
487
|
result = result.replace(/<\|"\|>/g, '"');
|
|
430
488
|
|
|
489
|
+
// Gemma sometimes leaks OpenAI-harmony channel markers around its
|
|
490
|
+
// real XML output: `<|channel>thought\n<channel|>…<set path=…/>`.
|
|
491
|
+
// These aren't tool calls (handled above), they're role/channel
|
|
492
|
+
// tokens. Strip any remaining `<|name>` / `<name|>` pseudo-tags
|
|
493
|
+
// before the XML parser sees them.
|
|
494
|
+
result = result.replace(/<\|[\w:/-]+>/g, "");
|
|
495
|
+
result = result.replace(/<[\w:/-]+\|>/g, "");
|
|
496
|
+
|
|
431
497
|
return result;
|
|
432
498
|
}
|
|
433
499
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { countTokens } from "./tokens.js";
|
|
2
|
+
|
|
3
|
+
const CEILING_RATIO = Number(process.env.RUMMY_BUDGET_CEILING);
|
|
4
|
+
if (!CEILING_RATIO) throw new Error("RUMMY_BUDGET_CEILING must be set");
|
|
5
|
+
|
|
6
|
+
export function ceiling(contextSize) {
|
|
7
|
+
return Math.floor(contextSize * CEILING_RATIO);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sum assembled-message token counts.
|
|
12
|
+
* Used by the budget enforce gate, which has the real messages.
|
|
13
|
+
*/
|
|
14
|
+
export function measureMessages(messages) {
|
|
15
|
+
return messages.reduce((sum, m) => sum + countTokens(m.content), 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Sum projected row body token counts — what's actually in the packet
|
|
20
|
+
* for each entry at its current visibility. Used by prompt.js while
|
|
21
|
+
* generating the <prompt> tag (before assembly completes).
|
|
22
|
+
*/
|
|
23
|
+
export function measureRows(rows) {
|
|
24
|
+
return rows.reduce((sum, r) => sum + countTokens(r.body), 0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Single source of truth for budget numbers. Every caller — prompt.js
|
|
29
|
+
* generating the <prompt> tag, budget.js enforcing the ceiling,
|
|
30
|
+
* AgentLoop emitting telemetry — passes in its own measured totalTokens
|
|
31
|
+
* and reads the same object back. No fallbacks: callers produce the
|
|
32
|
+
* measurement they have.
|
|
33
|
+
*
|
|
34
|
+
* Returns:
|
|
35
|
+
* ceiling — floor(contextSize × CEILING_RATIO), the hard wall
|
|
36
|
+
* totalTokens — echoed back (the full packet size the caller measured)
|
|
37
|
+
* tokenUsage — same as totalTokens. Kept under this name for the
|
|
38
|
+
* `<prompt tokenUsage="N">` attribute on the wire. Must
|
|
39
|
+
* agree with totalTokens so the model's math is honest.
|
|
40
|
+
* tokensFree — ceiling − totalTokens (floor 0)
|
|
41
|
+
* overflow — max(0, totalTokens − ceiling)
|
|
42
|
+
* ok — overflow === 0
|
|
43
|
+
*/
|
|
44
|
+
export function computeBudget({ contextSize, totalTokens }) {
|
|
45
|
+
const cap = ceiling(contextSize);
|
|
46
|
+
const tokensFree = Math.max(0, cap - totalTokens);
|
|
47
|
+
const overflow = Math.max(0, totalTokens - cap);
|
|
48
|
+
return {
|
|
49
|
+
ceiling: cap,
|
|
50
|
+
totalTokens,
|
|
51
|
+
tokenUsage: totalTokens,
|
|
52
|
+
tokensFree,
|
|
53
|
+
overflow,
|
|
54
|
+
ok: overflow === 0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for the agent/Entries layer. Callers catch by type,
|
|
3
|
+
* not by regex.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thrown when a writer tier isn't permitted to write to a scheme.
|
|
8
|
+
* See SPEC writer_tiers: schemes declare writable_by = subset of
|
|
9
|
+
* {system, plugin, client, model}. A write from an excluded tier
|
|
10
|
+
* rejects with this error.
|
|
11
|
+
*/
|
|
12
|
+
export class PermissionError extends Error {
|
|
13
|
+
constructor(scheme, writer, allowed) {
|
|
14
|
+
super(
|
|
15
|
+
`403: writer "${writer}" not permitted for scheme "${scheme ?? "file"}" (allowed: ${allowed.join(", ")})`,
|
|
16
|
+
);
|
|
17
|
+
this.name = "PermissionError";
|
|
18
|
+
this.scheme = scheme ?? "file";
|
|
19
|
+
this.writer = writer;
|
|
20
|
+
this.allowed = [...allowed];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map the entry-layer (state, outcome) tuple to an HTTP status number for
|
|
3
|
+
* model-facing tag rendering.
|
|
4
|
+
*
|
|
5
|
+
* Model-facing tags still carry `status="NNN"` because the model's
|
|
6
|
+
* vocabulary (instructions + tooldocs + training) is HTTP-shaped. The DB
|
|
7
|
+
* stores categorical state + textual outcome (see SPEC entries); this helper
|
|
8
|
+
* is the one-way translation for rendering.
|
|
9
|
+
*
|
|
10
|
+
* Outcome strings prefixed with a 3-digit HTTP code (e.g.
|
|
11
|
+
* `"overflow:413:..."` or `"permission:403:..."`) extract the code
|
|
12
|
+
* verbatim. Otherwise state maps to a canonical HTTP:
|
|
13
|
+
*
|
|
14
|
+
* resolved → 200
|
|
15
|
+
* proposed → 202
|
|
16
|
+
* streaming → 102
|
|
17
|
+
* cancelled → 499
|
|
18
|
+
* failed → 500 (unless outcome carries a code)
|
|
19
|
+
*/
|
|
20
|
+
export function stateToStatus(state, outcome = null) {
|
|
21
|
+
if (outcome) {
|
|
22
|
+
const match = /(\d{3})/.exec(outcome);
|
|
23
|
+
if (match) return Number(match[1]);
|
|
24
|
+
}
|
|
25
|
+
switch (state) {
|
|
26
|
+
case "resolved":
|
|
27
|
+
return 200;
|
|
28
|
+
case "proposed":
|
|
29
|
+
return 202;
|
|
30
|
+
case "streaming":
|
|
31
|
+
return 102;
|
|
32
|
+
case "cancelled":
|
|
33
|
+
return 499;
|
|
34
|
+
case "failed":
|
|
35
|
+
return 500;
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`stateToStatus: unknown state "${state}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -17,27 +17,31 @@ SELECT path, body, attributes, turn
|
|
|
17
17
|
FROM known_entries
|
|
18
18
|
WHERE
|
|
19
19
|
run_id = :run_id
|
|
20
|
-
AND
|
|
20
|
+
AND state = 'proposed';
|
|
21
21
|
|
|
22
22
|
-- PREP: has_rejections
|
|
23
|
+
-- Any failed entry in this loop counts as a rejection. Callers use
|
|
24
|
+
-- this to mark the turn as having errors. Specific failure categories
|
|
25
|
+
-- live in run_views.outcome (permission:, overflow:, validation:, ...).
|
|
23
26
|
SELECT COUNT(*) AS count
|
|
24
27
|
FROM known_entries
|
|
25
28
|
WHERE
|
|
26
29
|
run_id = :run_id
|
|
27
30
|
AND loop_id = :loop_id
|
|
28
|
-
AND
|
|
31
|
+
AND state = 'failed';
|
|
29
32
|
|
|
30
33
|
-- PREP: has_accepted_actions
|
|
31
34
|
SELECT COUNT(*) AS count
|
|
32
35
|
FROM known_entries
|
|
33
36
|
WHERE
|
|
34
37
|
run_id = :run_id
|
|
35
|
-
AND
|
|
38
|
+
AND state = 'resolved'
|
|
36
39
|
AND scheme IN ('set', 'sh', 'rm', 'mv', 'cp');
|
|
37
40
|
|
|
38
41
|
-- PREP: get_file_entries
|
|
39
|
-
SELECT path,
|
|
42
|
+
SELECT path, state, outcome, visibility, hash, updated_at
|
|
40
43
|
FROM known_entries
|
|
41
44
|
WHERE
|
|
42
45
|
run_id = :run_id
|
|
43
46
|
AND scheme IS NULL;
|
|
47
|
+
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
-- PREP: get_known_entries
|
|
2
|
-
SELECT
|
|
2
|
+
SELECT
|
|
3
|
+
path, scheme, state, outcome, visibility, body, turn, hash
|
|
4
|
+
, attributes, countTokens(body) AS tokens, scope
|
|
3
5
|
FROM known_entries
|
|
4
6
|
WHERE run_id = :run_id
|
|
5
7
|
ORDER BY path;
|
|
6
8
|
|
|
7
9
|
-- PREP: get_results
|
|
8
|
-
SELECT tool,
|
|
10
|
+
SELECT tool, state, outcome, path, body, turn, attributes
|
|
9
11
|
FROM v_run_log
|
|
10
12
|
WHERE run_id = :run_id;
|
|
11
13
|
|
|
@@ -18,7 +20,7 @@ WHERE
|
|
|
18
20
|
ORDER BY id;
|
|
19
21
|
|
|
20
22
|
-- PREP: get_turn_audit
|
|
21
|
-
SELECT path, scheme,
|
|
23
|
+
SELECT path, scheme, state, outcome, visibility, turn, body, attributes
|
|
22
24
|
FROM known_entries
|
|
23
25
|
WHERE
|
|
24
26
|
run_id = :run_id
|
|
@@ -53,24 +55,18 @@ ORDER BY id DESC
|
|
|
53
55
|
LIMIT 1;
|
|
54
56
|
|
|
55
57
|
-- PREP: get_latest_summary
|
|
58
|
+
-- Updates live in the unified log namespace at log://turn_N/update/<slug>,
|
|
59
|
+
-- not at a dedicated "update" scheme. Match path shape instead of scheme.
|
|
56
60
|
SELECT body
|
|
57
61
|
FROM known_entries
|
|
58
62
|
WHERE
|
|
59
63
|
run_id = :run_id
|
|
60
64
|
AND loop_id = :loop_id
|
|
61
|
-
AND
|
|
65
|
+
AND path LIKE 'log://turn_%/update/%'
|
|
62
66
|
ORDER BY id DESC
|
|
63
67
|
LIMIT 1;
|
|
64
68
|
|
|
65
|
-
--
|
|
66
|
-
SELECT ke.path, ke.status, ke.body, ke.attributes, ke.turn
|
|
67
|
-
FROM known_entries AS ke
|
|
68
|
-
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
69
|
-
WHERE
|
|
70
|
-
ke.run_id = :run_id
|
|
71
|
-
AND ke.scheme IS NOT NULL
|
|
72
|
-
AND s.category NOT IN ('knowledge', 'file', 'audit')
|
|
73
|
-
ORDER BY ke.id;
|
|
69
|
+
-- get_history retired — use get_results (v_run_log) for both run/state and getRun.
|
|
74
70
|
|
|
75
71
|
-- PREP: get_content
|
|
76
72
|
SELECT path, body, turn
|