@possumtech/rummy 0.3.1 → 0.5.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 +12 -0
- package/FIDELITY_CONTRACT.md +172 -0
- package/README.md +5 -1
- package/SPEC.md +31 -17
- package/migrations/001_initial_schema.sql +3 -4
- package/package.json +1 -1
- package/src/agent/AgentLoop.js +51 -153
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/KnownStore.js +16 -9
- package/src/agent/ResponseHealer.js +54 -1
- package/src/agent/TurnExecutor.js +125 -323
- package/src/agent/XmlParser.js +172 -42
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +29 -72
- package/src/agent/runs.sql +2 -2
- package/src/hooks/Hooks.js +1 -0
- package/src/hooks/PluginContext.js +8 -2
- package/src/hooks/RummyContext.js +6 -3
- package/src/hooks/ToolRegistry.js +29 -32
- package/src/plugins/ask_user/ask_user.js +2 -2
- package/src/plugins/ask_user/ask_userDoc.js +7 -10
- package/src/plugins/budget/README.md +28 -18
- package/src/plugins/budget/budget.js +80 -3
- package/src/plugins/budget/recovery.js +47 -0
- package/src/plugins/cp/cp.js +5 -5
- package/src/plugins/cp/cpDoc.js +1 -14
- package/src/plugins/engine/engine.sql +1 -1
- package/src/plugins/env/env.js +4 -4
- package/src/plugins/env/envDoc.js +4 -9
- package/src/plugins/file/file.js +2 -7
- package/src/plugins/get/get.js +32 -13
- package/src/plugins/get/getDoc.js +26 -44
- package/src/plugins/helpers.js +4 -4
- package/src/plugins/instructions/instructions.js +9 -7
- package/src/plugins/instructions/preamble.md +45 -26
- package/src/plugins/known/known.js +71 -15
- package/src/plugins/known/knownDoc.js +4 -20
- package/src/plugins/mv/mv.js +6 -6
- package/src/plugins/mv/mvDoc.js +4 -30
- package/src/plugins/policy/policy.js +47 -0
- package/src/plugins/previous/previous.js +10 -14
- package/src/plugins/progress/progress.js +29 -48
- package/src/plugins/prompt/prompt.js +18 -6
- package/src/plugins/rm/rm.js +4 -4
- package/src/plugins/rm/rmDoc.js +5 -14
- package/src/plugins/rpc/rpc.js +4 -2
- package/src/plugins/set/set.js +86 -91
- package/src/plugins/set/setDoc.js +28 -41
- package/src/plugins/sh/sh.js +4 -4
- package/src/plugins/sh/shDoc.js +4 -9
- package/src/plugins/skill/skill.js +2 -1
- package/src/plugins/summarize/summarize.js +9 -2
- package/src/plugins/summarize/summarizeDoc.js +10 -16
- package/src/plugins/telemetry/telemetry.js +36 -11
- package/src/plugins/think/think.js +13 -0
- package/src/plugins/think/thinkDoc.js +16 -0
- package/src/plugins/unknown/unknown.js +37 -9
- package/src/plugins/unknown/unknownDoc.js +7 -16
- package/src/plugins/update/update.js +9 -2
- package/src/plugins/update/updateDoc.js +12 -14
- package/src/server/ClientConnection.js +11 -1
- package/src/sql/functions/slugify.js +13 -1
- package/src/sql/v_model_context.sql +6 -6
|
@@ -14,6 +14,7 @@ export default class ContextAssembler {
|
|
|
14
14
|
toolSet = null,
|
|
15
15
|
lastContextTokens = 0,
|
|
16
16
|
turn = 1,
|
|
17
|
+
baselineTokens = 0,
|
|
17
18
|
} = {},
|
|
18
19
|
hooks,
|
|
19
20
|
) {
|
|
@@ -32,6 +33,7 @@ export default class ContextAssembler {
|
|
|
32
33
|
demoted,
|
|
33
34
|
toolSet,
|
|
34
35
|
turn,
|
|
36
|
+
baselineTokens,
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
const system = await hooks.assembly.system.filter(systemPrompt, ctx);
|
package/src/agent/KnownStore.js
CHANGED
|
@@ -5,6 +5,7 @@ export default class KnownStore {
|
|
|
5
5
|
#onChanged;
|
|
6
6
|
#schemes = new Map();
|
|
7
7
|
#seq = 0;
|
|
8
|
+
#pendingResolutions = new Map();
|
|
8
9
|
|
|
9
10
|
constructor(db, { onChanged } = {}) {
|
|
10
11
|
this.#db = db;
|
|
@@ -83,7 +84,7 @@ export default class KnownStore {
|
|
|
83
84
|
body,
|
|
84
85
|
status,
|
|
85
86
|
{
|
|
86
|
-
fidelity = "
|
|
87
|
+
fidelity = "promoted",
|
|
87
88
|
attributes = null,
|
|
88
89
|
hash = null,
|
|
89
90
|
updatedAt = null,
|
|
@@ -225,6 +226,20 @@ export default class KnownStore {
|
|
|
225
226
|
body,
|
|
226
227
|
});
|
|
227
228
|
this.#emitChanged(runId, normalized, "resolve");
|
|
229
|
+
const key = `${runId}:${normalized}`;
|
|
230
|
+
const resolver = this.#pendingResolutions.get(key);
|
|
231
|
+
if (resolver) {
|
|
232
|
+
this.#pendingResolutions.delete(key);
|
|
233
|
+
resolver();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
waitForResolution(runId, path) {
|
|
238
|
+
const normalized = KnownStore.normalizePath(path);
|
|
239
|
+
const key = `${runId}:${normalized}`;
|
|
240
|
+
return new Promise((resolve) => {
|
|
241
|
+
this.#pendingResolutions.set(key, resolve);
|
|
242
|
+
});
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
async restoreSummarizedPrompts(runId) {
|
|
@@ -232,14 +247,6 @@ export default class KnownStore {
|
|
|
232
247
|
this.#emitChanged(runId, "prompt://batch", "fidelity");
|
|
233
248
|
}
|
|
234
249
|
|
|
235
|
-
async demotePreviousLoopLogging(runId, loopId) {
|
|
236
|
-
await this.#db.demote_previous_loop_logging.run({
|
|
237
|
-
run_id: runId,
|
|
238
|
-
loop_id: loopId,
|
|
239
|
-
});
|
|
240
|
-
this.#emitChanged(runId, "logging://batch", "fidelity");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
250
|
async getLog(runId) {
|
|
244
251
|
return this.#db.get_results.all({ run_id: runId });
|
|
245
252
|
}
|
|
@@ -2,6 +2,8 @@ const MAX_STALLS = Number(process.env.RUMMY_MAX_STALLS) || 3;
|
|
|
2
2
|
const MIN_CYCLES = Number(process.env.RUMMY_MIN_CYCLES) || 3;
|
|
3
3
|
const MAX_CYCLE_PERIOD = Number(process.env.RUMMY_MAX_CYCLE_PERIOD) || 4;
|
|
4
4
|
const MAX_UPDATE_REPEATS = Number(process.env.RUMMY_MAX_UPDATE_REPEATS) || 3;
|
|
5
|
+
const MAX_PATH_STAGNATION =
|
|
6
|
+
Number(process.env.RUMMY_MAX_PATH_STAGNATION) || 5;
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Build a stable fingerprint for a single recorded entry.
|
|
@@ -47,11 +49,28 @@ function detectCycle(history) {
|
|
|
47
49
|
return { detected: false };
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Extract the target paths a command touches for stagnation detection.
|
|
54
|
+
* Same target logic as cmdFingerprint but returns the raw path for set
|
|
55
|
+
* comparison across turns.
|
|
56
|
+
*/
|
|
57
|
+
function cmdPaths(entry) {
|
|
58
|
+
const attrs = entry.attributes ?? {};
|
|
59
|
+
const paths = [];
|
|
60
|
+
if (attrs.path) paths.push(attrs.path);
|
|
61
|
+
if (attrs.to) paths.push(attrs.to);
|
|
62
|
+
if (attrs.command) paths.push(attrs.command);
|
|
63
|
+
if (attrs.query) paths.push(attrs.query);
|
|
64
|
+
if (attrs.question) paths.push(attrs.question);
|
|
65
|
+
return paths;
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
export default class ResponseHealer {
|
|
51
69
|
#stallCount = 0;
|
|
52
70
|
#turnHistory = [];
|
|
53
71
|
#lastUpdateText = null;
|
|
54
72
|
#updateRepeatCount = 0;
|
|
73
|
+
#pathRuns = new Map(); // path → consecutive turns touched
|
|
55
74
|
|
|
56
75
|
/**
|
|
57
76
|
* Heal a missing status tag. Called when the model emits
|
|
@@ -67,8 +86,15 @@ export default class ResponseHealer {
|
|
|
67
86
|
static healStatus(content, commands) {
|
|
68
87
|
const trimmed = content.trim();
|
|
69
88
|
|
|
89
|
+
// Detect malformed-glitch content — model attempted a tool invocation
|
|
90
|
+
// (native call, malformed XML, etc.) that the parser couldn't dispatch.
|
|
91
|
+
// This is NOT an answer; it's a glitch that deserves the 3-strikes
|
|
92
|
+
// stall path so the model can recover. Without this check, the model
|
|
93
|
+
// emits one malformed call and the run terminates after a single turn.
|
|
94
|
+
const looksGlitched = /<\|tool_call>|<tool_call\|>/.test(trimmed);
|
|
95
|
+
|
|
70
96
|
// No commands + plain text = answered. Treat as summary.
|
|
71
|
-
if (commands.length === 0 && trimmed) {
|
|
97
|
+
if (commands.length === 0 && trimmed && !looksGlitched) {
|
|
72
98
|
console.warn("[RUMMY] Healed: plain text response treated as summary");
|
|
73
99
|
return { summaryText: trimmed.slice(0, 500), updateText: null };
|
|
74
100
|
}
|
|
@@ -120,6 +146,32 @@ export default class ResponseHealer {
|
|
|
120
146
|
return { continue: false, reason };
|
|
121
147
|
}
|
|
122
148
|
|
|
149
|
+
// Distinct-paths stagnation: the model might vary commands turn-to-turn
|
|
150
|
+
// (avoiding exact-cycle detection) but still churn on a single path.
|
|
151
|
+
// Track per-path consecutive touches; flag if any path is touched in
|
|
152
|
+
// MAX_PATH_STAGNATION consecutive turns. Catches semantic stagnation
|
|
153
|
+
// where the fingerprints differ in micro-detail but the work is stuck
|
|
154
|
+
// on one entry (e.g. endlessly re-setting/re-getting the same plan).
|
|
155
|
+
const touchedPaths = new Set();
|
|
156
|
+
for (const cmd of commands) {
|
|
157
|
+
for (const p of cmdPaths(cmd)) touchedPaths.add(p);
|
|
158
|
+
}
|
|
159
|
+
// Paths not touched this turn — run broken, remove from map.
|
|
160
|
+
for (const path of [...this.#pathRuns.keys()]) {
|
|
161
|
+
if (!touchedPaths.has(path)) this.#pathRuns.delete(path);
|
|
162
|
+
}
|
|
163
|
+
// Paths touched this turn — increment run.
|
|
164
|
+
for (const path of touchedPaths) {
|
|
165
|
+
this.#pathRuns.set(path, (this.#pathRuns.get(path) || 0) + 1);
|
|
166
|
+
}
|
|
167
|
+
for (const [path, run] of this.#pathRuns) {
|
|
168
|
+
if (run >= MAX_PATH_STAGNATION) {
|
|
169
|
+
const reason = `Path stagnation: ${path} touched ${run} consecutive turns`;
|
|
170
|
+
console.warn(`[RUMMY] ${reason}. Force-completing.`);
|
|
171
|
+
return { continue: false, reason };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
123
175
|
return { continue: true };
|
|
124
176
|
}
|
|
125
177
|
|
|
@@ -184,5 +236,6 @@ export default class ResponseHealer {
|
|
|
184
236
|
this.#turnHistory = [];
|
|
185
237
|
this.#lastUpdateText = null;
|
|
186
238
|
this.#updateRepeatCount = 0;
|
|
239
|
+
this.#pathRuns = new Map();
|
|
187
240
|
}
|
|
188
241
|
}
|