@possumtech/rummy 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +55 -0
- package/LICENSE +21 -0
- package/PLUGINS.md +302 -0
- package/README.md +41 -0
- package/SPEC.md +524 -0
- package/lang/en.json +34 -0
- package/migrations/001_initial_schema.sql +226 -0
- package/package.json +54 -0
- package/service.js +143 -0
- package/src/agent/AgentLoop.js +553 -0
- package/src/agent/ContextAssembler.js +29 -0
- package/src/agent/KnownStore.js +254 -0
- package/src/agent/ProjectAgent.js +101 -0
- package/src/agent/ResponseHealer.js +134 -0
- package/src/agent/TurnExecutor.js +457 -0
- package/src/agent/XmlParser.js +247 -0
- package/src/agent/known_checks.sql +42 -0
- package/src/agent/known_queries.sql +80 -0
- package/src/agent/known_store.sql +161 -0
- package/src/agent/messages.js +17 -0
- package/src/agent/prompt_queue.sql +39 -0
- package/src/agent/runs.sql +114 -0
- package/src/agent/schemes.sql +3 -0
- package/src/agent/sessions.sql +51 -0
- package/src/agent/tokens.js +28 -0
- package/src/agent/turns.sql +36 -0
- package/src/hooks/HookRegistry.js +72 -0
- package/src/hooks/Hooks.js +115 -0
- package/src/hooks/PluginContext.js +116 -0
- package/src/hooks/RummyContext.js +181 -0
- package/src/hooks/ToolRegistry.js +83 -0
- package/src/llm/LlmProvider.js +107 -0
- package/src/llm/OllamaClient.js +88 -0
- package/src/llm/OpenAiClient.js +80 -0
- package/src/llm/OpenRouterClient.js +78 -0
- package/src/llm/XaiClient.js +113 -0
- package/src/plugins/ask_user/README.md +18 -0
- package/src/plugins/ask_user/ask_user.js +48 -0
- package/src/plugins/ask_user/docs.md +2 -0
- package/src/plugins/cp/README.md +18 -0
- package/src/plugins/cp/cp.js +55 -0
- package/src/plugins/cp/docs.md +2 -0
- package/src/plugins/current/README.md +14 -0
- package/src/plugins/current/current.js +48 -0
- package/src/plugins/engine/README.md +12 -0
- package/src/plugins/engine/engine.sql +18 -0
- package/src/plugins/engine/turn_context.sql +51 -0
- package/src/plugins/env/README.md +14 -0
- package/src/plugins/env/docs.md +2 -0
- package/src/plugins/env/env.js +32 -0
- package/src/plugins/file/README.md +25 -0
- package/src/plugins/file/file.js +85 -0
- package/src/plugins/get/README.md +19 -0
- package/src/plugins/get/docs.md +6 -0
- package/src/plugins/get/get.js +53 -0
- package/src/plugins/hedberg/README.md +72 -0
- package/src/plugins/hedberg/docs.md +9 -0
- package/src/plugins/hedberg/edits.js +65 -0
- package/src/plugins/hedberg/hedberg.js +89 -0
- package/src/plugins/hedberg/matcher.js +181 -0
- package/src/plugins/hedberg/normalize.js +41 -0
- package/src/plugins/hedberg/patterns.js +452 -0
- package/src/plugins/hedberg/sed.js +48 -0
- package/src/plugins/helpers.js +22 -0
- package/src/plugins/index.js +180 -0
- package/src/plugins/instructions/README.md +11 -0
- package/src/plugins/instructions/instructions.js +37 -0
- package/src/plugins/instructions/preamble.md +12 -0
- package/src/plugins/known/README.md +18 -0
- package/src/plugins/known/docs.md +3 -0
- package/src/plugins/known/known.js +57 -0
- package/src/plugins/mv/README.md +18 -0
- package/src/plugins/mv/docs.md +2 -0
- package/src/plugins/mv/mv.js +56 -0
- package/src/plugins/previous/README.md +15 -0
- package/src/plugins/previous/previous.js +50 -0
- package/src/plugins/progress/README.md +17 -0
- package/src/plugins/progress/progress.js +44 -0
- package/src/plugins/prompt/README.md +16 -0
- package/src/plugins/prompt/prompt.js +45 -0
- package/src/plugins/rm/README.md +18 -0
- package/src/plugins/rm/docs.md +4 -0
- package/src/plugins/rm/rm.js +51 -0
- package/src/plugins/rpc/README.md +45 -0
- package/src/plugins/rpc/rpc.js +587 -0
- package/src/plugins/set/README.md +32 -0
- package/src/plugins/set/docs.md +4 -0
- package/src/plugins/set/set.js +268 -0
- package/src/plugins/sh/README.md +18 -0
- package/src/plugins/sh/docs.md +2 -0
- package/src/plugins/sh/sh.js +32 -0
- package/src/plugins/skills/README.md +25 -0
- package/src/plugins/skills/skills.js +175 -0
- package/src/plugins/store/README.md +20 -0
- package/src/plugins/store/docs.md +5 -0
- package/src/plugins/store/store.js +52 -0
- package/src/plugins/summarize/README.md +18 -0
- package/src/plugins/summarize/docs.md +4 -0
- package/src/plugins/summarize/summarize.js +24 -0
- package/src/plugins/telemetry/README.md +19 -0
- package/src/plugins/telemetry/rpc_log.sql +28 -0
- package/src/plugins/telemetry/telemetry.js +186 -0
- package/src/plugins/unknown/README.md +23 -0
- package/src/plugins/unknown/docs.md +5 -0
- package/src/plugins/unknown/unknown.js +31 -0
- package/src/plugins/update/README.md +18 -0
- package/src/plugins/update/docs.md +4 -0
- package/src/plugins/update/update.js +24 -0
- package/src/server/ClientConnection.js +228 -0
- package/src/server/RpcRegistry.js +52 -0
- package/src/server/SocketServer.js +43 -0
- package/src/sql/file_constraints.sql +15 -0
- package/src/sql/functions/countTokens.js +7 -0
- package/src/sql/functions/hedmatch.js +8 -0
- package/src/sql/functions/hedreplace.js +8 -0
- package/src/sql/functions/hedsearch.js +8 -0
- package/src/sql/functions/schemeOf.js +7 -0
- package/src/sql/functions/slugify.js +6 -0
- package/src/sql/v_model_context.sql +101 -0
- package/src/sql/v_run_log.sql +23 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
-- INIT: enable_mmap
|
|
2
|
+
PRAGMA mmap_size = $mmap_size;
|
|
3
|
+
|
|
4
|
+
-- INIT: initial_schema
|
|
5
|
+
|
|
6
|
+
-- Scheme registry: single source of truth for all scheme metadata.
|
|
7
|
+
-- fidelity: 'full' = always visible, 'turn' = visible when turn>0, 'null' = never visible.
|
|
8
|
+
-- valid_states: JSON array of allowed state values for this scheme.
|
|
9
|
+
CREATE TABLE IF NOT EXISTS schemes (
|
|
10
|
+
name TEXT PRIMARY KEY
|
|
11
|
+
, fidelity TEXT NOT NULL CHECK (fidelity IN ('full', 'turn', 'null'))
|
|
12
|
+
, model_visible BOOLEAN NOT NULL DEFAULT 1
|
|
13
|
+
, valid_states TEXT NOT NULL
|
|
14
|
+
, category TEXT
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
-- Schemes are registered by plugins at startup via core.registerScheme().
|
|
18
|
+
-- Audit schemes are bootstrapped here since they have no plugin owner.
|
|
19
|
+
|
|
20
|
+
-- Projects: top-level organizational unit.
|
|
21
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
23
|
+
, name TEXT UNIQUE NOT NULL
|
|
24
|
+
, project_root TEXT
|
|
25
|
+
, config_path TEXT
|
|
26
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- Models: available LLM configurations.
|
|
30
|
+
-- Populated from RUMMY_MODEL_* env vars at startup.
|
|
31
|
+
CREATE TABLE IF NOT EXISTS models (
|
|
32
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
33
|
+
, alias TEXT UNIQUE NOT NULL
|
|
34
|
+
, actual TEXT NOT NULL
|
|
35
|
+
, context_length INTEGER
|
|
36
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
-- Runs: execution units belonging to a project.
|
|
40
|
+
-- Each run has its own config (temperature, persona, context_limit).
|
|
41
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
43
|
+
, project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE
|
|
44
|
+
, parent_run_id INTEGER REFERENCES runs (id) ON DELETE SET NULL
|
|
45
|
+
, model TEXT
|
|
46
|
+
, status TEXT NOT NULL DEFAULT 'queued' CHECK (
|
|
47
|
+
status IN ('queued', 'running', 'proposed', 'completed', 'failed', 'aborted')
|
|
48
|
+
)
|
|
49
|
+
, alias TEXT NOT NULL UNIQUE
|
|
50
|
+
, temperature REAL CHECK (
|
|
51
|
+
temperature IS NULL OR (temperature >= 0 AND temperature <= 2)
|
|
52
|
+
)
|
|
53
|
+
, persona TEXT
|
|
54
|
+
, context_limit INTEGER CHECK (context_limit IS NULL OR context_limit >= 1024)
|
|
55
|
+
, next_turn INTEGER NOT NULL DEFAULT 1 CHECK (next_turn >= 1)
|
|
56
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_runs_alias ON runs (alias);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_runs_project ON runs (project_id);
|
|
61
|
+
|
|
62
|
+
-- Turns: usage stats and sequencing (operational, not model-facing)
|
|
63
|
+
CREATE TABLE IF NOT EXISTS turns (
|
|
64
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
65
|
+
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
66
|
+
, sequence INTEGER NOT NULL CHECK (sequence >= 1)
|
|
67
|
+
, prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0)
|
|
68
|
+
, cached_tokens INTEGER NOT NULL DEFAULT 0 CHECK (cached_tokens >= 0)
|
|
69
|
+
, completion_tokens INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens >= 0)
|
|
70
|
+
, reasoning_tokens INTEGER NOT NULL DEFAULT 0 CHECK (reasoning_tokens >= 0)
|
|
71
|
+
, total_tokens INTEGER NOT NULL DEFAULT 0 CHECK (total_tokens >= 0)
|
|
72
|
+
, cost REAL NOT NULL DEFAULT 0 CHECK (cost >= 0)
|
|
73
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_turns_run_seq ON turns (run_id, sequence);
|
|
76
|
+
|
|
77
|
+
-- File constraints: client-set visibility rules, project-scoped.
|
|
78
|
+
-- Persists across runs. Orthogonal to fidelity.
|
|
79
|
+
CREATE TABLE IF NOT EXISTS file_constraints (
|
|
80
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
81
|
+
, project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE
|
|
82
|
+
, pattern TEXT NOT NULL
|
|
83
|
+
, visibility TEXT NOT NULL CHECK (visibility IN ('active', 'readonly', 'ignore'))
|
|
84
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
85
|
+
, UNIQUE (project_id, pattern)
|
|
86
|
+
);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_file_constraints_project
|
|
88
|
+
ON file_constraints (project_id);
|
|
89
|
+
|
|
90
|
+
-- Known K/V Store: the unified state machine.
|
|
91
|
+
-- Files, knowledge, tool results, audit — everything is a keyed entry.
|
|
92
|
+
-- scheme: derived from path via schemeOf(). Generated column.
|
|
93
|
+
-- State validated by trigger against schemes.valid_states.
|
|
94
|
+
CREATE TABLE IF NOT EXISTS known_entries (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
96
|
+
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
97
|
+
, turn INTEGER NOT NULL DEFAULT 0 CHECK (turn >= 0)
|
|
98
|
+
, path TEXT NOT NULL
|
|
99
|
+
, body TEXT NOT NULL DEFAULT ''
|
|
100
|
+
, scheme TEXT GENERATED ALWAYS AS (schemeOf(path)) STORED
|
|
101
|
+
, state TEXT NOT NULL
|
|
102
|
+
, hash TEXT
|
|
103
|
+
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
104
|
+
, tokens INTEGER NOT NULL DEFAULT 0 CHECK (tokens >= 0)
|
|
105
|
+
, tokens_full INTEGER NOT NULL DEFAULT 0 CHECK (tokens_full >= 0)
|
|
106
|
+
, refs INTEGER NOT NULL DEFAULT 0 CHECK (refs >= 0)
|
|
107
|
+
, write_count INTEGER NOT NULL DEFAULT 1 CHECK (write_count >= 1)
|
|
108
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
109
|
+
, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
110
|
+
);
|
|
111
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_known_entries_run_path
|
|
112
|
+
ON known_entries (run_id, path);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_known_entries_scheme_state
|
|
114
|
+
ON known_entries (run_id, scheme, state);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_known_entries_turn
|
|
116
|
+
ON known_entries (run_id, turn);
|
|
117
|
+
|
|
118
|
+
-- Validate state against schemes.valid_states on insert.
|
|
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;
|
|
148
|
+
|
|
149
|
+
-- UNRESOLVED VIEW: all entries awaiting user action
|
|
150
|
+
CREATE VIEW IF NOT EXISTS v_unresolved AS
|
|
151
|
+
SELECT
|
|
152
|
+
run_id
|
|
153
|
+
, path
|
|
154
|
+
, body
|
|
155
|
+
, attributes
|
|
156
|
+
, turn
|
|
157
|
+
FROM known_entries
|
|
158
|
+
WHERE state = 'proposed';
|
|
159
|
+
|
|
160
|
+
-- Turn context: materialized snapshot of what the model sees each turn.
|
|
161
|
+
-- known_entries is the warehouse. turn_context is the shipment.
|
|
162
|
+
CREATE TABLE IF NOT EXISTS turn_context (
|
|
163
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
164
|
+
, run_id INTEGER NOT NULL REFERENCES runs (id) ON DELETE CASCADE
|
|
165
|
+
, turn INTEGER NOT NULL CHECK (turn >= 1)
|
|
166
|
+
, ordinal INTEGER NOT NULL CHECK (ordinal >= 0)
|
|
167
|
+
, path TEXT NOT NULL
|
|
168
|
+
, scheme TEXT GENERATED ALWAYS AS (schemeOf(path)) STORED
|
|
169
|
+
, fidelity TEXT NOT NULL CHECK (fidelity IN ('full', 'summary', 'index'))
|
|
170
|
+
, state TEXT NOT NULL DEFAULT 'full'
|
|
171
|
+
, body TEXT NOT NULL DEFAULT ''
|
|
172
|
+
, tokens INTEGER NOT NULL DEFAULT 0 CHECK (tokens >= 0)
|
|
173
|
+
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
174
|
+
, category TEXT NOT NULL DEFAULT 'result'
|
|
175
|
+
, source_turn INTEGER DEFAULT 0
|
|
176
|
+
);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_turn_context_run_turn
|
|
178
|
+
ON turn_context (run_id, turn);
|
|
179
|
+
|
|
180
|
+
-- Enforce valid run state transitions.
|
|
181
|
+
-- completed → running: continuation (new turn on finished run)
|
|
182
|
+
CREATE TRIGGER IF NOT EXISTS trg_run_state_transition
|
|
183
|
+
BEFORE UPDATE OF status ON runs
|
|
184
|
+
FOR EACH ROW
|
|
185
|
+
WHEN OLD.status != NEW.status
|
|
186
|
+
BEGIN
|
|
187
|
+
SELECT RAISE(ABORT, 'invalid run state transition')
|
|
188
|
+
WHERE NOT (
|
|
189
|
+
(OLD.status = 'queued' AND NEW.status IN ('running', 'aborted'))
|
|
190
|
+
OR (OLD.status = 'running' AND NEW.status IN ('proposed', 'completed', 'failed', 'aborted'))
|
|
191
|
+
OR (OLD.status = 'proposed' AND NEW.status IN ('running', 'completed', 'aborted'))
|
|
192
|
+
OR (OLD.status = 'completed' AND NEW.status IN ('running', 'aborted'))
|
|
193
|
+
OR (OLD.status = 'failed' AND NEW.status IN ('running', 'aborted'))
|
|
194
|
+
OR (OLD.status = 'aborted' AND NEW.status IN ('running'))
|
|
195
|
+
);
|
|
196
|
+
END;
|
|
197
|
+
|
|
198
|
+
-- Prompt queue. All prompts flow through here. Worker consumes FIFO per run.
|
|
199
|
+
CREATE TABLE IF NOT EXISTS prompt_queue (
|
|
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);
|
|
213
|
+
|
|
214
|
+
-- RPC audit log. Every call recorded unconditionally.
|
|
215
|
+
CREATE TABLE IF NOT EXISTS rpc_log (
|
|
216
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT
|
|
217
|
+
, project_id INTEGER REFERENCES projects (id) ON DELETE CASCADE
|
|
218
|
+
, method TEXT NOT NULL
|
|
219
|
+
, rpc_id INTEGER
|
|
220
|
+
, params JSON
|
|
221
|
+
, result JSON
|
|
222
|
+
, error TEXT
|
|
223
|
+
, created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
224
|
+
);
|
|
225
|
+
CREATE INDEX IF NOT EXISTS idx_rpc_log_project ON rpc_log (project_id);
|
|
226
|
+
CREATE INDEX IF NOT EXISTS idx_rpc_log_method ON rpc_log (method);
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@possumtech/rummy",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Relational Unknowns Memory Management Yoke",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"llm"
|
|
7
|
+
],
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/possumtech/rummy#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/possumtech/rummy/issues"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/possumtech/rummy.git"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "@wikitopian",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=25",
|
|
23
|
+
"npm": ">=11"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "service.js",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "node --env-file-if-exists=.env.example --env-file-if-exists=.env service.js",
|
|
29
|
+
"dev": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.dev --watch service.js",
|
|
30
|
+
"debug": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.debug --inspect service.js",
|
|
31
|
+
"lint": "biome check src test migrations --fix --unsafe && npm run lint:sql",
|
|
32
|
+
"lint:sql": ". .venv/bin/activate && sqlfluff lint .",
|
|
33
|
+
"fix:sql": ". .venv/bin/activate && sqlfluff fix . --force",
|
|
34
|
+
"test": "npm run lint && npm run test:unit && npm run test:intg",
|
|
35
|
+
"test:all": "npm run lint && npm run test:unit && npm run test:intg && npm run test:e2e",
|
|
36
|
+
"test:unit": "node --env-file-if-exists=.env.example --env-file-if-exists=.env --env-file-if-exists=.env.test --experimental-test-coverage --test-coverage-lines=70 --test-coverage-branches=70 --test-coverage-functions=70 --test-concurrency=1 --test $(find src -name '*.test.js')",
|
|
37
|
+
"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')",
|
|
38
|
+
"test:e2e": "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')",
|
|
39
|
+
"test:live": "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')"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@biomejs/biome": "^2.4.6"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@possumtech/sqlrite": "^3.1.0",
|
|
46
|
+
"htmlparser2": "^12.0.0",
|
|
47
|
+
"tiktoken": "^1.0.22",
|
|
48
|
+
"ws": "^8.19.0"
|
|
49
|
+
},
|
|
50
|
+
"optionalDependencies": {
|
|
51
|
+
"@possumtech/rummy.repo": "^0.0.4",
|
|
52
|
+
"@possumtech/rummy.web": "^0.0.7"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/service.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
// Helper to expand ~ in paths since node --env-file doesn't do it
|
|
7
|
+
// 0. Pre-flight Check: Environment and Dependencies
|
|
8
|
+
const rummyHome = process.env.RUMMY_HOME;
|
|
9
|
+
|
|
10
|
+
if (!rummyHome) {
|
|
11
|
+
console.error("RUMMY Configuration Error: RUMMY_HOME is not defined in environment.");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check for optional system dependencies
|
|
16
|
+
const gitCheck = spawnSync("git", ["--version"]);
|
|
17
|
+
if (gitCheck.error || gitCheck.status !== 0) {
|
|
18
|
+
console.warn("[RUMMY] WARNING: 'git' not found. File tracking will use manual activation only.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let SqlRite, SocketServer, registerPlugins, createHooks, RpcRegistry;
|
|
22
|
+
try {
|
|
23
|
+
SqlRite = (await import("@possumtech/sqlrite")).default;
|
|
24
|
+
SocketServer = (await import("./src/server/SocketServer.js")).default;
|
|
25
|
+
const pluginIndex = await import("./src/plugins/index.js");
|
|
26
|
+
registerPlugins = pluginIndex.registerPlugins;
|
|
27
|
+
var initPlugins = pluginIndex.initPlugins;
|
|
28
|
+
createHooks = (await import("./src/hooks/Hooks.js")).default;
|
|
29
|
+
RpcRegistry = (await import("./src/server/RpcRegistry.js")).default;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
32
|
+
console.error("RUMMY Dependency Error: node_modules not found or incomplete.");
|
|
33
|
+
console.error("Please run: npm install");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
// 1. Initialize Hooks (Agnostic Engine)
|
|
41
|
+
const debug = process.env.RUMMY_DEBUG === "true";
|
|
42
|
+
const hooks = createHooks(debug);
|
|
43
|
+
hooks.rpc.registry = new RpcRegistry();
|
|
44
|
+
|
|
45
|
+
// 2. Resolve Directories
|
|
46
|
+
const userPluginsDir = join(rummyHome, "plugins");
|
|
47
|
+
const pluginsDir = fileURLToPath(new URL("./src/plugins", import.meta.url));
|
|
48
|
+
|
|
49
|
+
// 3. Ensure Directory Structure
|
|
50
|
+
mkdirSync(userPluginsDir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
// 4. Register Plugins
|
|
53
|
+
await registerPlugins([pluginsDir, userPluginsDir], hooks);
|
|
54
|
+
|
|
55
|
+
// 5. Bootstrap Persistence
|
|
56
|
+
const dbPath = process.env.RUMMY_DB_PATH || join(rummyHome, "rummy.db");
|
|
57
|
+
const functionsDir = fileURLToPath(new URL("./src/sql/functions", import.meta.url));
|
|
58
|
+
const sqlFunctions = readdirSync(functionsDir)
|
|
59
|
+
.filter((f) => f.endsWith(".js") && !f.endsWith(".test.js"))
|
|
60
|
+
.map((f) => join(functionsDir, f));
|
|
61
|
+
|
|
62
|
+
const mmapMb = Number.parseInt(process.env.RUMMY_MMAP_MB || "0", 10);
|
|
63
|
+
const db = await SqlRite.open({
|
|
64
|
+
path: dbPath,
|
|
65
|
+
dir: ["migrations", "src"],
|
|
66
|
+
functions: sqlFunctions,
|
|
67
|
+
params: {
|
|
68
|
+
mmap_size: mmapMb * 1024 * 1024,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 6. Initialize plugins (inject DB, register schemes)
|
|
73
|
+
await initPlugins(db, null, hooks);
|
|
74
|
+
|
|
75
|
+
// 7. Bootstrap models from env vars
|
|
76
|
+
{
|
|
77
|
+
const modelAliases = [];
|
|
78
|
+
for (const key of Object.keys(process.env)) {
|
|
79
|
+
if (!key.startsWith("RUMMY_MODEL_")) continue;
|
|
80
|
+
const alias = key.replace("RUMMY_MODEL_", "");
|
|
81
|
+
const actual = process.env[key];
|
|
82
|
+
await db.upsert_model.get({
|
|
83
|
+
alias,
|
|
84
|
+
actual,
|
|
85
|
+
context_length: null,
|
|
86
|
+
});
|
|
87
|
+
modelAliases.push(alias);
|
|
88
|
+
}
|
|
89
|
+
if (modelAliases.length > 0) {
|
|
90
|
+
console.log(`[RUMMY] Models: ${modelAliases.join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 6b. Database Hygiene
|
|
95
|
+
const { statSync } = await import("node:fs");
|
|
96
|
+
try {
|
|
97
|
+
const dbSizeBefore = statSync(dbPath).size;
|
|
98
|
+
const retentionDays = Number.parseInt(process.env.RUMMY_RETENTION_DAYS || "31", 10);
|
|
99
|
+
await db.purge_old_runs.run({ retention_days: retentionDays });
|
|
100
|
+
const dbSizeAfter = statSync(dbPath).size;
|
|
101
|
+
const dbSizeMB = (dbSizeAfter / 1024 / 1024).toFixed(2);
|
|
102
|
+
const freed = dbSizeBefore - dbSizeAfter;
|
|
103
|
+
if (freed > 0) {
|
|
104
|
+
console.log(`[RUMMY] Hygiene: freed ${(freed / 1024).toFixed(1)}KB, DB is ${dbSizeMB}MB`);
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`[RUMMY] DB size: ${dbSizeMB}MB`);
|
|
107
|
+
}
|
|
108
|
+
if (dbSizeAfter > 100 * 1024 * 1024) {
|
|
109
|
+
console.warn(`[RUMMY] WARNING: Database exceeds 100MB. Consider manual cleanup.`);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.warn(`[RUMMY] Hygiene skipped: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 6b. Abort stuck runs (can't be running if the server just started)
|
|
116
|
+
await db.reset_active_prompts.run({});
|
|
117
|
+
const aborted = await db.abort_stuck_runs.run({});
|
|
118
|
+
if (aborted.changes > 0) {
|
|
119
|
+
console.log(`[RUMMY] Recovered ${aborted.changes} stuck run(s)`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 7. Start RPC Server
|
|
123
|
+
const port = Number.parseInt(process.env.PORT);
|
|
124
|
+
const server = new SocketServer(db, { port, hooks });
|
|
125
|
+
|
|
126
|
+
server.on("error", (err) => {
|
|
127
|
+
if (err.code === "EADDRINUSE") {
|
|
128
|
+
console.error(`RUMMY Critical: Port ${port} is already in use.`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
throw err;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log(`RUMMY Service Operational`);
|
|
135
|
+
console.log(`- Home: ${rummyHome}`);
|
|
136
|
+
console.log(`- DB: ${dbPath}`);
|
|
137
|
+
console.log(`- Port: ${port}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main().catch((err) => {
|
|
141
|
+
console.error("RUMMY Failed to boot:", err.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|