@possumtech/rummy 0.5.0 → 2.0.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 +42 -5
- package/PLUGINS.md +389 -194
- package/README.md +25 -8
- package/SPEC.md +934 -373
- package/bin/demo.js +166 -0
- package/bin/rummy.js +9 -3
- package/biome/no-fallbacks.grit +50 -0
- package/lang/en.json +2 -2
- package/migrations/001_initial_schema.sql +88 -37
- package/package.json +13 -11
- package/scriptify/ask_run.js +77 -0
- package/service.js +50 -9
- package/src/agent/AgentLoop.js +476 -335
- package/src/agent/ContextAssembler.js +4 -4
- package/src/agent/Entries.js +676 -0
- package/src/agent/ProjectAgent.js +30 -18
- package/src/agent/TurnExecutor.js +232 -421
- package/src/agent/XmlParser.js +99 -33
- package/src/agent/budget.js +56 -0
- package/src/agent/errors.js +22 -0
- package/src/agent/httpStatus.js +39 -0
- package/src/agent/known_checks.sql +8 -4
- package/src/agent/known_queries.sql +9 -13
- package/src/agent/known_store.sql +280 -125
- package/src/agent/materializeContext.js +104 -0
- package/src/agent/runs.sql +29 -7
- package/src/agent/schemes.sql +14 -3
- package/src/agent/tokens.js +6 -0
- package/src/agent/turns.sql +9 -9
- package/src/hooks/HookRegistry.js +6 -5
- package/src/hooks/Hooks.js +44 -3
- package/src/hooks/PluginContext.js +29 -21
- package/src/{server → hooks}/RpcRegistry.js +2 -1
- package/src/hooks/RummyContext.js +139 -35
- package/src/hooks/ToolRegistry.js +21 -16
- package/src/llm/LlmProvider.js +66 -89
- package/src/llm/errors.js +21 -0
- package/src/llm/retry.js +63 -0
- package/src/plugins/ask_user/README.md +1 -1
- package/src/plugins/ask_user/ask_user.js +37 -12
- package/src/plugins/ask_user/ask_userDoc.js +2 -25
- package/src/plugins/ask_user/ask_userDoc.md +10 -0
- package/src/plugins/budget/README.md +27 -25
- package/src/plugins/budget/budget.js +306 -88
- package/src/plugins/cp/README.md +2 -2
- package/src/plugins/cp/cp.js +29 -11
- package/src/plugins/cp/cpDoc.js +2 -15
- package/src/plugins/cp/cpDoc.md +7 -0
- package/src/plugins/engine/README.md +2 -2
- package/src/plugins/engine/engine.sql +4 -4
- package/src/plugins/engine/turn_context.sql +10 -10
- package/src/plugins/env/README.md +20 -5
- package/src/plugins/env/env.js +45 -6
- package/src/plugins/env/envDoc.js +2 -23
- package/src/plugins/env/envDoc.md +13 -0
- package/src/plugins/error/README.md +16 -0
- package/src/plugins/error/error.js +151 -0
- package/src/plugins/file/README.md +6 -6
- package/src/plugins/file/file.js +15 -2
- package/src/plugins/get/README.md +1 -1
- package/src/plugins/get/get.js +103 -48
- package/src/plugins/get/getDoc.js +2 -32
- package/src/plugins/get/getDoc.md +36 -0
- package/src/plugins/hedberg/README.md +1 -2
- package/src/plugins/hedberg/hedberg.js +8 -4
- package/src/plugins/hedberg/matcher.js +16 -17
- package/src/plugins/hedberg/normalize.js +0 -48
- package/src/plugins/helpers.js +42 -2
- package/src/plugins/index.js +146 -123
- package/src/plugins/instructions/README.md +35 -9
- package/src/plugins/instructions/instructions.js +244 -9
- package/src/plugins/instructions/instructions.md +33 -0
- package/src/plugins/instructions/instructions_104.md +7 -0
- package/src/plugins/instructions/instructions_105.md +38 -0
- package/src/plugins/instructions/instructions_106.md +21 -0
- package/src/plugins/instructions/instructions_107.md +10 -0
- package/src/plugins/instructions/instructions_108.md +0 -0
- package/src/plugins/instructions/protocol.js +12 -0
- package/src/plugins/known/README.md +2 -2
- package/src/plugins/known/known.js +68 -36
- package/src/plugins/known/knownDoc.js +2 -17
- package/src/plugins/known/knownDoc.md +8 -0
- package/src/plugins/log/README.md +48 -0
- package/src/plugins/log/log.js +129 -0
- package/src/plugins/mv/README.md +2 -2
- package/src/plugins/mv/mv.js +55 -22
- package/src/plugins/mv/mvDoc.js +2 -18
- package/src/plugins/mv/mvDoc.md +10 -0
- package/src/plugins/ollama/README.md +15 -0
- package/src/{llm/OllamaClient.js → plugins/ollama/ollama.js} +40 -18
- package/src/plugins/openai/README.md +17 -0
- package/src/plugins/openai/openai.js +120 -0
- package/src/plugins/openrouter/README.md +27 -0
- package/src/plugins/openrouter/openrouter.js +121 -0
- package/src/plugins/persona/README.md +20 -0
- package/src/plugins/persona/persona.js +9 -16
- package/src/plugins/policy/README.md +21 -0
- package/src/plugins/policy/policy.js +29 -14
- package/src/plugins/prompt/README.md +1 -1
- package/src/plugins/prompt/prompt.js +64 -16
- package/src/plugins/rm/README.md +1 -1
- package/src/plugins/rm/rm.js +56 -12
- package/src/plugins/rm/rmDoc.js +2 -20
- package/src/plugins/rm/rmDoc.md +13 -0
- package/src/plugins/rpc/README.md +2 -2
- package/src/plugins/rpc/rpc.js +525 -296
- package/src/plugins/set/README.md +1 -1
- package/src/plugins/set/set.js +318 -75
- package/src/plugins/set/setDoc.js +2 -35
- package/src/plugins/set/setDoc.md +22 -0
- package/src/plugins/sh/README.md +28 -5
- package/src/plugins/sh/sh.js +50 -6
- package/src/plugins/sh/shDoc.js +2 -23
- package/src/plugins/sh/shDoc.md +13 -0
- package/src/plugins/skill/README.md +23 -0
- package/src/plugins/skill/skill.js +14 -18
- package/src/plugins/stream/README.md +101 -0
- package/src/plugins/stream/stream.js +290 -0
- package/src/plugins/telemetry/README.md +1 -1
- package/src/plugins/telemetry/telemetry.js +129 -80
- package/src/plugins/think/README.md +1 -1
- package/src/plugins/think/think.js +12 -0
- package/src/plugins/think/thinkDoc.js +2 -15
- package/src/plugins/think/thinkDoc.md +7 -0
- package/src/plugins/unknown/README.md +3 -3
- package/src/plugins/unknown/unknown.js +47 -19
- package/src/plugins/unknown/unknownDoc.js +2 -21
- package/src/plugins/unknown/unknownDoc.md +11 -0
- package/src/plugins/update/README.md +1 -1
- package/src/plugins/update/update.js +83 -5
- package/src/plugins/update/updateDoc.js +2 -30
- package/src/plugins/update/updateDoc.md +8 -0
- package/src/plugins/xai/README.md +23 -0
- package/src/{llm/XaiClient.js → plugins/xai/xai.js} +58 -37
- package/src/plugins/yolo/yolo.js +192 -0
- package/src/server/ClientConnection.js +64 -37
- package/src/server/SocketServer.js +23 -10
- package/src/server/protocol.js +11 -0
- package/src/sql/v_model_context.sql +27 -31
- package/src/sql/v_run_log.sql +9 -14
- package/EXCEPTIONS.md +0 -46
- package/FIDELITY_CONTRACT.md +0 -172
- package/src/agent/KnownStore.js +0 -337
- package/src/agent/ResponseHealer.js +0 -241
- package/src/llm/OpenAiClient.js +0 -100
- package/src/llm/OpenRouterClient.js +0 -100
- package/src/plugins/budget/recovery.js +0 -47
- package/src/plugins/instructions/preamble.md +0 -45
- package/src/plugins/performed/README.md +0 -15
- package/src/plugins/performed/performed.js +0 -45
- package/src/plugins/previous/README.md +0 -16
- package/src/plugins/previous/previous.js +0 -56
- package/src/plugins/progress/README.md +0 -16
- package/src/plugins/progress/progress.js +0 -43
- package/src/plugins/summarize/README.md +0 -19
- package/src/plugins/summarize/summarize.js +0 -32
- package/src/plugins/summarize/summarizeDoc.js +0 -27
package/PLUGINS.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Every `<tag>` the model sees is a plugin. Every scheme is registered by
|
|
4
4
|
its owner. Every operation — model, client, plugin — flows through the
|
|
5
|
-
same tool handler.
|
|
5
|
+
same tool handler. Exceptions to that discipline must justify themselves
|
|
6
|
+
in the architecture spec (SPEC.md).
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Quickstart
|
|
8
9
|
|
|
9
10
|
A complete tool plugin in four parts: register, handle, render, document.
|
|
10
11
|
|
|
@@ -20,7 +21,8 @@ export default class Ping {
|
|
|
20
21
|
core.ensureTool();
|
|
21
22
|
core.registerScheme({ category: "logging" });
|
|
22
23
|
core.on("handler", this.handler.bind(this));
|
|
23
|
-
core.on("
|
|
24
|
+
core.on("visible", this.full.bind(this));
|
|
25
|
+
core.on("summarized", this.summary.bind(this));
|
|
24
26
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
25
27
|
docsMap.ping = docs;
|
|
26
28
|
return docsMap;
|
|
@@ -32,14 +34,13 @@ export default class Ping {
|
|
|
32
34
|
await rummy.set({
|
|
33
35
|
path: entry.resultPath,
|
|
34
36
|
body: `pong ${now}`,
|
|
35
|
-
|
|
37
|
+
state: "resolved",
|
|
36
38
|
attributes: { path: entry.path },
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
full(entry) {
|
|
41
|
-
|
|
42
|
-
}
|
|
42
|
+
full(entry) { return entry.body; }
|
|
43
|
+
summary(entry) { return ""; }
|
|
43
44
|
}
|
|
44
45
|
```
|
|
45
46
|
|
|
@@ -62,7 +63,7 @@ Install external plugins via npm + env var:
|
|
|
62
63
|
RUMMY_PLUGIN_PING=@myorg/rummy.ping
|
|
63
64
|
```
|
|
64
65
|
|
|
65
|
-
##
|
|
66
|
+
## Plugin Contract {#plugins_contract}
|
|
66
67
|
|
|
67
68
|
A plugin is a directory under `src/plugins/` containing a `.js` file
|
|
68
69
|
that exports a default class. The class name matches the file name.
|
|
@@ -78,8 +79,8 @@ export default class MyTool {
|
|
|
78
79
|
core.ensureTool();
|
|
79
80
|
core.registerScheme({ category: "logging" });
|
|
80
81
|
core.on("handler", this.handler.bind(this));
|
|
81
|
-
core.on("
|
|
82
|
-
core.on("
|
|
82
|
+
core.on("visible", this.full.bind(this));
|
|
83
|
+
core.on("summarized", this.summary.bind(this));
|
|
83
84
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
84
85
|
docsMap.mytool = docs;
|
|
85
86
|
return docsMap;
|
|
@@ -90,13 +91,8 @@ export default class MyTool {
|
|
|
90
91
|
// What the tool does (rummy is per-turn RummyContext)
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
full(entry)
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
summary(entry) {
|
|
98
|
-
return entry.body;
|
|
99
|
-
}
|
|
94
|
+
full(entry) { return entry.body; }
|
|
95
|
+
summary(entry) { return entry.body; }
|
|
100
96
|
}
|
|
101
97
|
```
|
|
102
98
|
|
|
@@ -110,74 +106,106 @@ RUMMY_PLUGIN_WEB=@possumtech/rummy.web
|
|
|
110
106
|
RUMMY_PLUGIN_REPO=@possumtech/rummy.repo
|
|
111
107
|
```
|
|
112
108
|
|
|
113
|
-
##
|
|
109
|
+
## Unified API {#plugins_unified_api}
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
= method name (plugin). The params shape is the same at every tier.
|
|
111
|
+
Three tiers share the tool vocabulary, but the invocation shape and
|
|
112
|
+
dispatch path differ.
|
|
118
113
|
|
|
119
114
|
```
|
|
120
|
-
Model:
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
Model: <rm path="file.txt"/> → { name: "rm", path: "file.txt" }
|
|
116
|
+
→ TurnExecutor.#record()
|
|
117
|
+
→ hooks.tools.dispatch("rm", entry, rummy)
|
|
118
|
+
Client: { method: "rm", params: {...} } → rpc.js #dispatchRm(...)
|
|
119
|
+
→ Entries.rm({...})
|
|
120
|
+
Plugin: rummy.rm(path) / rummy.set({...}) → Entries.set / Entries.rm
|
|
121
|
+
→ (Entries also fires entry events)
|
|
123
122
|
```
|
|
124
123
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
Three surfaces, one grammar (see [surfaces](SPEC.md#surfaces)). The model dispatches through
|
|
125
|
+
the handler chain (`TurnExecutor.#record()` → `hooks.tools.dispatch`
|
|
126
|
+
→ policy filter → turn-scoped recording → abort cascade → budget
|
|
127
|
+
lifecycle around it). The client primitives (`set`/`get`/`rm`/`cp`/
|
|
128
|
+
`mv`/`update` RPCs) talk directly to Entries — `writer: "client"`
|
|
129
|
+
on every call, permissions enforced per-scheme. Plugins use
|
|
130
|
+
RummyContext verbs; the `rummy.entries` accessor is a Proxy that
|
|
131
|
+
auto-binds `writer: rummy.writer` on every write, so a plugin writing
|
|
132
|
+
on behalf of the model gets `writer: "model"` without opt-in.
|
|
133
|
+
|
|
134
|
+
Plugin code wanting full handler semantics (policy filter, proposal
|
|
135
|
+
flow, turn recording) calls `hooks.tools.dispatch` directly instead
|
|
136
|
+
of going through a primitive.
|
|
137
|
+
|
|
138
|
+
Verb signatures vary. See [plugins_rummy_verbs](#plugins_rummy_verbs).
|
|
128
139
|
|
|
129
|
-
##
|
|
140
|
+
## Registration {#plugins_registration}
|
|
130
141
|
|
|
131
142
|
All registration happens in the constructor via `core.on()`,
|
|
132
143
|
`core.filter()`, `core.ensureTool()`, and `core.registerScheme()`.
|
|
133
144
|
|
|
134
|
-
###
|
|
145
|
+
### core.ensureTool() {#plugins_ensure_tool}
|
|
135
146
|
|
|
136
147
|
Declares this plugin as a model-facing tool. Required for the tool
|
|
137
148
|
to appear in the model's tool list. Called automatically by
|
|
138
149
|
`core.on("handler", ...)` but must be called explicitly for tools
|
|
139
|
-
without handlers (e.g., `
|
|
150
|
+
without handlers (e.g., `update`, `unknown`).
|
|
140
151
|
|
|
141
|
-
###
|
|
152
|
+
### core.registerScheme(config?) {#plugins_register_scheme}
|
|
142
153
|
|
|
143
154
|
Registers this plugin's scheme in the database. Called once in the
|
|
144
155
|
constructor.
|
|
145
156
|
|
|
146
157
|
```js
|
|
147
158
|
core.registerScheme({
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
name: "mytool", // defaults to plugin name
|
|
160
|
+
modelVisible: 1, // 1 or 0 — appears in v_model_context
|
|
161
|
+
category: "logging", // "data" | "logging" | "unknown" | "prompt"
|
|
162
|
+
scope: "run", // "run" | "project" | "global" — default scope
|
|
163
|
+
writableBy: ["model", "plugin"], // subset of: system | plugin | client | model
|
|
150
164
|
});
|
|
151
165
|
```
|
|
152
166
|
|
|
153
167
|
All fields optional. `core.registerScheme()` with no args gives a
|
|
154
|
-
sensible result-type scheme
|
|
168
|
+
sensible result-type scheme (logging category, run scope, writable by
|
|
169
|
+
model + plugin).
|
|
155
170
|
|
|
156
|
-
|
|
171
|
+
`scope` determines where entries at this scheme land (see
|
|
172
|
+
[entries](SPEC.md#entries) / [physical_layout](SPEC.md#physical_layout)).
|
|
173
|
+
`writableBy` is enforced at `Entries.set` — writes from a writer
|
|
174
|
+
not in the list throw a typed `PermissionError` (importable from
|
|
175
|
+
`src/agent/errors.js`). The four writer tiers
|
|
176
|
+
(see [writer_tiers](SPEC.md#writer_tiers)) form
|
|
177
|
+
a strict hierarchy: **system > plugin > client > model**. Each tier
|
|
178
|
+
is a superset of what's below.
|
|
179
|
+
|
|
180
|
+
### core.on(event, callback, priority?) {#plugins_on}
|
|
157
181
|
|
|
158
182
|
| Event | Payload | Purpose |
|
|
159
183
|
|-------|---------|---------|
|
|
160
184
|
| `"handler"` | `(entry, rummy)` | Tool handler — called when model/client invokes this tool |
|
|
161
|
-
| `"
|
|
162
|
-
| `"
|
|
163
|
-
| `"turn.started"` | `(
|
|
164
|
-
| `"turn.response"` | `(result,
|
|
165
|
-
| `"
|
|
166
|
-
| `"
|
|
167
|
-
| `"
|
|
168
|
-
|
|
|
185
|
+
| `"visible"` | `(entry)` | Visible-visibility projection — body shown in `<knowns>` / `<performed>` |
|
|
186
|
+
| `"summarized"` | `(entry)` | Summarized-visibility projection — path + summary only (body hidden) |
|
|
187
|
+
| `"turn.started"` | `({rummy, mode, prompt, loopIteration, isContinuation})` | Turn beginning — plugins write prompt/instructions entries |
|
|
188
|
+
| `"turn.response"` | `({rummy, turn, result, responseMessage, content, commands, ...})` | LLM responded — write audit entries, commit usage |
|
|
189
|
+
| `"proposal.prepare"` | `({rummy, recorded})` | Tool dispatched — materialize proposals (e.g. file edit 202 revisions) |
|
|
190
|
+
| `"proposal.pending"` | `({projectId, run, proposed})` | Proposal awaits client resolution |
|
|
191
|
+
| `"turn.completed"` | `(turnResult)` | Turn resolved — full turnResult |
|
|
192
|
+
| `"entry.created"` | `(entry)` | Entry created during dispatch |
|
|
193
|
+
| `"entry.changed"` | `({runId, path, changeType})` | Entry content, visibility, or status modified |
|
|
194
|
+
| `"run.state"` | `({projectId, run, turn, status, summary, history, unknowns, telemetry})` | Incremental client-facing state push (wire-layer `status` HTTP code stays; DB stores the 5-value state enum) |
|
|
195
|
+
| `"error.log"` | `({runId, turn, loopId, message})` | Runtime error — creates an `error://` entry |
|
|
196
|
+
| Any `"dotted.name"` | varies | Resolves to the matching hook in `src/hooks/Hooks.js` |
|
|
169
197
|
|
|
170
198
|
```js
|
|
171
199
|
// One-liner examples
|
|
172
200
|
core.on("handler", async (entry, rummy) => { /* tool logic */ });
|
|
173
|
-
core.on("
|
|
174
|
-
core.on("
|
|
175
|
-
core.on("turn.started", async (
|
|
176
|
-
core.on("turn.response", async (
|
|
201
|
+
core.on("visible", (entry) => entry.body);
|
|
202
|
+
core.on("summarized", (entry) => entry.attributes?.summary || "");
|
|
203
|
+
core.on("turn.started", async ({ rummy, mode }) => { /* write entries */ });
|
|
204
|
+
core.on("turn.response", async ({ rummy, result }) => { /* audit */ });
|
|
177
205
|
core.on("entry.changed", ({ runId, path, changeType }) => { /* react */ });
|
|
178
206
|
```
|
|
179
207
|
|
|
180
|
-
###
|
|
208
|
+
### core.filter(name, callback, priority?) {#plugins_filter}
|
|
181
209
|
|
|
182
210
|
| Filter | Signature | Purpose |
|
|
183
211
|
|--------|-----------|---------|
|
|
@@ -186,6 +214,7 @@ core.on("entry.changed", ({ runId, path, changeType }) => { /* react */ });
|
|
|
186
214
|
| `"assembly.user"` | `(content, ctx) → content` | Contribute to user message |
|
|
187
215
|
| `"llm.messages"` | `(messages) → messages` | Transform final messages before LLM call |
|
|
188
216
|
| `"llm.response"` | `(response) → response` | Transform LLM response |
|
|
217
|
+
| `"llm.reasoning"` | `(reasoning, {commands}) → reasoning` | Contribute to `reasoning_content` (the think plugin subscribes here to merge `<think>` tag bodies) |
|
|
189
218
|
| Any `"dotted.name"` | varies | Resolves to the matching filter in the hook tree |
|
|
190
219
|
|
|
191
220
|
```js
|
|
@@ -209,13 +238,15 @@ ctx = {
|
|
|
209
238
|
rows, // turn_context rows (materialized entries)
|
|
210
239
|
loopStartTurn, // First turn of current loop
|
|
211
240
|
type, // "ask" or "act"
|
|
212
|
-
|
|
241
|
+
toolSet, // Set<string> of active tool names for this loop
|
|
213
242
|
contextSize, // Model context window size
|
|
214
|
-
lastContextTokens, //
|
|
243
|
+
lastContextTokens, // Actual API tokens from the prior turn (0 on turn 1)
|
|
244
|
+
demoted, // Mutable array — plugins push paths they summarized
|
|
245
|
+
turn, // Current turn number
|
|
215
246
|
}
|
|
216
247
|
```
|
|
217
248
|
|
|
218
|
-
###
|
|
249
|
+
### Tool Docs {#plugins_tool_docs}
|
|
219
250
|
|
|
220
251
|
Each tool plugin has a `*Doc.js` file with annotated line arrays.
|
|
221
252
|
Text goes to the model. Rationale stays in source. Registered via
|
|
@@ -233,7 +264,7 @@ core.filter("instructions.toolDocs", async (docsMap) => {
|
|
|
233
264
|
The instructions plugin filters by the active tool set — tools
|
|
234
265
|
excluded by mode or flags are automatically omitted from the docs.
|
|
235
266
|
|
|
236
|
-
###
|
|
267
|
+
### handler(entry, rummy) {#plugins_handler}
|
|
237
268
|
|
|
238
269
|
The handler receives the parsed command entry and a per-turn
|
|
239
270
|
RummyContext:
|
|
@@ -251,17 +282,17 @@ entry = {
|
|
|
251
282
|
Multiple handlers per scheme. Lower priority runs first. Return
|
|
252
283
|
`false` to stop the chain.
|
|
253
284
|
|
|
254
|
-
###
|
|
285
|
+
### full(entry) / summary(entry) {#plugins_views}
|
|
255
286
|
|
|
256
287
|
Returns the string the model sees for this tool's entries at the
|
|
257
|
-
given
|
|
288
|
+
given visibility. Every tool MUST register `full`. `summary` is
|
|
258
289
|
optional — if unregistered, falls back to `attributes.summary`
|
|
259
290
|
(model-authored keyword description) or empty string.
|
|
260
291
|
|
|
261
|
-
At summary
|
|
292
|
+
At summary visibility, `attributes.summary` is prepended above the
|
|
262
293
|
plugin's summary output automatically by ToolRegistry.view().
|
|
263
294
|
|
|
264
|
-
##
|
|
295
|
+
## Two Objects {#plugins_two_objects}
|
|
265
296
|
|
|
266
297
|
Plugins interact with two objects at different scopes:
|
|
267
298
|
|
|
@@ -273,59 +304,76 @@ lifetime.
|
|
|
273
304
|
**RummyContext** (`rummy`) — turn-scoped. Passed to handlers per
|
|
274
305
|
invocation. Has tool verbs, per-turn state, database access.
|
|
275
306
|
|
|
276
|
-
###
|
|
307
|
+
### Tool Verbs (on RummyContext) {#plugins_rummy_verbs}
|
|
308
|
+
|
|
309
|
+
Convenience wrappers that bind `runId`, `turn`, `loopId` from context
|
|
310
|
+
and delegate to Entries. Signatures vary per verb. For full
|
|
311
|
+
handler-chain semantics (policy filtering, proposal flow, abort
|
|
312
|
+
cascade), call `rummy.hooks.tools.dispatch(scheme, entry, rummy)`
|
|
313
|
+
instead.
|
|
277
314
|
|
|
278
315
|
| Method | Effect |
|
|
279
316
|
|--------|--------|
|
|
280
|
-
| `rummy.set({ path
|
|
281
|
-
| `rummy.get(
|
|
282
|
-
| `rummy.rm(
|
|
283
|
-
| `rummy.mv(
|
|
284
|
-
| `rummy.cp(
|
|
317
|
+
| `rummy.set({ path?, body?, state?, visibility?, outcome?, attributes? })` | Create/update entry. If `path` omitted, slugifies from body/summary. State defaults to `"resolved"`. |
|
|
318
|
+
| `rummy.get(path)` | Promote entries matching a pattern (default visibility `"visible"`). |
|
|
319
|
+
| `rummy.rm(path)` | Remove entry's view. |
|
|
320
|
+
| `rummy.mv(from, to)` | Rename entry. |
|
|
321
|
+
| `rummy.cp(from, to)` | Copy entry to a new path. |
|
|
322
|
+
| `rummy.update(body, { status?, attributes? })` | Write the once-per-turn lifecycle signal to `update://<slug>`. |
|
|
285
323
|
|
|
286
|
-
###
|
|
324
|
+
### Query Methods {#plugins_rummy_queries}
|
|
287
325
|
|
|
288
326
|
| Method | Returns |
|
|
289
327
|
|--------|---------|
|
|
290
328
|
| `rummy.getBody(path)` | Body text or null |
|
|
291
|
-
| `rummy.getState(path)` |
|
|
292
|
-
| `rummy.
|
|
293
|
-
| `rummy.
|
|
329
|
+
| `rummy.getState(path)` | Categorical state (`"proposed"` \| `"streaming"` \| `"resolved"` \| `"failed"` \| `"cancelled"`) or null |
|
|
330
|
+
| `rummy.getOutcome(path)` | Outcome string (populated when state ∈ {failed, cancelled}) or null |
|
|
331
|
+
| `rummy.getAttributes(path)` | Parsed attributes `{}` or null |
|
|
332
|
+
| `rummy.getEntry(path)` | First matching entry or null |
|
|
333
|
+
| `rummy.getEntries(pattern, bodyFilter?)` | Array of matching entries |
|
|
334
|
+
| `rummy.setAttributes(path, attrs)` | Merge attributes via json_patch |
|
|
294
335
|
|
|
295
|
-
###
|
|
336
|
+
### Properties {#plugins_rummy_properties}
|
|
296
337
|
|
|
297
|
-
| Property | Type |
|
|
338
|
+
| Property | Type | Notes |
|
|
298
339
|
|----------|------|-------|
|
|
299
|
-
| `rummy.entries` |
|
|
300
|
-
| `rummy.db` |
|
|
301
|
-
| `rummy.
|
|
302
|
-
| `rummy.
|
|
303
|
-
| `rummy.
|
|
304
|
-
| `rummy.
|
|
305
|
-
| `rummy.
|
|
306
|
-
|
|
307
|
-
|
|
340
|
+
| `rummy.entries` | Entries proxy | Write calls auto-carry `writer: rummy.writer`. Read-through for reads + internal ops. |
|
|
341
|
+
| `rummy.db` | SqlRite db | Prefer `entries` for plugin-facing data access |
|
|
342
|
+
| `rummy.hooks` | Hook registry | |
|
|
343
|
+
| `rummy.runId` | number | Current run |
|
|
344
|
+
| `rummy.projectId` | number | |
|
|
345
|
+
| `rummy.sequence` | number | Current turn number |
|
|
346
|
+
| `rummy.loopId` / `rummy.turnId` | number | |
|
|
347
|
+
| `rummy.type` | `"ask"` \| `"act"` | Current mode |
|
|
348
|
+
| `rummy.toolSet` | Set<string> \| null | Active tool list for this loop |
|
|
349
|
+
| `rummy.contextSize` | number \| null | Model context window |
|
|
350
|
+
| `rummy.systemPrompt` / `rummy.loopPrompt` | string | |
|
|
351
|
+
| `rummy.noRepo` / `rummy.noInteraction` / `rummy.noWeb` | boolean | Loop flags |
|
|
352
|
+
| `rummy.writer` | `"system"` \| `"plugin"` \| `"client"` \| `"model"` | Default `"model"` in handler dispatch. The Proxy on `rummy.entries` binds this to every write for permission checks (see [writer_tiers](SPEC.md#writer_tiers)). |
|
|
353
|
+
|
|
354
|
+
## Tool Display Order {#plugins_display_order}
|
|
308
355
|
|
|
309
356
|
Tools are presented to the model in priority order:
|
|
310
357
|
gather → reason → act → communicate.
|
|
311
358
|
|
|
312
|
-
Defined in `ToolRegistry.TOOL_ORDER`.
|
|
313
|
-
|
|
359
|
+
Defined in `ToolRegistry.TOOL_ORDER`. `resolveForLoop(mode, flags)`
|
|
360
|
+
handles all exclusions:
|
|
314
361
|
|
|
315
|
-
|
|
|
316
|
-
|
|
362
|
+
| Condition | Excludes |
|
|
363
|
+
|-----------|----------|
|
|
317
364
|
| `mode === "ask"` | `sh` |
|
|
318
|
-
| `noInteraction` | `ask_user` |
|
|
319
|
-
| `noWeb` | `search` |
|
|
365
|
+
| `noInteraction` flag | `ask_user` |
|
|
366
|
+
| `noWeb` flag | `search` |
|
|
367
|
+
| `noProposals` flag | `ask_user`, `env`, `sh` |
|
|
320
368
|
|
|
321
|
-
##
|
|
369
|
+
## Hedberg {#plugins_hedberg}
|
|
322
370
|
|
|
323
371
|
The hedberg plugin exposes pattern matching and interpretation
|
|
324
372
|
utilities on `core.hooks.hedberg` for all plugins to use:
|
|
325
373
|
|
|
326
374
|
```js
|
|
327
375
|
const { match, search, replace, parseSed, parseEdits,
|
|
328
|
-
|
|
376
|
+
generatePatch } = core.hooks.hedberg;
|
|
329
377
|
```
|
|
330
378
|
|
|
331
379
|
| Method | Purpose |
|
|
@@ -335,23 +383,22 @@ const { match, search, replace, parseSed, parseEdits,
|
|
|
335
383
|
| `replace(body, search, replacement, opts?)` | Apply replacement |
|
|
336
384
|
| `parseSed(input)` | Parse sed syntax (any delimiter) |
|
|
337
385
|
| `parseEdits(content)` | Detect edit format (merge conflict, udiff, sed) |
|
|
338
|
-
| `normalizeAttrs(attrs)` | Heal model attribute names |
|
|
339
386
|
| `generatePatch(path, old, new)` | Generate unified diff |
|
|
340
387
|
|
|
341
|
-
##
|
|
388
|
+
## Events & Filters {#plugins_events_overview}
|
|
342
389
|
|
|
343
390
|
**Events** are fire-and-forget. All handlers run. Return values ignored.
|
|
344
391
|
**Filters** transform data through a chain. Lower priority runs first.
|
|
345
392
|
All hooks are async.
|
|
346
393
|
|
|
347
|
-
###
|
|
394
|
+
### Project Lifecycle {#plugins_project_lifecycle}
|
|
348
395
|
|
|
349
396
|
| Hook | Type | When |
|
|
350
397
|
|------|------|------|
|
|
351
398
|
| `project.init.started` | event | Before project DB upsert |
|
|
352
399
|
| `project.init.completed` | event | After project created |
|
|
353
400
|
|
|
354
|
-
###
|
|
401
|
+
### Run & Loop Lifecycle {#plugins_run_loop_lifecycle}
|
|
355
402
|
|
|
356
403
|
| Hook | Type | When |
|
|
357
404
|
|------|------|------|
|
|
@@ -360,129 +407,224 @@ All hooks are async.
|
|
|
360
407
|
| `act.started` | event | Run requested in act mode |
|
|
361
408
|
| `loop.started` | event | Loop execution beginning |
|
|
362
409
|
| `run.config` | filter | Before run config applied |
|
|
363
|
-
| `run.progress` | event |
|
|
364
|
-
| `run.state` | event |
|
|
365
|
-
| `run.step.completed` | event | Turn resolved,
|
|
366
|
-
| `loop.completed` | event | Loop
|
|
367
|
-
| `ask.completed` | event | Ask run finished |
|
|
368
|
-
| `act.completed` | event | Act run finished |
|
|
410
|
+
| `run.progress` | event | Transient turn activity (`thinking` / `processing` / `retrying`) |
|
|
411
|
+
| `run.state` | event | Turn conclusion, per-command incremental, or terminal run close — full state snapshot (status, history, unknowns, telemetry) |
|
|
412
|
+
| `run.step.completed` | event | Turn verdict resolved (post-healer, pre-close) |
|
|
413
|
+
| `loop.completed` | event | Loop exit — fires from `finally`, guaranteed on every exit path |
|
|
414
|
+
| `ask.completed` | event | Ask-mode run finished |
|
|
415
|
+
| `act.completed` | event | Act-mode run finished |
|
|
416
|
+
| `proposal.prepare` | event | Per recorded entry — plugins materialize proposals (e.g. set plugin turns search/replace revisions into 202 entries) |
|
|
417
|
+
| `proposal.pending` | event | A materialized proposal awaits client resolution |
|
|
369
418
|
|
|
370
|
-
###
|
|
419
|
+
### Turn Pipeline {#plugins_turn_pipeline}
|
|
371
420
|
|
|
372
421
|
Hooks fire in this order every turn:
|
|
373
422
|
|
|
374
423
|
| # | Hook | Type | When |
|
|
375
424
|
|---|------|------|------|
|
|
376
|
-
| 1 | `turn.started` | event | Plugins write prompt/
|
|
425
|
+
| 1 | `turn.started` | event | Plugins write prompt/instructions entries |
|
|
377
426
|
| 2 | `context.materialized` | event | turn_context populated from v_model_context |
|
|
378
427
|
| 3 | `assembly.system` | filter | Build system message from entries |
|
|
379
|
-
| 4 | `assembly.user` | filter | Build user message
|
|
380
|
-
| 5 | `budget.enforce` |
|
|
428
|
+
| 4 | `assembly.user` | filter | Build user message (prompt plugin adds `<prompt tokensFree tokenUsage>`) |
|
|
429
|
+
| 5 | `budget.enforce` | call | Measure assembled tokens; if over and it's turn 1, demote prompt, re-materialize, re-check; still over → 413 |
|
|
381
430
|
| 6 | `llm.messages` | filter | Transform messages before LLM call |
|
|
382
431
|
| 7 | `llm.request.started` | event | LLM call about to fire |
|
|
383
432
|
| 8 | `llm.response` | filter | Transform raw LLM response |
|
|
384
433
|
| 9 | `llm.request.completed` | event | LLM call finished |
|
|
385
|
-
| 10 | `turn.response` | event | Plugins write audit entries |
|
|
386
|
-
| 11 | `entry.recording` | filter |
|
|
387
|
-
| 12 |
|
|
388
|
-
|
|
|
389
|
-
|
|
|
390
|
-
|
|
|
391
|
-
|
|
|
392
|
-
|
|
|
393
|
-
|
|
|
394
|
-
|
|
395
|
-
|
|
434
|
+
| 10 | `turn.response` | event | Plugins write audit entries (telemetry) |
|
|
435
|
+
| 11 | `entry.recording` | filter | Per command, during `#record()`. Returning an entry with `state: "failed"` (or `"cancelled"`) rejects it. |
|
|
436
|
+
| 12 | Per recorded entry (sequential, abort-on-failure): | | |
|
|
437
|
+
| | `tool.before` | event | Before handler dispatch |
|
|
438
|
+
| | `tools.dispatch` | — | Scheme's registered handler runs |
|
|
439
|
+
| | `tool.after` | event | Handler finished |
|
|
440
|
+
| | `entry.created` | event | Entry written to store |
|
|
441
|
+
| | `run.state` | event | Incremental state push to connected clients |
|
|
442
|
+
| | `proposal.prepare` | event | This entry's dispatch may have created proposals (e.g. set → 202 revisions) |
|
|
443
|
+
| | `proposal.pending` | event | Per each materialized proposal — client is notified, dispatch awaits resolution |
|
|
444
|
+
| 13 | `budget.postDispatch` | call | Re-materialize + check. If over ceiling → Turn Demotion (visibility=summarized on turn's visible rows) + emit 413 error. |
|
|
445
|
+
| 14 | `hooks.update.resolve` | call | Update plugin classifies this turn's `<update>` (terminal/continuation, override-to-continuation if actions failed, heal from raw content if missing) |
|
|
446
|
+
| 15 | `turn.completed` | event | Turn fully resolved with final status |
|
|
447
|
+
|
|
448
|
+
`entry.changed` fires asynchronously from mutation points — not
|
|
449
|
+
pipeline-ordered. Subscribe when you need to react to any entry
|
|
450
|
+
modification (used by budget remeasurement and file-on-disk detection).
|
|
451
|
+
|
|
452
|
+
### Entry Events {#plugins_entry_events}
|
|
396
453
|
|
|
397
454
|
| Hook | Type | When |
|
|
398
455
|
|------|------|------|
|
|
399
|
-
| `entry.recording` | filter | Before entry stored. Return `{
|
|
456
|
+
| `entry.recording` | filter | Before entry stored. Return `{ state: "failed", outcome }` to reject. |
|
|
400
457
|
| `entry.created` | event | New entry added during dispatch |
|
|
401
|
-
| `entry.changed` | event | Entry content,
|
|
458
|
+
| `entry.changed` | event | Entry content, visibility, or state modified |
|
|
402
459
|
|
|
403
460
|
`entry.recording` is a filter — plugins can validate, transform, or
|
|
404
461
|
reject entries before they hit the store. Payload:
|
|
405
|
-
`{ scheme, path, body, attributes,
|
|
406
|
-
|
|
462
|
+
`{ scheme, path, body, attributes, state, outcome }`. Second arg is
|
|
463
|
+
a context bag: `{ store, runId, turn, loopId, mode }`. Return the
|
|
464
|
+
entry object (modified or not). Set `state: "failed"` with an
|
|
465
|
+
`outcome` string (e.g. `"permission"`, `"validation"`) to reject —
|
|
466
|
+
the policy plugin uses this pattern for ask-mode rejections.
|
|
407
467
|
|
|
408
468
|
`entry.changed` fires on any mutation to an existing entry — body
|
|
409
|
-
update,
|
|
469
|
+
update, visibility change, state change, attribute update. Payload:
|
|
410
470
|
`{ runId, path, changeType }`. Subscribers include the budget plugin
|
|
411
471
|
(remeasure context) and the repo plugin (detect file changes on disk).
|
|
412
472
|
|
|
413
|
-
###
|
|
473
|
+
### Budget {#plugins_budget}
|
|
414
474
|
|
|
415
475
|
| Hook | Type | When |
|
|
416
476
|
|------|------|------|
|
|
417
|
-
| `budget.enforce` |
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
477
|
+
| `hooks.budget.enforce` | method | Pre-LLM ceiling check. On first-turn 413 → Prompt Demotion + re-check. |
|
|
478
|
+
| `hooks.budget.postDispatch` | method | Post-dispatch re-check. On 413 → Turn Demotion + 413 `error://` entry via `hooks.error.log.emit`. |
|
|
479
|
+
|
|
480
|
+
The budget plugin measures tokens on the assembled messages — the
|
|
481
|
+
actual content being sent to the LLM. No estimates at the ceiling,
|
|
482
|
+
no SQL token sums. The assembled message IS the measurement. When
|
|
483
|
+
turn 2+ information is available, `budget.enforce` prefers the actual
|
|
484
|
+
API-reported token count (`turns.context_tokens` from the prior
|
|
485
|
+
turn) over re-measuring the assembled string.
|
|
486
|
+
|
|
487
|
+
**DB tokens vs assembled tokens:** The `tokens` column on `entries`
|
|
488
|
+
is strictly for DISPLAY — showing token costs in `<knowns>` tags so
|
|
425
489
|
the model can reason about entry sizes. It is NEVER used for budget
|
|
426
490
|
decisions. Budget math uses only assembled message token counts.
|
|
427
|
-
These are two separate numbers that must never be conflated.
|
|
491
|
+
These are two separate numbers that must never be conflated. See
|
|
492
|
+
See [budget_enforcement](SPEC.md#budget_enforcement) for the three-measure table.
|
|
428
493
|
|
|
429
|
-
###
|
|
494
|
+
### Client Notifications {#plugins_client_notifications}
|
|
430
495
|
|
|
431
496
|
| Hook | Type | When |
|
|
432
497
|
|------|------|------|
|
|
433
498
|
| `ui.render` | event | Text for client display |
|
|
434
499
|
| `ui.notify` | event | Status notification |
|
|
435
500
|
|
|
436
|
-
##
|
|
501
|
+
## Entry Lifecycle {#plugins_entry_lifecycle}
|
|
437
502
|
|
|
438
503
|
Every entry follows the same lifecycle regardless of origin:
|
|
439
504
|
|
|
440
|
-
1. **Created** — `
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
505
|
+
1. **Created** — `entries` row (content) + `run_views` row (per-run
|
|
506
|
+
projection) via the two-prep upsert flow (see [physical_layout](SPEC.md#physical_layout)).
|
|
507
|
+
2. **Dispatched** — tool handler chain executes.
|
|
508
|
+
3. **State set** — handler sets `state` (`"proposed"` \| `"streaming"`
|
|
509
|
+
\| `"resolved"` \| `"failed"` \| `"cancelled"`) + optional
|
|
510
|
+
`outcome` string on the `run_views` row. State is view-side; body
|
|
511
|
+
is content-side. (See [entries](SPEC.md#entries).)
|
|
512
|
+
4. **Materialized** — `v_model_context` joins entries + run_views,
|
|
513
|
+
projects into `turn_context`.
|
|
514
|
+
5. **Assembled** — filter chain renders into system/user messages.
|
|
515
|
+
Model-facing tags carry `status="NNN"` (HTTP code) via
|
|
516
|
+
`src/agent/httpStatus.js`'s state-to-HTTP mapping — the model's
|
|
517
|
+
vocabulary is HTTP; the DB is categorical.
|
|
518
|
+
6. **Visible** — model sees the entry in its context.
|
|
519
|
+
|
|
520
|
+
Entries at `visibility = 'archived'` skip steps 4–6 (invisible to
|
|
521
|
+
model, discoverable via pattern search). Entries at `visibility =
|
|
522
|
+
'summarized'` render with `attributes.summary` (model-authored keyword
|
|
523
|
+
description) prepended above the plugin's `summarized` view output —
|
|
524
|
+
the body is hidden; promoting with `<get>` brings it back.
|
|
525
|
+
|
|
526
|
+
**Per-plugin visibility projection reference.** Each plugin chooses
|
|
527
|
+
what its `visible` / `summarized` view hooks return. Renderers trust
|
|
528
|
+
the projected body — they do NOT re-check `entry.visibility`.
|
|
529
|
+
|
|
530
|
+
| Plugin | Category | `visible` body | `summarized` body | Notes |
|
|
531
|
+
|--------|----------|-----------------|----------------|-------|
|
|
532
|
+
| `known` | data | `entry.body` | `""` | Tag's `summary` attr carries the keywords at summarized visibility |
|
|
533
|
+
| `unknown` | unknown | `entry.body` | `""` | Same pattern as known |
|
|
534
|
+
| `prompt` | prompt | `entry.body` | 500-char truncation with `[truncated — promote to see the complete prompt]` marker | |
|
|
535
|
+
| `budget` | logging | `entry.body` | `entry.body` | Feedback signal — kept visible |
|
|
536
|
+
| `update` | logging | `# update\n${entry.body}` | same as visible | Already 80-char capped by tool doc rule |
|
|
537
|
+
| `get` / `set` / `rm` / `cp` / `mv` / `sh` / `env` / `search` | logging | result body | `""` | Just the self-closing tag at summarized |
|
|
538
|
+
| `skill` | data | `entry.body` | `""` | Same as known |
|
|
539
|
+
| `file` (bare paths) | data | `entry.body` | `""` | Same as known |
|
|
540
|
+
|
|
541
|
+
Plugins providing only a `visible` hook fall back to
|
|
542
|
+
`attributes.summary` (model-authored keyword description) at summarized;
|
|
543
|
+
the renderer inserts it automatically. Plugins providing neither
|
|
544
|
+
default to empty body — the tag still renders with its attributes so
|
|
545
|
+
the model can pattern-match the path.
|
|
546
|
+
|
|
547
|
+
### Streaming Entries {#plugins_streaming_entries}
|
|
548
|
+
|
|
549
|
+
Producers whose output arrives over time (shell commands, web fetches,
|
|
550
|
+
log tails, file watches) use the **streaming entry pattern**. The
|
|
551
|
+
lifecycle extends beyond 202→200:
|
|
446
552
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
553
|
+
```
|
|
554
|
+
state: "proposed" (user decision pending)
|
|
555
|
+
→ accept → state: "resolved" (log entry: action happened)
|
|
556
|
+
+ state: "streaming" data entries (one per channel, growing)
|
|
557
|
+
→ "resolved" / "failed" on completion
|
|
558
|
+
```
|
|
451
559
|
|
|
452
|
-
|
|
560
|
+
**Producer plugin contract:**
|
|
561
|
+
|
|
562
|
+
1. On dispatch, create a **proposal entry** at `{scheme}://turn_N/{slug}`
|
|
563
|
+
with `state: "proposed"`, category=logging. Body empty;
|
|
564
|
+
`summary=command` attr.
|
|
565
|
+
2. On user accept (client sends `set { state: "resolved" }` on the
|
|
566
|
+
proposal path), `AgentLoop.resolve()` transitions the proposal
|
|
567
|
+
entry to `state: "resolved"` (it becomes the **log entry**) and
|
|
568
|
+
creates **data entries** at `{path}_1`, `{path}_2`, etc. with
|
|
569
|
+
`state: "streaming"`, category=data, visibility=summarized, empty body.
|
|
570
|
+
3. Producer/client calls `stream { run, path, channel, chunk }` RPC
|
|
571
|
+
to append chunks to the appropriate channel.
|
|
572
|
+
4. When the producer is done, `stream/completed { run, path, exit_code? }`
|
|
573
|
+
transitions all `{path}_*` data entries to a terminal state
|
|
574
|
+
(`"resolved"` on exit_code=0 or omitted; `"failed"` with outcome
|
|
575
|
+
`"exit:N"` otherwise) and rewrites the log entry body with final
|
|
576
|
+
stats. For client-initiated cancellation, the client calls
|
|
577
|
+
`stream/aborted { run, path, reason? }` instead — transitions
|
|
578
|
+
channels to `state: "cancelled"` with outcome=reason.
|
|
579
|
+
|
|
580
|
+
**Channel numbering:** Unix file descriptor convention — `_1` is the
|
|
581
|
+
primary stream (stdout for shell, body for fetch, lines for tail);
|
|
582
|
+
`_2` is alternate/error (stderr, redirects, anomalies); `_3`+ for
|
|
583
|
+
additional producer-specific streams.
|
|
584
|
+
|
|
585
|
+
**The `stream` plugin** owns the RPC infrastructure. Producer plugins
|
|
586
|
+
only need to:
|
|
587
|
+
- Create the proposal entry on dispatch (status=202)
|
|
588
|
+
- Rely on `AgentLoop.resolve()` to create data channels on accept
|
|
589
|
+
- Let clients/external producers call `stream`, `stream/completed`,
|
|
590
|
+
and `stream/aborted`
|
|
591
|
+
|
|
592
|
+
No scheme registration or tooldoc for the stream plugin itself — it's
|
|
593
|
+
pure RPC plumbing shared across all streaming producers.
|
|
594
|
+
|
|
595
|
+
## Bundled Plugins
|
|
453
596
|
|
|
454
597
|
| Plugin | Type | Description |
|
|
455
598
|
|--------|------|-------------|
|
|
456
599
|
| `get` | Core tool | Load file/entry into context |
|
|
457
|
-
| `set` | Core tool | Edit file/entry,
|
|
600
|
+
| `set` | Core tool | Edit file/entry, visibility control |
|
|
458
601
|
| `known` | Core tool + Assembly | Save knowledge, render `<knowns>` section |
|
|
459
602
|
| `rm` | Core tool | Delete permanently |
|
|
460
603
|
| `mv` | Core tool | Move entry |
|
|
461
604
|
| `cp` | Core tool | Copy entry |
|
|
462
|
-
| `sh` | Core tool | Shell command (act mode only) |
|
|
463
|
-
| `env` | Core tool | Exploratory command |
|
|
605
|
+
| `sh` | Core tool | Shell command (act mode only). Streaming producer — see [plugins_streaming_entries](#plugins_streaming_entries) |
|
|
606
|
+
| `env` | Core tool | Exploratory command. Streaming producer — see §8.1 |
|
|
607
|
+
| `stream` | Internal | Generic streaming-entry RPC (`stream`, `stream/completed`, `stream/aborted`, `stream/cancel`) for sh/env and future producers |
|
|
464
608
|
| `ask_user` | Core tool | Ask the user |
|
|
465
609
|
| `search` | Core tool | Web search (via external plugin) |
|
|
466
|
-
| `
|
|
467
|
-
| `update` | Structural | Signal continued work |
|
|
610
|
+
| `update` | Structural | Status report + lifecycle signal. `status="200\|204\|422"` terminates; `status="102"` continues. Exposes `hooks.update.resolve` for TurnExecutor. |
|
|
468
611
|
| `unknown` | Structural + Assembly | Register unknowns, render `<unknowns>` |
|
|
469
612
|
| `previous` | Assembly | Render `<previous>` loop history |
|
|
470
613
|
| `performed` | Assembly | Render `<performed>` active loop work |
|
|
471
|
-
| `
|
|
472
|
-
| `prompt` | Assembly | Render `<prompt mode="ask|act">` tag |
|
|
614
|
+
| `prompt` | Assembly | Render `<prompt mode="ask\|act" tokensFree="N" tokenUsage="M">` tag |
|
|
473
615
|
| `hedberg` | Utility | Pattern matching, interpretation, normalization |
|
|
474
|
-
| `instructions` | Internal | Preamble + tool docs + persona assembly |
|
|
475
|
-
| `file` | Internal | File entry projections and constraints |
|
|
476
|
-
| `rpc` | Internal | RPC method registration |
|
|
616
|
+
| `instructions` | Internal | Preamble + tool docs + persona assembly; exposes `hooks.instructions.resolveSystemPrompt` |
|
|
617
|
+
| `file` | Internal | File entry projections and constraints (`scheme IS NULL`) |
|
|
618
|
+
| `rpc` | Internal | RPC method registration + tool-fallback dispatch |
|
|
477
619
|
| `telemetry` | Internal | Audit entries, usage stats, reasoning_content |
|
|
478
|
-
| `budget` | Internal | Context ceiling enforcement (413)
|
|
479
|
-
| `
|
|
480
|
-
| `
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
`
|
|
620
|
+
| `budget` | Internal | Context ceiling enforcement: Prompt Demotion (pre-LLM first-turn 413) + Turn Demotion (post-dispatch). Exposes `hooks.budget.enforce` / `hooks.budget.postDispatch`. |
|
|
621
|
+
| `policy` | Internal | Ask-mode per-invocation rejections via `entry.recording` filter |
|
|
622
|
+
| `error` | Internal | `error.log` hook → `error://` entries |
|
|
623
|
+
| `think` | Tool | Private reasoning tag; contributes to `reasoning_content` via the `llm.reasoning` filter |
|
|
624
|
+
| `openai` / `ollama` / `xai` / `openrouter` | LLM provider | Register with `hooks.llm.providers`; handle `{prefix}/...` model aliases. Silently inert if their env isn't configured. |
|
|
625
|
+
| `persona` / `skill` | Internal | Runtime persona/skill management via RPC |
|
|
484
626
|
|
|
485
|
-
##
|
|
627
|
+
## External Plugins
|
|
486
628
|
|
|
487
629
|
| Plugin | Package | Description |
|
|
488
630
|
|--------|---------|-------------|
|
|
@@ -492,68 +634,121 @@ Removed: `crunch` (dead code, replaced by model-owned context management),
|
|
|
492
634
|
Loaded via `RUMMY_PLUGIN_*` env vars. External plugins have access
|
|
493
635
|
to the same PluginContext API as bundled plugins.
|
|
494
636
|
|
|
495
|
-
##
|
|
637
|
+
## RPC Methods {#plugins_rpc}
|
|
496
638
|
|
|
497
|
-
Client-facing JSON-RPC 2.0 over WebSocket.
|
|
498
|
-
|
|
639
|
+
Client-facing JSON-RPC 2.0 over WebSocket. Protocol version **2.0.0**.
|
|
640
|
+
The client surface is a thin projection of the plugin API (SPEC §0.3):
|
|
641
|
+
the six primitives match the plugin's `rummy.set` / `rummy.get` / etc.
|
|
642
|
+
exactly, plus a connection handshake and a few config verbs.
|
|
499
643
|
|
|
500
|
-
###
|
|
644
|
+
### Wire Format {#plugins_rpc_wire_format}
|
|
501
645
|
|
|
502
646
|
```json
|
|
503
647
|
// Request
|
|
504
|
-
{ "jsonrpc": "2.0", "id": 1, "method": "
|
|
648
|
+
{ "jsonrpc": "2.0", "id": 1, "method": "set", "params": { "run": "my_run", "path": "known://fact", "body": "...", "state": "resolved" } }
|
|
505
649
|
|
|
506
650
|
// Success response
|
|
507
|
-
{ "jsonrpc": "2.0", "id": 1, "result": { "
|
|
651
|
+
{ "jsonrpc": "2.0", "id": 1, "result": { "ok": true } }
|
|
508
652
|
|
|
509
653
|
// Error response
|
|
510
|
-
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -
|
|
654
|
+
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32603, "message": "set: path is required" } }
|
|
511
655
|
|
|
512
656
|
// Notification (server → client, no id)
|
|
513
|
-
{ "jsonrpc": "2.0", "method": "run/state", "params": { "run": "my_run", "status": 200 } }
|
|
657
|
+
{ "jsonrpc": "2.0", "method": "run/state", "params": { "run": "my_run", "turn": 3, "status": 200, ... } }
|
|
514
658
|
```
|
|
515
659
|
|
|
516
|
-
###
|
|
660
|
+
### Connection Handshake {#plugins_rpc_handshake}
|
|
661
|
+
|
|
662
|
+
First call every client makes. Establishes project identity and
|
|
663
|
+
enforces protocol-version compatibility.
|
|
517
664
|
|
|
518
665
|
| Method | Params | Notes |
|
|
519
666
|
|--------|--------|-------|
|
|
520
|
-
| `
|
|
521
|
-
| `set` | `{ path, body, run, attributes? }` | All entries go through handler chain |
|
|
522
|
-
| `rm` | `{ path, run }` | |
|
|
523
|
-
| `mv` | `{ path, to, run }` | |
|
|
524
|
-
| `cp` | `{ path, to, run }` | |
|
|
525
|
-
| `store` | `{ path, run?, persist?, ignore?, clear? }` | File constraints only — not a model tool |
|
|
526
|
-
| `getEntries` | `{ pattern?, body?, run?, limit?, offset? }` | Query entries |
|
|
667
|
+
| `rummy/hello` | `{ name, projectRoot, configPath?, clientVersion? }` | Returns `{ rummyVersion, projectId, projectRoot }`. Server rejects MAJOR mismatch with a protocol-mismatch error. |
|
|
527
668
|
|
|
528
|
-
###
|
|
669
|
+
### Primitives (see [primitives](SPEC.md#primitives)) {#plugins_rpc_primitives}
|
|
670
|
+
|
|
671
|
+
Six verbs. Object-args matching the entry grammar. Writer is fixed to
|
|
672
|
+
`"client"` server-side; permissions enforced per-scheme via the
|
|
673
|
+
scheme's `writable_by`.
|
|
529
674
|
|
|
530
675
|
| Method | Params | Notes |
|
|
531
676
|
|--------|--------|-------|
|
|
532
|
-
| `
|
|
533
|
-
| `
|
|
534
|
-
| `
|
|
535
|
-
| `
|
|
536
|
-
| `
|
|
537
|
-
| `
|
|
538
|
-
|
|
539
|
-
|
|
677
|
+
| `set` | `{ run, path, body?, state?, visibility?, outcome?, attributes?, append?, pattern?, bodyFilter? }` | Wide semantic: write content, change visibility/state, merge attributes, append (streaming), pattern update. Writing to `run://<alias>` starts or cancels a run (see §11.4). State transitions on proposed entries route through `AgentLoop.resolve()` for scheme-specific side effects. |
|
|
678
|
+
| `get` | `{ run, path, bodyFilter?, visibility? }` | Promote an entry (or pattern) to visible visibility. |
|
|
679
|
+
| `rm` | `{ run, path, bodyFilter? }` | Remove entry's view. |
|
|
680
|
+
| `cp` | `{ run, from, to, visibility? }` | Copy entry to new path. |
|
|
681
|
+
| `mv` | `{ run, from, to, visibility? }` | Rename entry. |
|
|
682
|
+
| `update` | `{ run, body, status?, attributes? }` | Write the once-per-turn lifecycle signal to `update://<slug>`. |
|
|
683
|
+
|
|
684
|
+
### Run Lifecycle via Primitives {#plugins_rpc_run_lifecycle}
|
|
685
|
+
|
|
686
|
+
Runs are addressable as `run://<alias>` entries (SPEC §0.5). The
|
|
687
|
+
client manipulates run lifecycle via ordinary `set` calls:
|
|
540
688
|
|
|
541
|
-
|
|
689
|
+
| Action | Call |
|
|
690
|
+
|--------|------|
|
|
691
|
+
| Start a run (named) | `set { path: "run://<alias>", body: <prompt>, attributes: { model, mode?, persona?, temperature?, contextLimit?, noRepo?, noInteraction?, noWeb?, noProposals? } }` |
|
|
692
|
+
| Start a run (anonymous) | `set { path: "run://", body: <prompt>, attributes: { model, ... } }` — server synthesizes alias as `${model}_${unixEpochMs}` and returns it in the response |
|
|
693
|
+
| Cancel a run | `set { path: "run://<alias>", state: "cancelled" }` |
|
|
694
|
+
| Inject continuation | `set { path: "run://<alias>", body: <message> }` on an existing run |
|
|
695
|
+
| Accept a proposal | `set { run, path: "<entry>", state: "resolved", body?: <output> }` |
|
|
696
|
+
| Reject a proposal | `set { run, path: "<entry>", state: "cancelled", body?: <reason> }` |
|
|
697
|
+
|
|
698
|
+
Starting a new run is fire-and-forget: server returns `{ ok: true, alias }`
|
|
699
|
+
immediately; client watches the run's state transitions via the
|
|
700
|
+
`run/state` notification (and the `run://` entry itself).
|
|
701
|
+
|
|
702
|
+
### Config & Query Methods {#plugins_rpc_queries}
|
|
703
|
+
|
|
704
|
+
Not every server capability fits the entry grammar. These are
|
|
705
|
+
dedicated verbs with 1:1 plugin-API equivalents.
|
|
542
706
|
|
|
543
707
|
| Method | Params | Notes |
|
|
544
708
|
|--------|--------|-------|
|
|
545
|
-
| `
|
|
546
|
-
| `
|
|
547
|
-
| `
|
|
548
|
-
| `getRuns` | `{ limit?, offset? }` |
|
|
549
|
-
| `
|
|
550
|
-
| `
|
|
709
|
+
| `ping` | — | Liveness check |
|
|
710
|
+
| `discover` | — | Return the live RPC catalog |
|
|
711
|
+
| `getModels` / `addModel` / `removeModel` | (see rpc.js) | Model aliases |
|
|
712
|
+
| `getRuns` / `getRun` | `{ limit?, offset? }` / `{ run }` | Run listing and detail |
|
|
713
|
+
| `getEntries` | `{ run, pattern?, scheme?, state?, visibility?, bodyFilter? }` | Read-only entry query. Returns `[{path, scheme, state, visibility, attributes, turn, tokens}]`. No promotion side-effect. Pair with `get` primitive (which is a write verb). |
|
|
714
|
+
| `file/constraint` | `{ pattern, visibility }` | Project-scoped: set overlay. `visibility ∈ {active, readonly, ignore}`. Patterns can be globs. `readonly` is enforced on `set://` accept in `AgentLoop.resolve()`. |
|
|
715
|
+
| `file/drop` | `{ pattern }` | Project-scoped: remove overlay row. |
|
|
716
|
+
| `getConstraints` | — | Project-scoped: returns `[{pattern, visibility}]`. |
|
|
717
|
+
| `skill/add` / `skill/remove` / `getSkills` / `listSkills` | | Skill management |
|
|
718
|
+
| `persona/set` / `listPersonas` | | Persona management |
|
|
719
|
+
| `stream` / `stream/completed` / `stream/aborted` / `stream/cancel` | | Streaming RPC (§8.1) |
|
|
720
|
+
|
|
721
|
+
**Why file constraints are typed RPCs and not `set` entries:** they
|
|
722
|
+
are project-scoped (no `run`), persist across runs, and `readonly`
|
|
723
|
+
requires enforcement server-side on `set://` accept. Every `set`
|
|
724
|
+
primitive call requires a run alias; constraints don't have one. The
|
|
725
|
+
typed verbs match the capability's actual shape rather than contorting
|
|
726
|
+
the grammar.
|
|
727
|
+
|
|
728
|
+
### Notifications (server → client) {#plugins_rpc_notifications}
|
|
551
729
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
| Method | Payload |
|
|
730
|
+
| Method | Purpose |
|
|
555
731
|
|--------|---------|
|
|
556
|
-
| `run/state` |
|
|
557
|
-
| `run/
|
|
558
|
-
| `
|
|
559
|
-
| `ui/
|
|
732
|
+
| `run/state` | Incremental state push per tool dispatch |
|
|
733
|
+
| `run/proposal` | A proposed entry awaits client resolution |
|
|
734
|
+
| `stream/cancelled` | Server-initiated streaming cancellation |
|
|
735
|
+
| `ui/render` | Streaming UI output |
|
|
736
|
+
| `ui/notify` | Toast notification |
|
|
737
|
+
|
|
738
|
+
### Retired Methods (2.0.0)
|
|
739
|
+
|
|
740
|
+
Protocol 1.x shipped many methods that collapsed into the primitive
|
|
741
|
+
grammar. Clients migrating from 1.x need to replace the following:
|
|
742
|
+
|
|
743
|
+
| 1.x method | Replacement |
|
|
744
|
+
|------------|-------------|
|
|
745
|
+
| `init` | `rummy/hello` |
|
|
746
|
+
| `ask` / `act` / `startRun` | `set { path: "run://<alias>", body: <prompt>, attributes: { model, mode, ... } }` |
|
|
747
|
+
| `run/resolve` | `set { run, path, state, body? }` |
|
|
748
|
+
| `run/abort` / `run/cancel` | `set { path: "run://<alias>", state: "cancelled" }` |
|
|
749
|
+
| `run/rename` | `mv { run, from: "run://<old>", to: "run://<new>" }` |
|
|
750
|
+
| `run/inject` | `set { path: "run://<alias>", body: <message> }` on an existing run |
|
|
751
|
+
| `run/config` | `set { path: "run://<alias>", attributes: { ... } }` |
|
|
752
|
+
| `store` (demote) | `set { run, path, visibility: "summarized", pattern: true }` |
|
|
753
|
+
| `getEntries` | Kept as §11.5 typed helper — now filter-capable (scheme/state/visibility). Pairs with the `get` write primitive. |
|
|
754
|
+
| `get { persist }` / `store { persist, clear, ignore }` (file constraints) | `file/constraint { pattern, visibility }` and `file/drop { pattern }`. Project-scoped helpers in §11.5 with real server enforcement for `readonly`. |
|