@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/plugins/mv/mv.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import docs from "./mvDoc.js";
|
|
3
3
|
|
|
4
4
|
export default class Mv {
|
|
5
5
|
#core;
|
|
6
6
|
|
|
7
7
|
constructor(core) {
|
|
8
8
|
this.#core = core;
|
|
9
|
-
core.registerScheme(
|
|
10
|
-
validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
|
|
11
|
-
});
|
|
9
|
+
core.registerScheme();
|
|
12
10
|
core.on("handler", this.handler.bind(this));
|
|
13
11
|
core.on("full", this.full.bind(this));
|
|
14
12
|
core.on("summary", this.summary.bind(this));
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
13
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
|
+
docsMap.mv = docs;
|
|
15
|
+
return docsMap;
|
|
16
|
+
});
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
async handler(entry, rummy) {
|
|
22
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
20
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
23
21
|
const { path, to } = entry.attributes;
|
|
22
|
+
const VALID = { stored: 1, summary: 1, index: 1, full: 1 };
|
|
23
|
+
const fidelity = VALID[entry.attributes.fidelity]
|
|
24
|
+
? entry.attributes.fidelity
|
|
25
|
+
: undefined;
|
|
24
26
|
|
|
25
27
|
const source = await store.getBody(runId, path);
|
|
26
28
|
if (source === null) return;
|
|
@@ -34,14 +36,16 @@ export default class Mv {
|
|
|
34
36
|
|
|
35
37
|
const body = `${path} ${to}`;
|
|
36
38
|
if (destScheme === null) {
|
|
37
|
-
await store.upsert(runId, turn, entry.resultPath, body,
|
|
39
|
+
await store.upsert(runId, turn, entry.resultPath, body, 202, {
|
|
38
40
|
attributes: { from: path, to, isMove: true, warning },
|
|
41
|
+
loopId,
|
|
39
42
|
});
|
|
40
43
|
} else {
|
|
41
|
-
await store.upsert(runId, turn, to, source,
|
|
44
|
+
await store.upsert(runId, turn, to, source, 200, { fidelity, loopId });
|
|
42
45
|
await store.remove(runId, path);
|
|
43
|
-
await store.upsert(runId, turn, entry.resultPath, body,
|
|
46
|
+
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
44
47
|
attributes: { from: path, to, isMove: true, warning },
|
|
48
|
+
loopId,
|
|
45
49
|
});
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Tool doc for <mv>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax: path attr = source, body = destination
|
|
6
|
+
[
|
|
7
|
+
'## <mv path="[source]">[destination]</mv> - Move or rename a file or entry',
|
|
8
|
+
],
|
|
9
|
+
|
|
10
|
+
// --- Examples: entry rename and file move
|
|
11
|
+
[
|
|
12
|
+
'Example: <mv path="known://active_task">known://completed_task</mv>',
|
|
13
|
+
"Entry rename. Most common mv use case. Shows known:// path convention.",
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
'Example: <mv path="src/old_name.js">src/new_name.js</mv>',
|
|
17
|
+
"File rename. Shows that mv works on files too, not just known entries.",
|
|
18
|
+
],
|
|
19
|
+
|
|
20
|
+
// --- Constraints
|
|
21
|
+
[
|
|
22
|
+
"* Source path accepts globs for batch moves",
|
|
23
|
+
"Pattern support consistent with get/cp/rm.",
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"* In ask mode, destination MUST be a scheme path (not a file)",
|
|
27
|
+
"Mode constraint. Prevents file mutations in ask mode via mv.",
|
|
28
|
+
],
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export default class Persona {
|
|
5
|
+
#core;
|
|
6
|
+
|
|
7
|
+
constructor(core) {
|
|
8
|
+
this.#core = core;
|
|
9
|
+
const r = core.hooks.rpc.registry;
|
|
10
|
+
|
|
11
|
+
r.register("persona/set", {
|
|
12
|
+
handler: async (params, ctx) => {
|
|
13
|
+
if (!params.run) throw new Error("run is required");
|
|
14
|
+
|
|
15
|
+
const runRow = await ctx.db.get_run_by_alias.get({ alias: params.run });
|
|
16
|
+
if (!runRow) throw new Error(`Run not found: ${params.run}`);
|
|
17
|
+
|
|
18
|
+
let text = params.text;
|
|
19
|
+
if (params.name && !text) {
|
|
20
|
+
text = await loadFile(params.name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await ctx.db.update_run_config.run({
|
|
24
|
+
id: runRow.id,
|
|
25
|
+
temperature: null,
|
|
26
|
+
persona: text || null,
|
|
27
|
+
context_limit: null,
|
|
28
|
+
model: null,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return { status: "ok" };
|
|
32
|
+
},
|
|
33
|
+
description:
|
|
34
|
+
"Set persona on a run. Pass name or text. Pass neither to clear.",
|
|
35
|
+
params: {
|
|
36
|
+
run: "string — run alias",
|
|
37
|
+
name: "string? — persona filename (without .md)",
|
|
38
|
+
text: "string? — raw persona text (overrides name)",
|
|
39
|
+
},
|
|
40
|
+
requiresInit: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
r.register("listPersonas", {
|
|
44
|
+
handler: async () => {
|
|
45
|
+
const dir = configDir();
|
|
46
|
+
if (!dir) return [];
|
|
47
|
+
try {
|
|
48
|
+
const files = await fs.readdir(dir);
|
|
49
|
+
return files
|
|
50
|
+
.filter((f) => f.endsWith(".md"))
|
|
51
|
+
.map((f) => ({ name: f.replace(".md", ""), path: join(dir, f) }));
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
description: "List available persona files. Returns [{ name, path }].",
|
|
57
|
+
requiresInit: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function configDir() {
|
|
63
|
+
const home = process.env.RUMMY_HOME;
|
|
64
|
+
if (home) return join(home, "personas");
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function loadFile(name) {
|
|
69
|
+
const dir = configDir();
|
|
70
|
+
if (!dir) throw new Error("RUMMY_HOME not configured");
|
|
71
|
+
const path = join(dir, `${name}.md`);
|
|
72
|
+
try {
|
|
73
|
+
return await fs.readFile(path, "utf8");
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code === "ENOENT") throw new Error(`Not found: ${path}`);
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -10,6 +10,6 @@ history from prior ask/act invocations on this run.
|
|
|
10
10
|
|
|
11
11
|
## Behavior
|
|
12
12
|
|
|
13
|
-
Filters turn_context rows where `category` is `
|
|
13
|
+
Filters turn_context rows where `category` is `logging` or `prompt`
|
|
14
14
|
and `source_turn < loopStartTurn`. Renders each entry chronologically
|
|
15
|
-
with
|
|
15
|
+
with turn number and status.
|
|
@@ -11,32 +11,36 @@ export default class Previous {
|
|
|
11
11
|
|
|
12
12
|
const entries = ctx.rows.filter(
|
|
13
13
|
(r) =>
|
|
14
|
-
(r.category === "
|
|
14
|
+
(r.category === "logging" || r.category === "prompt") &&
|
|
15
15
|
r.source_turn < ctx.loopStartTurn,
|
|
16
16
|
);
|
|
17
17
|
if (entries.length === 0) return content;
|
|
18
18
|
|
|
19
19
|
const lines = await Promise.all(
|
|
20
|
-
entries.map((e) => renderToolTag(e,
|
|
20
|
+
entries.map((e) => renderToolTag(e, this.#core)),
|
|
21
21
|
);
|
|
22
22
|
return `${content}\n\n<previous>\n${lines.join("\n")}\n</previous>`;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
async function renderToolTag(entry,
|
|
26
|
+
async function renderToolTag(entry, core) {
|
|
27
27
|
const attrs =
|
|
28
28
|
typeof entry.attributes === "string"
|
|
29
29
|
? JSON.parse(entry.attributes)
|
|
30
30
|
: entry.attributes;
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
const
|
|
32
|
+
const target = attrs?.path || attrs?.file || attrs?.command || "";
|
|
33
|
+
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
34
|
+
const status = entry.status ? ` status="${entry.status}"` : "";
|
|
35
|
+
const summary =
|
|
36
|
+
typeof attrs?.summary === "string"
|
|
37
|
+
? ` summary="${attrs.summary.slice(0, 80)}"`
|
|
38
|
+
: "";
|
|
34
39
|
|
|
35
40
|
let body;
|
|
36
41
|
try {
|
|
37
42
|
body = await core.hooks.tools.view(entry.scheme, {
|
|
38
43
|
...entry,
|
|
39
|
-
fidelity,
|
|
40
44
|
attributes: attrs,
|
|
41
45
|
});
|
|
42
46
|
} catch {
|
|
@@ -44,7 +48,7 @@ async function renderToolTag(entry, fidelity, core) {
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
if (body) {
|
|
47
|
-
return
|
|
51
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}>${body}</${entry.scheme}>`;
|
|
48
52
|
}
|
|
49
|
-
return
|
|
53
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}/>`;
|
|
50
54
|
}
|
|
@@ -7,31 +7,63 @@ export default class Progress {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
async assembleProgress(content, ctx) {
|
|
10
|
-
|
|
10
|
+
// Use last turn's real assembled token count when available.
|
|
11
|
+
// Falls back to row token sum (less accurate — missing system prompt overhead).
|
|
12
|
+
const rowTokens = ctx.rows.reduce((sum, r) => sum + (r.tokens || 0), 0);
|
|
13
|
+
const usedTokens = ctx.lastContextTokens || rowTokens;
|
|
11
14
|
const contextSize = ctx.contextSize || 0;
|
|
12
15
|
const pct = contextSize ? Math.round((usedTokens / contextSize) * 100) : 0;
|
|
13
16
|
|
|
17
|
+
// Fidelity distribution across known/file entries
|
|
18
|
+
const entries = ctx.rows.filter((r) => r.category === "data");
|
|
19
|
+
const fullEntries = entries.filter((r) => r.fidelity === "full");
|
|
20
|
+
const summaryEntries = entries.filter((r) => r.fidelity === "summary");
|
|
21
|
+
const indexEntries = entries.filter((r) => r.fidelity === "index");
|
|
22
|
+
const fullTokens = fullEntries.reduce((s, r) => s + (r.tokens || 0), 0);
|
|
23
|
+
const summaryTokens = summaryEntries.reduce(
|
|
24
|
+
(s, r) => s + (r.tokens || 0),
|
|
25
|
+
0,
|
|
26
|
+
);
|
|
27
|
+
const indexTokens = indexEntries.reduce((s, r) => s + (r.tokens || 0), 0);
|
|
28
|
+
|
|
14
29
|
const unknownCount = ctx.rows.filter(
|
|
15
30
|
(r) => r.category === "unknown",
|
|
16
31
|
).length;
|
|
17
32
|
|
|
18
33
|
const hasCurrent = ctx.rows.some(
|
|
19
|
-
(r) =>
|
|
20
|
-
(r.category === "result" || r.category === "structural") &&
|
|
21
|
-
r.source_turn >= ctx.loopStartTurn,
|
|
34
|
+
(r) => r.category === "logging" && r.source_turn >= ctx.loopStartTurn,
|
|
22
35
|
);
|
|
23
36
|
|
|
24
37
|
const parts = [];
|
|
25
38
|
|
|
26
|
-
const
|
|
27
|
-
|
|
39
|
+
const knownCount = entries.length;
|
|
40
|
+
const tokenLine = contextSize
|
|
41
|
+
? `${usedTokens} of ${contextSize} tokens (${pct}%) · ${knownCount} known${knownCount !== 1 ? "s" : ""} · ${unknownCount} unknown${unknownCount !== 1 ? "s" : ""}`
|
|
28
42
|
: "";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
if (tokenLine) parts.push(tokenLine);
|
|
44
|
+
|
|
45
|
+
// Fidelity distribution
|
|
46
|
+
const fidelityParts = [];
|
|
47
|
+
if (fullEntries.length > 0)
|
|
48
|
+
fidelityParts.push(`${fullEntries.length} full (${fullTokens} tok)`);
|
|
49
|
+
if (summaryEntries.length > 0)
|
|
50
|
+
fidelityParts.push(
|
|
51
|
+
`${summaryEntries.length} summary (${summaryTokens} tok)`,
|
|
52
|
+
);
|
|
53
|
+
if (indexEntries.length > 0)
|
|
54
|
+
fidelityParts.push(`${indexEntries.length} index (${indexTokens} tok)`);
|
|
55
|
+
if (fidelityParts.length > 0)
|
|
56
|
+
parts.push(`Entries: ${fidelityParts.join(" · ")}`);
|
|
57
|
+
|
|
58
|
+
if (pct > 75) {
|
|
59
|
+
parts.push(
|
|
60
|
+
'Context above 75%. YOU MUST free space: <set fidelity="summary" summary="topic,detail,keyword"/>, <set fidelity="archive"/>, or <rm/>. Target the largest entries.',
|
|
61
|
+
);
|
|
62
|
+
} else if (pct > 50) {
|
|
63
|
+
parts.push(
|
|
64
|
+
'Context above 50%. You may free space: <set fidelity="summary" summary="topic,detail,keyword"/>, <set fidelity="archive"/>, or <rm/>.',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
35
67
|
|
|
36
68
|
if (hasCurrent) {
|
|
37
69
|
parts.push(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# prompt
|
|
2
2
|
|
|
3
|
-
Renders the `<ask
|
|
3
|
+
Renders the `<prompt mode="ask|act">` tag at the end of the user message.
|
|
4
4
|
Always present on every turn — the model always sees its task.
|
|
5
5
|
|
|
6
6
|
## Registration
|
|
@@ -9,8 +9,8 @@ Always present on every turn — the model always sees its task.
|
|
|
9
9
|
|
|
10
10
|
## Behavior
|
|
11
11
|
|
|
12
|
-
Finds the latest `
|
|
13
|
-
|
|
14
|
-
attribute
|
|
15
|
-
|
|
12
|
+
Finds the latest `prompt://` entry in the turn_context rows. The mode
|
|
13
|
+
(`ask` or `act`) is stored in `attributes.mode`. Renders with `tools`
|
|
14
|
+
attribute (available tool list) and optional `warn` attribute in ask
|
|
15
|
+
mode. Falls back to the mode passed by the core if no prompt entry
|
|
16
16
|
exists.
|
|
@@ -3,43 +3,47 @@ export default class Prompt {
|
|
|
3
3
|
|
|
4
4
|
constructor(core) {
|
|
5
5
|
this.#core = core;
|
|
6
|
+
core.hooks.tools.onView("prompt", (entry) => entry.body);
|
|
7
|
+
core.hooks.tools.onView("progress", (entry) => entry.body);
|
|
6
8
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
7
9
|
core.filter("assembly.user", this.assemblePrompt.bind(this), 300);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
|
|
11
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
13
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
12
14
|
|
|
13
15
|
if (!isContinuation && prompt) {
|
|
14
|
-
await store.upsert(runId, turn, `prompt://${turn}`,
|
|
16
|
+
await store.upsert(runId, turn, `prompt://${turn}`, prompt, 200, {
|
|
15
17
|
attributes: { mode },
|
|
18
|
+
loopId,
|
|
16
19
|
});
|
|
17
|
-
await store.upsert(runId, turn, `${mode}://${turn}`, prompt, "info");
|
|
18
20
|
} else {
|
|
19
|
-
await store.upsert(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`progress://${turn}`,
|
|
23
|
-
prompt || "",
|
|
24
|
-
"info",
|
|
25
|
-
);
|
|
21
|
+
await store.upsert(runId, turn, `progress://${turn}`, prompt || "", 200, {
|
|
22
|
+
loopId,
|
|
23
|
+
});
|
|
26
24
|
}
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
async assemblePrompt(content, ctx) {
|
|
30
28
|
const promptEntry = ctx.rows.findLast(
|
|
31
|
-
(r) =>
|
|
32
|
-
r.category === "prompt" && (r.scheme === "ask" || r.scheme === "act"),
|
|
29
|
+
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
33
30
|
);
|
|
34
31
|
|
|
35
|
-
const
|
|
32
|
+
const attrs =
|
|
33
|
+
typeof promptEntry?.attributes === "string"
|
|
34
|
+
? JSON.parse(promptEntry.attributes)
|
|
35
|
+
: promptEntry?.attributes;
|
|
36
|
+
const mode = attrs?.mode || ctx.type;
|
|
36
37
|
const body = promptEntry?.body || "";
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
mode
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
const toolNames = ctx.toolSet
|
|
39
|
+
? [...ctx.toolSet]
|
|
40
|
+
: [...this.#core.hooks.tools.resolveForLoop(mode)];
|
|
41
|
+
const tools = toolNames.join(",");
|
|
42
|
+
let warn = "";
|
|
43
|
+
if (mode === "ask") warn = ' warn="File editing disallowed."';
|
|
44
|
+
if (mode === "panic")
|
|
45
|
+
warn = ' warn="Context overflow. Free space to continue."';
|
|
42
46
|
|
|
43
|
-
return `${content}
|
|
47
|
+
return `${content}<prompt mode="${mode}" tools="${tools}"${warn}>${body}</prompt>`;
|
|
44
48
|
}
|
|
45
49
|
}
|
package/src/plugins/rm/README.md
CHANGED
|
@@ -5,9 +5,8 @@ Removes entries by path or glob pattern.
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `rm`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Matches entries by pattern. K/V entries are removed immediately (`pass`); file entries produce `proposed` state for client approval.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Matches entries by pattern. Scheme entries are removed immediately (status 200); file entries produce status 202 (proposed) for client approval.
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,5 @@ Shows `rm {path}`.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
Supports glob patterns and body filters via `getEntriesByPattern`. Each
|
|
17
|
+
Supports glob patterns and body filters via `getEntriesByPattern`. Each
|
|
18
|
+
matched entry is processed independently.
|
package/src/plugins/rm/rm.js
CHANGED
|
@@ -1,41 +1,58 @@
|
|
|
1
|
-
import
|
|
1
|
+
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import docs from "./rmDoc.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));
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
13
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
|
+
docsMap.rm = docs;
|
|
15
|
+
return docsMap;
|
|
16
|
+
});
|
|
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
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Tool doc for <rm>. Each entry: [text, rationale].
|
|
2
|
+
// Text goes to the model. Rationale stays in source.
|
|
3
|
+
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
+
const LINES = [
|
|
5
|
+
// --- Syntax: path attr, self-closing
|
|
6
|
+
['## <rm path="[path]"/> - Remove a file or entry'],
|
|
7
|
+
|
|
8
|
+
// --- Examples: file, known (with slug path), preview safety
|
|
9
|
+
['Example: <rm path="src/config.js"/>', "File removal. Simplest form."],
|
|
10
|
+
[
|
|
11
|
+
'Example: <rm path="known://donald-rumsfeld-was-born-in-1932"/>',
|
|
12
|
+
"Shows the slugified path convention. Model sees these paths in <knowns> section.",
|
|
13
|
+
],
|
|
14
|
+
[
|
|
15
|
+
'Example: <rm path="known://temp_*" preview/>',
|
|
16
|
+
"Preview before deleting. Glob pattern. Safety pattern for bulk operations.",
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
// --- Constraints
|
|
20
|
+
[
|
|
21
|
+
'* Permanent. Prefer <set fidelity="archive"/> to preserve for later retrieval',
|
|
22
|
+
"Nudges toward archive over rm. Archive keeps the key; rm deletes permanently.",
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
"* Paths accept globs — use `preview` to check matches first",
|
|
26
|
+
"Reinforces preview safety pattern. Prevents accidental bulk deletion.",
|
|
27
|
+
],
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -1,45 +1,32 @@
|
|
|
1
1
|
# rpc
|
|
2
2
|
|
|
3
|
-
Registers
|
|
3
|
+
Registers core RPC methods and provides automatic tool dispatch for
|
|
4
|
+
all registered tools.
|
|
4
5
|
|
|
5
6
|
## Registration
|
|
6
7
|
|
|
7
|
-
- **No tool handler** —
|
|
8
|
+
- **No tool handler** — registers RPC methods on `hooks.rpc.registry`.
|
|
9
|
+
- **Tool fallback** — any registered tool is automatically callable via
|
|
10
|
+
RPC without explicit registration. Third-party plugins get RPC for free.
|
|
8
11
|
|
|
9
12
|
## RPC Methods
|
|
10
13
|
|
|
11
14
|
### Protocol
|
|
12
|
-
- `ping`
|
|
13
|
-
- `discover` — returns method/notification catalog.
|
|
14
|
-
- `init` — initialize project (sets projectId, projectRoot, configPath).
|
|
15
|
+
- `ping`, `discover`, `init`
|
|
15
16
|
|
|
16
17
|
### Models
|
|
17
|
-
- `getModels`, `addModel`, `removeModel`
|
|
18
|
+
- `getModels`, `addModel`, `removeModel`
|
|
18
19
|
|
|
19
|
-
### Entry Operations
|
|
20
|
-
- `
|
|
21
|
-
- `store` — demote entry
|
|
22
|
-
-
|
|
23
|
-
- `delete` — remove entry via `rm` handler dispatch.
|
|
20
|
+
### Entry Operations (all dispatch through tool handler chain)
|
|
21
|
+
- `get` — promote entry; with `persist` flag, also sets file constraint.
|
|
22
|
+
- `store` — demote entry or manage file constraints (not a model tool).
|
|
23
|
+
- All other registered tools — auto-dispatched via tool fallback.
|
|
24
24
|
- `getEntries` — query entries by glob pattern.
|
|
25
25
|
|
|
26
26
|
### Runs
|
|
27
|
-
- `startRun`
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
- `run/resolve` — resolve a proposed entry (accept/reject).
|
|
31
|
-
- `run/abort` — abort an in-flight run.
|
|
32
|
-
- `run/rename` — rename a run alias.
|
|
33
|
-
- `run/inject` — inject a message into an idle or active run.
|
|
34
|
-
- `run/config` — update run parameters (temperature, persona, context_limit, model).
|
|
35
|
-
- `getRuns`, `getRun` — query run list and full run detail.
|
|
27
|
+
- `startRun`, `ask`, `act`
|
|
28
|
+
- `run/resolve`, `run/abort`, `run/rename`, `run/inject`, `run/config`
|
|
29
|
+
- `getRuns`, `getRun`
|
|
36
30
|
|
|
37
31
|
### Notifications
|
|
38
|
-
- `run/state`
|
|
39
|
-
- `run/progress` — turn status (thinking/processing).
|
|
40
|
-
- `ui/render` — streaming output.
|
|
41
|
-
- `ui/notify` — toast notification.
|
|
42
|
-
|
|
43
|
-
## Behavior
|
|
44
|
-
|
|
45
|
-
Client operations (read, write, delete, store) build a `RummyContext` for the target run and dispatch through the same handler chain as model operations via `dispatchTool`.
|
|
32
|
+
- `run/state`, `run/progress`, `ui/render`, `ui/notify`
|