@possumtech/rummy 0.2.8 → 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 +11 -1
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +422 -188
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +6 -4
- package/package.json +13 -5
- package/src/agent/AgentLoop.js +166 -15
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +127 -13
- package/src/agent/ProjectAgent.js +4 -1
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +365 -175
- package/src/agent/XmlParser.js +72 -39
- package/src/agent/known_store.sql +20 -4
- package/src/agent/schemes.sql +3 -0
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +10 -1
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -1
- package/src/hooks/RummyContext.js +16 -4
- 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 +5 -5
- 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 +10 -6
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +9 -6
- package/src/plugins/engine/engine.sql +1 -8
- package/src/plugins/engine/turn_context.sql +4 -9
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +5 -5
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -35
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +6 -5
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/hedberg.js +2 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +25 -27
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/index.js +66 -14
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +20 -4
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +29 -17
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +10 -6
- 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 +9 -6
- package/src/plugins/progress/progress.js +41 -15
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +18 -13
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +5 -5
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +42 -77
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +60 -5
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +5 -5
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +10 -51
- 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 +3 -1
- 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 +9 -7
- 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/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +16 -21
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -4
- package/src/plugins/get/docs.md +0 -10
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -6
- package/src/plugins/set/docs.md +0 -6
- 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 -6
- package/src/plugins/store/store.js +0 -63
- package/src/plugins/summarize/docs.md +0 -4
- package/src/plugins/unknown/docs.md +0 -5
- package/src/plugins/update/docs.md +0 -4
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import docs from "./knownDoc.js";
|
|
2
2
|
|
|
3
3
|
export default class Known {
|
|
4
4
|
#core;
|
|
5
5
|
|
|
6
6
|
constructor(core) {
|
|
7
7
|
this.#core = core;
|
|
8
|
-
core.registerScheme({ category: "
|
|
8
|
+
core.registerScheme({ category: "data" });
|
|
9
9
|
core.on("handler", this.handler.bind(this));
|
|
10
10
|
core.on("full", this.full.bind(this));
|
|
11
11
|
core.filter("assembly.system", this.assembleKnown.bind(this), 100);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
);
|
|
12
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
|
+
docsMap.known = docs;
|
|
14
|
+
return docsMap;
|
|
15
|
+
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async handler(entry, rummy) {
|
|
@@ -26,30 +26,42 @@ export default class Known {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
async assembleKnown(content, ctx) {
|
|
29
|
-
const entries = ctx.rows.filter(
|
|
30
|
-
(r) =>
|
|
31
|
-
r.category === "file" ||
|
|
32
|
-
r.category === "file_index" ||
|
|
33
|
-
r.category === "known" ||
|
|
34
|
-
r.category === "known_index",
|
|
35
|
-
);
|
|
29
|
+
const entries = ctx.rows.filter((r) => r.category === "data");
|
|
36
30
|
if (entries.length === 0) return content;
|
|
37
31
|
|
|
38
32
|
// Rows arrive pre-sorted by SQL: skill → index → summary → full, then by recency
|
|
39
33
|
const demotedSet = new Set(ctx.demoted || []);
|
|
40
|
-
const
|
|
34
|
+
const panic = ctx.type === "panic";
|
|
35
|
+
const lines = entries.map((e) => renderKnownTag(e, demotedSet, panic));
|
|
41
36
|
return `${content}\n\n<knowns>\n${lines.join("\n")}\n</knowns>`;
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
function renderKnownTag(entry, demotedSet) {
|
|
40
|
+
function renderKnownTag(entry, demotedSet, panic = false) {
|
|
41
|
+
const tag = entry.scheme || "file";
|
|
42
|
+
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
46
43
|
const tokens = entry.tokens ? ` tokens="${entry.tokens}"` : "";
|
|
47
44
|
const status = entry.status ? ` status="${entry.status}"` : "";
|
|
45
|
+
const fidelity = entry.fidelity ? ` fidelity="${entry.fidelity}"` : "";
|
|
48
46
|
const flag = demotedSet?.has(entry.path) ? " demoted" : "";
|
|
49
47
|
|
|
48
|
+
// Panic mode: index-only view so context fits in LLM window
|
|
49
|
+
if (panic) {
|
|
50
|
+
return `<${tag} path="${entry.path}"${turn}${fidelity}${tokens}/>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const attrs =
|
|
54
|
+
typeof entry.attributes === "string"
|
|
55
|
+
? JSON.parse(entry.attributes)
|
|
56
|
+
: entry.attributes;
|
|
57
|
+
const summary =
|
|
58
|
+
typeof attrs?.summary === "string"
|
|
59
|
+
? ` summary="${attrs.summary.slice(0, 80)}"`
|
|
60
|
+
: "";
|
|
61
|
+
|
|
50
62
|
if (entry.body) {
|
|
51
|
-
return
|
|
63
|
+
return `<${tag} path="${entry.path}"${turn}${status}${fidelity}${summary}${tokens}${flag}>${entry.body}</${tag}>`;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
|
-
return
|
|
66
|
+
return `<${tag} path="${entry.path}"${turn}${status}${fidelity}${summary}${tokens}${flag}/>`;
|
|
55
67
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Tool doc for <known/>. 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: body = the information to save
|
|
6
|
+
[
|
|
7
|
+
"## <known>[specific information, ideas, or plans]</known> - Sort and save what you learn for later recall",
|
|
8
|
+
],
|
|
9
|
+
// --- Examples: summary-with-keywords first (teaches the right pattern)
|
|
10
|
+
[
|
|
11
|
+
'Example: <known summary="hedberg,comedian,death,2005">Mitch Hedberg died on March 30, 2005</known>',
|
|
12
|
+
"Primary pattern: comma-separated keywords in summary. Path auto-generated from summary as known://hedberg/comedian/death/2005. Keywords become searchable path segments.",
|
|
13
|
+
],
|
|
14
|
+
[
|
|
15
|
+
'Example: <known path="known://people/rumsfeld" summary="defense,secretary,born,1932">Donald Rumsfeld was born in 1932 and served as Secretary of Defense</known>',
|
|
16
|
+
"Explicit path form: slashed path=category/key, summary=keywords. For when the model wants direct control over taxonomy.",
|
|
17
|
+
],
|
|
18
|
+
// --- Lifecycle
|
|
19
|
+
[
|
|
20
|
+
'* Recall with <get path="known://people/*">keyword</get>',
|
|
21
|
+
"Cross-tool lifecycle: glob by category, filter by keyword. Matches the slashed path convention.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* `summary` keywords survive compression — write keywords you'll search for later",
|
|
25
|
+
"Teaches WHY summaries matter. Keywords become the path AND the compressed view.",
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"* YOU MUST sort and save all new information, ideas, and plans in their own <known> entries",
|
|
29
|
+
"Critical behavioral constraint. 'new' prevents re-saving known facts.",
|
|
30
|
+
],
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export default LINES.map(([text]) => text).join("\n");
|
package/src/plugins/mv/README.md
CHANGED
|
@@ -5,9 +5,8 @@ Moves (renames) an entry from one path to another within the K/V store.
|
|
|
5
5
|
## Registration
|
|
6
6
|
|
|
7
7
|
- **Tool**: `mv`
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **Handler**: Reads source body, writes to destination, removes source. K/V destinations resolve immediately (`pass`); file destinations produce a `proposed` entry.
|
|
8
|
+
- **Category**: `logging`
|
|
9
|
+
- **Handler**: Reads source body, writes to destination, removes source. Scheme destinations resolve immediately (status 200); file destinations produce status 202 (proposed).
|
|
11
10
|
|
|
12
11
|
## Projection
|
|
13
12
|
|
|
@@ -15,4 +14,6 @@ Shows `mv {from} {to}`.
|
|
|
15
14
|
|
|
16
15
|
## Behavior
|
|
17
16
|
|
|
18
|
-
Warns if the destination already exists and will be overwritten. Uses
|
|
17
|
+
Warns if the destination already exists and will be overwritten. Uses
|
|
18
|
+
`KnownStore.scheme()` to determine scheme vs file paths. Source entry
|
|
19
|
+
is removed on successful scheme moves.
|
package/src/plugins/mv/mv.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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;
|
|
@@ -10,15 +10,19 @@ export default class Mv {
|
|
|
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));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
13
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
|
+
docsMap.mv = docs;
|
|
15
|
+
return docsMap;
|
|
16
|
+
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async handler(entry, rummy) {
|
|
20
20
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
21
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;
|
|
22
26
|
|
|
23
27
|
const source = await store.getBody(runId, path);
|
|
24
28
|
if (source === null) return;
|
|
@@ -37,7 +41,7 @@ export default class Mv {
|
|
|
37
41
|
loopId,
|
|
38
42
|
});
|
|
39
43
|
} else {
|
|
40
|
-
await store.upsert(runId, turn, to, source, 200, { loopId });
|
|
44
|
+
await store.upsert(runId, turn, to, source, 200, { fidelity, loopId });
|
|
41
45
|
await store.remove(runId, path);
|
|
42
46
|
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
43
47
|
attributes: { from: path, to, isMove: true, warning },
|
|
@@ -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,9 +11,7 @@ export default class Previous {
|
|
|
11
11
|
|
|
12
12
|
const entries = ctx.rows.filter(
|
|
13
13
|
(r) =>
|
|
14
|
-
(r.category === "
|
|
15
|
-
r.category === "structural" ||
|
|
16
|
-
r.category === "prompt") &&
|
|
14
|
+
(r.category === "logging" || r.category === "prompt") &&
|
|
17
15
|
r.source_turn < ctx.loopStartTurn,
|
|
18
16
|
);
|
|
19
17
|
if (entries.length === 0) return content;
|
|
@@ -31,8 +29,13 @@ async function renderToolTag(entry, core) {
|
|
|
31
29
|
? JSON.parse(entry.attributes)
|
|
32
30
|
: entry.attributes;
|
|
33
31
|
|
|
34
|
-
const
|
|
32
|
+
const target = attrs?.path || attrs?.file || attrs?.command || "";
|
|
33
|
+
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
35
34
|
const status = entry.status ? ` status="${entry.status}"` : "";
|
|
35
|
+
const summary =
|
|
36
|
+
typeof attrs?.summary === "string"
|
|
37
|
+
? ` summary="${attrs.summary.slice(0, 80)}"`
|
|
38
|
+
: "";
|
|
36
39
|
|
|
37
40
|
let body;
|
|
38
41
|
try {
|
|
@@ -45,7 +48,7 @@ async function renderToolTag(entry, core) {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
if (body) {
|
|
48
|
-
return
|
|
51
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}>${body}</${entry.scheme}>`;
|
|
49
52
|
}
|
|
50
|
-
return
|
|
53
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}/>`;
|
|
51
54
|
}
|
|
@@ -7,35 +7,61 @@ 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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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) {
|
|
37
63
|
parts.push(
|
|
38
|
-
|
|
64
|
+
'Context above 50%. You may free space: <set fidelity="summary" summary="topic,detail,keyword"/>, <set fidelity="archive"/>, or <rm/>.',
|
|
39
65
|
);
|
|
40
66
|
}
|
|
41
67
|
|
|
@@ -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,6 +3,8 @@ 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
|
}
|
|
@@ -11,13 +13,10 @@ export default class Prompt {
|
|
|
11
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 },
|
|
16
18
|
loopId,
|
|
17
19
|
});
|
|
18
|
-
await store.upsert(runId, turn, `${mode}://${turn}`, prompt, 200, {
|
|
19
|
-
loopId,
|
|
20
|
-
});
|
|
21
20
|
} else {
|
|
22
21
|
await store.upsert(runId, turn, `progress://${turn}`, prompt || "", 200, {
|
|
23
22
|
loopId,
|
|
@@ -27,18 +26,24 @@ export default class Prompt {
|
|
|
27
26
|
|
|
28
27
|
async assemblePrompt(content, ctx) {
|
|
29
28
|
const promptEntry = ctx.rows.findLast(
|
|
30
|
-
(r) =>
|
|
31
|
-
r.category === "prompt" && (r.scheme === "ask" || r.scheme === "act"),
|
|
29
|
+
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
32
30
|
);
|
|
33
31
|
|
|
34
|
-
const
|
|
32
|
+
const attrs =
|
|
33
|
+
typeof promptEntry?.attributes === "string"
|
|
34
|
+
? JSON.parse(promptEntry.attributes)
|
|
35
|
+
: promptEntry?.attributes;
|
|
36
|
+
const mode = attrs?.mode || ctx.type;
|
|
35
37
|
const body = promptEntry?.body || "";
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
mode
|
|
39
|
-
|
|
40
|
-
|
|
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."';
|
|
41
46
|
|
|
42
|
-
return `${content}
|
|
47
|
+
return `${content}<prompt mode="${mode}" tools="${tools}"${warn}>${body}</prompt>`;
|
|
43
48
|
}
|
|
44
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,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
import docs from "./rmDoc.js";
|
|
3
3
|
|
|
4
4
|
export default class Rm {
|
|
5
5
|
#core;
|
|
@@ -10,10 +10,10 @@ export default class Rm {
|
|
|
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));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
13
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
|
+
docsMap.rm = docs;
|
|
15
|
+
return docsMap;
|
|
16
|
+
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async handler(entry, rummy) {
|
|
@@ -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`
|