@possumtech/rummy 0.2.1
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 +55 -0
- package/LICENSE +21 -0
- package/PLUGINS.md +302 -0
- package/README.md +41 -0
- package/SPEC.md +524 -0
- package/lang/en.json +34 -0
- package/migrations/001_initial_schema.sql +226 -0
- package/package.json +54 -0
- package/service.js +143 -0
- package/src/agent/AgentLoop.js +553 -0
- package/src/agent/ContextAssembler.js +29 -0
- package/src/agent/KnownStore.js +254 -0
- package/src/agent/ProjectAgent.js +101 -0
- package/src/agent/ResponseHealer.js +134 -0
- package/src/agent/TurnExecutor.js +457 -0
- package/src/agent/XmlParser.js +247 -0
- package/src/agent/known_checks.sql +42 -0
- package/src/agent/known_queries.sql +80 -0
- package/src/agent/known_store.sql +161 -0
- package/src/agent/messages.js +17 -0
- package/src/agent/prompt_queue.sql +39 -0
- package/src/agent/runs.sql +114 -0
- package/src/agent/schemes.sql +3 -0
- package/src/agent/sessions.sql +51 -0
- package/src/agent/tokens.js +28 -0
- package/src/agent/turns.sql +36 -0
- package/src/hooks/HookRegistry.js +72 -0
- package/src/hooks/Hooks.js +115 -0
- package/src/hooks/PluginContext.js +116 -0
- package/src/hooks/RummyContext.js +181 -0
- package/src/hooks/ToolRegistry.js +83 -0
- package/src/llm/LlmProvider.js +107 -0
- package/src/llm/OllamaClient.js +88 -0
- package/src/llm/OpenAiClient.js +80 -0
- package/src/llm/OpenRouterClient.js +78 -0
- package/src/llm/XaiClient.js +113 -0
- package/src/plugins/ask_user/README.md +18 -0
- package/src/plugins/ask_user/ask_user.js +48 -0
- package/src/plugins/ask_user/docs.md +2 -0
- package/src/plugins/cp/README.md +18 -0
- package/src/plugins/cp/cp.js +55 -0
- package/src/plugins/cp/docs.md +2 -0
- package/src/plugins/current/README.md +14 -0
- package/src/plugins/current/current.js +48 -0
- package/src/plugins/engine/README.md +12 -0
- package/src/plugins/engine/engine.sql +18 -0
- package/src/plugins/engine/turn_context.sql +51 -0
- package/src/plugins/env/README.md +14 -0
- package/src/plugins/env/docs.md +2 -0
- package/src/plugins/env/env.js +32 -0
- package/src/plugins/file/README.md +25 -0
- package/src/plugins/file/file.js +85 -0
- package/src/plugins/get/README.md +19 -0
- package/src/plugins/get/docs.md +6 -0
- package/src/plugins/get/get.js +53 -0
- package/src/plugins/hedberg/README.md +72 -0
- package/src/plugins/hedberg/docs.md +9 -0
- package/src/plugins/hedberg/edits.js +65 -0
- package/src/plugins/hedberg/hedberg.js +89 -0
- package/src/plugins/hedberg/matcher.js +181 -0
- package/src/plugins/hedberg/normalize.js +41 -0
- package/src/plugins/hedberg/patterns.js +452 -0
- package/src/plugins/hedberg/sed.js +48 -0
- package/src/plugins/helpers.js +22 -0
- package/src/plugins/index.js +180 -0
- package/src/plugins/instructions/README.md +11 -0
- package/src/plugins/instructions/instructions.js +37 -0
- package/src/plugins/instructions/preamble.md +12 -0
- package/src/plugins/known/README.md +18 -0
- package/src/plugins/known/docs.md +3 -0
- package/src/plugins/known/known.js +57 -0
- package/src/plugins/mv/README.md +18 -0
- package/src/plugins/mv/docs.md +2 -0
- package/src/plugins/mv/mv.js +56 -0
- package/src/plugins/previous/README.md +15 -0
- package/src/plugins/previous/previous.js +50 -0
- package/src/plugins/progress/README.md +17 -0
- package/src/plugins/progress/progress.js +44 -0
- package/src/plugins/prompt/README.md +16 -0
- package/src/plugins/prompt/prompt.js +45 -0
- package/src/plugins/rm/README.md +18 -0
- package/src/plugins/rm/docs.md +4 -0
- package/src/plugins/rm/rm.js +51 -0
- package/src/plugins/rpc/README.md +45 -0
- package/src/plugins/rpc/rpc.js +587 -0
- package/src/plugins/set/README.md +32 -0
- package/src/plugins/set/docs.md +4 -0
- package/src/plugins/set/set.js +268 -0
- package/src/plugins/sh/README.md +18 -0
- package/src/plugins/sh/docs.md +2 -0
- package/src/plugins/sh/sh.js +32 -0
- package/src/plugins/skills/README.md +25 -0
- package/src/plugins/skills/skills.js +175 -0
- package/src/plugins/store/README.md +20 -0
- package/src/plugins/store/docs.md +5 -0
- package/src/plugins/store/store.js +52 -0
- package/src/plugins/summarize/README.md +18 -0
- package/src/plugins/summarize/docs.md +4 -0
- package/src/plugins/summarize/summarize.js +24 -0
- package/src/plugins/telemetry/README.md +19 -0
- package/src/plugins/telemetry/rpc_log.sql +28 -0
- package/src/plugins/telemetry/telemetry.js +186 -0
- package/src/plugins/unknown/README.md +23 -0
- package/src/plugins/unknown/docs.md +5 -0
- package/src/plugins/unknown/unknown.js +31 -0
- package/src/plugins/update/README.md +18 -0
- package/src/plugins/update/docs.md +4 -0
- package/src/plugins/update/update.js +24 -0
- package/src/server/ClientConnection.js +228 -0
- package/src/server/RpcRegistry.js +52 -0
- package/src/server/SocketServer.js +43 -0
- package/src/sql/file_constraints.sql +15 -0
- package/src/sql/functions/countTokens.js +7 -0
- package/src/sql/functions/hedmatch.js +8 -0
- package/src/sql/functions/hedreplace.js +8 -0
- package/src/sql/functions/hedsearch.js +8 -0
- package/src/sql/functions/schemeOf.js +7 -0
- package/src/sql/functions/slugify.js +6 -0
- package/src/sql/v_model_context.sql +101 -0
- package/src/sql/v_run_log.sql +23 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const preamble = readFileSync(
|
|
4
|
+
new URL("./preamble.md", import.meta.url),
|
|
5
|
+
"utf8",
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export default class Instructions {
|
|
9
|
+
#core;
|
|
10
|
+
|
|
11
|
+
constructor(core) {
|
|
12
|
+
this.#core = core;
|
|
13
|
+
core.on("full", this.full.bind(this));
|
|
14
|
+
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async onTurnStarted({ rummy }) {
|
|
18
|
+
const { entries: store, sequence: turn, runId } = rummy;
|
|
19
|
+
const runRow = await rummy.db.get_run_by_id.get({ id: runId });
|
|
20
|
+
await store.upsert(runId, turn, "instructions://system", "", "info", {
|
|
21
|
+
attributes: { persona: runRow?.persona || null },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async full(entry) {
|
|
26
|
+
const attrs = entry.attributes;
|
|
27
|
+
const tools = this.#core.hooks.tools.names.join(", ");
|
|
28
|
+
let prompt = preamble.replace("[%TOOLS%]", tools);
|
|
29
|
+
const toolDocs = await this.#core.hooks.instructions.toolDocs.filter(
|
|
30
|
+
"",
|
|
31
|
+
{},
|
|
32
|
+
);
|
|
33
|
+
if (toolDocs) prompt += `\n\n${toolDocs}`;
|
|
34
|
+
if (attrs.persona) prompt += `\n\n## Persona\n\n${attrs.persona}`;
|
|
35
|
+
return prompt;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You are an assistant. You gather information, then either answer questions or take action.
|
|
2
|
+
|
|
3
|
+
# Response Rules
|
|
4
|
+
|
|
5
|
+
* You must register unknowns with <unknown>(thing I don't know yet)</unknown> before acting.
|
|
6
|
+
* Save known information with <known>(thing I know now)</known>.
|
|
7
|
+
* Respond with Tool Commands. You may use multiple tools in your response.
|
|
8
|
+
|
|
9
|
+
# Tool Commands
|
|
10
|
+
|
|
11
|
+
Tools: [%TOOLS%]
|
|
12
|
+
Required: Either `<update/>` if still working or `<summarize/>` if done. Never both.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# known
|
|
2
|
+
|
|
3
|
+
Writes arbitrary key/value entries into the store at full fidelity.
|
|
4
|
+
|
|
5
|
+
## Registration
|
|
6
|
+
|
|
7
|
+
- **Tool**: `known`
|
|
8
|
+
- **Modes**: ask, act
|
|
9
|
+
- **Category**: act
|
|
10
|
+
- **Handler**: Upserts the entry body at the target path with `full` state.
|
|
11
|
+
|
|
12
|
+
## Projection
|
|
13
|
+
|
|
14
|
+
Shows `known {path}` followed by the entry body.
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
The target path defaults to `entry.resultPath` but can be overridden via `attrs.path`. Used by the model to persist structured notes and context.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export default class Known {
|
|
4
|
+
#core;
|
|
5
|
+
|
|
6
|
+
constructor(core) {
|
|
7
|
+
this.#core = core;
|
|
8
|
+
core.registerScheme({
|
|
9
|
+
fidelity: "turn",
|
|
10
|
+
validStates: ["full", "stored"],
|
|
11
|
+
category: "knowledge",
|
|
12
|
+
});
|
|
13
|
+
core.on("handler", this.handler.bind(this));
|
|
14
|
+
core.on("full", this.full.bind(this));
|
|
15
|
+
core.filter("assembly.system", this.assembleKnown.bind(this), 100);
|
|
16
|
+
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
17
|
+
core.filter("instructions.toolDocs", async (content) =>
|
|
18
|
+
content ? `${content}\n\n${docs}` : docs,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async handler(entry, rummy) {
|
|
23
|
+
const { entries: store, sequence: turn, runId } = rummy;
|
|
24
|
+
const target = entry.attributes.path || entry.resultPath;
|
|
25
|
+
await store.upsert(runId, turn, target, entry.body, "full");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
full(entry) {
|
|
29
|
+
return `# known ${entry.path}\n${entry.body}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async assembleKnown(content, ctx) {
|
|
33
|
+
const entries = ctx.rows.filter(
|
|
34
|
+
(r) =>
|
|
35
|
+
r.category === "file" ||
|
|
36
|
+
r.category === "file_index" ||
|
|
37
|
+
r.category === "known" ||
|
|
38
|
+
r.category === "known_index",
|
|
39
|
+
);
|
|
40
|
+
if (entries.length === 0) return content;
|
|
41
|
+
|
|
42
|
+
// Rows arrive pre-sorted by SQL: skill → index → summary → full, then by recency
|
|
43
|
+
const lines = entries.map((e) => renderKnownTag(e));
|
|
44
|
+
return `${content}\n\n<knowns>\n${lines.join("\n")}\n</knowns>`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderKnownTag(entry) {
|
|
49
|
+
const tokens = entry.tokens ? ` tokens="${entry.tokens}"` : "";
|
|
50
|
+
const state = entry.state ? ` state="${entry.state}"` : "";
|
|
51
|
+
|
|
52
|
+
if (entry.body) {
|
|
53
|
+
return `<known path="${entry.path}"${state}${tokens}>${entry.body}</known>`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return `<known path="${entry.path}"${state}${tokens}/>`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# mv
|
|
2
|
+
|
|
3
|
+
Moves (renames) an entry from one path to another within the K/V store.
|
|
4
|
+
|
|
5
|
+
## Registration
|
|
6
|
+
|
|
7
|
+
- **Tool**: `mv`
|
|
8
|
+
- **Modes**: ask, act
|
|
9
|
+
- **Category**: act
|
|
10
|
+
- **Handler**: Reads source body, writes to destination, removes source. K/V destinations resolve immediately (`pass`); file destinations produce a `proposed` entry.
|
|
11
|
+
|
|
12
|
+
## Projection
|
|
13
|
+
|
|
14
|
+
Shows `mv {from} {to}`.
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
Warns if the destination already exists and will be overwritten. Uses `KnownStore.scheme()` to determine K/V vs file paths. Source entry is removed on successful K/V moves.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import KnownStore from "../../agent/KnownStore.js";
|
|
3
|
+
|
|
4
|
+
export default class Mv {
|
|
5
|
+
#core;
|
|
6
|
+
|
|
7
|
+
constructor(core) {
|
|
8
|
+
this.#core = core;
|
|
9
|
+
core.registerScheme({
|
|
10
|
+
validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
|
|
11
|
+
});
|
|
12
|
+
core.on("handler", this.handler.bind(this));
|
|
13
|
+
core.on("full", this.full.bind(this));
|
|
14
|
+
core.on("summary", this.summary.bind(this));
|
|
15
|
+
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
16
|
+
core.filter("instructions.toolDocs", async (content) =>
|
|
17
|
+
content ? `${content}\n\n${docs}` : docs,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async handler(entry, rummy) {
|
|
22
|
+
const { entries: store, sequence: turn, runId } = rummy;
|
|
23
|
+
const { path, to } = entry.attributes;
|
|
24
|
+
|
|
25
|
+
const source = await store.getBody(runId, path);
|
|
26
|
+
if (source === null) return;
|
|
27
|
+
|
|
28
|
+
const destScheme = KnownStore.scheme(to);
|
|
29
|
+
const existing = await store.getBody(runId, to);
|
|
30
|
+
const warning =
|
|
31
|
+
existing !== null && destScheme !== null
|
|
32
|
+
? `Overwrote existing entry at ${to}`
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
const body = `${path} ${to}`;
|
|
36
|
+
if (destScheme === null) {
|
|
37
|
+
await store.upsert(runId, turn, entry.resultPath, body, "proposed", {
|
|
38
|
+
attributes: { from: path, to, isMove: true, warning },
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
await store.upsert(runId, turn, to, source, "full");
|
|
42
|
+
await store.remove(runId, path);
|
|
43
|
+
await store.upsert(runId, turn, entry.resultPath, body, "pass", {
|
|
44
|
+
attributes: { from: path, to, isMove: true, warning },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
full(entry) {
|
|
50
|
+
return `# mv ${entry.attributes.from || ""} ${entry.attributes.to || ""}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
summary(entry) {
|
|
54
|
+
return this.full(entry);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# previous
|
|
2
|
+
|
|
3
|
+
Renders the `<previous>` section of the system message — completed loop
|
|
4
|
+
history from prior ask/act invocations on this run.
|
|
5
|
+
|
|
6
|
+
## Registration
|
|
7
|
+
|
|
8
|
+
- **Filter**: `assembly.system` at priority 200
|
|
9
|
+
- **Condition**: Omitted when `loopStartTurn <= 1` (first loop has no history)
|
|
10
|
+
|
|
11
|
+
## Behavior
|
|
12
|
+
|
|
13
|
+
Filters turn_context rows where `category` is `result` or `structural`
|
|
14
|
+
and `source_turn < loopStartTurn`. Renders each entry chronologically
|
|
15
|
+
with status symbols (✓/✗/·).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export default class Previous {
|
|
2
|
+
#core;
|
|
3
|
+
|
|
4
|
+
constructor(core) {
|
|
5
|
+
this.#core = core;
|
|
6
|
+
core.filter("assembly.system", this.assemblePrevious.bind(this), 200);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async assemblePrevious(content, ctx) {
|
|
10
|
+
if (ctx.loopStartTurn <= 1) return content;
|
|
11
|
+
|
|
12
|
+
const entries = ctx.rows.filter(
|
|
13
|
+
(r) =>
|
|
14
|
+
(r.category === "result" || r.category === "structural") &&
|
|
15
|
+
r.source_turn < ctx.loopStartTurn,
|
|
16
|
+
);
|
|
17
|
+
if (entries.length === 0) return content;
|
|
18
|
+
|
|
19
|
+
const lines = await Promise.all(
|
|
20
|
+
entries.map((e) => renderToolTag(e, "summary", this.#core)),
|
|
21
|
+
);
|
|
22
|
+
return `${content}\n\n<previous>\n${lines.join("\n")}\n</previous>`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function renderToolTag(entry, fidelity, core) {
|
|
27
|
+
const attrs =
|
|
28
|
+
typeof entry.attributes === "string"
|
|
29
|
+
? JSON.parse(entry.attributes)
|
|
30
|
+
: entry.attributes;
|
|
31
|
+
|
|
32
|
+
const path = `${entry.scheme}://${attrs?.path || attrs?.file || attrs?.command || ""}`;
|
|
33
|
+
const status = entry.state ? ` status="${entry.state}"` : "";
|
|
34
|
+
|
|
35
|
+
let body;
|
|
36
|
+
try {
|
|
37
|
+
body = await core.hooks.tools.view(entry.scheme, {
|
|
38
|
+
...entry,
|
|
39
|
+
fidelity,
|
|
40
|
+
attributes: attrs,
|
|
41
|
+
});
|
|
42
|
+
} catch {
|
|
43
|
+
body = entry.body;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (body) {
|
|
47
|
+
return `<tool path="${path}"${status}>${body}</tool>`;
|
|
48
|
+
}
|
|
49
|
+
return `<tool path="${path}"${status}/>`;
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# progress
|
|
2
|
+
|
|
3
|
+
Renders the `<progress>` section of the user message — bridges the
|
|
4
|
+
current work log to the active prompt.
|
|
5
|
+
|
|
6
|
+
## Registration
|
|
7
|
+
|
|
8
|
+
- **Filter**: `assembly.user` at priority 200
|
|
9
|
+
|
|
10
|
+
## Behavior
|
|
11
|
+
|
|
12
|
+
On first turn: "Begin."
|
|
13
|
+
On continuation turns with current entries: "The above actions were
|
|
14
|
+
performed in response to the following prompt:"
|
|
15
|
+
If a `progress://` entry exists, uses its body directly.
|
|
16
|
+
|
|
17
|
+
Progress text is the tuning knob for model orientation between turns.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export default class Progress {
|
|
2
|
+
#core;
|
|
3
|
+
|
|
4
|
+
constructor(core) {
|
|
5
|
+
this.#core = core;
|
|
6
|
+
core.filter("assembly.user", this.assembleProgress.bind(this), 200);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async assembleProgress(content, ctx) {
|
|
10
|
+
const usedTokens = ctx.rows.reduce((sum, r) => sum + (r.tokens || 0), 0);
|
|
11
|
+
const contextSize = ctx.contextSize || 0;
|
|
12
|
+
const pct = contextSize ? Math.round((usedTokens / contextSize) * 100) : 0;
|
|
13
|
+
|
|
14
|
+
const unknownCount = ctx.rows.filter(
|
|
15
|
+
(r) => r.category === "unknown",
|
|
16
|
+
).length;
|
|
17
|
+
|
|
18
|
+
const hasCurrent = ctx.rows.some(
|
|
19
|
+
(r) =>
|
|
20
|
+
(r.category === "result" || r.category === "structural") &&
|
|
21
|
+
r.source_turn >= ctx.loopStartTurn,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const parts = [];
|
|
25
|
+
|
|
26
|
+
const tokenInfo = contextSize
|
|
27
|
+
? `${usedTokens} of ${contextSize} tokens (${pct}%)`
|
|
28
|
+
: "";
|
|
29
|
+
const unknownInfo =
|
|
30
|
+
unknownCount > 0
|
|
31
|
+
? `${unknownCount} unknown${unknownCount > 1 ? "s" : ""} remaining`
|
|
32
|
+
: "0 unknowns";
|
|
33
|
+
const status = [tokenInfo, unknownInfo].filter(Boolean).join(" · ");
|
|
34
|
+
if (status) parts.push(status);
|
|
35
|
+
|
|
36
|
+
if (hasCurrent) {
|
|
37
|
+
parts.push(
|
|
38
|
+
"The above actions were performed in response to the following prompt:",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return `${content}<progress>${parts.join("\n")}</progress>\n`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# prompt
|
|
2
|
+
|
|
3
|
+
Renders the `<ask>` or `<act>` tag at the end of the user message.
|
|
4
|
+
Always present on every turn — the model always sees its task.
|
|
5
|
+
|
|
6
|
+
## Registration
|
|
7
|
+
|
|
8
|
+
- **Filter**: `assembly.user` at priority 300 (always last)
|
|
9
|
+
|
|
10
|
+
## Behavior
|
|
11
|
+
|
|
12
|
+
Finds the latest `ask://` or `act://` entry in the turn_context rows.
|
|
13
|
+
Renders with `tools` attribute (available tool list) and optional `warn`
|
|
14
|
+
attribute in ask mode ("File and system modification prohibited on this
|
|
15
|
+
turn."). Falls back to the mode passed by the core if no prompt entry
|
|
16
|
+
exists.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export default class Prompt {
|
|
2
|
+
#core;
|
|
3
|
+
|
|
4
|
+
constructor(core) {
|
|
5
|
+
this.#core = core;
|
|
6
|
+
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
7
|
+
core.filter("assembly.user", this.assemblePrompt.bind(this), 300);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async onTurnStarted({ rummy, mode, prompt, isContinuation }) {
|
|
11
|
+
const { entries: store, sequence: turn, runId } = rummy;
|
|
12
|
+
|
|
13
|
+
if (!isContinuation && prompt) {
|
|
14
|
+
await store.upsert(runId, turn, `prompt://${turn}`, "", "info", {
|
|
15
|
+
attributes: { mode },
|
|
16
|
+
});
|
|
17
|
+
await store.upsert(runId, turn, `${mode}://${turn}`, prompt, "info");
|
|
18
|
+
} else {
|
|
19
|
+
await store.upsert(
|
|
20
|
+
runId,
|
|
21
|
+
turn,
|
|
22
|
+
`progress://${turn}`,
|
|
23
|
+
prompt || "",
|
|
24
|
+
"info",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async assemblePrompt(content, ctx) {
|
|
30
|
+
const promptEntry = ctx.rows.findLast(
|
|
31
|
+
(r) =>
|
|
32
|
+
r.category === "prompt" && (r.scheme === "ask" || r.scheme === "act"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const mode = promptEntry?.scheme || ctx.type;
|
|
36
|
+
const body = promptEntry?.body || "";
|
|
37
|
+
const tools = this.#core.hooks.tools.namesForMode(mode).join(", ");
|
|
38
|
+
const warn =
|
|
39
|
+
mode === "ask"
|
|
40
|
+
? ' warn="File and system modification prohibited on this turn."'
|
|
41
|
+
: "";
|
|
42
|
+
|
|
43
|
+
return `${content}<${mode} tools="${tools}"${warn}>${body}</${mode}>`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# rm
|
|
2
|
+
|
|
3
|
+
Removes entries by path or glob pattern.
|
|
4
|
+
|
|
5
|
+
## Registration
|
|
6
|
+
|
|
7
|
+
- **Tool**: `rm`
|
|
8
|
+
- **Modes**: ask, act
|
|
9
|
+
- **Category**: act
|
|
10
|
+
- **Handler**: Matches entries by pattern. K/V entries are removed immediately (`pass`); file entries produce `proposed` state for client approval.
|
|
11
|
+
|
|
12
|
+
## Projection
|
|
13
|
+
|
|
14
|
+
Shows `rm {path}`.
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
Supports glob patterns and body filters via `getEntriesByPattern`. Each matched entry is processed independently.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export default class Rm {
|
|
4
|
+
#core;
|
|
5
|
+
|
|
6
|
+
constructor(core) {
|
|
7
|
+
this.#core = core;
|
|
8
|
+
core.registerScheme({
|
|
9
|
+
validStates: ["full", "proposed", "pass", "rejected", "error", "pattern"],
|
|
10
|
+
});
|
|
11
|
+
core.on("handler", this.handler.bind(this));
|
|
12
|
+
core.on("full", this.full.bind(this));
|
|
13
|
+
core.on("summary", this.summary.bind(this));
|
|
14
|
+
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
15
|
+
core.filter("instructions.toolDocs", async (content) =>
|
|
16
|
+
content ? `${content}\n\n${docs}` : docs,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async handler(entry, rummy) {
|
|
21
|
+
const { entries: store, sequence: turn, runId } = rummy;
|
|
22
|
+
const target = entry.attributes.path;
|
|
23
|
+
const matches = await store.getEntriesByPattern(
|
|
24
|
+
runId,
|
|
25
|
+
target,
|
|
26
|
+
entry.attributes.body,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
for (const match of matches) {
|
|
30
|
+
const resultPath = `rm://${match.path}`;
|
|
31
|
+
if (match.scheme === null) {
|
|
32
|
+
await store.upsert(runId, turn, resultPath, match.path, "proposed", {
|
|
33
|
+
attributes: { path: match.path },
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
await store.remove(runId, match.path);
|
|
37
|
+
await store.upsert(runId, turn, resultPath, match.path, "pass", {
|
|
38
|
+
attributes: { path: match.path },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
full(entry) {
|
|
45
|
+
return `# rm ${entry.attributes.path || entry.path}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
summary(entry) {
|
|
49
|
+
return this.full(entry);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# rpc
|
|
2
|
+
|
|
3
|
+
Registers all core RPC methods and dispatches client operations through the tool handler chain.
|
|
4
|
+
|
|
5
|
+
## Registration
|
|
6
|
+
|
|
7
|
+
- **No tool handler** — this plugin registers RPC methods on `hooks.rpc.registry`, not tool handlers.
|
|
8
|
+
|
|
9
|
+
## RPC Methods
|
|
10
|
+
|
|
11
|
+
### Protocol
|
|
12
|
+
- `ping` — liveness check.
|
|
13
|
+
- `discover` — returns method/notification catalog.
|
|
14
|
+
- `init` — initialize project (sets projectId, projectRoot, configPath).
|
|
15
|
+
|
|
16
|
+
### Models
|
|
17
|
+
- `getModels`, `addModel`, `removeModel` — CRUD for model aliases.
|
|
18
|
+
|
|
19
|
+
### Entry Operations
|
|
20
|
+
- `read` — promote entry to full state, or create persistent file constraint.
|
|
21
|
+
- `store` — demote entry to stored state, or manage file constraints (ignore/clear).
|
|
22
|
+
- `write` — create/update entry. K/V paths write directly; file paths dispatch through `set` handler.
|
|
23
|
+
- `delete` — remove entry via `rm` handler dispatch.
|
|
24
|
+
- `getEntries` — query entries by glob pattern.
|
|
25
|
+
|
|
26
|
+
### Runs
|
|
27
|
+
- `startRun` — pre-create a run with model/config.
|
|
28
|
+
- `ask` — non-mutating model query.
|
|
29
|
+
- `act` — mutating model directive.
|
|
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.
|
|
36
|
+
|
|
37
|
+
### Notifications
|
|
38
|
+
- `run/state` — turn state update with history, unknowns, proposed, telemetry.
|
|
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`.
|