@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/bin/demo.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fast inspector for the most recent (or a named) run.
|
|
3
|
+
//
|
|
4
|
+
// Pulls the packet the model actually saw (system://N, user://N), the
|
|
5
|
+
// model's response (assistant://N, reasoning://N), the log entries for
|
|
6
|
+
// the turn, and optional summaries across the whole run.
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// npm run test:demo # latest run, latest turn
|
|
10
|
+
// npm run test:demo -- --turn 5 # latest run, turn 5
|
|
11
|
+
// npm run test:demo -- --run alias # specific run, its latest turn
|
|
12
|
+
// npm run test:demo -- --all # all turns' headers + final packet
|
|
13
|
+
// npm run test:demo -- --packet # only dump system/user/assistant
|
|
14
|
+
|
|
15
|
+
import { parseArgs } from "node:util";
|
|
16
|
+
import { DatabaseSync } from "node:sqlite";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
|
|
19
|
+
const args = parseArgs({
|
|
20
|
+
options: {
|
|
21
|
+
run: { type: "string" },
|
|
22
|
+
turn: { type: "string" },
|
|
23
|
+
all: { type: "boolean", default: false },
|
|
24
|
+
packet: { type: "boolean", default: false },
|
|
25
|
+
db: { type: "string", default: "rummy_dev.db" },
|
|
26
|
+
},
|
|
27
|
+
allowPositionals: false,
|
|
28
|
+
}).values;
|
|
29
|
+
|
|
30
|
+
const db = new DatabaseSync(args.db);
|
|
31
|
+
|
|
32
|
+
const runRow = args.run
|
|
33
|
+
? db.prepare("SELECT * FROM runs WHERE alias = ?").get(args.run)
|
|
34
|
+
: db.prepare("SELECT * FROM runs ORDER BY id DESC LIMIT 1").get();
|
|
35
|
+
|
|
36
|
+
if (!runRow) {
|
|
37
|
+
console.error(args.run ? `run ${args.run} not found` : "no runs in db");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const runId = runRow.id;
|
|
42
|
+
const alias = runRow.alias;
|
|
43
|
+
|
|
44
|
+
const turns = db
|
|
45
|
+
.prepare(
|
|
46
|
+
"SELECT sequence, context_tokens, prompt_tokens, completion_tokens, reasoning_tokens, total_tokens, cost FROM turns WHERE run_id = ? ORDER BY sequence",
|
|
47
|
+
)
|
|
48
|
+
.all(runId);
|
|
49
|
+
|
|
50
|
+
const selectedTurn = args.turn ? Number(args.turn) : turns.at(-1)?.sequence;
|
|
51
|
+
|
|
52
|
+
const banner = (s) => console.log(`\n━━━ ${s} ${"━".repeat(Math.max(4, 70 - s.length))}`);
|
|
53
|
+
|
|
54
|
+
const loadBody = (path) => {
|
|
55
|
+
const row = db
|
|
56
|
+
.prepare(
|
|
57
|
+
"SELECT e.body, e.attributes, rv.state, rv.outcome, rv.visibility FROM entries e JOIN run_views rv ON rv.entry_id = e.id WHERE rv.run_id = ? AND e.path = ?",
|
|
58
|
+
)
|
|
59
|
+
.get(runId, path);
|
|
60
|
+
return row ? row : null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ── header ─────────────────────────────────────────────────────────
|
|
64
|
+
const totalCost = turns.reduce((s, t) => s + (t.cost || 0), 0);
|
|
65
|
+
banner(`run ${alias} (id=${runId}, status=${runRow.status})`);
|
|
66
|
+
console.log(
|
|
67
|
+
`model=${runRow.model} turns=${turns.length} cost=${totalCost.toFixed(6)}`,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!args.packet) {
|
|
71
|
+
banner("turns");
|
|
72
|
+
for (const t of turns) {
|
|
73
|
+
const marker = t.sequence === selectedTurn ? "▶" : " ";
|
|
74
|
+
console.log(
|
|
75
|
+
`${marker} turn ${String(t.sequence).padStart(2)} ctx=${String(t.context_tokens).padStart(6)} in=${String(t.prompt_tokens).padStart(6)} out=${String(t.completion_tokens).padStart(5)} reason=${String(t.reasoning_tokens).padStart(5)}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── packet for the selected turn ───────────────────────────────────
|
|
81
|
+
if (selectedTurn) {
|
|
82
|
+
const system = loadBody(`system://${selectedTurn}`);
|
|
83
|
+
const user = loadBody(`user://${selectedTurn}`);
|
|
84
|
+
const assistant = loadBody(`assistant://${selectedTurn}`);
|
|
85
|
+
const reasoning = loadBody(`reasoning://${selectedTurn}`);
|
|
86
|
+
|
|
87
|
+
banner(`turn ${selectedTurn} — system://`);
|
|
88
|
+
console.log(system ? system.body : "(not recorded)");
|
|
89
|
+
banner(`turn ${selectedTurn} — user://`);
|
|
90
|
+
console.log(user ? user.body : "(not recorded)");
|
|
91
|
+
banner(`turn ${selectedTurn} — assistant://`);
|
|
92
|
+
console.log(assistant ? assistant.body : "(not recorded)");
|
|
93
|
+
if (reasoning) {
|
|
94
|
+
banner(`turn ${selectedTurn} — reasoning://`);
|
|
95
|
+
console.log(reasoning.body);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (args.packet) process.exit(0);
|
|
100
|
+
|
|
101
|
+
// ── log entries for the selected turn ──────────────────────────────
|
|
102
|
+
if (selectedTurn) {
|
|
103
|
+
banner(`turn ${selectedTurn} — log entries`);
|
|
104
|
+
const logs = db
|
|
105
|
+
.prepare(
|
|
106
|
+
"SELECT e.path, e.body, e.attributes, rv.state, rv.outcome, rv.visibility FROM entries e JOIN run_views rv ON rv.entry_id = e.id WHERE rv.run_id = ? AND rv.turn = ? AND e.path LIKE 'log://%' ORDER BY e.id",
|
|
107
|
+
)
|
|
108
|
+
.all(runId, selectedTurn);
|
|
109
|
+
if (logs.length === 0) console.log("(none)");
|
|
110
|
+
for (const l of logs) {
|
|
111
|
+
const attrs = l.attributes ? JSON.parse(l.attributes) : {};
|
|
112
|
+
const body = l.body ? l.body.replace(/\n/g, "⏎").slice(0, 100) : "";
|
|
113
|
+
console.log(
|
|
114
|
+
` ${l.state.padEnd(9)} ${l.visibility.padEnd(10)} ${l.path}`,
|
|
115
|
+
);
|
|
116
|
+
if (Object.keys(attrs).length > 0)
|
|
117
|
+
console.log(` attrs: ${JSON.stringify(attrs).slice(0, 200)}`);
|
|
118
|
+
if (body) console.log(` body: ${body}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── unresolved (proposals awaiting client) ─────────────────────────
|
|
123
|
+
banner("unresolved");
|
|
124
|
+
const pending = db
|
|
125
|
+
.prepare(
|
|
126
|
+
"SELECT e.path, substr(e.attributes,1,200) AS attrs, rv.turn FROM entries e JOIN run_views rv ON rv.entry_id = e.id WHERE rv.run_id = ? AND rv.state IN ('proposed','streaming') ORDER BY e.id",
|
|
127
|
+
)
|
|
128
|
+
.all(runId);
|
|
129
|
+
if (pending.length === 0) console.log("(none)");
|
|
130
|
+
for (const p of pending) console.log(` turn ${p.turn} ${p.path}\n ${p.attrs}`);
|
|
131
|
+
|
|
132
|
+
// ── unknowns + knowns tally ────────────────────────────────────────
|
|
133
|
+
const unknowns = db
|
|
134
|
+
.prepare(
|
|
135
|
+
"SELECT e.path, e.body FROM entries e JOIN run_views rv ON rv.entry_id = e.id WHERE rv.run_id = ? AND e.scheme = 'unknown' ORDER BY e.id",
|
|
136
|
+
)
|
|
137
|
+
.all(runId);
|
|
138
|
+
banner(`unknowns (${unknowns.length})`);
|
|
139
|
+
for (const u of unknowns)
|
|
140
|
+
console.log(` ${u.path}\n ${u.body.slice(0, 120)}`);
|
|
141
|
+
|
|
142
|
+
const knowns = db
|
|
143
|
+
.prepare(
|
|
144
|
+
"SELECT e.path FROM entries e JOIN run_views rv ON rv.entry_id = e.id WHERE rv.run_id = ? AND e.scheme = 'known' ORDER BY e.id",
|
|
145
|
+
)
|
|
146
|
+
.all(runId);
|
|
147
|
+
banner(`knowns (${knowns.length})`);
|
|
148
|
+
for (const k of knowns) console.log(` ${k.path}`);
|
|
149
|
+
|
|
150
|
+
// ── all turns (if --all) ───────────────────────────────────────────
|
|
151
|
+
if (args.all) {
|
|
152
|
+
for (const t of turns) {
|
|
153
|
+
if (t.sequence === selectedTurn) continue;
|
|
154
|
+
const system = loadBody(`system://${t.sequence}`);
|
|
155
|
+
const user = loadBody(`user://${t.sequence}`);
|
|
156
|
+
const assistant = loadBody(`assistant://${t.sequence}`);
|
|
157
|
+
banner(`turn ${t.sequence} — system://`);
|
|
158
|
+
console.log(system ? system.body : "(not recorded)");
|
|
159
|
+
banner(`turn ${t.sequence} — user://`);
|
|
160
|
+
console.log(user ? user.body : "(not recorded)");
|
|
161
|
+
banner(`turn ${t.sequence} — assistant://`);
|
|
162
|
+
console.log(assistant ? assistant.body : "(not recorded)");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
db.close();
|
package/bin/rummy.js
CHANGED
|
@@ -10,9 +10,15 @@ const packageRoot = join(__dirname, "..");
|
|
|
10
10
|
|
|
11
11
|
const rummyHome = process.env.RUMMY_HOME || join(homedir(), ".rummy");
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// Base dir for env files: cwd if it has .env.example, else $RUMMY_HOME.
|
|
14
|
+
// The package's own .env.example is never consulted — silent package-
|
|
15
|
+
// root defaults break the project-as-context model and hide behavior
|
|
16
|
+
// from the user.
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const baseDir = existsSync(join(cwd, ".env.example")) ? cwd : rummyHome;
|
|
19
|
+
|
|
20
|
+
process.loadEnvFile(join(baseDir, ".env.example"));
|
|
21
|
+
const userEnv = join(baseDir, ".env");
|
|
16
22
|
if (existsSync(userEnv)) process.loadEnvFile(userEnv);
|
|
17
23
|
|
|
18
24
|
// Resolve RUMMY_HOME and make DB path absolute relative to it
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// No silent fallbacks outside hedberg.
|
|
2
|
+
// Rule: interiors crash on contract violation, boundaries validate.
|
|
3
|
+
// Patterns like `|| 0`, `?? ""` silently mask missing data.
|
|
4
|
+
// hedberg is the stochastic-interpretation boundary — fallbacks
|
|
5
|
+
// there are legitimate and filtered out below.
|
|
6
|
+
//
|
|
7
|
+
// Two classes caught:
|
|
8
|
+
// 1. Falsy-literal defaults: `$_ || 0` / `$_ ?? ""` / etc.
|
|
9
|
+
// 2. Env-var defaults: `process.env.X || <anything>`. These mask
|
|
10
|
+
// a missing documented env var; the right shape is either a
|
|
11
|
+
// naked `Number(process.env.X)` (NaN propagates on absence)
|
|
12
|
+
// or `process.env.X` directly.
|
|
13
|
+
|
|
14
|
+
language js
|
|
15
|
+
|
|
16
|
+
or {
|
|
17
|
+
`$_ || 0`,
|
|
18
|
+
`$_ || ""`,
|
|
19
|
+
`$_ || ''`,
|
|
20
|
+
`$_ || null`,
|
|
21
|
+
`$_ || false`,
|
|
22
|
+
`$_ || []`,
|
|
23
|
+
`$_ || {}`,
|
|
24
|
+
`$_ ?? 0`,
|
|
25
|
+
`$_ ?? ""`,
|
|
26
|
+
`$_ ?? ''`,
|
|
27
|
+
`$_ ?? null`,
|
|
28
|
+
`$_ ?? false`,
|
|
29
|
+
`$_ ?? []`,
|
|
30
|
+
`$_ ?? {}`,
|
|
31
|
+
`process.env.$_ || $_`,
|
|
32
|
+
`process.env.$_ ?? $_`,
|
|
33
|
+
`Number(process.env.$_) || $_`,
|
|
34
|
+
`Number(process.env.$_) ?? $_`,
|
|
35
|
+
`parseInt(process.env.$_, $_) || $_`,
|
|
36
|
+
`parseInt(process.env.$_, $_) ?? $_`,
|
|
37
|
+
`Number.parseInt(process.env.$_, $_) || $_`,
|
|
38
|
+
`Number.parseInt(process.env.$_, $_) ?? $_`,
|
|
39
|
+
`Number.parseFloat(process.env.$_) || $_`,
|
|
40
|
+
`Number.parseFloat(process.env.$_) ?? $_`
|
|
41
|
+
} as $match where {
|
|
42
|
+
$filename <: not includes "src/plugins/hedberg/",
|
|
43
|
+
$filename <: not includes "src/agent/XmlParser.js",
|
|
44
|
+
$filename <: not includes "/test/",
|
|
45
|
+
$filename <: not includes ".test.js",
|
|
46
|
+
register_diagnostic(
|
|
47
|
+
span = $match,
|
|
48
|
+
message = "Silent fallback outside hedberg masks contract violations — fix the contract instead."
|
|
49
|
+
)
|
|
50
|
+
}
|
package/lang/en.json
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"error.tool_already_registered": "Tool '{name}' already registered.",
|
|
28
28
|
"error.rpc_already_registered": "RPC method '{name}' already registered.",
|
|
29
29
|
"error.resolution_invalid": "Invalid resolution action: {action}. Use 'accept' or 'reject'.",
|
|
30
|
-
"error.xai_base_url_missing": "
|
|
31
|
-
"error.xai_api_key_missing": "
|
|
30
|
+
"error.xai_base_url_missing": "xai/ model requested but XAI_BASE_URL is not set.",
|
|
31
|
+
"error.xai_api_key_missing": "xai/ model requested but XAI_API_KEY is not set.",
|
|
32
32
|
"error.xai_auth": "xAI Authentication Error: {status}. Please check your XAI_API_KEY.",
|
|
33
33
|
"error.xai_api": "xAI API error: {status}"
|
|
34
34
|
}
|
|
@@ -4,13 +4,19 @@ 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
|
-
--
|
|
9
|
-
--
|
|
7
|
+
-- writable_by: JSON array of {system, plugin, client, model} — four writer tiers.
|
|
8
|
+
-- capability_class: optional restriction group (e.g. "shell", "files", "web")
|
|
9
|
+
-- so the policy plugin can compute the effective toolset from a run's
|
|
10
|
+
-- restriction list. Null means the scheme is always available.
|
|
10
11
|
CREATE TABLE IF NOT EXISTS schemes (
|
|
11
12
|
name TEXT PRIMARY KEY
|
|
12
13
|
, model_visible BOOLEAN NOT NULL DEFAULT 1
|
|
13
14
|
, category TEXT
|
|
15
|
+
, default_scope TEXT NOT NULL DEFAULT 'run'
|
|
16
|
+
CHECK (default_scope IN ('run', 'project', 'global'))
|
|
17
|
+
, writable_by JSON NOT NULL DEFAULT '["model","plugin"]'
|
|
18
|
+
CHECK (json_valid(writable_by))
|
|
19
|
+
, capability_class TEXT
|
|
14
20
|
);
|
|
15
21
|
|
|
16
22
|
-- Schemes are registered by plugins at startup via core.registerScheme().
|
|
@@ -98,7 +104,7 @@ CREATE TABLE IF NOT EXISTS turns (
|
|
|
98
104
|
CREATE INDEX IF NOT EXISTS idx_turns_run_seq ON turns (run_id, sequence);
|
|
99
105
|
|
|
100
106
|
-- File constraints: client-set visibility rules, project-scoped.
|
|
101
|
-
-- Persists across runs. Orthogonal to
|
|
107
|
+
-- Persists across runs. Orthogonal to visibility.
|
|
102
108
|
CREATE TABLE IF NOT EXISTS file_constraints (
|
|
103
109
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
104
110
|
, project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE
|
|
@@ -110,50 +116,93 @@ CREATE TABLE IF NOT EXISTS file_constraints (
|
|
|
110
116
|
CREATE INDEX IF NOT EXISTS idx_file_constraints_project
|
|
111
117
|
ON file_constraints (project_id);
|
|
112
118
|
|
|
113
|
-
--
|
|
114
|
-
--
|
|
119
|
+
-- Entries: content-addressable by (scope, path). The actual payload.
|
|
120
|
+
-- scope: 'global' | 'project:N' | 'run:N'. Determines read access.
|
|
115
121
|
-- scheme: derived from path via schemeOf(). Generated column.
|
|
116
|
-
--
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
-- No visibility, status, turn, loop — those are view-side concerns.
|
|
123
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
124
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
125
|
+
, scope TEXT NOT NULL
|
|
126
|
+
, path TEXT NOT NULL CHECK (length(path) <= 2048)
|
|
127
|
+
, scheme TEXT GENERATED ALWAYS AS (schemeOf(path)) STORED
|
|
128
|
+
, body TEXT NOT NULL DEFAULT ''
|
|
129
|
+
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
130
|
+
, hash TEXT
|
|
131
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
132
|
+
, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
133
|
+
);
|
|
134
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_entries_scope_path
|
|
135
|
+
ON entries (scope, path);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_entries_scope_scheme
|
|
137
|
+
ON entries (scope, scheme);
|
|
138
|
+
|
|
139
|
+
-- Run views: per-run projection of entries. State, visibility, turn live here.
|
|
140
|
+
-- A run has at most one view of any given entry. Absent view = not in context.
|
|
141
|
+
-- state: lifecycle. visibility: what the model sees. Orthogonal axes (SPEC §0.1).
|
|
142
|
+
-- outcome: short reason string when state ∈ {failed, cancelled}; NULL otherwise.
|
|
143
|
+
CREATE TABLE IF NOT EXISTS run_views (
|
|
119
144
|
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
120
145
|
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
146
|
+
, entry_id INTEGER NOT NULL REFERENCES entries (id) ON DELETE CASCADE
|
|
121
147
|
, loop_id INTEGER REFERENCES loops (id) ON DELETE CASCADE
|
|
122
148
|
, turn INTEGER NOT NULL DEFAULT 0 CHECK (turn >= 0)
|
|
123
|
-
,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
,
|
|
127
|
-
,
|
|
128
|
-
|
|
149
|
+
, state TEXT NOT NULL DEFAULT 'resolved' CHECK (
|
|
150
|
+
state IN ('proposed', 'streaming', 'resolved', 'failed', 'cancelled')
|
|
151
|
+
)
|
|
152
|
+
, outcome TEXT
|
|
153
|
+
, visibility TEXT NOT NULL DEFAULT 'visible' CHECK (
|
|
154
|
+
visibility IN ('visible', 'summarized', 'archived')
|
|
129
155
|
)
|
|
130
|
-
, hash TEXT
|
|
131
|
-
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
132
|
-
, tokens INTEGER NOT NULL DEFAULT 0 CHECK (tokens >= 0)
|
|
133
|
-
, refs INTEGER NOT NULL DEFAULT 0 CHECK (refs >= 0)
|
|
134
156
|
, write_count INTEGER NOT NULL DEFAULT 1 CHECK (write_count >= 1)
|
|
157
|
+
, refs INTEGER NOT NULL DEFAULT 0 CHECK (refs >= 0)
|
|
135
158
|
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
136
159
|
, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
137
160
|
);
|
|
138
|
-
CREATE UNIQUE INDEX IF NOT EXISTS
|
|
139
|
-
ON
|
|
140
|
-
CREATE INDEX IF NOT EXISTS
|
|
141
|
-
ON
|
|
142
|
-
CREATE INDEX IF NOT EXISTS
|
|
143
|
-
ON
|
|
161
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_run_views_run_entry
|
|
162
|
+
ON run_views (run_id, entry_id);
|
|
163
|
+
CREATE INDEX IF NOT EXISTS idx_run_views_run_turn
|
|
164
|
+
ON run_views (run_id, turn);
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_run_views_run_visibility
|
|
166
|
+
ON run_views (run_id, visibility);
|
|
144
167
|
|
|
145
|
-
--
|
|
168
|
+
-- Legacy-shape compatibility view. Joins run_views to entries; reads
|
|
169
|
+
-- against this view keep one shape. Writes MUST target entries +
|
|
170
|
+
-- run_views directly.
|
|
171
|
+
CREATE VIEW IF NOT EXISTS known_entries AS
|
|
172
|
+
SELECT
|
|
173
|
+
rv.id AS id
|
|
174
|
+
, rv.run_id AS run_id
|
|
175
|
+
, rv.loop_id AS loop_id
|
|
176
|
+
, rv.turn AS turn
|
|
177
|
+
, e.path AS path
|
|
178
|
+
, e.body AS body
|
|
179
|
+
, e.scheme AS scheme
|
|
180
|
+
, rv.state AS state
|
|
181
|
+
, rv.outcome AS outcome
|
|
182
|
+
, rv.visibility AS visibility
|
|
183
|
+
, e.hash AS hash
|
|
184
|
+
, e.attributes AS attributes
|
|
185
|
+
, rv.refs AS refs
|
|
186
|
+
, rv.write_count AS write_count
|
|
187
|
+
, e.created_at AS created_at
|
|
188
|
+
, rv.updated_at AS updated_at
|
|
189
|
+
, e.id AS entry_id
|
|
190
|
+
, e.scope AS scope
|
|
191
|
+
FROM run_views AS rv
|
|
192
|
+
JOIN entries AS e ON e.id = rv.entry_id;
|
|
146
193
|
|
|
147
|
-
-- UNRESOLVED VIEW:
|
|
194
|
+
-- UNRESOLVED VIEW: entries that haven't reached a terminal state.
|
|
195
|
+
-- Proposed (awaiting user decision) or streaming (in-flight).
|
|
148
196
|
CREATE VIEW IF NOT EXISTS v_unresolved AS
|
|
149
197
|
SELECT
|
|
150
|
-
run_id
|
|
151
|
-
, path
|
|
152
|
-
, body
|
|
153
|
-
, attributes
|
|
154
|
-
, turn
|
|
155
|
-
FROM
|
|
156
|
-
|
|
198
|
+
rv.run_id
|
|
199
|
+
, e.path
|
|
200
|
+
, e.body
|
|
201
|
+
, e.attributes
|
|
202
|
+
, rv.turn
|
|
203
|
+
FROM run_views AS rv
|
|
204
|
+
JOIN entries AS e ON e.id = rv.entry_id
|
|
205
|
+
WHERE rv.state IN ('proposed', 'streaming');
|
|
157
206
|
|
|
158
207
|
-- Turn context: materialized snapshot of what the model sees each turn.
|
|
159
208
|
-- known_entries is the warehouse. turn_context is the shipment.
|
|
@@ -165,10 +214,12 @@ CREATE TABLE IF NOT EXISTS turn_context (
|
|
|
165
214
|
, ordinal INTEGER NOT NULL CHECK (ordinal >= 0)
|
|
166
215
|
, path TEXT NOT NULL
|
|
167
216
|
, scheme TEXT GENERATED ALWAYS AS (schemeOf(path)) STORED
|
|
168
|
-
,
|
|
169
|
-
|
|
217
|
+
, state TEXT NOT NULL DEFAULT 'resolved' CHECK (
|
|
218
|
+
state IN ('proposed', 'streaming', 'resolved', 'failed', 'cancelled')
|
|
219
|
+
)
|
|
220
|
+
, outcome TEXT
|
|
221
|
+
, visibility TEXT NOT NULL CHECK (visibility IN ('visible', 'summarized'))
|
|
170
222
|
, body TEXT NOT NULL DEFAULT ''
|
|
171
|
-
, tokens INTEGER NOT NULL DEFAULT 0 CHECK (tokens >= 0)
|
|
172
223
|
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
173
224
|
, category TEXT NOT NULL DEFAULT 'logging'
|
|
174
225
|
, source_turn INTEGER DEFAULT 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@possumtech/rummy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Relational Unknowns Memory Management Yoke",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm"
|
|
@@ -37,21 +37,23 @@
|
|
|
37
37
|
"fix:sql": ". .venv/bin/activate && sqlfluff fix . --force",
|
|
38
38
|
"test": "npm run lint && npm run test:unit && npm run test:intg",
|
|
39
39
|
"test:all": "npm run lint && npm run test:unit && npm run test:intg && npm run test:e2e",
|
|
40
|
-
"test:unit": "node --env-file-if-exists=.env.example --env-file-if-exists=.env
|
|
41
|
-
"test:intg": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --test-concurrency=1 --test $(find test/integration -name '*.test.js')",
|
|
40
|
+
"test:unit": "node --env-file-if-exists=.env.example --env-file-if-exists=.env.test --experimental-test-coverage --test-coverage-lines=50 --test-coverage-branches=50 --test-coverage-functions=50 --test-concurrency=1 --test-force-exit --test $(find src -name '*.test.js')",
|
|
41
|
+
"test:intg": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --test-concurrency=1 --test-force-exit --test $(find test/integration -name '*.test.js')",
|
|
42
42
|
"test:e2e": "mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --test-concurrency=1 --test-force-exit --test-reporter=spec --test $(find test/e2e -name '*.test.js') 2>&1 | tee /tmp/rummy_test_diag/e2e_$(date +%Y%m%dT%H%M%S).log",
|
|
43
43
|
"test:live": "mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --test-concurrency=1 --test-force-exit --test-reporter=spec --test $(find test/live -name '*.test.js') 2>&1 | tee /tmp/rummy_test_diag/live_$(date +%Y%m%dT%H%M%S).log",
|
|
44
|
-
"test:clean": "rm -rf test/lme/results test/
|
|
45
|
-
"test:mab:get": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/mab/download.js",
|
|
46
|
-
"test:mab": "bash -c 'mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/mab/runner.js \"$@\" 2>&1 | tee /tmp/rummy_test_diag/mab_$(date +%Y%m%dT%H%M%S).log' --",
|
|
47
|
-
"test:grok": "bash -c 'mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --env-file-if-exists=.env.grok test/mab/runner.js \"$@\" 2>&1 | tee /tmp/rummy_test_diag/mab_grok_$(date +%Y%m%dT%H%M%S).log' --",
|
|
48
|
-
"test:mab:taxonomy": "bash -c 'mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/mab/runner.js --split Conflict_Resolution --row 0 --no-questions 2>&1 | tee /tmp/rummy_test_diag/taxonomy_$(date +%Y%m%dT%H%M%S).log' --",
|
|
49
|
-
"test:grok:taxonomy": "bash -c 'mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --env-file-if-exists=.env.grok test/mab/runner.js --split Conflict_Resolution --row 0 --no-questions 2>&1 | tee /tmp/rummy_test_diag/taxonomy_grok_$(date +%Y%m%dT%H%M%S).log' --",
|
|
44
|
+
"test:clean": "rm -rf test/lme/results test/swe/results test/swe/repos test/tmp /tmp/rummy_test_diag /tmp/rummy_test_*.db /tmp/rummy_test_*.db-shm /tmp/rummy_test_*.db-wal && echo 'Test artifacts cleaned.'",
|
|
50
45
|
"test:lme:get": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/lme/download.js",
|
|
51
46
|
"test:lme": "bash -c 'mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/lme/runner.js \"$@\" 2>&1 | tee /tmp/rummy_test_diag/lme_$(date +%Y%m%dT%H%M%S).log' --",
|
|
52
|
-
"test:
|
|
47
|
+
"test:swe:setup": "bash test/swe/setup.sh",
|
|
48
|
+
"test:swe:get": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/swe/download.js",
|
|
49
|
+
"test:swe": "bash -c 'mkdir -p /tmp/rummy_test_diag && node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test test/swe/runner.js \"$@\" 2>&1 | tee /tmp/rummy_test_diag/swe_$(date +%Y%m%dT%H%M%S).log' --",
|
|
50
|
+
"test:swe:eval": "bash -c 'cd test/swe && source .venv/bin/activate && python evaluate.py \"$@\"' --",
|
|
51
|
+
"test:swe:baseline": "bash -c 'cd test/swe && source .venv/bin/activate && python baseline.py \"$@\"' --",
|
|
53
52
|
"test:lme:clean": "rm -rf test/lme/results/*/",
|
|
54
|
-
"test:
|
|
53
|
+
"test:swe:clean": "rm -rf test/swe/results/*/ test/swe/repos/",
|
|
54
|
+
"test:clear": "rm -rf /tmp/rummy_test_diag /tmp/rummy_test_*.db /tmp/rummy_test_*.db-shm /tmp/rummy_test_*.db-wal /tmp/rummy-stories-*",
|
|
55
|
+
"test:demo": "node --env-file-if-exists=.env.example --env-file-if-exists=.env bin/demo.js",
|
|
56
|
+
"test:spec": "node test/spec-coverage.js"
|
|
55
57
|
},
|
|
56
58
|
"devDependencies": {
|
|
57
59
|
"@biomejs/biome": "^2.4.6"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inject a follow-up question into an existing LME run and print the answer.
|
|
3
|
+
*
|
|
4
|
+
* Usage: node scriptify/ask_run.js <db_path> <run_alias> "your question"
|
|
5
|
+
*
|
|
6
|
+
* Reuses the run's full ingested context so the model answers with all
|
|
7
|
+
* its accumulated knowledge. Used as a debugging tool to interrogate
|
|
8
|
+
* the model's reasoning after a benchmark completes.
|
|
9
|
+
*/
|
|
10
|
+
import TestDb from "../test/helpers/TestDb.js";
|
|
11
|
+
import TestServer from "../test/helpers/TestServer.js";
|
|
12
|
+
import RpcClient from "../test/helpers/RpcClient.js";
|
|
13
|
+
|
|
14
|
+
const [, , dbPath, alias, ...questionParts] = process.argv;
|
|
15
|
+
const question = questionParts.join(" ");
|
|
16
|
+
|
|
17
|
+
if (!dbPath || !alias || !question) {
|
|
18
|
+
console.error(
|
|
19
|
+
'Usage: node scriptify/ask_run.js <db_path> <run_alias> "your question"',
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const tdb = await TestDb.createAt(dbPath);
|
|
25
|
+
const tserver = await TestServer.start(tdb);
|
|
26
|
+
const client = new RpcClient(tserver.url);
|
|
27
|
+
await client.connect();
|
|
28
|
+
await client.call("rummy/hello", {
|
|
29
|
+
name: "ask_run",
|
|
30
|
+
projectRoot: "/tmp/rummy-lme",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(`Asking ${alias}: ${question}\n`);
|
|
34
|
+
|
|
35
|
+
const TERMINAL = [200, 204, 413, 422, 499, 500];
|
|
36
|
+
const startRes = await client.call("set", {
|
|
37
|
+
path: `run://${alias}`,
|
|
38
|
+
body: question,
|
|
39
|
+
attributes: {
|
|
40
|
+
model: "grok",
|
|
41
|
+
mode: "ask",
|
|
42
|
+
noRepo: true,
|
|
43
|
+
noInteraction: true,
|
|
44
|
+
noWeb: true,
|
|
45
|
+
noProposals: true,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const deadline = Date.now() + 600_000;
|
|
50
|
+
while (Date.now() < deadline) {
|
|
51
|
+
const row = await tdb.db.get_run_by_alias.get({ alias });
|
|
52
|
+
if (TERMINAL.includes(row.status)) break;
|
|
53
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const runRow = await tdb.db.get_run_by_alias.get({ alias });
|
|
57
|
+
const entries = await tdb.db.get_known_entries.all({ run_id: runRow.id });
|
|
58
|
+
const reasoning = entries
|
|
59
|
+
.filter((e) => e.scheme === "reasoning")
|
|
60
|
+
.toSorted((a, b) => b.turn - a.turn)[0];
|
|
61
|
+
const assistant = entries
|
|
62
|
+
.filter((e) => e.scheme === "assistant")
|
|
63
|
+
.toSorted((a, b) => b.turn - a.turn)[0];
|
|
64
|
+
|
|
65
|
+
if (reasoning) {
|
|
66
|
+
console.log("=== REASONING ===");
|
|
67
|
+
console.log(reasoning.body);
|
|
68
|
+
console.log("");
|
|
69
|
+
}
|
|
70
|
+
if (assistant) {
|
|
71
|
+
console.log("=== ANSWER ===");
|
|
72
|
+
console.log(assistant.body);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await client.close();
|
|
76
|
+
await tserver.stop();
|
|
77
|
+
await tdb.cleanup();
|
package/service.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { mkdirSync, readdirSync } from "node:fs";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
-
// Helper to expand ~ in paths since node --env-file doesn't do it
|
|
7
6
|
// 0. Pre-flight Check: Environment and Dependencies
|
|
8
7
|
const rummyHome = process.env.RUMMY_HOME;
|
|
9
8
|
|
|
@@ -12,13 +11,54 @@ if (!rummyHome) {
|
|
|
12
11
|
process.exit(1);
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
// 0a. Env resolution: local project config wins over RUMMY_HOME.
|
|
15
|
+
//
|
|
16
|
+
// If CWD has a rummy-shaped `.env.example` (contains any RUMMY_* var),
|
|
17
|
+
// this is an authoritative local config — the npm script's
|
|
18
|
+
// --env-file-if-exists flags already loaded it, and RUMMY_HOME would
|
|
19
|
+
// only pollute it with machine-wide config that doesn't belong to this
|
|
20
|
+
// instance.
|
|
21
|
+
//
|
|
22
|
+
// If CWD has no rummy-shaped config, fall back to
|
|
23
|
+
// `${RUMMY_HOME}/.env.example` (canonical defaults, shipped with the
|
|
24
|
+
// package) → `${RUMMY_HOME}/.env` (user overrides). On first run we
|
|
25
|
+
// seed the bundled .env.example into RUMMY_HOME so the user has
|
|
26
|
+
// something to edit.
|
|
27
|
+
//
|
|
28
|
+
// This makes multiple rummy instances on the same box cleanly
|
|
29
|
+
// independent: each owns its own .env.example in its CWD.
|
|
30
|
+
{
|
|
31
|
+
const cwdExample = join(process.cwd(), ".env.example");
|
|
32
|
+
const isLocalRummyConfig =
|
|
33
|
+
existsSync(cwdExample) && /^\s*(#\s*)?RUMMY_\w+\s*=/m.test(readFileSync(cwdExample, "utf8"));
|
|
34
|
+
|
|
35
|
+
if (!isLocalRummyConfig) {
|
|
36
|
+
mkdirSync(rummyHome, { recursive: true });
|
|
37
|
+
const homeExample = join(rummyHome, ".env.example");
|
|
38
|
+
const homeEnv = join(rummyHome, ".env");
|
|
39
|
+
const bundledExample = fileURLToPath(new URL("./.env.example", import.meta.url));
|
|
40
|
+
if (!existsSync(homeExample) && existsSync(bundledExample)) {
|
|
41
|
+
copyFileSync(bundledExample, homeExample);
|
|
42
|
+
console.log(`[RUMMY] Seeded ${homeExample} from package defaults.`);
|
|
43
|
+
}
|
|
44
|
+
for (const path of [homeExample, homeEnv]) {
|
|
45
|
+
if (!existsSync(path)) continue;
|
|
46
|
+
try {
|
|
47
|
+
process.loadEnvFile(path);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn(`[RUMMY] Failed to load ${path}: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
// Check for optional system dependencies
|
|
16
56
|
const gitCheck = spawnSync("git", ["--version"]);
|
|
17
57
|
if (gitCheck.error || gitCheck.status !== 0) {
|
|
18
58
|
console.warn("[RUMMY] WARNING: 'git' not found. File tracking will use manual activation only.");
|
|
19
59
|
}
|
|
20
60
|
|
|
21
|
-
let SqlRite, SocketServer, registerPlugins, initPlugins, createHooks
|
|
61
|
+
let SqlRite, SocketServer, registerPlugins, initPlugins, createHooks;
|
|
22
62
|
try {
|
|
23
63
|
SqlRite = (await import("@possumtech/sqlrite")).default;
|
|
24
64
|
SocketServer = (await import("./src/server/SocketServer.js")).default;
|
|
@@ -26,7 +66,6 @@ try {
|
|
|
26
66
|
registerPlugins = pluginIndex.registerPlugins;
|
|
27
67
|
initPlugins = pluginIndex.initPlugins;
|
|
28
68
|
createHooks = (await import("./src/hooks/Hooks.js")).default;
|
|
29
|
-
RpcRegistry = (await import("./src/server/RpcRegistry.js")).default;
|
|
30
69
|
} catch (err) {
|
|
31
70
|
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
32
71
|
console.error("RUMMY Dependency Error: node_modules not found or incomplete.");
|
|
@@ -40,7 +79,6 @@ async function main() {
|
|
|
40
79
|
// 1. Initialize Hooks (Agnostic Engine)
|
|
41
80
|
const debug = process.env.RUMMY_DEBUG === "true";
|
|
42
81
|
const hooks = createHooks(debug);
|
|
43
|
-
hooks.rpc.registry = new RpcRegistry();
|
|
44
82
|
|
|
45
83
|
// 2. Resolve Directories
|
|
46
84
|
const userPluginsDir = join(rummyHome, "plugins");
|
|
@@ -50,7 +88,10 @@ async function main() {
|
|
|
50
88
|
mkdirSync(userPluginsDir, { recursive: true });
|
|
51
89
|
|
|
52
90
|
// 4. Register Plugins
|
|
53
|
-
await registerPlugins(
|
|
91
|
+
const pluginInstances = await registerPlugins(
|
|
92
|
+
[pluginsDir, userPluginsDir],
|
|
93
|
+
hooks,
|
|
94
|
+
);
|
|
54
95
|
|
|
55
96
|
// 5. Bootstrap Persistence
|
|
56
97
|
const dbPath = process.env.RUMMY_DB_PATH;
|
|
@@ -71,8 +112,8 @@ async function main() {
|
|
|
71
112
|
},
|
|
72
113
|
});
|
|
73
114
|
|
|
74
|
-
// 6. Initialize plugins (
|
|
75
|
-
await initPlugins(db,
|
|
115
|
+
// 6. Initialize plugins (register schemes)
|
|
116
|
+
await initPlugins(db, hooks, pluginInstances);
|
|
76
117
|
|
|
77
118
|
// 7. Bootstrap models from env vars
|
|
78
119
|
{
|
|
@@ -99,7 +140,7 @@ async function main() {
|
|
|
99
140
|
const { statSync } = await import("node:fs");
|
|
100
141
|
try {
|
|
101
142
|
const dbSizeBefore = statSync(dbPath).size;
|
|
102
|
-
const retentionDays = Number.parseInt(process.env.RUMMY_RETENTION_DAYS
|
|
143
|
+
const retentionDays = Number.parseInt(process.env.RUMMY_RETENTION_DAYS, 10);
|
|
103
144
|
await db.purge_old_runs.run({ retention_days: retentionDays });
|
|
104
145
|
const dbSizeAfter = statSync(dbPath).size;
|
|
105
146
|
const dbSizeMB = (dbSizeAfter / 1024 / 1024).toFixed(2);
|