@possumtech/rummy 0.2.6 → 0.2.8
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 +2 -3
- package/PLUGINS.md +105 -82
- package/bin/rummy.js +9 -2
- package/migrations/001_initial_schema.sql +53 -68
- package/package.json +4 -6
- package/service.js +2 -2
- package/src/agent/AgentLoop.js +91 -58
- package/src/agent/ContextAssembler.js +2 -2
- package/src/agent/KnownStore.js +30 -11
- package/src/agent/ProjectAgent.js +1 -3
- package/src/agent/TurnExecutor.js +119 -31
- package/src/agent/XmlParser.js +20 -0
- package/src/agent/known_checks.sql +5 -4
- package/src/agent/known_queries.sql +4 -3
- package/src/agent/known_store.sql +29 -15
- package/src/agent/loops.sql +63 -0
- package/src/agent/runs.sql +7 -7
- package/src/agent/schemes.sql +2 -2
- package/src/agent/turns.sql +3 -3
- package/src/hooks/PluginContext.js +1 -10
- package/src/hooks/RummyContext.js +16 -8
- package/src/plugins/ask_user/ask_user.js +3 -2
- package/src/plugins/cp/cp.js +7 -7
- package/src/plugins/current/current.js +3 -4
- package/src/plugins/engine/engine.sql +5 -3
- package/src/plugins/engine/turn_context.sql +9 -4
- package/src/plugins/env/docs.md +2 -0
- package/src/plugins/env/env.js +3 -2
- package/src/plugins/file/file.js +9 -19
- package/src/plugins/get/docs.md +7 -3
- package/src/plugins/get/get.js +22 -6
- package/src/plugins/hedberg/docs.md +0 -9
- package/src/plugins/hedberg/hedberg.js +2 -5
- package/src/plugins/hedberg/matcher.js +1 -1
- package/src/plugins/hedberg/patterns.js +6 -6
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/index.js +28 -15
- package/src/plugins/instructions/instructions.js +1 -1
- package/src/plugins/known/known.js +9 -11
- package/src/plugins/mv/mv.js +7 -7
- package/src/plugins/previous/previous.js +6 -5
- package/src/plugins/progress/progress.js +6 -0
- package/src/plugins/prompt/prompt.js +9 -10
- package/src/plugins/rm/docs.md +3 -1
- package/src/plugins/rm/rm.js +24 -7
- package/src/plugins/rpc/rpc.js +33 -42
- package/src/plugins/set/docs.md +3 -1
- package/src/plugins/set/set.js +22 -16
- package/src/plugins/sh/sh.js +3 -2
- package/src/plugins/skills/skills.js +3 -4
- package/src/plugins/store/docs.md +2 -1
- package/src/plugins/store/store.js +14 -3
- package/src/plugins/summarize/summarize.js +1 -1
- package/src/plugins/telemetry/telemetry.js +17 -7
- package/src/plugins/unknown/unknown.js +3 -2
- package/src/plugins/update/update.js +1 -1
- package/src/server/ClientConnection.js +3 -5
- package/src/sql/v_model_context.sql +20 -23
- package/src/sql/v_run_log.sql +3 -3
- package/src/agent/prompt_queue.sql +0 -39
|
@@ -33,6 +33,12 @@ export default class Progress {
|
|
|
33
33
|
const status = [tokenInfo, unknownInfo].filter(Boolean).join(" · ");
|
|
34
34
|
if (status) parts.push(status);
|
|
35
35
|
|
|
36
|
+
if (ctx.demoted?.length > 0) {
|
|
37
|
+
parts.push(
|
|
38
|
+
`⚠ ${ctx.demoted.length} entries demoted to summary to fit context budget. Use <get/> to restore.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
if (hasCurrent) {
|
|
37
43
|
parts.push(
|
|
38
44
|
"The above actions were performed in response to the following prompt:",
|
|
@@ -8,21 +8,20 @@ export default class Prompt {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
|
|
11
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
11
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
12
12
|
|
|
13
13
|
if (!isContinuation && prompt) {
|
|
14
|
-
await store.upsert(runId, turn, `prompt://${turn}`, "",
|
|
14
|
+
await store.upsert(runId, turn, `prompt://${turn}`, "", 200, {
|
|
15
15
|
attributes: { mode },
|
|
16
|
+
loopId,
|
|
17
|
+
});
|
|
18
|
+
await store.upsert(runId, turn, `${mode}://${turn}`, prompt, 200, {
|
|
19
|
+
loopId,
|
|
16
20
|
});
|
|
17
|
-
await store.upsert(runId, turn, `${mode}://${turn}`, prompt, "info");
|
|
18
21
|
} else {
|
|
19
|
-
await store.upsert(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`progress://${turn}`,
|
|
23
|
-
prompt || "",
|
|
24
|
-
"info",
|
|
25
|
-
);
|
|
22
|
+
await store.upsert(runId, turn, `progress://${turn}`, prompt || "", 200, {
|
|
23
|
+
loopId,
|
|
24
|
+
});
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
|
package/src/plugins/rm/docs.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
## <rm path="[path/to/file]"/> - Remove a file or entry
|
|
2
2
|
Example: <rm path="src/config.js"/>
|
|
3
|
-
Example: <rm path="
|
|
3
|
+
Example: <rm path="known://donald-rumsfeld-was-born-in-1932"/>
|
|
4
|
+
Example: <rm path="known://temp_*" preview/> (preview before deleting)
|
|
4
5
|
* <rm/> removes the file or entry from context and deletes it PERMANENTLY
|
|
6
|
+
* Paths accept globs — use `preview` to check matches before deleting
|
package/src/plugins/rm/rm.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import KnownStore from "../../agent/KnownStore.js";
|
|
2
3
|
|
|
3
4
|
export default class Rm {
|
|
4
5
|
#core;
|
|
5
6
|
|
|
6
7
|
constructor(core) {
|
|
7
8
|
this.#core = core;
|
|
8
|
-
core.registerScheme(
|
|
9
|
-
validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
|
|
10
|
-
});
|
|
9
|
+
core.registerScheme();
|
|
11
10
|
core.on("handler", this.handler.bind(this));
|
|
12
11
|
core.on("full", this.full.bind(this));
|
|
13
12
|
core.on("summary", this.summary.bind(this));
|
|
@@ -18,24 +17,42 @@ export default class Rm {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
async handler(entry, rummy) {
|
|
21
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
20
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
22
21
|
const target = entry.attributes.path;
|
|
22
|
+
if (!target) {
|
|
23
|
+
await store.upsert(runId, turn, entry.resultPath, "", 400, {
|
|
24
|
+
attributes: { error: "path is required" },
|
|
25
|
+
loopId,
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const normalized = KnownStore.normalizePath(target);
|
|
23
30
|
const matches = await store.getEntriesByPattern(
|
|
24
31
|
runId,
|
|
25
|
-
|
|
32
|
+
normalized,
|
|
26
33
|
entry.attributes.body,
|
|
27
34
|
);
|
|
28
35
|
|
|
36
|
+
if (matches.length === 0) {
|
|
37
|
+
await store.upsert(runId, turn, entry.resultPath, "", 404, {
|
|
38
|
+
attributes: { path: target, error: `${target} not found` },
|
|
39
|
+
loopId,
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
29
44
|
for (const match of matches) {
|
|
30
45
|
const resultPath = `rm://${match.path}`;
|
|
31
46
|
if (match.scheme === null) {
|
|
32
|
-
await store.upsert(runId, turn, resultPath, match.path,
|
|
47
|
+
await store.upsert(runId, turn, resultPath, match.path, 202, {
|
|
33
48
|
attributes: { path: match.path },
|
|
49
|
+
loopId,
|
|
34
50
|
});
|
|
35
51
|
} else {
|
|
36
52
|
await store.remove(runId, match.path);
|
|
37
|
-
await store.upsert(runId, turn, resultPath, match.path,
|
|
53
|
+
await store.upsert(runId, turn, resultPath, match.path, 200, {
|
|
38
54
|
attributes: { path: match.path },
|
|
55
|
+
loopId,
|
|
39
56
|
});
|
|
40
57
|
}
|
|
41
58
|
}
|
package/src/plugins/rpc/rpc.js
CHANGED
|
@@ -67,7 +67,7 @@ export default class Rpc {
|
|
|
67
67
|
const row = await ctx.db.upsert_model.get({
|
|
68
68
|
alias: params.alias,
|
|
69
69
|
actual: params.actual,
|
|
70
|
-
context_length: params.contextLength
|
|
70
|
+
context_length: params.contextLength ?? null,
|
|
71
71
|
});
|
|
72
72
|
return { id: row.id, alias: params.alias };
|
|
73
73
|
},
|
|
@@ -90,7 +90,7 @@ export default class Rpc {
|
|
|
90
90
|
|
|
91
91
|
// --- Entry operations (same dispatch as model) ---
|
|
92
92
|
|
|
93
|
-
r.register("
|
|
93
|
+
r.register("get", {
|
|
94
94
|
handler: async (params, ctx) => {
|
|
95
95
|
if (!params.path) throw new Error("path is required");
|
|
96
96
|
|
|
@@ -159,7 +159,7 @@ export default class Rpc {
|
|
|
159
159
|
requiresInit: true,
|
|
160
160
|
});
|
|
161
161
|
|
|
162
|
-
r.register("
|
|
162
|
+
r.register("set", {
|
|
163
163
|
handler: async (params, ctx) => {
|
|
164
164
|
if (!params.path) throw new Error("path is required");
|
|
165
165
|
if (!params.run) throw new Error("run is required");
|
|
@@ -169,19 +169,15 @@ export default class Rpc {
|
|
|
169
169
|
if (scheme) {
|
|
170
170
|
await rummy.set({
|
|
171
171
|
path: params.path,
|
|
172
|
-
body: params.body
|
|
173
|
-
|
|
172
|
+
body: params.body,
|
|
173
|
+
status: params.status || 200,
|
|
174
174
|
attributes: params.attributes,
|
|
175
175
|
});
|
|
176
176
|
} else {
|
|
177
|
-
await dispatchTool(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
params.path,
|
|
182
|
-
params.body || "",
|
|
183
|
-
{ path: params.path, ...params.attributes },
|
|
184
|
-
);
|
|
177
|
+
await dispatchTool(hooks, rummy, "set", params.path, params.body, {
|
|
178
|
+
path: params.path,
|
|
179
|
+
...params.attributes,
|
|
180
|
+
});
|
|
185
181
|
}
|
|
186
182
|
return { status: "ok" };
|
|
187
183
|
},
|
|
@@ -190,13 +186,13 @@ export default class Rpc {
|
|
|
190
186
|
run: "string — run alias",
|
|
191
187
|
path: "string — entry path",
|
|
192
188
|
body: "string? — entry content",
|
|
193
|
-
|
|
189
|
+
status: "number? — HTTP status code (default: 200)",
|
|
194
190
|
attributes: "object? — JSON attributes",
|
|
195
191
|
},
|
|
196
192
|
requiresInit: true,
|
|
197
193
|
});
|
|
198
194
|
|
|
199
|
-
r.register("
|
|
195
|
+
r.register("rm", {
|
|
200
196
|
handler: async (params, ctx) => {
|
|
201
197
|
if (!params.path) throw new Error("path is required");
|
|
202
198
|
if (!params.run) throw new Error("run is required");
|
|
@@ -225,14 +221,15 @@ export default class Rpc {
|
|
|
225
221
|
if (!run) return [];
|
|
226
222
|
const entries = await ctx.projectAgent.entries.getEntriesByPattern(
|
|
227
223
|
run.id,
|
|
228
|
-
params.pattern
|
|
229
|
-
params.body
|
|
230
|
-
{ limit: params.limit, offset: params.offset },
|
|
224
|
+
params.pattern ?? "*",
|
|
225
|
+
params.body ?? null,
|
|
226
|
+
{ limit: params.limit ?? null, offset: params.offset ?? null },
|
|
231
227
|
);
|
|
232
228
|
return entries.map((e) => ({
|
|
233
229
|
path: e.path,
|
|
234
230
|
scheme: e.scheme,
|
|
235
|
-
|
|
231
|
+
status: e.status,
|
|
232
|
+
fidelity: e.fidelity,
|
|
236
233
|
tokens: e.tokens_full,
|
|
237
234
|
}));
|
|
238
235
|
},
|
|
@@ -256,7 +253,7 @@ export default class Rpc {
|
|
|
256
253
|
const runRow = await ctx.db.create_run.get({
|
|
257
254
|
project_id: ctx.projectId,
|
|
258
255
|
parent_run_id: null,
|
|
259
|
-
model: params.model,
|
|
256
|
+
model: params.model ?? null,
|
|
260
257
|
alias,
|
|
261
258
|
temperature: params.temperature ?? null,
|
|
262
259
|
persona: params.persona ?? null,
|
|
@@ -283,8 +280,8 @@ export default class Rpc {
|
|
|
283
280
|
params.prompt,
|
|
284
281
|
params.run,
|
|
285
282
|
{
|
|
286
|
-
temperature: params.temperature,
|
|
287
|
-
persona: params.persona,
|
|
283
|
+
temperature: params.temperature ?? null,
|
|
284
|
+
persona: params.persona ?? null,
|
|
288
285
|
contextLimit: params.contextLimit,
|
|
289
286
|
noContext: params.noContext,
|
|
290
287
|
fork: params.fork,
|
|
@@ -315,8 +312,8 @@ export default class Rpc {
|
|
|
315
312
|
params.prompt,
|
|
316
313
|
params.run,
|
|
317
314
|
{
|
|
318
|
-
temperature: params.temperature,
|
|
319
|
-
persona: params.persona,
|
|
315
|
+
temperature: params.temperature ?? null,
|
|
316
|
+
persona: params.persona ?? null,
|
|
320
317
|
contextLimit: params.contextLimit,
|
|
321
318
|
noContext: params.noContext,
|
|
322
319
|
fork: params.fork,
|
|
@@ -358,7 +355,7 @@ export default class Rpc {
|
|
|
358
355
|
ctx.projectAgent.abortRun(runRow.id);
|
|
359
356
|
await ctx.db.update_run_status.run({
|
|
360
357
|
id: runRow.id,
|
|
361
|
-
status:
|
|
358
|
+
status: 499,
|
|
362
359
|
});
|
|
363
360
|
return { status: "ok" };
|
|
364
361
|
},
|
|
@@ -447,7 +444,7 @@ export default class Rpc {
|
|
|
447
444
|
run: row.alias,
|
|
448
445
|
status: row.status,
|
|
449
446
|
turn: row.turn,
|
|
450
|
-
summary: row.summary
|
|
447
|
+
summary: row.summary,
|
|
451
448
|
created: row.created_at,
|
|
452
449
|
}));
|
|
453
450
|
},
|
|
@@ -512,8 +509,8 @@ export default class Rpc {
|
|
|
512
509
|
};
|
|
513
510
|
}),
|
|
514
511
|
},
|
|
515
|
-
last_user_prompt: promptRow?.body
|
|
516
|
-
last_summary: summaryRow?.body
|
|
512
|
+
last_user_prompt: promptRow?.body,
|
|
513
|
+
last_summary: summaryRow?.body,
|
|
517
514
|
};
|
|
518
515
|
},
|
|
519
516
|
description: "Full run detail.",
|
|
@@ -558,25 +555,19 @@ async function buildRunContext(hooks, ctx, runAlias) {
|
|
|
558
555
|
|
|
559
556
|
async function dispatchTool(hooks, rummy, scheme, path, body, attributes) {
|
|
560
557
|
const store = rummy.entries;
|
|
561
|
-
const resultPath = await store.dedup(rummy.runId, scheme, path
|
|
558
|
+
const resultPath = await store.dedup(rummy.runId, scheme, path);
|
|
562
559
|
|
|
563
|
-
await store.upsert(
|
|
564
|
-
|
|
565
|
-
rummy.
|
|
566
|
-
|
|
567
|
-
body || "",
|
|
568
|
-
"full",
|
|
569
|
-
{
|
|
570
|
-
attributes: attributes || null,
|
|
571
|
-
},
|
|
572
|
-
);
|
|
560
|
+
await store.upsert(rummy.runId, rummy.sequence, resultPath, body, 200, {
|
|
561
|
+
attributes: attributes,
|
|
562
|
+
loopId: rummy.loopId,
|
|
563
|
+
});
|
|
573
564
|
|
|
574
565
|
const entry = {
|
|
575
566
|
scheme,
|
|
576
567
|
path: resultPath,
|
|
577
|
-
body: body
|
|
578
|
-
attributes: attributes
|
|
579
|
-
|
|
568
|
+
body: body,
|
|
569
|
+
attributes: attributes,
|
|
570
|
+
status: 200,
|
|
580
571
|
resultPath,
|
|
581
572
|
};
|
|
582
573
|
|
package/src/plugins/set/docs.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
## <set path="[path/to/file]">[edit]</set> - Edit a file or entry
|
|
2
2
|
Example: <set path="src/config.js">s/base_url = http:\/\/localhost/base_url = http:\/\/0.0.0.0/g s/port = 3000/port = 8080/g</set>
|
|
3
|
-
* All editing syntaxes supported: s/old/new/, {"search":"old","replace":"new"}, SEARCH/REPLACE blocks
|
|
3
|
+
* All editing syntaxes supported: s/old/new/, {"search":"old","replace":"new"}, literal SEARCH/REPLACE blocks
|
|
4
|
+
* Chain multiple replacements: `s/old/new/ s/foo/bar/`
|
|
5
|
+
* Regex patterns use /slashes/: `s/console\.log.*/\/\/ removed/g`
|
|
4
6
|
* Do not use <sh/> or <env/> to read, create, update, or delete files or entries
|
package/src/plugins/set/set.js
CHANGED
|
@@ -9,9 +9,7 @@ export default class Set {
|
|
|
9
9
|
|
|
10
10
|
constructor(core) {
|
|
11
11
|
this.#core = core;
|
|
12
|
-
core.registerScheme(
|
|
13
|
-
validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
|
|
14
|
-
});
|
|
12
|
+
core.registerScheme();
|
|
15
13
|
core.on("handler", this.handler.bind(this));
|
|
16
14
|
core.on("full", this.full.bind(this));
|
|
17
15
|
core.on("summary", this.summary.bind(this));
|
|
@@ -23,7 +21,7 @@ export default class Set {
|
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
async handler(entry, rummy) {
|
|
26
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
24
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
27
25
|
const attrs = entry.attributes;
|
|
28
26
|
|
|
29
27
|
if (attrs.blocks || attrs.search != null) {
|
|
@@ -45,7 +43,7 @@ export default class Set {
|
|
|
45
43
|
attrs.path,
|
|
46
44
|
attrs.body,
|
|
47
45
|
matches,
|
|
48
|
-
true,
|
|
46
|
+
{ preview: true, loopId },
|
|
49
47
|
);
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
@@ -57,8 +55,9 @@ export default class Set {
|
|
|
57
55
|
if (scheme === null) {
|
|
58
56
|
const udiff = generatePatch(target, "", entry.body || "");
|
|
59
57
|
const merge = `<<<<<<< SEARCH\n=======\n${entry.body || ""}\n>>>>>>> REPLACE`;
|
|
60
|
-
await store.upsert(runId, turn, entry.resultPath, "",
|
|
58
|
+
await store.upsert(runId, turn, entry.resultPath, "", 202, {
|
|
61
59
|
attributes: { file: target, patch: udiff, merge },
|
|
60
|
+
loopId,
|
|
62
61
|
});
|
|
63
62
|
} else if (attrs.filter || target.includes("*")) {
|
|
64
63
|
const matches = await store.getEntriesByPattern(
|
|
@@ -80,9 +79,10 @@ export default class Set {
|
|
|
80
79
|
target,
|
|
81
80
|
attrs.filter,
|
|
82
81
|
matches,
|
|
82
|
+
{ loopId },
|
|
83
83
|
);
|
|
84
84
|
} else {
|
|
85
|
-
await store.upsert(runId, turn, target, entry.body,
|
|
85
|
+
await store.upsert(runId, turn, target, entry.body, 200, { loopId });
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -103,13 +103,14 @@ export default class Set {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
async #processEdit(rummy, entry, attrs) {
|
|
106
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
106
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
107
107
|
const target = attrs.path;
|
|
108
108
|
const matches = await store.getEntriesByPattern(runId, target, attrs.body);
|
|
109
109
|
|
|
110
110
|
if (matches.length === 0) {
|
|
111
|
-
await store.upsert(runId, turn, entry.resultPath, "",
|
|
111
|
+
await store.upsert(runId, turn, entry.resultPath, "", 404, {
|
|
112
112
|
attributes: { file: target, error: `${target} not found in context` },
|
|
113
|
+
loopId,
|
|
113
114
|
});
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
@@ -121,8 +122,9 @@ export default class Set {
|
|
|
121
122
|
const existingAttrs = await rummy.getAttributes(canonicalPath);
|
|
122
123
|
const revisions = existingAttrs?.revisions || [];
|
|
123
124
|
revisions.push(revision);
|
|
124
|
-
await store.upsert(runId, turn, canonicalPath, "",
|
|
125
|
+
await store.upsert(runId, turn, canonicalPath, "", 200, {
|
|
125
126
|
attributes: { file: match.path, revisions },
|
|
127
|
+
loopId,
|
|
126
128
|
});
|
|
127
129
|
if (KnownStore.normalizePath(entry.resultPath) !== canonicalPath) {
|
|
128
130
|
await store.remove(runId, entry.resultPath);
|
|
@@ -133,7 +135,7 @@ export default class Set {
|
|
|
133
135
|
const { patch, searchText, replaceText, warning, error } =
|
|
134
136
|
Set.#applyRevision(match.body, attrs);
|
|
135
137
|
|
|
136
|
-
const
|
|
138
|
+
const status = error ? 409 : 200;
|
|
137
139
|
const resultPath = `set://${match.path}`;
|
|
138
140
|
const udiff = patch ? generatePatch(match.path, match.body, patch) : null;
|
|
139
141
|
const merge =
|
|
@@ -143,7 +145,7 @@ export default class Set {
|
|
|
143
145
|
const beforeTokens = match.tokens_full || 0;
|
|
144
146
|
const afterTokens = patch ? (patch.length / 4) | 0 : beforeTokens;
|
|
145
147
|
|
|
146
|
-
await store.upsert(runId, turn, resultPath, match.body,
|
|
148
|
+
await store.upsert(runId, turn, resultPath, match.body, status, {
|
|
147
149
|
attributes: {
|
|
148
150
|
file: match.path,
|
|
149
151
|
patch: udiff,
|
|
@@ -153,16 +155,19 @@ export default class Set {
|
|
|
153
155
|
warning,
|
|
154
156
|
error,
|
|
155
157
|
},
|
|
158
|
+
loopId,
|
|
156
159
|
});
|
|
157
160
|
|
|
158
|
-
if (
|
|
159
|
-
await store.upsert(runId, turn, match.path, patch, match.
|
|
161
|
+
if (status === 200 && patch) {
|
|
162
|
+
await store.upsert(runId, turn, match.path, patch, match.status, {
|
|
163
|
+
loopId,
|
|
164
|
+
});
|
|
160
165
|
}
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
async #materializeRevisions({ rummy }) {
|
|
165
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
170
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
166
171
|
const setEntries = await store.getEntriesByPattern(runId, "set://*");
|
|
167
172
|
|
|
168
173
|
for (const entry of setEntries) {
|
|
@@ -198,7 +203,7 @@ export default class Set {
|
|
|
198
203
|
}
|
|
199
204
|
}
|
|
200
205
|
|
|
201
|
-
const state = lastError ?
|
|
206
|
+
const state = lastError ? 409 : 202;
|
|
202
207
|
const udiff =
|
|
203
208
|
current !== original
|
|
204
209
|
? generatePatch(filePath, original, current)
|
|
@@ -217,6 +222,7 @@ export default class Set {
|
|
|
217
222
|
warning: lastWarning,
|
|
218
223
|
error: lastError,
|
|
219
224
|
},
|
|
225
|
+
loopId,
|
|
220
226
|
});
|
|
221
227
|
}
|
|
222
228
|
}
|
package/src/plugins/sh/sh.js
CHANGED
|
@@ -16,9 +16,10 @@ export default class Sh {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async handler(entry, rummy) {
|
|
19
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
20
|
-
await store.upsert(runId, turn, entry.resultPath, entry.body,
|
|
19
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
20
|
+
await store.upsert(runId, turn, entry.resultPath, entry.body, 202, {
|
|
21
21
|
attributes: entry.attributes,
|
|
22
|
+
loopId,
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -8,7 +8,6 @@ export default class Skills {
|
|
|
8
8
|
this.#core = core;
|
|
9
9
|
core.registerScheme({
|
|
10
10
|
name: "skill",
|
|
11
|
-
validStates: ["full", "stored"],
|
|
12
11
|
category: "knowledge",
|
|
13
12
|
});
|
|
14
13
|
const r = core.hooks.rpc.registry;
|
|
@@ -28,7 +27,7 @@ export default class Skills {
|
|
|
28
27
|
runRow.next_turn,
|
|
29
28
|
`skill://${params.name}`,
|
|
30
29
|
body,
|
|
31
|
-
|
|
30
|
+
200,
|
|
32
31
|
{
|
|
33
32
|
attributes: {
|
|
34
33
|
name: params.name,
|
|
@@ -84,10 +83,10 @@ export default class Skills {
|
|
|
84
83
|
);
|
|
85
84
|
return entries.map((e) => ({
|
|
86
85
|
name: e.path.replace("skill://", ""),
|
|
87
|
-
|
|
86
|
+
status: e.status,
|
|
88
87
|
}));
|
|
89
88
|
},
|
|
90
|
-
description: "List skills active on a run. Returns [{ name,
|
|
89
|
+
description: "List skills active on a run. Returns [{ name, status }].",
|
|
91
90
|
params: { run: "string — run alias" },
|
|
92
91
|
requiresInit: true,
|
|
93
92
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
## <store path="[path/to/file]"/> - Store a file or entry
|
|
2
2
|
Example: <store path="src/config.js"/>
|
|
3
|
-
Example: <store path="
|
|
3
|
+
Example: <store path="src/**/*.test.js"/> (store all test files at once)
|
|
4
4
|
* <store/> removes the file or entry from context, but does not delete it
|
|
5
5
|
* A stored file or entry can be restored with <get/>
|
|
6
|
+
* Paths accept globs for bulk operations
|
|
@@ -6,7 +6,7 @@ export default class Store {
|
|
|
6
6
|
|
|
7
7
|
constructor(core) {
|
|
8
8
|
this.#core = core;
|
|
9
|
-
core.registerScheme(
|
|
9
|
+
core.registerScheme();
|
|
10
10
|
core.on("handler", this.handler.bind(this));
|
|
11
11
|
core.on("full", this.full.bind(this));
|
|
12
12
|
core.on("summary", this.summary.bind(this));
|
|
@@ -17,8 +17,15 @@ export default class Store {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async handler(entry, rummy) {
|
|
20
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
20
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
21
21
|
const target = entry.attributes.path;
|
|
22
|
+
if (!target) {
|
|
23
|
+
await store.upsert(runId, turn, entry.resultPath, "", 400, {
|
|
24
|
+
attributes: { error: "path is required" },
|
|
25
|
+
loopId,
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
22
29
|
const bodyFilter = entry.attributes.body || null;
|
|
23
30
|
const isPattern = bodyFilter || target.includes("*");
|
|
24
31
|
const matches = await store.getEntriesByPattern(runId, target, bodyFilter);
|
|
@@ -33,12 +40,16 @@ export default class Store {
|
|
|
33
40
|
target,
|
|
34
41
|
bodyFilter,
|
|
35
42
|
matches,
|
|
43
|
+
{ loopId },
|
|
36
44
|
);
|
|
37
45
|
} else {
|
|
38
46
|
const paths = matches.map((m) => m.path).join(", ");
|
|
39
47
|
const body =
|
|
40
48
|
matches.length > 0 ? `${paths} stored` : `${target} not found`;
|
|
41
|
-
await store.upsert(runId, turn, entry.resultPath, body,
|
|
49
|
+
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
50
|
+
fidelity: "stored",
|
|
51
|
+
loopId,
|
|
52
|
+
});
|
|
42
53
|
}
|
|
43
54
|
}
|
|
44
55
|
|
|
@@ -5,7 +5,7 @@ export default class Summarize {
|
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
-
core.registerScheme({
|
|
8
|
+
core.registerScheme({ category: "structural" });
|
|
9
9
|
core.on("full", this.full.bind(this));
|
|
10
10
|
core.on("summary", this.summary.bind(this));
|
|
11
11
|
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
@@ -80,17 +80,23 @@ export default class Telemetry {
|
|
|
80
80
|
systemMsg,
|
|
81
81
|
userMsg,
|
|
82
82
|
}) {
|
|
83
|
-
const { entries: store, runId } = rummy;
|
|
83
|
+
const { entries: store, runId, loopId } = rummy;
|
|
84
84
|
|
|
85
85
|
// assistant://N — the model's raw response
|
|
86
|
-
await store.upsert(runId, turn, `assistant://${turn}`, content,
|
|
86
|
+
await store.upsert(runId, turn, `assistant://${turn}`, content, 200, {
|
|
87
|
+
loopId,
|
|
88
|
+
});
|
|
87
89
|
|
|
88
90
|
// system://N, user://N — assembled messages as audit
|
|
89
91
|
if (systemMsg) {
|
|
90
|
-
await store.upsert(runId, turn, `system://${turn}`, systemMsg,
|
|
92
|
+
await store.upsert(runId, turn, `system://${turn}`, systemMsg, 200, {
|
|
93
|
+
loopId,
|
|
94
|
+
});
|
|
91
95
|
}
|
|
92
96
|
if (userMsg) {
|
|
93
|
-
await store.upsert(runId, turn, `user://${turn}`, userMsg,
|
|
97
|
+
await store.upsert(runId, turn, `user://${turn}`, userMsg, 200, {
|
|
98
|
+
loopId,
|
|
99
|
+
});
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
// model://N — raw API response diagnostics
|
|
@@ -105,7 +111,8 @@ export default class Telemetry {
|
|
|
105
111
|
usage: result.usage || null,
|
|
106
112
|
model: result.model || null,
|
|
107
113
|
}),
|
|
108
|
-
|
|
114
|
+
200,
|
|
115
|
+
{ loopId },
|
|
109
116
|
);
|
|
110
117
|
|
|
111
118
|
// reasoning://N
|
|
@@ -115,13 +122,16 @@ export default class Telemetry {
|
|
|
115
122
|
turn,
|
|
116
123
|
`reasoning://${turn}`,
|
|
117
124
|
responseMessage.reasoning_content,
|
|
118
|
-
|
|
125
|
+
200,
|
|
126
|
+
{ loopId },
|
|
119
127
|
);
|
|
120
128
|
}
|
|
121
129
|
|
|
122
130
|
// content://N — unparsed text
|
|
123
131
|
if (unparsed) {
|
|
124
|
-
await store.upsert(runId, turn, `content://${turn}`, unparsed,
|
|
132
|
+
await store.upsert(runId, turn, `content://${turn}`, unparsed, 200, {
|
|
133
|
+
loopId,
|
|
134
|
+
});
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
// Commit usage stats
|
|
@@ -6,7 +6,6 @@ export default class Unknown {
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
8
|
core.registerScheme({
|
|
9
|
-
validStates: ["full", "stored"],
|
|
10
9
|
category: "knowledge",
|
|
11
10
|
});
|
|
12
11
|
core.on("full", this.full.bind(this));
|
|
@@ -25,7 +24,9 @@ export default class Unknown {
|
|
|
25
24
|
const entries = ctx.rows.filter((r) => r.category === "unknown");
|
|
26
25
|
if (entries.length === 0) return content;
|
|
27
26
|
|
|
28
|
-
const lines = entries.map(
|
|
27
|
+
const lines = entries.map(
|
|
28
|
+
(u) => `<unknown path="${u.path}">${u.body}</unknown>`,
|
|
29
|
+
);
|
|
29
30
|
return `${content}\n\n<unknowns>\n${lines.join("\n")}\n</unknowns>`;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -5,7 +5,7 @@ export default class Update {
|
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
-
core.registerScheme({
|
|
8
|
+
core.registerScheme({ category: "structural" });
|
|
9
9
|
core.on("full", this.full.bind(this));
|
|
10
10
|
core.on("summary", this.summary.bind(this));
|
|
11
11
|
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
@@ -110,7 +110,7 @@ export default class ClientConnection {
|
|
|
110
110
|
|
|
111
111
|
try {
|
|
112
112
|
const logRow = await this.#db.log_rpc_call.get({
|
|
113
|
-
project_id: this.#context.projectId
|
|
113
|
+
project_id: this.#context.projectId ?? null,
|
|
114
114
|
method,
|
|
115
115
|
rpc_id: id,
|
|
116
116
|
params: params ? JSON.stringify(params) : null,
|
|
@@ -184,10 +184,8 @@ export default class ClientConnection {
|
|
|
184
184
|
} catch {}
|
|
185
185
|
}
|
|
186
186
|
} catch (error) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
console.error(`[DEBUG] Stack: ${error.stack}`);
|
|
190
|
-
}
|
|
187
|
+
console.error(`[RUMMY] RPC Error: ${error.message}`);
|
|
188
|
+
console.error(`[RUMMY] Stack: ${error.stack}`);
|
|
191
189
|
this.#send({
|
|
192
190
|
jsonrpc: "2.0",
|
|
193
191
|
error: { code: -32603, message: error.message },
|