@possumtech/rummy 0.2.7 → 0.3.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 +12 -3
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +454 -197
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +57 -70
- package/package.json +16 -10
- package/service.js +1 -1
- package/src/agent/AgentLoop.js +254 -70
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +156 -23
- package/src/agent/ProjectAgent.js +5 -4
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +393 -115
- package/src/agent/XmlParser.js +92 -39
- package/src/agent/known_checks.sql +5 -4
- package/src/agent/known_queries.sql +4 -3
- package/src/agent/known_store.sql +45 -15
- package/src/agent/loops.sql +63 -0
- package/src/agent/runs.sql +7 -7
- package/src/agent/schemes.sql +5 -2
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +13 -4
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -10
- package/src/hooks/RummyContext.js +30 -10
- package/src/hooks/ToolRegistry.js +83 -19
- package/src/llm/LlmProvider.js +27 -8
- package/src/llm/OpenAiClient.js +20 -0
- package/src/llm/OpenRouterClient.js +24 -2
- package/src/llm/XaiClient.js +47 -2
- package/src/plugins/ask_user/README.md +4 -4
- package/src/plugins/ask_user/ask_user.js +8 -7
- package/src/plugins/ask_user/ask_userDoc.js +29 -0
- package/src/plugins/budget/BudgetGuard.js +74 -0
- package/src/plugins/budget/README.md +43 -0
- package/src/plugins/budget/budget.js +79 -0
- package/src/plugins/cp/README.md +5 -4
- package/src/plugins/cp/cp.js +16 -12
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +12 -10
- package/src/plugins/engine/engine.sql +5 -10
- package/src/plugins/engine/turn_context.sql +13 -13
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +8 -7
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -45
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +28 -11
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/docs.md +0 -9
- package/src/plugins/hedberg/hedberg.js +4 -6
- package/src/plugins/hedberg/matcher.js +1 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +31 -33
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/index.js +93 -28
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +21 -5
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +33 -23
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +16 -12
- package/src/plugins/mv/mvDoc.js +31 -0
- package/src/plugins/persona/persona.js +78 -0
- package/src/plugins/previous/README.md +2 -2
- package/src/plugins/previous/previous.js +12 -8
- package/src/plugins/progress/progress.js +44 -12
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +23 -19
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +29 -12
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +63 -107
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +82 -21
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +8 -7
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +12 -54
- package/src/plugins/summarize/README.md +6 -5
- package/src/plugins/summarize/summarize.js +7 -6
- package/src/plugins/summarize/summarizeDoc.js +33 -0
- package/src/plugins/telemetry/telemetry.js +20 -8
- package/src/plugins/think/README.md +20 -0
- package/src/plugins/think/think.js +5 -0
- package/src/plugins/unknown/README.md +5 -5
- package/src/plugins/unknown/unknown.js +11 -8
- package/src/plugins/unknown/unknownDoc.js +31 -0
- package/src/plugins/update/README.md +3 -8
- package/src/plugins/update/update.js +7 -6
- package/src/plugins/update/updateDoc.js +33 -0
- package/src/server/ClientConnection.js +3 -5
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +31 -39
- package/src/sql/v_run_log.sql +3 -3
- package/src/agent/prompt_queue.sql +0 -39
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -2
- package/src/plugins/get/docs.md +0 -6
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -4
- package/src/plugins/set/docs.md +0 -4
- package/src/plugins/sh/docs.md +0 -2
- package/src/plugins/skills/README.md +0 -25
- package/src/plugins/store/README.md +0 -20
- package/src/plugins/store/docs.md +0 -5
- package/src/plugins/store/store.js +0 -52
- package/src/plugins/summarize/docs.md +0 -4
- package/src/plugins/unknown/docs.md +0 -5
- package/src/plugins/update/docs.md +0 -4
package/src/agent/XmlParser.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { Parser } from "htmlparser2";
|
|
2
2
|
import { parseEditContent } from "../plugins/hedberg/edits.js";
|
|
3
|
-
import { normalizeAttrs } from "../plugins/hedberg/normalize.js";
|
|
3
|
+
import { normalizeAttrs, parseJsonEdit } from "../plugins/hedberg/normalize.js";
|
|
4
4
|
import { parseSed } from "../plugins/hedberg/sed.js";
|
|
5
5
|
|
|
6
|
-
const STORE_TOOLS = new Set([
|
|
7
|
-
"get",
|
|
8
|
-
"store",
|
|
9
|
-
"rm",
|
|
10
|
-
"set",
|
|
11
|
-
"mv",
|
|
12
|
-
"cp",
|
|
13
|
-
"search",
|
|
14
|
-
]);
|
|
6
|
+
const STORE_TOOLS = new Set(["get", "rm", "set", "mv", "cp", "search"]);
|
|
15
7
|
const ALL_TOOLS = new Set([
|
|
16
8
|
...STORE_TOOLS,
|
|
17
9
|
"known",
|
|
@@ -51,31 +43,10 @@ function resolveCommand(name, attrs, rawBody) {
|
|
|
51
43
|
};
|
|
52
44
|
}
|
|
53
45
|
}
|
|
54
|
-
// JSON-style { search, replace }
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const json = JSON.parse(trimmed);
|
|
60
|
-
search = json.search;
|
|
61
|
-
replace = json.replace ?? "";
|
|
62
|
-
} catch {
|
|
63
|
-
// Try = style: { search="old", replace="new" }
|
|
64
|
-
const searchMatch = trimmed.match(/search\s*=\s*"([^"]*)"/);
|
|
65
|
-
const replaceMatch = trimmed.match(/replace\s*=\s*"([^"]*)"/);
|
|
66
|
-
if (searchMatch) {
|
|
67
|
-
search = searchMatch[1];
|
|
68
|
-
replace = replaceMatch?.[1] ?? "";
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (search != null) {
|
|
72
|
-
return {
|
|
73
|
-
name,
|
|
74
|
-
path: a.path,
|
|
75
|
-
search,
|
|
76
|
-
replace,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
46
|
+
// JSON-style { search, replace }
|
|
47
|
+
const jsonEdit = parseJsonEdit(trimmed);
|
|
48
|
+
if (jsonEdit) {
|
|
49
|
+
return { name, path: a.path, ...jsonEdit };
|
|
79
50
|
}
|
|
80
51
|
// Sed syntax: s/search/replace/flags — supports chained commands
|
|
81
52
|
if (trimmed.startsWith("s/")) {
|
|
@@ -116,9 +87,9 @@ function resolveCommand(name, attrs, rawBody) {
|
|
|
116
87
|
preview: a.preview,
|
|
117
88
|
};
|
|
118
89
|
}
|
|
119
|
-
// Plain write
|
|
90
|
+
// Plain write or fidelity change
|
|
120
91
|
const body = trimmed || a.body || "";
|
|
121
|
-
return { name,
|
|
92
|
+
return { name, ...a, body };
|
|
122
93
|
}
|
|
123
94
|
|
|
124
95
|
if (name === "summarize" || name === "update" || name === "unknown") {
|
|
@@ -132,7 +103,7 @@ function resolveCommand(name, attrs, rawBody) {
|
|
|
132
103
|
return { name, path, body };
|
|
133
104
|
}
|
|
134
105
|
|
|
135
|
-
if (name === "get" || name === "
|
|
106
|
+
if (name === "get" || name === "rm") {
|
|
136
107
|
const path = a.path || trimmed || null;
|
|
137
108
|
return { name, path, body: a.body, preview: a.preview };
|
|
138
109
|
}
|
|
@@ -174,6 +145,9 @@ export default class XmlParser {
|
|
|
174
145
|
static parse(content) {
|
|
175
146
|
if (!content) return { commands: [], warnings: [], unparsed: "" };
|
|
176
147
|
|
|
148
|
+
// Normalize native tool call formats to rummy XML
|
|
149
|
+
const normalized = XmlParser.#normalizeToolCalls(content);
|
|
150
|
+
|
|
177
151
|
const commands = [];
|
|
178
152
|
const warnings = [];
|
|
179
153
|
const textChunks = [];
|
|
@@ -190,6 +164,16 @@ export default class XmlParser {
|
|
|
190
164
|
return;
|
|
191
165
|
}
|
|
192
166
|
|
|
167
|
+
// Known tool opened while another is still open — close the old one.
|
|
168
|
+
if (current) {
|
|
169
|
+
warnings.push(
|
|
170
|
+
`Unclosed <${current.name}> before <${name}> — recovered`,
|
|
171
|
+
);
|
|
172
|
+
commands.push(
|
|
173
|
+
resolveCommand(current.name, current.attrs, current.rawBody),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
193
177
|
current = { name, attrs, rawBody: "" };
|
|
194
178
|
},
|
|
195
179
|
|
|
@@ -210,6 +194,16 @@ export default class XmlParser {
|
|
|
210
194
|
resolveCommand(current.name, current.attrs, current.rawBody),
|
|
211
195
|
);
|
|
212
196
|
current = null;
|
|
197
|
+
} else if (current && ALL_TOOLS.has(name)) {
|
|
198
|
+
// Mismatched close tag for a known tool — close current tag,
|
|
199
|
+
// don't swallow subsequent commands as body text.
|
|
200
|
+
warnings.push(
|
|
201
|
+
`Mismatched </${name}> closing <${current.name}> — recovered`,
|
|
202
|
+
);
|
|
203
|
+
commands.push(
|
|
204
|
+
resolveCommand(current.name, current.attrs, current.rawBody),
|
|
205
|
+
);
|
|
206
|
+
current = null;
|
|
213
207
|
} else if (current) {
|
|
214
208
|
current.rawBody += `</${name}>`;
|
|
215
209
|
} else if (isImplied && ALL_TOOLS.has(name)) {
|
|
@@ -228,7 +222,7 @@ export default class XmlParser {
|
|
|
228
222
|
},
|
|
229
223
|
);
|
|
230
224
|
|
|
231
|
-
parser.write(
|
|
225
|
+
parser.write(normalized);
|
|
232
226
|
ended = true;
|
|
233
227
|
parser.end();
|
|
234
228
|
|
|
@@ -244,4 +238,63 @@ export default class XmlParser {
|
|
|
244
238
|
const unparsed = textChunks.join("").trim();
|
|
245
239
|
return { commands, warnings, unparsed };
|
|
246
240
|
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Normalize native tool call formats to rummy XML.
|
|
244
|
+
* Models sometimes emit their training-format tool calls instead of
|
|
245
|
+
* our XML tags. The intent is unambiguous — translate silently.
|
|
246
|
+
*/
|
|
247
|
+
static #normalizeToolCalls(content) {
|
|
248
|
+
// Gemma: ```tool_code\n<xml>...\n``` — strip code fences around valid XML
|
|
249
|
+
let result = content.replace(
|
|
250
|
+
/```(?:tool_code|tool_command|xml)\n([\s\S]*?)```/g,
|
|
251
|
+
(_, inner) => inner.trim(),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Qwen/gemma: <|tool_call>call:NAME{key:"value"}<tool_call|>
|
|
255
|
+
result = result.replace(
|
|
256
|
+
/<\|tool_call>call:(\w+)\{([^}]*)\}<(?:tool_call\||\|tool_call)>/g,
|
|
257
|
+
(_, name, params) => {
|
|
258
|
+
if (!ALL_TOOLS.has(name)) return _;
|
|
259
|
+
const valueMatch = params.match(/["']([^"']+)["']/);
|
|
260
|
+
const body = valueMatch?.[1] || "";
|
|
261
|
+
return `<${name}>${body}</${name}>`;
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
// OpenAI function_call JSON: {"name":"search","arguments":{"query":"..."}}
|
|
266
|
+
result = result.replace(
|
|
267
|
+
/\{"name"\s*:\s*"(\w+)"\s*,\s*"arguments"\s*:\s*\{([^}]*)\}\}/g,
|
|
268
|
+
(_, name, args) => {
|
|
269
|
+
if (!ALL_TOOLS.has(name)) return _;
|
|
270
|
+
const pairs = [...args.matchAll(/"(\w+)"\s*:\s*"([^"]*)"/g)];
|
|
271
|
+
const body = pairs[0]?.[2] || "";
|
|
272
|
+
return `<${name}>${body}</${name}>`;
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Anthropic: <tool_use><name>search</name><input>{"query":"..."}</input></tool_use>
|
|
277
|
+
result = result.replace(
|
|
278
|
+
/<tool_use>\s*<name>(\w+)<\/name>\s*<input>\{([^}]*)\}<\/input>\s*<\/tool_use>/g,
|
|
279
|
+
(_, name, args) => {
|
|
280
|
+
if (!ALL_TOOLS.has(name)) return _;
|
|
281
|
+
const pairs = [...args.matchAll(/"(\w+)"\s*:\s*"([^"]*)"/g)];
|
|
282
|
+
const body = pairs[0]?.[2] || "";
|
|
283
|
+
return `<${name}>${body}</${name}>`;
|
|
284
|
+
},
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Mistral: [TOOL_CALLS] [{"name":"search","arguments":{"query":"..."}}]
|
|
288
|
+
result = result.replace(
|
|
289
|
+
/\[TOOL_CALLS\]\s*\[\{"name"\s*:\s*"(\w+)"\s*,\s*"arguments"\s*:\s*\{([^}]*)\}\}\]/g,
|
|
290
|
+
(_, name, args) => {
|
|
291
|
+
if (!ALL_TOOLS.has(name)) return _;
|
|
292
|
+
const pairs = [...args.matchAll(/"(\w+)"\s*:\s*"([^"]*)"/g)];
|
|
293
|
+
const body = pairs[0]?.[2] || "";
|
|
294
|
+
return `<${name}>${body}</${name}>`;
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
247
300
|
}
|
|
@@ -17,25 +17,26 @@ SELECT path, body, attributes, turn
|
|
|
17
17
|
FROM known_entries
|
|
18
18
|
WHERE
|
|
19
19
|
run_id = :run_id
|
|
20
|
-
AND
|
|
20
|
+
AND status = 202;
|
|
21
21
|
|
|
22
22
|
-- PREP: has_rejections
|
|
23
23
|
SELECT COUNT(*) AS count
|
|
24
24
|
FROM known_entries
|
|
25
25
|
WHERE
|
|
26
26
|
run_id = :run_id
|
|
27
|
-
AND
|
|
27
|
+
AND loop_id = :loop_id
|
|
28
|
+
AND status = 403;
|
|
28
29
|
|
|
29
30
|
-- PREP: has_accepted_actions
|
|
30
31
|
SELECT COUNT(*) AS count
|
|
31
32
|
FROM known_entries
|
|
32
33
|
WHERE
|
|
33
34
|
run_id = :run_id
|
|
34
|
-
AND
|
|
35
|
+
AND status = 200
|
|
35
36
|
AND scheme IN ('set', 'sh', 'rm', 'mv', 'cp');
|
|
36
37
|
|
|
37
38
|
-- PREP: get_file_entries
|
|
38
|
-
SELECT path,
|
|
39
|
+
SELECT path, status, fidelity, hash, updated_at
|
|
39
40
|
FROM known_entries
|
|
40
41
|
WHERE
|
|
41
42
|
run_id = :run_id
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
-- PREP: get_known_entries
|
|
2
|
-
SELECT path, scheme,
|
|
2
|
+
SELECT path, scheme, status, fidelity, body, turn, hash, attributes
|
|
3
3
|
FROM known_entries
|
|
4
4
|
WHERE run_id = :run_id
|
|
5
5
|
ORDER BY path;
|
|
@@ -18,7 +18,7 @@ WHERE
|
|
|
18
18
|
ORDER BY id;
|
|
19
19
|
|
|
20
20
|
-- PREP: get_turn_audit
|
|
21
|
-
SELECT path, scheme,
|
|
21
|
+
SELECT path, scheme, status, fidelity, turn, body, attributes
|
|
22
22
|
FROM known_entries
|
|
23
23
|
WHERE
|
|
24
24
|
run_id = :run_id
|
|
@@ -57,12 +57,13 @@ SELECT body
|
|
|
57
57
|
FROM known_entries
|
|
58
58
|
WHERE
|
|
59
59
|
run_id = :run_id
|
|
60
|
+
AND loop_id = :loop_id
|
|
60
61
|
AND scheme = 'summarize'
|
|
61
62
|
ORDER BY id DESC
|
|
62
63
|
LIMIT 1;
|
|
63
64
|
|
|
64
65
|
-- PREP: get_history
|
|
65
|
-
SELECT ke.path, ke.
|
|
66
|
+
SELECT ke.path, ke.status, ke.body, ke.attributes, ke.turn
|
|
66
67
|
FROM known_entries AS ke
|
|
67
68
|
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
68
69
|
WHERE
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
-- PREP: upsert_known_entry
|
|
2
2
|
INSERT INTO known_entries (
|
|
3
|
-
run_id, turn, path, body,
|
|
4
|
-
, tokens, tokens_full, updated_at
|
|
3
|
+
run_id, loop_id, turn, path, body, status, fidelity, hash
|
|
4
|
+
, attributes, tokens, tokens_full, updated_at
|
|
5
5
|
)
|
|
6
6
|
VALUES (
|
|
7
|
-
:run_id, :turn, :path, :body, :
|
|
7
|
+
:run_id, :loop_id, :turn, :path, :body, :status, :fidelity, :hash
|
|
8
|
+
, COALESCE(:attributes, '{}')
|
|
8
9
|
, countTokens(:body)
|
|
9
10
|
, countTokens(:body)
|
|
10
11
|
, COALESCE(:updated_at, CURRENT_TIMESTAMP)
|
|
11
12
|
)
|
|
12
13
|
ON CONFLICT (run_id, path) DO UPDATE SET
|
|
13
14
|
body = excluded.body
|
|
14
|
-
,
|
|
15
|
+
, status = excluded.status
|
|
16
|
+
, fidelity = excluded.fidelity
|
|
15
17
|
, hash = COALESCE(excluded.hash, known_entries.hash)
|
|
16
18
|
, attributes = COALESCE(excluded.attributes, known_entries.attributes)
|
|
19
|
+
, loop_id = excluded.loop_id
|
|
17
20
|
, turn = excluded.turn
|
|
18
21
|
, tokens = countTokens(excluded.body)
|
|
19
22
|
, tokens_full = countTokens(excluded.body)
|
|
@@ -43,17 +46,25 @@ WHERE run_id = :run_id AND hedmatch(:pattern, path) AND scheme IS NULL;
|
|
|
43
46
|
-- PREP: resolve_known_entry
|
|
44
47
|
UPDATE known_entries
|
|
45
48
|
SET
|
|
46
|
-
|
|
49
|
+
status = :status
|
|
47
50
|
, body = :body
|
|
48
51
|
, updated_at = CURRENT_TIMESTAMP
|
|
49
52
|
WHERE run_id = :run_id AND path = :path;
|
|
50
53
|
|
|
51
|
-
-- PREP:
|
|
54
|
+
-- PREP: set_file_fidelity
|
|
52
55
|
UPDATE known_entries
|
|
53
56
|
SET
|
|
54
|
-
|
|
57
|
+
fidelity = :fidelity
|
|
55
58
|
, tokens = CASE
|
|
56
|
-
WHEN :
|
|
59
|
+
WHEN :fidelity = 'archive'
|
|
60
|
+
THEN 0
|
|
61
|
+
WHEN :fidelity = 'index'
|
|
62
|
+
THEN 0
|
|
63
|
+
WHEN :fidelity = 'summary'
|
|
64
|
+
THEN COALESCE(
|
|
65
|
+
countTokens(json_extract(attributes, '$.summary')),
|
|
66
|
+
countTokens(substr(body, 1, 80))
|
|
67
|
+
)
|
|
57
68
|
ELSE tokens_full
|
|
58
69
|
END
|
|
59
70
|
, updated_at = CURRENT_TIMESTAMP
|
|
@@ -62,7 +73,7 @@ WHERE run_id = :run_id AND hedmatch(:pattern, path) AND scheme IS NULL;
|
|
|
62
73
|
-- PREP: promote_path
|
|
63
74
|
UPDATE known_entries
|
|
64
75
|
SET
|
|
65
|
-
|
|
76
|
+
fidelity = 'full'
|
|
66
77
|
, turn = :turn
|
|
67
78
|
, tokens = tokens_full
|
|
68
79
|
, updated_at = CURRENT_TIMESTAMP
|
|
@@ -71,23 +82,42 @@ WHERE run_id = :run_id AND path = :path;
|
|
|
71
82
|
-- PREP: demote_path
|
|
72
83
|
UPDATE known_entries
|
|
73
84
|
SET
|
|
74
|
-
|
|
85
|
+
fidelity = 'archive'
|
|
75
86
|
, tokens = 0
|
|
76
87
|
, updated_at = CURRENT_TIMESTAMP
|
|
77
88
|
WHERE run_id = :run_id AND path = :path;
|
|
78
89
|
|
|
90
|
+
-- PREP: set_fidelity
|
|
91
|
+
UPDATE known_entries
|
|
92
|
+
SET
|
|
93
|
+
fidelity = :fidelity
|
|
94
|
+
, tokens = CASE
|
|
95
|
+
WHEN :fidelity = 'archive'
|
|
96
|
+
THEN 0
|
|
97
|
+
WHEN :fidelity = 'index'
|
|
98
|
+
THEN 0
|
|
99
|
+
WHEN :fidelity = 'summary'
|
|
100
|
+
THEN COALESCE(
|
|
101
|
+
countTokens(json_extract(attributes, '$.summary')),
|
|
102
|
+
countTokens(substr(body, 1, 80))
|
|
103
|
+
)
|
|
104
|
+
ELSE countTokens(body)
|
|
105
|
+
END
|
|
106
|
+
, updated_at = CURRENT_TIMESTAMP
|
|
107
|
+
WHERE run_id = :run_id AND path = :path;
|
|
108
|
+
|
|
79
109
|
-- PREP: get_entry_body
|
|
80
110
|
SELECT body
|
|
81
111
|
FROM known_entries
|
|
82
112
|
WHERE run_id = :run_id AND path = :path;
|
|
83
113
|
|
|
84
114
|
-- PREP: get_entry_state
|
|
85
|
-
SELECT
|
|
115
|
+
SELECT status, fidelity, scheme, turn
|
|
86
116
|
FROM known_entries
|
|
87
117
|
WHERE run_id = :run_id AND path = :path;
|
|
88
118
|
|
|
89
119
|
-- PREP: get_file_states_by_pattern
|
|
90
|
-
SELECT path,
|
|
120
|
+
SELECT path, status, fidelity, turn
|
|
91
121
|
FROM known_entries
|
|
92
122
|
WHERE run_id = :run_id AND hedmatch(:pattern, path) AND scheme IS NULL
|
|
93
123
|
ORDER BY path;
|
|
@@ -107,7 +137,7 @@ WHERE run_id = :run_id AND path = :path;
|
|
|
107
137
|
-- PREP: promote_by_pattern
|
|
108
138
|
UPDATE known_entries
|
|
109
139
|
SET
|
|
110
|
-
|
|
140
|
+
fidelity = 'full'
|
|
111
141
|
, turn = :turn
|
|
112
142
|
, tokens = tokens_full
|
|
113
143
|
, updated_at = CURRENT_TIMESTAMP
|
|
@@ -119,7 +149,7 @@ WHERE
|
|
|
119
149
|
-- PREP: demote_by_pattern
|
|
120
150
|
UPDATE known_entries
|
|
121
151
|
SET
|
|
122
|
-
|
|
152
|
+
fidelity = 'archive'
|
|
123
153
|
, tokens = 0
|
|
124
154
|
, updated_at = CURRENT_TIMESTAMP
|
|
125
155
|
WHERE
|
|
@@ -128,7 +158,7 @@ WHERE
|
|
|
128
158
|
AND (:body IS NULL OR hedsearch(:body, body));
|
|
129
159
|
|
|
130
160
|
-- PREP: get_entries_by_pattern
|
|
131
|
-
SELECT path, body, scheme,
|
|
161
|
+
SELECT path, body, scheme, status, fidelity, tokens_full, attributes
|
|
132
162
|
FROM known_entries
|
|
133
163
|
WHERE
|
|
134
164
|
run_id = :run_id
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
-- PREP: enqueue_loop
|
|
2
|
+
INSERT INTO loops (run_id, sequence, mode, model, prompt, config)
|
|
3
|
+
VALUES (:run_id, :sequence, :mode, :model, :prompt, :config)
|
|
4
|
+
RETURNING id, sequence;
|
|
5
|
+
|
|
6
|
+
-- PREP: next_loop
|
|
7
|
+
UPDATE runs
|
|
8
|
+
SET next_loop = next_loop + 1
|
|
9
|
+
WHERE id = :run_id
|
|
10
|
+
RETURNING next_loop - 1 AS sequence;
|
|
11
|
+
|
|
12
|
+
-- PREP: claim_next_loop
|
|
13
|
+
UPDATE loops
|
|
14
|
+
SET status = 102
|
|
15
|
+
WHERE
|
|
16
|
+
id = (
|
|
17
|
+
SELECT
|
|
18
|
+
id
|
|
19
|
+
FROM loops
|
|
20
|
+
WHERE run_id = :run_id AND status = 100
|
|
21
|
+
ORDER BY id
|
|
22
|
+
LIMIT 1
|
|
23
|
+
)
|
|
24
|
+
RETURNING id, run_id, sequence, mode, model, prompt, config;
|
|
25
|
+
|
|
26
|
+
-- PREP: complete_loop
|
|
27
|
+
UPDATE loops
|
|
28
|
+
SET status = :status, result = :result
|
|
29
|
+
WHERE id = :id;
|
|
30
|
+
|
|
31
|
+
-- PREP: abort_active_loop
|
|
32
|
+
UPDATE loops
|
|
33
|
+
SET status = 499
|
|
34
|
+
WHERE run_id = :run_id AND status = 102;
|
|
35
|
+
|
|
36
|
+
-- PREP: get_pending_loops
|
|
37
|
+
SELECT id, sequence, mode, model, prompt, status, created_at
|
|
38
|
+
FROM loops
|
|
39
|
+
WHERE run_id = :run_id AND status IN (100, 102)
|
|
40
|
+
ORDER BY id;
|
|
41
|
+
|
|
42
|
+
-- PREP: reset_active_loops
|
|
43
|
+
UPDATE loops
|
|
44
|
+
SET status = 100
|
|
45
|
+
WHERE status = 102;
|
|
46
|
+
|
|
47
|
+
-- PREP: get_current_loop
|
|
48
|
+
SELECT id, sequence, mode, model, prompt, status
|
|
49
|
+
FROM loops
|
|
50
|
+
WHERE run_id = :run_id AND status = 102
|
|
51
|
+
LIMIT 1;
|
|
52
|
+
|
|
53
|
+
-- PREP: get_loop_by_id
|
|
54
|
+
SELECT id, run_id, sequence, mode, model, prompt, status, config
|
|
55
|
+
FROM loops
|
|
56
|
+
WHERE id = :id;
|
|
57
|
+
|
|
58
|
+
-- PREP: get_latest_completed_loop
|
|
59
|
+
SELECT id, sequence, mode, status
|
|
60
|
+
FROM loops
|
|
61
|
+
WHERE run_id = :run_id AND status IN (200, 500)
|
|
62
|
+
ORDER BY id DESC
|
|
63
|
+
LIMIT 1;
|
package/src/agent/runs.sql
CHANGED
|
@@ -22,14 +22,14 @@ RETURNING id;
|
|
|
22
22
|
-- PREP: get_run_by_alias
|
|
23
23
|
SELECT
|
|
24
24
|
id, project_id, parent_run_id, model, status, alias
|
|
25
|
-
, temperature, persona, context_limit, next_turn, created_at
|
|
25
|
+
, temperature, persona, context_limit, next_turn, next_loop, created_at
|
|
26
26
|
FROM runs
|
|
27
27
|
WHERE alias = :alias;
|
|
28
28
|
|
|
29
29
|
-- PREP: get_run_by_id
|
|
30
30
|
SELECT
|
|
31
31
|
id, project_id, parent_run_id, model, status, alias
|
|
32
|
-
, temperature, persona, context_limit, next_turn, created_at
|
|
32
|
+
, temperature, persona, context_limit, next_turn, next_loop, created_at
|
|
33
33
|
FROM runs
|
|
34
34
|
WHERE id = :id;
|
|
35
35
|
|
|
@@ -80,11 +80,11 @@ RETURNING next_turn - 1 AS turn;
|
|
|
80
80
|
|
|
81
81
|
-- PREP: fork_known_entries
|
|
82
82
|
INSERT INTO known_entries (
|
|
83
|
-
run_id, turn, path, body,
|
|
83
|
+
run_id, loop_id, turn, path, body, status, fidelity
|
|
84
84
|
, hash, attributes, tokens, tokens_full, refs, write_count
|
|
85
85
|
)
|
|
86
86
|
SELECT
|
|
87
|
-
:new_run_id, turn, path, body,
|
|
87
|
+
:new_run_id, NULL, turn, path, body, status, fidelity
|
|
88
88
|
, hash, attributes, tokens, tokens_full, refs, write_count
|
|
89
89
|
FROM known_entries
|
|
90
90
|
WHERE run_id = :parent_run_id;
|
|
@@ -94,7 +94,7 @@ SELECT r.id
|
|
|
94
94
|
FROM runs AS r
|
|
95
95
|
WHERE
|
|
96
96
|
r.project_id = :project_id
|
|
97
|
-
AND r.status IN (
|
|
97
|
+
AND r.status IN (100, 102, 202);
|
|
98
98
|
|
|
99
99
|
-- PREP: get_latest_run
|
|
100
100
|
SELECT r.id
|
|
@@ -110,5 +110,5 @@ WHERE r.project_id = :project_id;
|
|
|
110
110
|
|
|
111
111
|
-- PREP: abort_stuck_runs
|
|
112
112
|
UPDATE runs
|
|
113
|
-
SET status =
|
|
114
|
-
WHERE status IN (
|
|
113
|
+
SET status = 499
|
|
114
|
+
WHERE status IN (100, 102);
|
package/src/agent/schemes.sql
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
-- PREP: upsert_scheme
|
|
2
|
-
INSERT OR REPLACE INTO schemes (name,
|
|
3
|
-
VALUES (:name, :
|
|
2
|
+
INSERT OR REPLACE INTO schemes (name, model_visible, category)
|
|
3
|
+
VALUES (:name, :model_visible, :category);
|
|
4
|
+
|
|
5
|
+
-- PREP: get_all_schemes
|
|
6
|
+
SELECT name, model_visible, category FROM schemes;
|
package/src/agent/tokens.js
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Token estimation. Conservative character-based approximation.
|
|
3
|
+
* RUMMY_TOKEN_DIVISOR controls characters per token.
|
|
4
|
+
* No external dependencies. The budget contract is exact.
|
|
5
|
+
* contextSize is the ceiling. countTokens is the measurement.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
const tiktoken = await import("tiktoken");
|
|
12
|
-
encoder = tiktoken.get_encoding("o200k_base");
|
|
13
|
-
} catch {
|
|
14
|
-
// tiktoken unavailable — use character-based estimate
|
|
15
|
-
}
|
|
8
|
+
const DIVISOR = Number(process.env.RUMMY_TOKEN_DIVISOR);
|
|
16
9
|
|
|
17
10
|
export function countTokens(text) {
|
|
18
11
|
if (!text) return 0;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const tokens = encoder.encode(text);
|
|
22
|
-
return tokens.length;
|
|
23
|
-
} catch {
|
|
24
|
-
// Fallback on encoding error
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return Math.ceil(text.length / 4);
|
|
12
|
+
return Math.ceil(text.length / DIVISOR);
|
|
28
13
|
}
|
package/src/agent/turns.sql
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
-- PREP: create_turn
|
|
2
|
-
INSERT INTO turns (run_id, sequence)
|
|
3
|
-
VALUES (:run_id, :sequence)
|
|
2
|
+
INSERT INTO turns (run_id, loop_id, sequence)
|
|
3
|
+
VALUES (:run_id, :loop_id, :sequence)
|
|
4
4
|
RETURNING id, sequence;
|
|
5
5
|
|
|
6
6
|
-- PREP: update_turn_stats
|
|
7
7
|
UPDATE turns
|
|
8
8
|
SET
|
|
9
|
-
|
|
9
|
+
context_tokens = :context_tokens
|
|
10
|
+
, reasoning_content = :reasoning_content
|
|
11
|
+
, prompt_tokens = :prompt_tokens
|
|
10
12
|
, cached_tokens = :cached_tokens
|
|
11
13
|
, completion_tokens = :completion_tokens
|
|
12
14
|
, reasoning_tokens = :reasoning_tokens
|
|
@@ -25,8 +27,15 @@ SELECT
|
|
|
25
27
|
FROM turns
|
|
26
28
|
WHERE run_id = :run_id;
|
|
27
29
|
|
|
30
|
+
-- PREP: get_last_context_tokens
|
|
31
|
+
SELECT context_tokens
|
|
32
|
+
FROM turns
|
|
33
|
+
WHERE run_id = :run_id AND context_tokens > 0
|
|
34
|
+
ORDER BY sequence DESC
|
|
35
|
+
LIMIT 1;
|
|
36
|
+
|
|
28
37
|
-- PREP: get_run_log
|
|
29
|
-
SELECT ke.path, ke.
|
|
38
|
+
SELECT ke.path, ke.status, ke.body, ke.attributes
|
|
30
39
|
FROM known_entries AS ke
|
|
31
40
|
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
32
41
|
WHERE
|
package/src/hooks/Hooks.js
CHANGED
|
@@ -39,6 +39,7 @@ export default function createHooks(debug = false) {
|
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
41
|
run: {
|
|
42
|
+
created: createEvent("run.created"),
|
|
42
43
|
started: createEvent("run.started"),
|
|
43
44
|
progress: createEvent("run.progress"),
|
|
44
45
|
state: createEvent("run.state"),
|
|
@@ -47,10 +48,15 @@ export default function createHooks(debug = false) {
|
|
|
47
48
|
completed: createEvent("run.step.completed"),
|
|
48
49
|
},
|
|
49
50
|
},
|
|
51
|
+
loop: {
|
|
52
|
+
started: createEvent("loop.started"),
|
|
53
|
+
completed: createEvent("loop.completed"),
|
|
54
|
+
},
|
|
50
55
|
turn: {
|
|
51
56
|
started: createEvent("turn.started"),
|
|
52
57
|
response: createEvent("turn.response"),
|
|
53
58
|
proposing: createEvent("turn.proposing"),
|
|
59
|
+
completed: createEvent("turn.completed"),
|
|
54
60
|
},
|
|
55
61
|
assembly: {
|
|
56
62
|
system: createFilter("assembly.system"),
|
|
@@ -67,6 +73,10 @@ export default function createHooks(debug = false) {
|
|
|
67
73
|
started: createEvent("act.started"),
|
|
68
74
|
completed: createEvent("act.completed"),
|
|
69
75
|
},
|
|
76
|
+
panic: {
|
|
77
|
+
started: createEvent("panic.started"),
|
|
78
|
+
completed: createEvent("panic.completed"),
|
|
79
|
+
},
|
|
70
80
|
llm: {
|
|
71
81
|
request: {
|
|
72
82
|
started: createEvent("llm.request.started"),
|
|
@@ -80,9 +90,17 @@ export default function createHooks(debug = false) {
|
|
|
80
90
|
tools: createFilter("prompt.tools"),
|
|
81
91
|
},
|
|
82
92
|
entry: {
|
|
93
|
+
recording: createFilter("entry.recording"),
|
|
83
94
|
created: createEvent("entry.created"),
|
|
84
95
|
changed: createEvent("entry.changed"),
|
|
85
96
|
},
|
|
97
|
+
tool: {
|
|
98
|
+
before: createEvent("tool.before"),
|
|
99
|
+
after: createEvent("tool.after"),
|
|
100
|
+
},
|
|
101
|
+
context: {
|
|
102
|
+
materialized: createEvent("context.materialized"),
|
|
103
|
+
},
|
|
86
104
|
action: {},
|
|
87
105
|
ui: {
|
|
88
106
|
render: createEvent("ui.render"),
|
|
@@ -48,22 +48,27 @@ export default class PluginContext {
|
|
|
48
48
|
return this.#schemes;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
registerScheme({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
} = {}) {
|
|
51
|
+
registerScheme({ name, modelVisible = 1, category = "logging" } = {}) {
|
|
52
|
+
if (!PluginContext.CATEGORIES.has(category)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Invalid category "${category}". Must be one of: ${[...PluginContext.CATEGORIES].join(", ")}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
58
57
|
this.#schemes.push({
|
|
59
58
|
name: name || this.#name,
|
|
60
|
-
fidelity,
|
|
61
59
|
model_visible: modelVisible,
|
|
62
|
-
valid_states: JSON.stringify(validStates),
|
|
63
60
|
category,
|
|
64
61
|
});
|
|
65
62
|
}
|
|
66
63
|
|
|
64
|
+
static CATEGORIES = Object.freeze(
|
|
65
|
+
new Set(["data", "logging", "unknown", "prompt"]),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
ensureTool() {
|
|
69
|
+
this.#hooks.tools.ensureTool(this.#name);
|
|
70
|
+
}
|
|
71
|
+
|
|
67
72
|
/**
|
|
68
73
|
* Register a named callback for this plugin.
|
|
69
74
|
* "handler" registers the tool handler.
|
|
@@ -78,7 +83,6 @@ export default class PluginContext {
|
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
85
|
if (event === "full" || event === "summary") {
|
|
81
|
-
this.#hooks.tools.ensureTool(this.#name);
|
|
82
86
|
this.#hooks.tools.onView(this.#name, callback, event);
|
|
83
87
|
return;
|
|
84
88
|
}
|