@possumtech/rummy 0.2.6 → 0.2.8
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 +2 -3
- package/PLUGINS.md +105 -82
- package/bin/rummy.js +9 -2
- package/migrations/001_initial_schema.sql +53 -68
- package/package.json +4 -6
- package/service.js +2 -2
- package/src/agent/AgentLoop.js +91 -58
- package/src/agent/ContextAssembler.js +2 -2
- package/src/agent/KnownStore.js +30 -11
- package/src/agent/ProjectAgent.js +1 -3
- package/src/agent/TurnExecutor.js +119 -31
- package/src/agent/XmlParser.js +20 -0
- package/src/agent/known_checks.sql +5 -4
- package/src/agent/known_queries.sql +4 -3
- package/src/agent/known_store.sql +29 -15
- package/src/agent/loops.sql +63 -0
- package/src/agent/runs.sql +7 -7
- package/src/agent/schemes.sql +2 -2
- package/src/agent/turns.sql +3 -3
- package/src/hooks/PluginContext.js +1 -10
- package/src/hooks/RummyContext.js +16 -8
- package/src/plugins/ask_user/ask_user.js +3 -2
- package/src/plugins/cp/cp.js +7 -7
- package/src/plugins/current/current.js +3 -4
- package/src/plugins/engine/engine.sql +5 -3
- package/src/plugins/engine/turn_context.sql +9 -4
- package/src/plugins/env/docs.md +2 -0
- package/src/plugins/env/env.js +3 -2
- package/src/plugins/file/file.js +9 -19
- package/src/plugins/get/docs.md +7 -3
- package/src/plugins/get/get.js +22 -6
- package/src/plugins/hedberg/docs.md +0 -9
- package/src/plugins/hedberg/hedberg.js +2 -5
- package/src/plugins/hedberg/matcher.js +1 -1
- package/src/plugins/hedberg/patterns.js +6 -6
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/index.js +28 -15
- package/src/plugins/instructions/instructions.js +1 -1
- package/src/plugins/known/known.js +9 -11
- package/src/plugins/mv/mv.js +7 -7
- package/src/plugins/previous/previous.js +6 -5
- package/src/plugins/progress/progress.js +6 -0
- package/src/plugins/prompt/prompt.js +9 -10
- package/src/plugins/rm/docs.md +3 -1
- package/src/plugins/rm/rm.js +24 -7
- package/src/plugins/rpc/rpc.js +33 -42
- package/src/plugins/set/docs.md +3 -1
- package/src/plugins/set/set.js +22 -16
- package/src/plugins/sh/sh.js +3 -2
- package/src/plugins/skills/skills.js +3 -4
- package/src/plugins/store/docs.md +2 -1
- package/src/plugins/store/store.js +14 -3
- package/src/plugins/summarize/summarize.js +1 -1
- package/src/plugins/telemetry/telemetry.js +17 -7
- package/src/plugins/unknown/unknown.js +3 -2
- package/src/plugins/update/update.js +1 -1
- package/src/server/ClientConnection.js +3 -5
- package/src/sql/v_model_context.sql +20 -23
- package/src/sql/v_run_log.sql +3 -3
- package/src/agent/prompt_queue.sql +0 -39
package/.env.example
CHANGED
|
@@ -11,7 +11,7 @@ PORT=3044
|
|
|
11
11
|
# Absolute path, no ~
|
|
12
12
|
# RUMMY_HOME=/home/ubuntu/.rummy
|
|
13
13
|
|
|
14
|
-
RUMMY_DB_PATH
|
|
14
|
+
RUMMY_DB_PATH=rummy.db
|
|
15
15
|
# SQLite mmap size in MB
|
|
16
16
|
RUMMY_MMAP_MB=0
|
|
17
17
|
|
|
@@ -57,7 +57,6 @@ RUMMY_X_TITLE=RUMMY
|
|
|
57
57
|
# Web Search
|
|
58
58
|
# RUMMY_SEARXNG_URL="http://127.0.0.1:8888"
|
|
59
59
|
|
|
60
|
-
#
|
|
60
|
+
# External plugins: npm i -g <package>, then uncomment
|
|
61
61
|
# RUMMY_PLUGIN_WEB="@possumtech/rummy.web"
|
|
62
|
-
# required for git repo and repomap/symbol functionality
|
|
63
62
|
# RUMMY_PLUGIN_REPO="@possumtech/rummy.repo"
|
package/PLUGINS.md
CHANGED
|
@@ -8,13 +8,21 @@ constructor receives `core` (a PluginContext) — the plugin's complete
|
|
|
8
8
|
interface with the system.
|
|
9
9
|
|
|
10
10
|
```js
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
|
|
11
13
|
export default class MyTool {
|
|
12
14
|
#core;
|
|
13
15
|
|
|
14
16
|
constructor(core) {
|
|
15
17
|
this.#core = core;
|
|
18
|
+
core.registerScheme();
|
|
16
19
|
core.on("handler", this.handler.bind(this));
|
|
17
20
|
core.on("full", this.full.bind(this));
|
|
21
|
+
core.on("summary", this.summary.bind(this));
|
|
22
|
+
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
23
|
+
core.filter("instructions.toolDocs", async (content) =>
|
|
24
|
+
content ? `${content}\n\n${docs}` : docs,
|
|
25
|
+
);
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
async handler(entry, rummy) {
|
|
@@ -22,13 +30,17 @@ export default class MyTool {
|
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
full(entry) {
|
|
25
|
-
// What the model sees at full fidelity
|
|
26
33
|
return `# mytool ${entry.path}\n${entry.body}`;
|
|
27
34
|
}
|
|
35
|
+
|
|
36
|
+
summary(entry) {
|
|
37
|
+
return entry.body;
|
|
38
|
+
}
|
|
28
39
|
}
|
|
29
40
|
```
|
|
30
41
|
|
|
31
42
|
File naming: `src/plugins/mytool/mytool.js`. Class name = file name.
|
|
43
|
+
Tool docs: `src/plugins/mytool/docs.md`.
|
|
32
44
|
|
|
33
45
|
External plugins install via npm and load via `RUMMY_PLUGIN_*` env vars:
|
|
34
46
|
|
|
@@ -51,18 +63,36 @@ Plugin: rummy.rm({ path: "file.txt" })
|
|
|
51
63
|
|
|
52
64
|
## Registration
|
|
53
65
|
|
|
54
|
-
All registration happens in the constructor via `core.on()
|
|
55
|
-
`core.filter()
|
|
66
|
+
All registration happens in the constructor via `core.on()`,
|
|
67
|
+
`core.filter()`, and `core.registerScheme()`.
|
|
68
|
+
|
|
69
|
+
### core.registerScheme(config?)
|
|
70
|
+
|
|
71
|
+
Registers this plugin's scheme in the database. Called once in the
|
|
72
|
+
constructor. Defaults are third-party friendly:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
core.registerScheme({
|
|
76
|
+
fidelity: "full", // "full", "turn", or "null"
|
|
77
|
+
modelVisible: 1, // 1 or 0
|
|
78
|
+
validStates: ["full", "proposed", "pass", "rejected", "error"],
|
|
79
|
+
category: "result", // "result", "file", "knowledge", "structural", "audit", "tool"
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
All fields optional. `core.registerScheme()` with no args gives a
|
|
84
|
+
sensible result-type scheme.
|
|
56
85
|
|
|
57
86
|
### core.on(event, callback, priority?)
|
|
58
87
|
|
|
59
88
|
| Event | Purpose |
|
|
60
89
|
|-------|---------|
|
|
61
90
|
| `"handler"` | Tool handler — called when model/client invokes this tool |
|
|
62
|
-
| `"full"` | Full
|
|
63
|
-
| `"summary"` | Summary
|
|
64
|
-
| `"
|
|
65
|
-
| `"turn"` |
|
|
91
|
+
| `"full"` | Full fidelity — what the model sees in `<current>` |
|
|
92
|
+
| `"summary"` | Summary fidelity — what the model sees in `<previous>` |
|
|
93
|
+
| `"turn.started"` | Turn beginning — write prompt/progress/instructions entries |
|
|
94
|
+
| `"turn.response"` | LLM responded — write audit entries, commit usage |
|
|
95
|
+
| `"turn.proposing"` | All dispatches done — materialize file edit proposals |
|
|
66
96
|
| `"entry.created"` | Entry created during dispatch |
|
|
67
97
|
| `"entry.changed"` | File entries changed on disk |
|
|
68
98
|
| Any `"dotted.name"` | Resolves to the matching hook in the hook tree |
|
|
@@ -71,12 +101,25 @@ All registration happens in the constructor via `core.on()` and
|
|
|
71
101
|
|
|
72
102
|
| Filter | Purpose |
|
|
73
103
|
|--------|---------|
|
|
104
|
+
| `"instructions.toolDocs"` | Append tool documentation to model prompt |
|
|
74
105
|
| `"assembly.system"` | Contribute to system message |
|
|
75
106
|
| `"assembly.user"` | Contribute to user message |
|
|
76
107
|
| `"llm.messages"` | Transform final messages before LLM call |
|
|
77
108
|
| `"llm.response"` | Transform LLM response |
|
|
78
109
|
| Any `"dotted.name"` | Resolves to the matching filter in the hook tree |
|
|
79
110
|
|
|
111
|
+
### Tool Docs
|
|
112
|
+
|
|
113
|
+
Each tool plugin has a `docs.md` file with model-facing documentation.
|
|
114
|
+
Registered via the `instructions.toolDocs` filter in the constructor:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const docs = readFileSync(new URL("./docs.md", import.meta.url), "utf8");
|
|
118
|
+
core.filter("instructions.toolDocs", async (content) =>
|
|
119
|
+
content ? `${content}\n\n${docs}` : docs,
|
|
120
|
+
);
|
|
121
|
+
```
|
|
122
|
+
|
|
80
123
|
### handler(entry, rummy)
|
|
81
124
|
|
|
82
125
|
The handler receives the parsed command entry and a per-turn RummyContext:
|
|
@@ -98,23 +141,24 @@ to stop the chain.
|
|
|
98
141
|
### full(entry) / summary(entry)
|
|
99
142
|
|
|
100
143
|
Returns the string the model sees for this tool's entries at the given
|
|
101
|
-
fidelity.
|
|
102
|
-
|
|
103
|
-
|
|
144
|
+
fidelity. `full` renders in `<current>` (active loop). `summary` renders
|
|
145
|
+
in `<previous>` (completed loops). Every tool MUST register `full`.
|
|
146
|
+
`summary` is optional — if unregistered, the entry is empty at summary
|
|
147
|
+
fidelity.
|
|
104
148
|
|
|
105
149
|
## Two Objects
|
|
106
150
|
|
|
107
151
|
Plugins interact with two objects at different scopes:
|
|
108
152
|
|
|
109
153
|
**PluginContext** (`this.#core`) — startup-scoped. Created once per plugin.
|
|
110
|
-
Used for registration (`on()`, `filter()`), database
|
|
111
|
-
|
|
112
|
-
|
|
154
|
+
Used for registration (`on()`, `filter()`, `registerScheme()`), database
|
|
155
|
+
access, store queries. This is `rummy.core` — the plugin-only tier that
|
|
156
|
+
clients cannot reach.
|
|
113
157
|
|
|
114
158
|
**RummyContext** (`rummy` argument) — turn-scoped. Passed to handlers
|
|
115
159
|
per-invocation. Has tool verbs, per-turn state (runId, turn, mode).
|
|
116
160
|
|
|
117
|
-
### Tool Verbs (
|
|
161
|
+
### Tool Verbs (on RummyContext)
|
|
118
162
|
|
|
119
163
|
| Method | Effect |
|
|
120
164
|
|--------|--------|
|
|
@@ -146,26 +190,42 @@ per-invocation. Has tool verbs, per-turn state (runId, turn, mode).
|
|
|
146
190
|
| `rummy.projectId` | Current project ID |
|
|
147
191
|
| `rummy.sequence` | Current turn number (RummyContext only) |
|
|
148
192
|
|
|
149
|
-
##
|
|
193
|
+
## Hedberg
|
|
150
194
|
|
|
151
|
-
|
|
195
|
+
The hedberg plugin exposes pattern matching and interpretation utilities
|
|
196
|
+
on `core.hooks.hedberg` for all plugins to use:
|
|
152
197
|
|
|
153
198
|
```js
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}, priority);
|
|
199
|
+
const { match, search, replace, parseSed, parseEdits, normalizeAttrs, generatePatch }
|
|
200
|
+
= core.hooks.hedberg;
|
|
157
201
|
```
|
|
158
202
|
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
| Method | Purpose |
|
|
204
|
+
|--------|---------|
|
|
205
|
+
| `match(pattern, string)` | Full-string pattern match (glob, regex, literal) |
|
|
206
|
+
| `search(pattern, string)` | Substring search, returns `{ found, match, index }` |
|
|
207
|
+
| `replace(body, search, replacement, opts?)` | Apply replacement (sed → literal → heuristic) |
|
|
208
|
+
| `parseSed(input)` | Parse sed syntax into `[{ search, replace, flags, sed }]` |
|
|
209
|
+
| `parseEdits(content)` | Detect edit format (merge conflict, udiff, Claude XML) |
|
|
210
|
+
| `normalizeAttrs(attrs)` | Heal model attribute names |
|
|
211
|
+
| `generatePatch(path, old, new)` | Generate unified diff |
|
|
161
212
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
213
|
+
Pattern types (auto-detected):
|
|
214
|
+
|
|
215
|
+
| Syntax | Type | Example |
|
|
216
|
+
|--------|------|---------|
|
|
217
|
+
| `s/old/new/flags` | Sed replace | `s/3000/8080/g` |
|
|
218
|
+
| `/pattern/flags` | Regex | `/\d+/g` |
|
|
219
|
+
| `$.path` | JSONPath | `$.config.port` |
|
|
220
|
+
| `//element` | XPath | `//div[@class]` |
|
|
221
|
+
| `*glob*` | Glob | `src/**/*.js` |
|
|
222
|
+
| Everything else | Literal | `port = 3000` |
|
|
167
223
|
|
|
168
|
-
|
|
224
|
+
## Events & Filters
|
|
225
|
+
|
|
226
|
+
**Events** are fire-and-forget. All handlers run. Return values ignored.
|
|
227
|
+
**Filters** transform data through a chain. Lower priority runs first.
|
|
228
|
+
All hooks are async.
|
|
169
229
|
|
|
170
230
|
### Project Lifecycle
|
|
171
231
|
|
|
@@ -193,7 +253,7 @@ Lower priority runs first. All hooks are async.
|
|
|
193
253
|
| `act.started` | event | `{ projectId, model, prompt, run }` | Run requested in act mode |
|
|
194
254
|
| `run.config` | filter | Config object, `{ projectId }` | Before run config applied |
|
|
195
255
|
| `run.progress` | event | `{ run, turn, status }` | Status change (thinking, processing) |
|
|
196
|
-
| `run.state` | event | `{ run, turn, status, summary, history, unknowns, proposed, telemetry }` | After each turn
|
|
256
|
+
| `run.state` | event | `{ run, turn, status, summary, history, unknowns, proposed, telemetry }` | After each turn |
|
|
197
257
|
| `run.step.completed` | event | `{ run, turn, flags }` | Turn resolved, no proposals pending |
|
|
198
258
|
| `ask.completed` | event | `{ projectId, run, status, turn }` | Ask run finished |
|
|
199
259
|
| `act.completed` | event | `{ projectId, run, status, turn }` | Act run finished |
|
|
@@ -204,14 +264,16 @@ Hooks fire in this order every turn:
|
|
|
204
264
|
|
|
205
265
|
| Hook | Type | Payload | When |
|
|
206
266
|
|------|------|---------|------|
|
|
207
|
-
| `
|
|
267
|
+
| `turn.started` | event | `{ rummy, mode, prompt, isContinuation }` | Plugins write prompt/progress/instructions entries |
|
|
268
|
+
| `entry.changed` | event | `{ rummy, runId, turn, paths }` | Files changed on disk (repo plugin) |
|
|
208
269
|
| `onTurn` | processor | `(rummy)` | Plugin turn setup, before context assembly |
|
|
209
|
-
| `assembly.system` | filter | `(content, { rows, loopStartTurn, type,
|
|
210
|
-
| `assembly.user` | filter | `(content, { rows, loopStartTurn, type,
|
|
211
|
-
| `llm.messages` | filter | `messages[], { model, projectId, runId }` | Before LLM call
|
|
270
|
+
| `assembly.system` | filter | `(content, { rows, loopStartTurn, type, contextSize })` | Build system message |
|
|
271
|
+
| `assembly.user` | filter | `(content, { rows, loopStartTurn, type, contextSize })` | Build user message |
|
|
272
|
+
| `llm.messages` | filter | `messages[], { model, projectId, runId }` | Before LLM call |
|
|
212
273
|
| `llm.request.started` | event | `{ model, turn }` | LLM call about to fire |
|
|
213
|
-
| `llm.response` | filter | `response, { model, projectId, runId }` | Raw LLM response
|
|
274
|
+
| `llm.response` | filter | `response, { model, projectId, runId }` | Raw LLM response |
|
|
214
275
|
| `llm.request.completed` | event | `{ model, turn, usage }` | LLM call finished |
|
|
276
|
+
| `turn.response` | event | `{ rummy, turn, result, responseMessage, content, ... }` | Plugins write audit entries |
|
|
215
277
|
| `tools.dispatch` | handler | `(entry, rummy)` | Per command — handler chain executes |
|
|
216
278
|
| `entry.created` | event | `{ scheme, path, body, attributes, state, resultPath }` | After each command dispatched |
|
|
217
279
|
| `turn.proposing` | event | `{ rummy, recorded }` | All dispatches done — materialize proposals |
|
|
@@ -223,46 +285,6 @@ Hooks fire in this order every turn:
|
|
|
223
285
|
| `ui.render` | event | `{ text, append }` | Text for client display |
|
|
224
286
|
| `ui.notify` | event | `{ text, level }` | Status notification |
|
|
225
287
|
|
|
226
|
-
## RPC Registration
|
|
227
|
-
|
|
228
|
-
```js
|
|
229
|
-
hooks.rpc.registry.register("myMethod", {
|
|
230
|
-
handler: async (params, ctx) => {
|
|
231
|
-
// ctx.projectAgent, ctx.db, ctx.projectId, ctx.projectRoot
|
|
232
|
-
return { result: "value" };
|
|
233
|
-
},
|
|
234
|
-
description: "What this method does",
|
|
235
|
-
params: { arg1: "description" },
|
|
236
|
-
requiresInit: true,
|
|
237
|
-
longRunning: true, // for methods that call the model
|
|
238
|
-
});
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Hedberg Pattern Library
|
|
242
|
-
|
|
243
|
-
Available in JS and SQL. Five pattern types, auto-detected:
|
|
244
|
-
|
|
245
|
-
| Syntax | Type | Example |
|
|
246
|
-
|--------|------|---------|
|
|
247
|
-
| `s/old/new/flags` | Sed replace | `s/3000/8080/g` |
|
|
248
|
-
| `/pattern/flags` | Regex | `/\d+/g` |
|
|
249
|
-
| `$.path` | JSONPath | `$.config.port` |
|
|
250
|
-
| `//element` | XPath | `//div[@class]` |
|
|
251
|
-
| `*glob*` | Glob | `src/**/*.js` |
|
|
252
|
-
| Everything else | Literal | `port = 3000` |
|
|
253
|
-
|
|
254
|
-
JS API:
|
|
255
|
-
|
|
256
|
-
```js
|
|
257
|
-
import { hedmatch, hedsearch, hedreplace } from "./sql/functions/hedberg.js";
|
|
258
|
-
|
|
259
|
-
hedmatch(pattern, string) // → boolean (full string match)
|
|
260
|
-
hedsearch(pattern, string) // → { found, match, index }
|
|
261
|
-
hedreplace(pattern, replacement, string) // → new string or null
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
SQL functions: `hedmatch()`, `hedsearch()`, `hedreplace()`.
|
|
265
|
-
|
|
266
288
|
## Bundled Plugins
|
|
267
289
|
|
|
268
290
|
Each plugin has its own README at `src/plugins/{name}/README.md`.
|
|
@@ -271,7 +293,7 @@ Each plugin has its own README at `src/plugins/{name}/README.md`.
|
|
|
271
293
|
|--------|------|-------------|
|
|
272
294
|
| [`get`](src/plugins/get/) | Core tool | Load file/entry into context |
|
|
273
295
|
| [`set`](src/plugins/set/) | Core tool | Edit file/entry |
|
|
274
|
-
| [`known`](src/plugins/known/) | Core tool | Save knowledge, render `<
|
|
296
|
+
| [`known`](src/plugins/known/) | Core tool + Assembly | Save knowledge, render `<knowns>` section |
|
|
275
297
|
| [`store`](src/plugins/store/) | Core tool | Remove from context |
|
|
276
298
|
| [`rm`](src/plugins/rm/) | Core tool | Delete permanently |
|
|
277
299
|
| [`mv`](src/plugins/mv/) | Core tool | Move entry |
|
|
@@ -281,22 +303,23 @@ Each plugin has its own README at `src/plugins/{name}/README.md`.
|
|
|
281
303
|
| [`ask_user`](src/plugins/ask_user/) | Core tool | Ask the user |
|
|
282
304
|
| [`summarize`](src/plugins/summarize/) | Structural | Signal completion |
|
|
283
305
|
| [`update`](src/plugins/update/) | Structural | Signal continued work |
|
|
284
|
-
| [`unknown`](src/plugins/unknown/) | Structural | Register unknowns, render `<unknowns>` |
|
|
306
|
+
| [`unknown`](src/plugins/unknown/) | Structural + Assembly | Register unknowns, render `<unknowns>` |
|
|
285
307
|
| [`previous`](src/plugins/previous/) | Assembly | Render `<previous>` loop history |
|
|
286
308
|
| [`current`](src/plugins/current/) | Assembly | Render `<current>` active loop work |
|
|
287
|
-
| [`progress`](src/plugins/progress/) | Assembly | Render `<progress>` bridge
|
|
309
|
+
| [`progress`](src/plugins/progress/) | Assembly | Render `<progress>` telemetry + bridge |
|
|
288
310
|
| [`prompt`](src/plugins/prompt/) | Assembly | Render `<ask>`/`<act>` prompt tag |
|
|
289
|
-
| [`
|
|
290
|
-
| [`
|
|
311
|
+
| [`hedberg`](src/plugins/hedberg/) | Utility | Pattern matching, interpretation, normalization |
|
|
312
|
+
| [`instructions`](src/plugins/instructions/) | Internal | Preamble + tool docs + persona assembly |
|
|
313
|
+
| [`file`](src/plugins/file/) | Internal | File entry projections and constraints |
|
|
291
314
|
| [`rpc`](src/plugins/rpc/) | Internal | RPC method registration |
|
|
292
315
|
| [`skills`](src/plugins/skills/) | Internal | Skill/persona management |
|
|
293
|
-
| [`telemetry`](src/plugins/telemetry/) | Internal |
|
|
316
|
+
| [`telemetry`](src/plugins/telemetry/) | Internal | Audit entries, usage stats, last_run.txt |
|
|
294
317
|
|
|
295
318
|
## External Plugins
|
|
296
319
|
|
|
297
320
|
| Plugin | Package | Description |
|
|
298
321
|
|--------|---------|-------------|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
322
|
+
| Repo | `@possumtech/rummy.repo` | Git-aware file scanning and symbol extraction |
|
|
323
|
+
| Web | `@possumtech/rummy.web` | Web search and URL fetching via searxng |
|
|
301
324
|
|
|
302
|
-
Loaded via `RUMMY_PLUGIN_*` env vars.
|
|
325
|
+
Loaded via `RUMMY_PLUGIN_*` env vars.
|
package/bin/rummy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { join, dirname } from "node:path";
|
|
4
|
+
import { isAbsolute, join, dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
|
|
@@ -9,10 +9,17 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
const packageRoot = join(__dirname, "..");
|
|
10
10
|
|
|
11
11
|
const rummyHome = process.env.RUMMY_HOME || join(homedir(), ".rummy");
|
|
12
|
-
process.env.RUMMY_HOME = rummyHome;
|
|
13
12
|
|
|
13
|
+
// Load defaults, then user overrides
|
|
14
14
|
process.loadEnvFile(join(packageRoot, ".env.example"));
|
|
15
15
|
const userEnv = join(rummyHome, ".env");
|
|
16
16
|
if (existsSync(userEnv)) process.loadEnvFile(userEnv);
|
|
17
17
|
|
|
18
|
+
// Resolve RUMMY_HOME and make DB path absolute relative to it
|
|
19
|
+
process.env.RUMMY_HOME = rummyHome;
|
|
20
|
+
const dbPath = process.env.RUMMY_DB_PATH;
|
|
21
|
+
if (dbPath && !isAbsolute(dbPath)) {
|
|
22
|
+
process.env.RUMMY_DB_PATH = join(rummyHome, dbPath);
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
await import(join(packageRoot, "service.js"));
|
|
@@ -4,13 +4,12 @@ PRAGMA mmap_size = $mmap_size;
|
|
|
4
4
|
-- INIT: initial_schema
|
|
5
5
|
|
|
6
6
|
-- Scheme registry: single source of truth for all scheme metadata.
|
|
7
|
-
--
|
|
8
|
-
-- valid_states
|
|
7
|
+
-- Status codes are HTTP: 2xx success, 3xx redirect, 4xx model error, 5xx system error.
|
|
8
|
+
-- No valid_states — HTTP semantics are universal.
|
|
9
|
+
-- No fidelity — entries don't decide their own importance.
|
|
9
10
|
CREATE TABLE IF NOT EXISTS schemes (
|
|
10
11
|
name TEXT PRIMARY KEY
|
|
11
|
-
, fidelity TEXT NOT NULL CHECK (fidelity IN ('full', 'turn', 'null'))
|
|
12
12
|
, model_visible BOOLEAN NOT NULL DEFAULT 1
|
|
13
|
-
, valid_states TEXT NOT NULL
|
|
14
13
|
, category TEXT
|
|
15
14
|
);
|
|
16
15
|
|
|
@@ -37,15 +36,14 @@ CREATE TABLE IF NOT EXISTS models (
|
|
|
37
36
|
);
|
|
38
37
|
|
|
39
38
|
-- Runs: execution units belonging to a project.
|
|
40
|
-
--
|
|
39
|
+
-- Status uses HTTP codes: 100=queued, 102=running, 200=completed,
|
|
40
|
+
-- 202=proposed, 500=failed, 499=aborted.
|
|
41
41
|
CREATE TABLE IF NOT EXISTS runs (
|
|
42
42
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
43
43
|
, project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE
|
|
44
44
|
, parent_run_id INTEGER REFERENCES runs (id) ON DELETE SET NULL
|
|
45
45
|
, model TEXT
|
|
46
|
-
, status
|
|
47
|
-
status IN ('queued', 'running', 'proposed', 'completed', 'failed', 'aborted')
|
|
48
|
-
)
|
|
46
|
+
, status INTEGER NOT NULL DEFAULT 100 CHECK (status BETWEEN 100 AND 599)
|
|
49
47
|
, alias TEXT NOT NULL UNIQUE
|
|
50
48
|
, temperature REAL CHECK (
|
|
51
49
|
temperature IS NULL OR (temperature >= 0 AND temperature <= 2)
|
|
@@ -53,16 +51,39 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
53
51
|
, persona TEXT
|
|
54
52
|
, context_limit INTEGER CHECK (context_limit IS NULL OR context_limit >= 1024)
|
|
55
53
|
, next_turn INTEGER NOT NULL DEFAULT 1 CHECK (next_turn >= 1)
|
|
54
|
+
, next_loop INTEGER NOT NULL DEFAULT 1 CHECK (next_loop >= 1)
|
|
56
55
|
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
57
56
|
);
|
|
58
57
|
|
|
59
58
|
CREATE INDEX IF NOT EXISTS idx_runs_alias ON runs (alias);
|
|
60
59
|
CREATE INDEX IF NOT EXISTS idx_runs_project ON runs (project_id);
|
|
61
60
|
|
|
61
|
+
-- Loops: execution units within a run. Each ask/act call creates a loop.
|
|
62
|
+
-- Status: 100=pending, 102=running, 200=completed, 202=proposed,
|
|
63
|
+
-- 500=failed, 499=aborted.
|
|
64
|
+
CREATE TABLE IF NOT EXISTS loops (
|
|
65
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
66
|
+
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
67
|
+
, sequence INTEGER NOT NULL CHECK (sequence >= 1)
|
|
68
|
+
, mode TEXT NOT NULL CHECK (mode IN ('ask', 'act'))
|
|
69
|
+
, model TEXT
|
|
70
|
+
, prompt TEXT NOT NULL DEFAULT ''
|
|
71
|
+
, status INTEGER NOT NULL DEFAULT 100 CHECK (status BETWEEN 100 AND 599)
|
|
72
|
+
, config JSON
|
|
73
|
+
, result JSON
|
|
74
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
75
|
+
, UNIQUE (run_id, sequence)
|
|
76
|
+
);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_loops_run ON loops (run_id);
|
|
78
|
+
-- Enforce at most one running loop per run.
|
|
79
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_loops_one_active
|
|
80
|
+
ON loops (run_id) WHERE status = 102;
|
|
81
|
+
|
|
62
82
|
-- Turns: usage stats and sequencing (operational, not model-facing)
|
|
63
83
|
CREATE TABLE IF NOT EXISTS turns (
|
|
64
84
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
65
85
|
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
86
|
+
, loop_id INTEGER NOT NULL REFERENCES loops (id) ON DELETE CASCADE
|
|
66
87
|
, sequence INTEGER NOT NULL CHECK (sequence >= 1)
|
|
67
88
|
, prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0)
|
|
68
89
|
, cached_tokens INTEGER NOT NULL DEFAULT 0 CHECK (cached_tokens >= 0)
|
|
@@ -90,15 +111,20 @@ ON file_constraints (project_id);
|
|
|
90
111
|
-- Known K/V Store: the unified state machine.
|
|
91
112
|
-- Files, knowledge, tool results, audit — everything is a keyed entry.
|
|
92
113
|
-- scheme: derived from path via schemeOf(). Generated column.
|
|
93
|
-
--
|
|
114
|
+
-- status: HTTP status code (2xx success, 4xx model error, 5xx system error).
|
|
115
|
+
-- fidelity: visibility level, independently managed by relevance engine.
|
|
94
116
|
CREATE TABLE IF NOT EXISTS known_entries (
|
|
95
117
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
96
118
|
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
119
|
+
, loop_id INTEGER REFERENCES loops (id) ON DELETE CASCADE
|
|
97
120
|
, turn INTEGER NOT NULL DEFAULT 0 CHECK (turn >= 0)
|
|
98
121
|
, path TEXT NOT NULL
|
|
99
122
|
, body TEXT NOT NULL DEFAULT ''
|
|
100
123
|
, scheme TEXT GENERATED ALWAYS AS (schemeOf(path)) STORED
|
|
101
|
-
,
|
|
124
|
+
, status INTEGER NOT NULL DEFAULT 200 CHECK (status BETWEEN 100 AND 599)
|
|
125
|
+
, fidelity TEXT NOT NULL DEFAULT 'full' CHECK (
|
|
126
|
+
fidelity IN ('full', 'summary', 'index', 'stored')
|
|
127
|
+
)
|
|
102
128
|
, hash TEXT
|
|
103
129
|
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
104
130
|
, tokens INTEGER NOT NULL DEFAULT 0 CHECK (tokens >= 0)
|
|
@@ -110,43 +136,14 @@ CREATE TABLE IF NOT EXISTS known_entries (
|
|
|
110
136
|
);
|
|
111
137
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_known_entries_run_path
|
|
112
138
|
ON known_entries (run_id, path);
|
|
113
|
-
CREATE INDEX IF NOT EXISTS
|
|
114
|
-
ON known_entries (run_id, scheme,
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_known_entries_scheme_status
|
|
140
|
+
ON known_entries (run_id, scheme, status);
|
|
115
141
|
CREATE INDEX IF NOT EXISTS idx_known_entries_turn
|
|
116
142
|
ON known_entries (run_id, turn);
|
|
117
143
|
|
|
118
|
-
--
|
|
119
|
-
CREATE TRIGGER IF NOT EXISTS trg_known_entry_state_insert
|
|
120
|
-
BEFORE INSERT ON known_entries
|
|
121
|
-
FOR EACH ROW
|
|
122
|
-
BEGIN
|
|
123
|
-
SELECT RAISE(ABORT, 'invalid state for scheme')
|
|
124
|
-
WHERE NOT EXISTS (
|
|
125
|
-
SELECT 1
|
|
126
|
-
FROM schemes AS s, json_each(s.valid_states) AS j
|
|
127
|
-
WHERE
|
|
128
|
-
s.name = COALESCE(schemeOf(NEW.path), 'file')
|
|
129
|
-
AND j.value = NEW.state
|
|
130
|
-
);
|
|
131
|
-
END;
|
|
132
|
-
|
|
133
|
-
-- Validate state against schemes.valid_states on update.
|
|
134
|
-
CREATE TRIGGER IF NOT EXISTS trg_known_entry_state_update
|
|
135
|
-
BEFORE UPDATE OF state ON known_entries
|
|
136
|
-
FOR EACH ROW
|
|
137
|
-
WHEN OLD.state != NEW.state
|
|
138
|
-
BEGIN
|
|
139
|
-
SELECT RAISE(ABORT, 'invalid state for scheme')
|
|
140
|
-
WHERE NOT EXISTS (
|
|
141
|
-
SELECT 1
|
|
142
|
-
FROM schemes AS s, json_each(s.valid_states) AS j
|
|
143
|
-
WHERE
|
|
144
|
-
s.name = COALESCE(schemeOf(NEW.path), 'file')
|
|
145
|
-
AND j.value = NEW.state
|
|
146
|
-
);
|
|
147
|
-
END;
|
|
144
|
+
-- No state validation triggers — HTTP status codes are universal.
|
|
148
145
|
|
|
149
|
-
-- UNRESOLVED VIEW: all entries awaiting user action
|
|
146
|
+
-- UNRESOLVED VIEW: all entries awaiting user action (202 Accepted)
|
|
150
147
|
CREATE VIEW IF NOT EXISTS v_unresolved AS
|
|
151
148
|
SELECT
|
|
152
149
|
run_id
|
|
@@ -155,19 +152,20 @@ SELECT
|
|
|
155
152
|
, attributes
|
|
156
153
|
, turn
|
|
157
154
|
FROM known_entries
|
|
158
|
-
WHERE
|
|
155
|
+
WHERE status = 202;
|
|
159
156
|
|
|
160
157
|
-- Turn context: materialized snapshot of what the model sees each turn.
|
|
161
158
|
-- known_entries is the warehouse. turn_context is the shipment.
|
|
162
159
|
CREATE TABLE IF NOT EXISTS turn_context (
|
|
163
160
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
164
161
|
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
162
|
+
, loop_id INTEGER REFERENCES loops (id) ON DELETE CASCADE
|
|
165
163
|
, turn INTEGER NOT NULL CHECK (turn >= 1)
|
|
166
164
|
, ordinal INTEGER NOT NULL CHECK (ordinal >= 0)
|
|
167
165
|
, path TEXT NOT NULL
|
|
168
166
|
, scheme TEXT GENERATED ALWAYS AS (schemeOf(path)) STORED
|
|
167
|
+
, status INTEGER NOT NULL DEFAULT 200 CHECK (status BETWEEN 100 AND 599)
|
|
169
168
|
, fidelity TEXT NOT NULL CHECK (fidelity IN ('full', 'summary', 'index'))
|
|
170
|
-
, state TEXT NOT NULL DEFAULT 'full'
|
|
171
169
|
, body TEXT NOT NULL DEFAULT ''
|
|
172
170
|
, tokens INTEGER NOT NULL DEFAULT 0 CHECK (tokens >= 0)
|
|
173
171
|
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
@@ -177,8 +175,8 @@ CREATE TABLE IF NOT EXISTS turn_context (
|
|
|
177
175
|
CREATE INDEX IF NOT EXISTS idx_turn_context_run_turn
|
|
178
176
|
ON turn_context (run_id, turn);
|
|
179
177
|
|
|
180
|
-
-- Enforce valid run state transitions.
|
|
181
|
-
--
|
|
178
|
+
-- Enforce valid run state transitions (HTTP status codes).
|
|
179
|
+
-- 100=queued, 102=running, 200=completed, 202=proposed, 499=aborted, 500=failed.
|
|
182
180
|
CREATE TRIGGER IF NOT EXISTS trg_run_state_transition
|
|
183
181
|
BEFORE UPDATE OF status ON runs
|
|
184
182
|
FOR EACH ROW
|
|
@@ -186,30 +184,17 @@ WHEN OLD.status != NEW.status
|
|
|
186
184
|
BEGIN
|
|
187
185
|
SELECT RAISE(ABORT, 'invalid run state transition')
|
|
188
186
|
WHERE NOT (
|
|
189
|
-
(OLD.status =
|
|
190
|
-
OR (OLD.status =
|
|
191
|
-
OR (OLD.status =
|
|
192
|
-
OR (OLD.status =
|
|
193
|
-
OR (OLD.status =
|
|
194
|
-
OR (OLD.status =
|
|
187
|
+
(OLD.status = 100 AND NEW.status IN (102, 499))
|
|
188
|
+
OR (OLD.status = 102 AND NEW.status IN (200, 202, 500, 499))
|
|
189
|
+
OR (OLD.status = 202 AND NEW.status IN (102, 200, 499))
|
|
190
|
+
OR (OLD.status = 200 AND NEW.status IN (102, 499))
|
|
191
|
+
OR (OLD.status = 500 AND NEW.status IN (102, 499))
|
|
192
|
+
OR (OLD.status = 499 AND NEW.status IN (102))
|
|
195
193
|
);
|
|
196
194
|
END;
|
|
197
195
|
|
|
198
|
-
-- Prompt queue
|
|
199
|
-
|
|
200
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
201
|
-
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
202
|
-
, mode TEXT NOT NULL CHECK (mode IN ('ask', 'act'))
|
|
203
|
-
, model TEXT
|
|
204
|
-
, prompt TEXT NOT NULL
|
|
205
|
-
, config JSON
|
|
206
|
-
, status TEXT NOT NULL DEFAULT 'pending'
|
|
207
|
-
CHECK (status IN ('pending', 'active', 'completed', 'aborted'))
|
|
208
|
-
, result JSON
|
|
209
|
-
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
CREATE INDEX IF NOT EXISTS idx_prompt_queue_run ON prompt_queue (run_id, status);
|
|
196
|
+
-- Prompt queue is the loops table (defined above).
|
|
197
|
+
-- Each ask/act enqueues a loop (status=pending). Worker claims FIFO per run.
|
|
213
198
|
|
|
214
199
|
-- RPC audit log. Every call recorded unconditionally.
|
|
215
200
|
CREATE TABLE IF NOT EXISTS rpc_log (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@possumtech/rummy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Relational Unknowns Memory Management Yoke",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm"
|
|
@@ -47,12 +47,10 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@possumtech/sqlrite": "^3.1.0",
|
|
50
|
+
"@xmldom/xmldom": "^0.9.9",
|
|
50
51
|
"htmlparser2": "^12.0.0",
|
|
51
52
|
"tiktoken": "^1.0.22",
|
|
52
|
-
"ws": "^8.19.0"
|
|
53
|
-
|
|
54
|
-
"optionalDependencies": {
|
|
55
|
-
"@possumtech/rummy.repo": "^0.0.4",
|
|
56
|
-
"@possumtech/rummy.web": "^0.0.7"
|
|
53
|
+
"ws": "^8.19.0",
|
|
54
|
+
"xpath": "^0.0.34"
|
|
57
55
|
}
|
|
58
56
|
}
|
package/service.js
CHANGED
|
@@ -53,7 +53,7 @@ async function main() {
|
|
|
53
53
|
await registerPlugins([pluginsDir, userPluginsDir], hooks);
|
|
54
54
|
|
|
55
55
|
// 5. Bootstrap Persistence
|
|
56
|
-
const dbPath = process.env.RUMMY_DB_PATH
|
|
56
|
+
const dbPath = process.env.RUMMY_DB_PATH;
|
|
57
57
|
const functionsDir = fileURLToPath(new URL("./src/sql/functions", import.meta.url));
|
|
58
58
|
const sqlFunctions = readdirSync(functionsDir)
|
|
59
59
|
.filter((f) => f.endsWith(".js") && !f.endsWith(".test.js"))
|
|
@@ -115,7 +115,7 @@ async function main() {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// 6b. Abort stuck runs (can't be running if the server just started)
|
|
118
|
-
await db.
|
|
118
|
+
await db.reset_active_loops.run({});
|
|
119
119
|
const aborted = await db.abort_stuck_runs.run({});
|
|
120
120
|
if (aborted.changes > 0) {
|
|
121
121
|
console.log(`[RUMMY] Recovered ${aborted.changes} stuck run(s)`);
|