@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.
Files changed (120) hide show
  1. package/.env.example +55 -0
  2. package/LICENSE +21 -0
  3. package/PLUGINS.md +302 -0
  4. package/README.md +41 -0
  5. package/SPEC.md +524 -0
  6. package/lang/en.json +34 -0
  7. package/migrations/001_initial_schema.sql +226 -0
  8. package/package.json +54 -0
  9. package/service.js +143 -0
  10. package/src/agent/AgentLoop.js +553 -0
  11. package/src/agent/ContextAssembler.js +29 -0
  12. package/src/agent/KnownStore.js +254 -0
  13. package/src/agent/ProjectAgent.js +101 -0
  14. package/src/agent/ResponseHealer.js +134 -0
  15. package/src/agent/TurnExecutor.js +457 -0
  16. package/src/agent/XmlParser.js +247 -0
  17. package/src/agent/known_checks.sql +42 -0
  18. package/src/agent/known_queries.sql +80 -0
  19. package/src/agent/known_store.sql +161 -0
  20. package/src/agent/messages.js +17 -0
  21. package/src/agent/prompt_queue.sql +39 -0
  22. package/src/agent/runs.sql +114 -0
  23. package/src/agent/schemes.sql +3 -0
  24. package/src/agent/sessions.sql +51 -0
  25. package/src/agent/tokens.js +28 -0
  26. package/src/agent/turns.sql +36 -0
  27. package/src/hooks/HookRegistry.js +72 -0
  28. package/src/hooks/Hooks.js +115 -0
  29. package/src/hooks/PluginContext.js +116 -0
  30. package/src/hooks/RummyContext.js +181 -0
  31. package/src/hooks/ToolRegistry.js +83 -0
  32. package/src/llm/LlmProvider.js +107 -0
  33. package/src/llm/OllamaClient.js +88 -0
  34. package/src/llm/OpenAiClient.js +80 -0
  35. package/src/llm/OpenRouterClient.js +78 -0
  36. package/src/llm/XaiClient.js +113 -0
  37. package/src/plugins/ask_user/README.md +18 -0
  38. package/src/plugins/ask_user/ask_user.js +48 -0
  39. package/src/plugins/ask_user/docs.md +2 -0
  40. package/src/plugins/cp/README.md +18 -0
  41. package/src/plugins/cp/cp.js +55 -0
  42. package/src/plugins/cp/docs.md +2 -0
  43. package/src/plugins/current/README.md +14 -0
  44. package/src/plugins/current/current.js +48 -0
  45. package/src/plugins/engine/README.md +12 -0
  46. package/src/plugins/engine/engine.sql +18 -0
  47. package/src/plugins/engine/turn_context.sql +51 -0
  48. package/src/plugins/env/README.md +14 -0
  49. package/src/plugins/env/docs.md +2 -0
  50. package/src/plugins/env/env.js +32 -0
  51. package/src/plugins/file/README.md +25 -0
  52. package/src/plugins/file/file.js +85 -0
  53. package/src/plugins/get/README.md +19 -0
  54. package/src/plugins/get/docs.md +6 -0
  55. package/src/plugins/get/get.js +53 -0
  56. package/src/plugins/hedberg/README.md +72 -0
  57. package/src/plugins/hedberg/docs.md +9 -0
  58. package/src/plugins/hedberg/edits.js +65 -0
  59. package/src/plugins/hedberg/hedberg.js +89 -0
  60. package/src/plugins/hedberg/matcher.js +181 -0
  61. package/src/plugins/hedberg/normalize.js +41 -0
  62. package/src/plugins/hedberg/patterns.js +452 -0
  63. package/src/plugins/hedberg/sed.js +48 -0
  64. package/src/plugins/helpers.js +22 -0
  65. package/src/plugins/index.js +180 -0
  66. package/src/plugins/instructions/README.md +11 -0
  67. package/src/plugins/instructions/instructions.js +37 -0
  68. package/src/plugins/instructions/preamble.md +12 -0
  69. package/src/plugins/known/README.md +18 -0
  70. package/src/plugins/known/docs.md +3 -0
  71. package/src/plugins/known/known.js +57 -0
  72. package/src/plugins/mv/README.md +18 -0
  73. package/src/plugins/mv/docs.md +2 -0
  74. package/src/plugins/mv/mv.js +56 -0
  75. package/src/plugins/previous/README.md +15 -0
  76. package/src/plugins/previous/previous.js +50 -0
  77. package/src/plugins/progress/README.md +17 -0
  78. package/src/plugins/progress/progress.js +44 -0
  79. package/src/plugins/prompt/README.md +16 -0
  80. package/src/plugins/prompt/prompt.js +45 -0
  81. package/src/plugins/rm/README.md +18 -0
  82. package/src/plugins/rm/docs.md +4 -0
  83. package/src/plugins/rm/rm.js +51 -0
  84. package/src/plugins/rpc/README.md +45 -0
  85. package/src/plugins/rpc/rpc.js +587 -0
  86. package/src/plugins/set/README.md +32 -0
  87. package/src/plugins/set/docs.md +4 -0
  88. package/src/plugins/set/set.js +268 -0
  89. package/src/plugins/sh/README.md +18 -0
  90. package/src/plugins/sh/docs.md +2 -0
  91. package/src/plugins/sh/sh.js +32 -0
  92. package/src/plugins/skills/README.md +25 -0
  93. package/src/plugins/skills/skills.js +175 -0
  94. package/src/plugins/store/README.md +20 -0
  95. package/src/plugins/store/docs.md +5 -0
  96. package/src/plugins/store/store.js +52 -0
  97. package/src/plugins/summarize/README.md +18 -0
  98. package/src/plugins/summarize/docs.md +4 -0
  99. package/src/plugins/summarize/summarize.js +24 -0
  100. package/src/plugins/telemetry/README.md +19 -0
  101. package/src/plugins/telemetry/rpc_log.sql +28 -0
  102. package/src/plugins/telemetry/telemetry.js +186 -0
  103. package/src/plugins/unknown/README.md +23 -0
  104. package/src/plugins/unknown/docs.md +5 -0
  105. package/src/plugins/unknown/unknown.js +31 -0
  106. package/src/plugins/update/README.md +18 -0
  107. package/src/plugins/update/docs.md +4 -0
  108. package/src/plugins/update/update.js +24 -0
  109. package/src/server/ClientConnection.js +228 -0
  110. package/src/server/RpcRegistry.js +52 -0
  111. package/src/server/SocketServer.js +43 -0
  112. package/src/sql/file_constraints.sql +15 -0
  113. package/src/sql/functions/countTokens.js +7 -0
  114. package/src/sql/functions/hedmatch.js +8 -0
  115. package/src/sql/functions/hedreplace.js +8 -0
  116. package/src/sql/functions/hedsearch.js +8 -0
  117. package/src/sql/functions/schemeOf.js +7 -0
  118. package/src/sql/functions/slugify.js +6 -0
  119. package/src/sql/v_model_context.sql +101 -0
  120. 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,3 @@
1
+ ## <known>[information]</known> - Save knowledge
2
+ Example: <known>Donald Rumsfeld was born in 1932</known>
3
+ Example: <known path="known://auth">OAuth2 PKCE</known>
@@ -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,2 @@
1
+ ## <mv path="[path/to/origin"]>[path/to/destination]</mv> - Move a file or entry
2
+ Example: <mv path="known://active_user">known://inactive_user</mv>
@@ -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,4 @@
1
+ ## <rm path="[path/to/file]"/> - Remove a file or entry
2
+ Example: <rm path="src/config.js"/>
3
+ Example: <rm path="unknown://42"/>
4
+ * <rm/> removes the file or entry from context and deletes it PERMANENTLY
@@ -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`.