@possumtech/rummy 2.0.0 → 2.1.0
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 +31 -5
- package/BENCH_ENVIRONMENT.md +230 -0
- package/CLIENT_INTERFACE.md +396 -0
- package/PLUGINS.md +93 -1
- package/SPEC.md +389 -28
- package/bin/postinstall.js +2 -2
- package/bin/rummy.js +2 -2
- package/last_run.txt +5617 -0
- package/migrations/001_initial_schema.sql +2 -1
- package/package.json +13 -9
- package/scriptify/ask_run.js +77 -0
- package/scriptify/cache_probe.js +66 -0
- package/scriptify/cache_probe_grok.js +74 -0
- package/service.js +22 -11
- package/src/agent/AgentLoop.js +62 -157
- package/src/agent/ContextAssembler.js +2 -9
- package/src/agent/Entries.js +54 -98
- package/src/agent/ProjectAgent.js +4 -11
- package/src/agent/TurnExecutor.js +48 -83
- package/src/agent/XmlParser.js +247 -273
- package/src/agent/budget.js +5 -28
- package/src/agent/config.js +38 -0
- package/src/agent/errors.js +7 -13
- package/src/agent/httpStatus.js +1 -19
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +12 -2
- package/src/agent/materializeContext.js +15 -18
- package/src/agent/pathEncode.js +5 -0
- package/src/agent/rummyHome.js +9 -0
- package/src/agent/runs.sql +37 -0
- package/src/agent/tokens.js +7 -7
- package/src/hooks/HookRegistry.js +1 -16
- package/src/hooks/Hooks.js +8 -33
- package/src/hooks/PluginContext.js +3 -21
- package/src/hooks/RpcRegistry.js +1 -4
- package/src/hooks/RummyContext.js +6 -16
- package/src/hooks/ToolRegistry.js +5 -15
- package/src/llm/LlmProvider.js +41 -33
- package/src/llm/errors.js +41 -4
- package/src/llm/openaiStream.js +125 -0
- package/src/llm/retry.js +109 -0
- package/src/plugins/budget/budget.js +55 -76
- package/src/plugins/cli/README.md +87 -0
- package/src/plugins/cli/bin.js +61 -0
- package/src/plugins/cli/cli.js +120 -0
- package/src/plugins/env/README.md +2 -1
- package/src/plugins/env/env.js +4 -6
- package/src/plugins/env/envDoc.md +2 -2
- package/src/plugins/error/error.js +23 -23
- package/src/plugins/file/file.js +2 -22
- package/src/plugins/get/get.js +12 -34
- package/src/plugins/get/getDoc.md +8 -6
- package/src/plugins/hedberg/edits.js +1 -11
- package/src/plugins/hedberg/hedberg.js +3 -26
- package/src/plugins/hedberg/normalize.js +1 -5
- package/src/plugins/hedberg/patterns.js +4 -15
- package/src/plugins/hedberg/sed.js +1 -7
- package/src/plugins/helpers.js +28 -20
- package/src/plugins/index.js +25 -41
- package/src/plugins/instructions/README.md +18 -0
- package/src/plugins/instructions/instructions.js +97 -38
- package/src/plugins/instructions/instructions.md +24 -15
- package/src/plugins/instructions/instructions_104.md +5 -4
- package/src/plugins/instructions/instructions_105.md +29 -36
- package/src/plugins/instructions/instructions_106.md +22 -0
- package/src/plugins/instructions/instructions_107.md +17 -0
- package/src/plugins/instructions/instructions_108.md +0 -8
- package/src/plugins/known/README.md +26 -6
- package/src/plugins/known/known.js +37 -34
- package/src/plugins/log/README.md +2 -2
- package/src/plugins/log/log.js +27 -34
- package/src/plugins/ollama/ollama.js +50 -66
- package/src/plugins/openai/openai.js +26 -44
- package/src/plugins/openrouter/openrouter.js +28 -52
- package/src/plugins/policy/README.md +8 -2
- package/src/plugins/policy/policy.js +8 -21
- package/src/plugins/prompt/README.md +22 -0
- package/src/plugins/prompt/prompt.js +14 -16
- package/src/plugins/rm/rm.js +5 -2
- package/src/plugins/rm/rmDoc.md +4 -4
- package/src/plugins/rpc/README.md +2 -1
- package/src/plugins/rpc/rpc.js +62 -48
- package/src/plugins/set/README.md +5 -1
- package/src/plugins/set/set.js +23 -33
- package/src/plugins/set/setDoc.md +1 -1
- package/src/plugins/sh/README.md +2 -1
- package/src/plugins/sh/sh.js +5 -11
- package/src/plugins/sh/shDoc.md +2 -2
- package/src/plugins/stream/README.md +6 -5
- package/src/plugins/stream/stream.js +6 -35
- package/src/plugins/telemetry/telemetry.js +26 -19
- package/src/plugins/think/think.js +4 -7
- package/src/plugins/unknown/unknown.js +8 -13
- package/src/plugins/update/update.js +42 -25
- package/src/plugins/update/updateDoc.md +3 -3
- package/src/plugins/xai/xai.js +30 -20
- package/src/plugins/yolo/yolo.js +159 -0
- package/src/server/ClientConnection.js +17 -47
- package/src/server/SocketServer.js +14 -14
- package/src/server/protocol.js +1 -10
- package/src/sql/functions/slugify.js +5 -7
- package/src/sql/v_model_context.sql +4 -11
- package/turns/cli_1777462658211/turn_001.txt +772 -0
- package/turns/cli_1777462658211/turn_002.txt +606 -0
- package/turns/cli_1777462658211/turn_003.txt +667 -0
- package/turns/cli_1777462658211/turn_004.txt +297 -0
- package/turns/cli_1777462658211/turn_005.txt +301 -0
- package/turns/cli_1777462658211/turn_006.txt +262 -0
- package/turns/cli_1777465095132/turn_001.txt +715 -0
- package/turns/cli_1777465095132/turn_002.txt +236 -0
- package/turns/cli_1777465095132/turn_003.txt +287 -0
- package/turns/cli_1777465095132/turn_004.txt +694 -0
- package/turns/cli_1777465095132/turn_005.txt +422 -0
- package/turns/cli_1777465095132/turn_006.txt +365 -0
- package/turns/cli_1777465095132/turn_007.txt +885 -0
- package/turns/cli_1777465095132/turn_008.txt +1277 -0
- package/turns/cli_1777465095132/turn_009.txt +736 -0
|
@@ -218,7 +218,8 @@ CREATE TABLE IF NOT EXISTS turn_context (
|
|
|
218
218
|
state IN ('proposed', 'streaming', 'resolved', 'failed', 'cancelled')
|
|
219
219
|
)
|
|
220
220
|
, outcome TEXT
|
|
221
|
-
|
|
221
|
+
-- 'archived' permitted; see prompt plugin README for the exception.
|
|
222
|
+
, visibility TEXT NOT NULL CHECK (visibility IN ('visible', 'summarized', 'archived'))
|
|
222
223
|
, body TEXT NOT NULL DEFAULT ''
|
|
223
224
|
, attributes JSON NOT NULL DEFAULT '{}' CHECK (json_valid(attributes))
|
|
224
225
|
, category TEXT NOT NULL DEFAULT 'logging'
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@possumtech/rummy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Relational Unknowns Memory Management Yoke",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm"
|
|
7
7
|
],
|
|
8
8
|
"bin": {
|
|
9
|
-
"rummy": "./bin/rummy.js"
|
|
9
|
+
"rummy": "./bin/rummy.js",
|
|
10
|
+
"rummy-cli": "./src/plugins/cli/bin.js"
|
|
10
11
|
},
|
|
11
12
|
"publishConfig": {
|
|
12
13
|
"access": "public"
|
|
@@ -41,16 +42,19 @@
|
|
|
41
42
|
"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
43
|
"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
44
|
"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' --",
|
|
45
|
+
"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
46
|
"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
47
|
"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:
|
|
48
|
+
"test:swe:setup": "bash test/swe/setup.sh",
|
|
49
|
+
"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",
|
|
50
|
+
"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' --",
|
|
51
|
+
"test:swe:eval": "bash -c 'cd test/swe && source .venv/bin/activate && python evaluate.py \"$@\"' --",
|
|
52
|
+
"test:swe:baseline": "bash -c 'cd test/swe && source .venv/bin/activate && python baseline.py \"$@\"' --",
|
|
53
53
|
"test:lme:clean": "rm -rf test/lme/results/*/",
|
|
54
|
+
"test:swe:clean": "rm -rf test/swe/results/*/ test/swe/repos/",
|
|
55
|
+
"test:tbench:setup": "bash -c 'set -a; source .env.tbench; set +a; bash test/tbench/setup.sh'",
|
|
56
|
+
"test:tbench": "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.tbench test/tbench/runner.js \"$@\" 2>&1 | tee /tmp/rummy_test_diag/tbench_$(date +%Y%m%dT%H%M%S).log' --",
|
|
57
|
+
"test:tbench:clean": "rm -rf test/tbench/results/*/",
|
|
54
58
|
"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
59
|
"test:demo": "node --env-file-if-exists=.env.example --env-file-if-exists=.env bin/demo.js",
|
|
56
60
|
"test:spec": "node test/spec-coverage.js"
|
|
@@ -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();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Probe llama-server cache behavior. Send variations of the same request
|
|
3
|
+
// and inspect cached_tokens in the response usage block to determine
|
|
4
|
+
// whether caching is token-prefix or message-hash level.
|
|
5
|
+
|
|
6
|
+
const URL = "http://127.0.0.1:11435/v1/chat/completions";
|
|
7
|
+
const MODEL = "gemma-4-26B-A4B-it-UD-Q3_K_XL.gguf";
|
|
8
|
+
|
|
9
|
+
async function probe(label, system, user) {
|
|
10
|
+
const body = {
|
|
11
|
+
model: MODEL,
|
|
12
|
+
messages: [
|
|
13
|
+
{ role: "system", content: system },
|
|
14
|
+
{ role: "user", content: user },
|
|
15
|
+
],
|
|
16
|
+
think: true,
|
|
17
|
+
temperature: 0.5,
|
|
18
|
+
};
|
|
19
|
+
const res = await fetch(URL, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify(body),
|
|
23
|
+
});
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
const u = data.usage || {};
|
|
26
|
+
const cached =
|
|
27
|
+
u.prompt_tokens_details?.cached_tokens ??
|
|
28
|
+
u.cached_tokens ??
|
|
29
|
+
0;
|
|
30
|
+
console.log(
|
|
31
|
+
`[${label}] prompt_tokens=${u.prompt_tokens ?? "?"} cached_tokens=${cached} system_chars=${system.length} user_chars=${user.length}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const STATIC_SYSTEM_BASE = `You are a helpful assistant.
|
|
36
|
+
|
|
37
|
+
Tools available:
|
|
38
|
+
- foo: does foo
|
|
39
|
+
- bar: does bar
|
|
40
|
+
- baz: does baz
|
|
41
|
+
|
|
42
|
+
Always be concise.`;
|
|
43
|
+
|
|
44
|
+
const ADDITION_A = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
45
|
+
const ADDITION_B = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n<known path=\"k2\">second known fact</known>\n</context>";
|
|
46
|
+
const ADDITION_C = "\n\n<context>\n<known path=\"k2\">second known fact</known>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
47
|
+
|
|
48
|
+
const USER_A = "Hello.";
|
|
49
|
+
|
|
50
|
+
console.log("=== Run 1: baseline (cold, then immediate repeat) ===");
|
|
51
|
+
await probe("1a baseline cold", STATIC_SYSTEM_BASE, USER_A);
|
|
52
|
+
await probe("1b same-as-1a ", STATIC_SYSTEM_BASE, USER_A);
|
|
53
|
+
|
|
54
|
+
console.log("\n=== Run 2: same base, then base + appended context (prefix unchanged) ===");
|
|
55
|
+
await probe("2a base only ", STATIC_SYSTEM_BASE, USER_A);
|
|
56
|
+
await probe("2b base + 1 entry", STATIC_SYSTEM_BASE + ADDITION_A, USER_A);
|
|
57
|
+
await probe("2c base + 2 entries", STATIC_SYSTEM_BASE + ADDITION_B, USER_A);
|
|
58
|
+
|
|
59
|
+
console.log("\n=== Run 3: prefix change (entries reordered, same body) ===");
|
|
60
|
+
await probe("3a base + 2 entries (k1,k2)", STATIC_SYSTEM_BASE + ADDITION_B, USER_A);
|
|
61
|
+
await probe("3b base + 2 entries (k2,k1) reordered", STATIC_SYSTEM_BASE + ADDITION_C, USER_A);
|
|
62
|
+
|
|
63
|
+
console.log("\n=== Run 4: small mid-prefix change ===");
|
|
64
|
+
const MIDDIFF = STATIC_SYSTEM_BASE.replace("baz", "qux");
|
|
65
|
+
await probe("4a stable base ", STATIC_SYSTEM_BASE, USER_A);
|
|
66
|
+
await probe("4b changed baz→qux", MIDDIFF, USER_A);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Same probe as cache_probe.js but against OpenRouter's grok endpoint.
|
|
3
|
+
// If cached_tokens behaves sanely (incremental matches preserve prefix),
|
|
4
|
+
// then llama-server's behavior was the local anomaly.
|
|
5
|
+
|
|
6
|
+
const URL = `${process.env.OPENROUTER_BASE_URL || "https://openrouter.ai/api/v1"}/chat/completions`;
|
|
7
|
+
const MODEL = "x-ai/grok-4.1-fast";
|
|
8
|
+
|
|
9
|
+
if (!process.env.OPENROUTER_API_KEY) {
|
|
10
|
+
console.error("OPENROUTER_API_KEY required");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function probe(label, system, user) {
|
|
15
|
+
const body = {
|
|
16
|
+
model: MODEL,
|
|
17
|
+
messages: [
|
|
18
|
+
{ role: "system", content: system },
|
|
19
|
+
{ role: "user", content: user },
|
|
20
|
+
],
|
|
21
|
+
include_reasoning: true,
|
|
22
|
+
temperature: 0.5,
|
|
23
|
+
};
|
|
24
|
+
const res = await fetch(URL, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
});
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
const u = data.usage || {};
|
|
34
|
+
const cached =
|
|
35
|
+
u.prompt_tokens_details?.cached_tokens ??
|
|
36
|
+
u.cached_tokens ??
|
|
37
|
+
u.cache_read_input_tokens ??
|
|
38
|
+
0;
|
|
39
|
+
console.log(
|
|
40
|
+
`[${label}] prompt_tokens=${u.prompt_tokens ?? "?"} cached_tokens=${cached} system_chars=${system.length}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const STATIC_SYSTEM_BASE = `You are a helpful assistant.
|
|
45
|
+
|
|
46
|
+
Tools available:
|
|
47
|
+
- foo: does foo
|
|
48
|
+
- bar: does bar
|
|
49
|
+
- baz: does baz
|
|
50
|
+
|
|
51
|
+
Always be concise.`;
|
|
52
|
+
|
|
53
|
+
const ADDITION_A = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
54
|
+
const ADDITION_B = "\n\n<context>\n<known path=\"k1\">first known fact</known>\n<known path=\"k2\">second known fact</known>\n</context>";
|
|
55
|
+
const ADDITION_C = "\n\n<context>\n<known path=\"k2\">second known fact</known>\n<known path=\"k1\">first known fact</known>\n</context>";
|
|
56
|
+
|
|
57
|
+
const USER = "Hello.";
|
|
58
|
+
|
|
59
|
+
console.log("=== Run 1: baseline (cold, then immediate repeat) ===");
|
|
60
|
+
await probe("1a baseline cold", STATIC_SYSTEM_BASE, USER);
|
|
61
|
+
await probe("1b same-as-1a ", STATIC_SYSTEM_BASE, USER);
|
|
62
|
+
|
|
63
|
+
console.log("\n=== Run 2: appended context (prefix unchanged) ===");
|
|
64
|
+
await probe("2a base + 1 ", STATIC_SYSTEM_BASE + ADDITION_A, USER);
|
|
65
|
+
await probe("2b base + 2 ", STATIC_SYSTEM_BASE + ADDITION_B, USER);
|
|
66
|
+
|
|
67
|
+
console.log("\n=== Run 3: reordered (entries shuffled) ===");
|
|
68
|
+
await probe("3a (k1,k2) ", STATIC_SYSTEM_BASE + ADDITION_B, USER);
|
|
69
|
+
await probe("3b (k2,k1) ", STATIC_SYSTEM_BASE + ADDITION_C, USER);
|
|
70
|
+
|
|
71
|
+
console.log("\n=== Run 4: mid-prefix character change ===");
|
|
72
|
+
const MIDDIFF = STATIC_SYSTEM_BASE.replace("baz", "qux");
|
|
73
|
+
await probe("4a stable base ", STATIC_SYSTEM_BASE, USER);
|
|
74
|
+
await probe("4b baz→qux ", MIDDIFF, USER);
|
package/service.js
CHANGED
|
@@ -43,11 +43,7 @@ if (!rummyHome) {
|
|
|
43
43
|
}
|
|
44
44
|
for (const path of [homeExample, homeEnv]) {
|
|
45
45
|
if (!existsSync(path)) continue;
|
|
46
|
-
|
|
47
|
-
process.loadEnvFile(path);
|
|
48
|
-
} catch (err) {
|
|
49
|
-
console.warn(`[RUMMY] Failed to load ${path}: ${err.message}`);
|
|
50
|
-
}
|
|
46
|
+
process.loadEnvFile(path);
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
49
|
}
|
|
@@ -136,11 +132,21 @@ async function main() {
|
|
|
136
132
|
}
|
|
137
133
|
}
|
|
138
134
|
|
|
139
|
-
// 6b. Database Hygiene
|
|
135
|
+
// 6b. Database Hygiene — opt-in via RUMMY_RETENTION_DAYS.
|
|
140
136
|
const { statSync } = await import("node:fs");
|
|
141
|
-
|
|
137
|
+
const retentionRaw = process.env.RUMMY_RETENTION_DAYS;
|
|
138
|
+
if (retentionRaw == null || retentionRaw === "") {
|
|
139
|
+
const dbSizeMB = (statSync(dbPath).size / 1024 / 1024).toFixed(2);
|
|
140
|
+
console.log(`[RUMMY] DB size: ${dbSizeMB}MB`);
|
|
141
|
+
} else {
|
|
142
|
+
const retentionDays = Number.parseInt(retentionRaw, 10);
|
|
143
|
+
if (!Number.isInteger(retentionDays) || retentionDays < 0) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Invalid RUMMY_RETENTION_DAYS=${JSON.stringify(retentionRaw)} ` +
|
|
146
|
+
"(expected non-negative integer)",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
142
149
|
const dbSizeBefore = statSync(dbPath).size;
|
|
143
|
-
const retentionDays = Number.parseInt(process.env.RUMMY_RETENTION_DAYS, 10);
|
|
144
150
|
await db.purge_old_runs.run({ retention_days: retentionDays });
|
|
145
151
|
const dbSizeAfter = statSync(dbPath).size;
|
|
146
152
|
const dbSizeMB = (dbSizeAfter / 1024 / 1024).toFixed(2);
|
|
@@ -153,8 +159,6 @@ async function main() {
|
|
|
153
159
|
if (dbSizeAfter > 100 * 1024 * 1024) {
|
|
154
160
|
console.warn(`[RUMMY] WARNING: Database exceeds 100MB. Consider manual cleanup.`);
|
|
155
161
|
}
|
|
156
|
-
} catch (err) {
|
|
157
|
-
console.warn(`[RUMMY] Hygiene skipped: ${err.message}`);
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
// 6b. Abort stuck runs (can't be running if the server just started)
|
|
@@ -164,8 +168,15 @@ async function main() {
|
|
|
164
168
|
console.log(`[RUMMY] Recovered ${aborted.changes} stuck run(s)`);
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
// 6c. Boot complete — DB open, plugins inited, models loaded,
|
|
172
|
+
// hygiene done. Plugins that need a one-shot post-boot action
|
|
173
|
+
// (e.g. the cli plugin firing a programmatic run) subscribe to
|
|
174
|
+
// this event. Fires BEFORE SocketServer so RPC clients can't
|
|
175
|
+
// race a one-shot run still being set up.
|
|
176
|
+
await hooks.boot.completed.emit({ db, hooks });
|
|
177
|
+
|
|
167
178
|
// 7. Start RPC Server
|
|
168
|
-
const port = Number.parseInt(process.env.
|
|
179
|
+
const port = Number.parseInt(process.env.RUMMY_PORT);
|
|
169
180
|
const server = new SocketServer(db, { port, hooks });
|
|
170
181
|
|
|
171
182
|
server.on("error", (err) => {
|