@possumtech/rummy 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.env.example +55 -0
  2. package/LICENSE +21 -0
  3. package/PLUGINS.md +302 -0
  4. package/README.md +41 -0
  5. package/SPEC.md +524 -0
  6. package/lang/en.json +34 -0
  7. package/migrations/001_initial_schema.sql +226 -0
  8. package/package.json +54 -0
  9. package/service.js +143 -0
  10. package/src/agent/AgentLoop.js +553 -0
  11. package/src/agent/ContextAssembler.js +29 -0
  12. package/src/agent/KnownStore.js +254 -0
  13. package/src/agent/ProjectAgent.js +101 -0
  14. package/src/agent/ResponseHealer.js +134 -0
  15. package/src/agent/TurnExecutor.js +457 -0
  16. package/src/agent/XmlParser.js +247 -0
  17. package/src/agent/known_checks.sql +42 -0
  18. package/src/agent/known_queries.sql +80 -0
  19. package/src/agent/known_store.sql +161 -0
  20. package/src/agent/messages.js +17 -0
  21. package/src/agent/prompt_queue.sql +39 -0
  22. package/src/agent/runs.sql +114 -0
  23. package/src/agent/schemes.sql +3 -0
  24. package/src/agent/sessions.sql +51 -0
  25. package/src/agent/tokens.js +28 -0
  26. package/src/agent/turns.sql +36 -0
  27. package/src/hooks/HookRegistry.js +72 -0
  28. package/src/hooks/Hooks.js +115 -0
  29. package/src/hooks/PluginContext.js +116 -0
  30. package/src/hooks/RummyContext.js +181 -0
  31. package/src/hooks/ToolRegistry.js +83 -0
  32. package/src/llm/LlmProvider.js +107 -0
  33. package/src/llm/OllamaClient.js +88 -0
  34. package/src/llm/OpenAiClient.js +80 -0
  35. package/src/llm/OpenRouterClient.js +78 -0
  36. package/src/llm/XaiClient.js +113 -0
  37. package/src/plugins/ask_user/README.md +18 -0
  38. package/src/plugins/ask_user/ask_user.js +48 -0
  39. package/src/plugins/ask_user/docs.md +2 -0
  40. package/src/plugins/cp/README.md +18 -0
  41. package/src/plugins/cp/cp.js +55 -0
  42. package/src/plugins/cp/docs.md +2 -0
  43. package/src/plugins/current/README.md +14 -0
  44. package/src/plugins/current/current.js +48 -0
  45. package/src/plugins/engine/README.md +12 -0
  46. package/src/plugins/engine/engine.sql +18 -0
  47. package/src/plugins/engine/turn_context.sql +51 -0
  48. package/src/plugins/env/README.md +14 -0
  49. package/src/plugins/env/docs.md +2 -0
  50. package/src/plugins/env/env.js +32 -0
  51. package/src/plugins/file/README.md +25 -0
  52. package/src/plugins/file/file.js +85 -0
  53. package/src/plugins/get/README.md +19 -0
  54. package/src/plugins/get/docs.md +6 -0
  55. package/src/plugins/get/get.js +53 -0
  56. package/src/plugins/hedberg/README.md +72 -0
  57. package/src/plugins/hedberg/docs.md +9 -0
  58. package/src/plugins/hedberg/edits.js +65 -0
  59. package/src/plugins/hedberg/hedberg.js +89 -0
  60. package/src/plugins/hedberg/matcher.js +181 -0
  61. package/src/plugins/hedberg/normalize.js +41 -0
  62. package/src/plugins/hedberg/patterns.js +452 -0
  63. package/src/plugins/hedberg/sed.js +48 -0
  64. package/src/plugins/helpers.js +22 -0
  65. package/src/plugins/index.js +180 -0
  66. package/src/plugins/instructions/README.md +11 -0
  67. package/src/plugins/instructions/instructions.js +37 -0
  68. package/src/plugins/instructions/preamble.md +12 -0
  69. package/src/plugins/known/README.md +18 -0
  70. package/src/plugins/known/docs.md +3 -0
  71. package/src/plugins/known/known.js +57 -0
  72. package/src/plugins/mv/README.md +18 -0
  73. package/src/plugins/mv/docs.md +2 -0
  74. package/src/plugins/mv/mv.js +56 -0
  75. package/src/plugins/previous/README.md +15 -0
  76. package/src/plugins/previous/previous.js +50 -0
  77. package/src/plugins/progress/README.md +17 -0
  78. package/src/plugins/progress/progress.js +44 -0
  79. package/src/plugins/prompt/README.md +16 -0
  80. package/src/plugins/prompt/prompt.js +45 -0
  81. package/src/plugins/rm/README.md +18 -0
  82. package/src/plugins/rm/docs.md +4 -0
  83. package/src/plugins/rm/rm.js +51 -0
  84. package/src/plugins/rpc/README.md +45 -0
  85. package/src/plugins/rpc/rpc.js +587 -0
  86. package/src/plugins/set/README.md +32 -0
  87. package/src/plugins/set/docs.md +4 -0
  88. package/src/plugins/set/set.js +268 -0
  89. package/src/plugins/sh/README.md +18 -0
  90. package/src/plugins/sh/docs.md +2 -0
  91. package/src/plugins/sh/sh.js +32 -0
  92. package/src/plugins/skills/README.md +25 -0
  93. package/src/plugins/skills/skills.js +175 -0
  94. package/src/plugins/store/README.md +20 -0
  95. package/src/plugins/store/docs.md +5 -0
  96. package/src/plugins/store/store.js +52 -0
  97. package/src/plugins/summarize/README.md +18 -0
  98. package/src/plugins/summarize/docs.md +4 -0
  99. package/src/plugins/summarize/summarize.js +24 -0
  100. package/src/plugins/telemetry/README.md +19 -0
  101. package/src/plugins/telemetry/rpc_log.sql +28 -0
  102. package/src/plugins/telemetry/telemetry.js +186 -0
  103. package/src/plugins/unknown/README.md +23 -0
  104. package/src/plugins/unknown/docs.md +5 -0
  105. package/src/plugins/unknown/unknown.js +31 -0
  106. package/src/plugins/update/README.md +18 -0
  107. package/src/plugins/update/docs.md +4 -0
  108. package/src/plugins/update/update.js +24 -0
  109. package/src/server/ClientConnection.js +228 -0
  110. package/src/server/RpcRegistry.js +52 -0
  111. package/src/server/SocketServer.js +43 -0
  112. package/src/sql/file_constraints.sql +15 -0
  113. package/src/sql/functions/countTokens.js +7 -0
  114. package/src/sql/functions/hedmatch.js +8 -0
  115. package/src/sql/functions/hedreplace.js +8 -0
  116. package/src/sql/functions/hedsearch.js +8 -0
  117. package/src/sql/functions/schemeOf.js +7 -0
  118. package/src/sql/functions/slugify.js +6 -0
  119. package/src/sql/v_model_context.sql +101 -0
  120. package/src/sql/v_run_log.sql +23 -0
@@ -0,0 +1,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
+ });