@possumtech/rummy 0.2.7 → 0.3.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 -3
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +454 -197
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +57 -70
- package/package.json +16 -10
- package/service.js +1 -1
- package/src/agent/AgentLoop.js +254 -70
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +156 -23
- package/src/agent/ProjectAgent.js +5 -4
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +393 -115
- package/src/agent/XmlParser.js +92 -39
- package/src/agent/known_checks.sql +5 -4
- package/src/agent/known_queries.sql +4 -3
- package/src/agent/known_store.sql +45 -15
- package/src/agent/loops.sql +63 -0
- package/src/agent/runs.sql +7 -7
- package/src/agent/schemes.sql +5 -2
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +13 -4
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -10
- package/src/hooks/RummyContext.js +30 -10
- package/src/hooks/ToolRegistry.js +83 -19
- package/src/llm/LlmProvider.js +27 -8
- package/src/llm/OpenAiClient.js +20 -0
- package/src/llm/OpenRouterClient.js +24 -2
- package/src/llm/XaiClient.js +47 -2
- package/src/plugins/ask_user/README.md +4 -4
- package/src/plugins/ask_user/ask_user.js +8 -7
- package/src/plugins/ask_user/ask_userDoc.js +29 -0
- package/src/plugins/budget/BudgetGuard.js +74 -0
- package/src/plugins/budget/README.md +43 -0
- package/src/plugins/budget/budget.js +79 -0
- package/src/plugins/cp/README.md +5 -4
- package/src/plugins/cp/cp.js +16 -12
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +12 -10
- package/src/plugins/engine/engine.sql +5 -10
- package/src/plugins/engine/turn_context.sql +13 -13
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +8 -7
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -45
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +28 -11
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/docs.md +0 -9
- package/src/plugins/hedberg/hedberg.js +4 -6
- package/src/plugins/hedberg/matcher.js +1 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +31 -33
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/index.js +93 -28
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +21 -5
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +33 -23
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +16 -12
- package/src/plugins/mv/mvDoc.js +31 -0
- package/src/plugins/persona/persona.js +78 -0
- package/src/plugins/previous/README.md +2 -2
- package/src/plugins/previous/previous.js +12 -8
- package/src/plugins/progress/progress.js +44 -12
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +23 -19
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +29 -12
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +63 -107
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +82 -21
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +8 -7
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +12 -54
- package/src/plugins/summarize/README.md +6 -5
- package/src/plugins/summarize/summarize.js +7 -6
- package/src/plugins/summarize/summarizeDoc.js +33 -0
- package/src/plugins/telemetry/telemetry.js +20 -8
- package/src/plugins/think/README.md +20 -0
- package/src/plugins/think/think.js +5 -0
- package/src/plugins/unknown/README.md +5 -5
- package/src/plugins/unknown/unknown.js +11 -8
- package/src/plugins/unknown/unknownDoc.js +31 -0
- package/src/plugins/update/README.md +3 -8
- package/src/plugins/update/update.js +7 -6
- package/src/plugins/update/updateDoc.js +33 -0
- package/src/server/ClientConnection.js +3 -5
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +31 -39
- package/src/sql/v_run_log.sql +3 -3
- package/src/agent/prompt_queue.sql +0 -39
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -2
- package/src/plugins/get/docs.md +0 -6
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -4
- package/src/plugins/set/docs.md +0 -4
- package/src/plugins/sh/docs.md +0 -2
- package/src/plugins/skills/README.md +0 -25
- package/src/plugins/store/README.md +0 -20
- package/src/plugins/store/docs.md +0 -5
- package/src/plugins/store/store.js +0 -52
- package/src/plugins/summarize/docs.md +0 -4
- package/src/plugins/unknown/docs.md +0 -5
- package/src/plugins/update/docs.md +0 -4
package/src/agent/KnownStore.js
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
import slugify from "../sql/functions/slugify.js";
|
|
2
|
+
import { countTokens } from "./tokens.js";
|
|
2
3
|
|
|
3
4
|
export default class KnownStore {
|
|
4
5
|
#db;
|
|
6
|
+
#onChanged;
|
|
7
|
+
#budgetGuard = null;
|
|
8
|
+
#schemes = new Map();
|
|
5
9
|
|
|
6
|
-
constructor(db) {
|
|
10
|
+
constructor(db, { onChanged } = {}) {
|
|
7
11
|
this.#db = db;
|
|
12
|
+
this.#onChanged = onChanged || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get budgetGuard() {
|
|
16
|
+
return this.#budgetGuard;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
set budgetGuard(guard) {
|
|
20
|
+
this.#budgetGuard = guard;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async loadSchemes(db) {
|
|
24
|
+
const rows = await (db || this.#db).get_all_schemes.all();
|
|
25
|
+
this.#schemes.clear();
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
this.#schemes.set(row.name, row);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#isVisible(path, fidelity) {
|
|
32
|
+
if (fidelity === "archive") return false;
|
|
33
|
+
const scheme = KnownStore.scheme(path) ?? "file";
|
|
34
|
+
const meta = this.#schemes.get(scheme);
|
|
35
|
+
return meta ? meta.model_visible !== 0 : true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#emitChanged(runId, path, changeType) {
|
|
39
|
+
if (this.#onChanged) this.#onChanged({ runId, path, changeType });
|
|
8
40
|
}
|
|
9
41
|
|
|
10
42
|
static scheme(path) {
|
|
@@ -30,18 +62,21 @@ export default class KnownStore {
|
|
|
30
62
|
return row.turn;
|
|
31
63
|
}
|
|
32
64
|
|
|
33
|
-
async dedup(runId, scheme, target) {
|
|
34
|
-
const
|
|
65
|
+
async dedup(runId, scheme, target, turn) {
|
|
66
|
+
const encodedTarget = encodeURIComponent(target);
|
|
67
|
+
const turnPrefix = turn ? `turn_${turn}/` : "";
|
|
68
|
+
const candidate = `${scheme}://${turnPrefix}${encodedTarget}`;
|
|
35
69
|
const existing = await this.#db.get_entry_body.get({
|
|
36
70
|
run_id: runId,
|
|
37
|
-
path:
|
|
71
|
+
path: candidate,
|
|
38
72
|
});
|
|
39
73
|
if (!existing) return candidate;
|
|
40
74
|
return `${candidate}_${Date.now()}`;
|
|
41
75
|
}
|
|
42
76
|
|
|
43
|
-
async slugPath(runId, scheme, content) {
|
|
44
|
-
const
|
|
77
|
+
async slugPath(runId, scheme, content, summary) {
|
|
78
|
+
const source = summary ? summary.replace(/,\s*/g, "/") : content || "";
|
|
79
|
+
const base = slugify(source);
|
|
45
80
|
const prefix = `${scheme}://`;
|
|
46
81
|
|
|
47
82
|
if (!base) return `${prefix}${Date.now()}`;
|
|
@@ -61,52 +96,97 @@ export default class KnownStore {
|
|
|
61
96
|
turn,
|
|
62
97
|
path,
|
|
63
98
|
body,
|
|
64
|
-
|
|
65
|
-
{
|
|
99
|
+
status,
|
|
100
|
+
{
|
|
101
|
+
fidelity = "full",
|
|
102
|
+
attributes = null,
|
|
103
|
+
hash = null,
|
|
104
|
+
updatedAt = null,
|
|
105
|
+
loopId = null,
|
|
106
|
+
} = {},
|
|
66
107
|
) {
|
|
108
|
+
const normalized = KnownStore.normalizePath(path);
|
|
109
|
+
let delta = 0;
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
this.#budgetGuard &&
|
|
113
|
+
status < 400 &&
|
|
114
|
+
this.#isVisible(normalized, fidelity)
|
|
115
|
+
) {
|
|
116
|
+
const existing = await this.#db.get_entry_body.get({
|
|
117
|
+
run_id: runId,
|
|
118
|
+
path: normalized,
|
|
119
|
+
});
|
|
120
|
+
delta =
|
|
121
|
+
countTokens(body) - (existing?.body ? countTokens(existing.body) : 0);
|
|
122
|
+
this.#budgetGuard.check(delta, normalized);
|
|
123
|
+
}
|
|
124
|
+
|
|
67
125
|
await this.#db.upsert_known_entry.run({
|
|
68
126
|
run_id: runId,
|
|
127
|
+
loop_id: loopId,
|
|
69
128
|
turn,
|
|
70
|
-
path:
|
|
129
|
+
path: normalized,
|
|
71
130
|
body,
|
|
72
|
-
|
|
131
|
+
status,
|
|
132
|
+
fidelity,
|
|
73
133
|
hash,
|
|
74
134
|
attributes: attributes ? JSON.stringify(attributes) : null,
|
|
75
135
|
updated_at: updatedAt,
|
|
76
136
|
});
|
|
137
|
+
this.#emitChanged(runId, normalized, "upsert");
|
|
138
|
+
|
|
139
|
+
if (delta > 0) this.#budgetGuard?.charge(delta);
|
|
77
140
|
}
|
|
78
141
|
|
|
79
142
|
async promote(runId, path, turn) {
|
|
143
|
+
const normalized = KnownStore.normalizePath(path);
|
|
80
144
|
await this.#db.promote_path.run({
|
|
81
145
|
run_id: runId,
|
|
82
|
-
path:
|
|
146
|
+
path: normalized,
|
|
83
147
|
turn,
|
|
84
148
|
});
|
|
149
|
+
this.#emitChanged(runId, normalized, "promote");
|
|
85
150
|
}
|
|
86
151
|
|
|
87
|
-
async
|
|
88
|
-
const result = await this.#db.
|
|
152
|
+
async setFileFidelity(runId, pattern, fidelity) {
|
|
153
|
+
const result = await this.#db.set_file_fidelity.run({
|
|
89
154
|
run_id: runId,
|
|
90
155
|
pattern,
|
|
91
|
-
|
|
156
|
+
fidelity,
|
|
92
157
|
});
|
|
93
158
|
if (result.changes === 0) {
|
|
94
|
-
await this.upsert(runId, 0, pattern, "",
|
|
159
|
+
await this.upsert(runId, 0, pattern, "", 200, { fidelity });
|
|
95
160
|
}
|
|
161
|
+
this.#emitChanged(runId, pattern, "fidelity");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async setFidelity(runId, path, fidelity) {
|
|
165
|
+
const normalized = KnownStore.normalizePath(path);
|
|
166
|
+
await this.#db.set_fidelity.run({
|
|
167
|
+
run_id: runId,
|
|
168
|
+
path: normalized,
|
|
169
|
+
fidelity,
|
|
170
|
+
});
|
|
171
|
+
this.#emitChanged(runId, normalized, "fidelity");
|
|
96
172
|
}
|
|
97
173
|
|
|
98
174
|
async demote(runId, path) {
|
|
175
|
+
const normalized = KnownStore.normalizePath(path);
|
|
99
176
|
await this.#db.demote_path.run({
|
|
100
177
|
run_id: runId,
|
|
101
|
-
path:
|
|
178
|
+
path: normalized,
|
|
102
179
|
});
|
|
180
|
+
this.#emitChanged(runId, normalized, "demote");
|
|
103
181
|
}
|
|
104
182
|
|
|
105
183
|
async remove(runId, path) {
|
|
184
|
+
const normalized = KnownStore.normalizePath(path);
|
|
106
185
|
await this.#db.delete_known_entry.run({
|
|
107
186
|
run_id: runId,
|
|
108
|
-
path:
|
|
187
|
+
path: normalized,
|
|
109
188
|
});
|
|
189
|
+
this.#emitChanged(runId, normalized, "remove");
|
|
110
190
|
}
|
|
111
191
|
|
|
112
192
|
async removeFilesByPattern(runId, pattern) {
|
|
@@ -114,6 +194,7 @@ export default class KnownStore {
|
|
|
114
194
|
run_id: runId,
|
|
115
195
|
pattern,
|
|
116
196
|
});
|
|
197
|
+
this.#emitChanged(runId, pattern, "remove");
|
|
117
198
|
}
|
|
118
199
|
|
|
119
200
|
static #bodyPattern(body) {
|
|
@@ -121,12 +202,30 @@ export default class KnownStore {
|
|
|
121
202
|
}
|
|
122
203
|
|
|
123
204
|
async promoteByPattern(runId, path, body, turn) {
|
|
205
|
+
let cost = 0;
|
|
206
|
+
if (this.#budgetGuard) {
|
|
207
|
+
const entries = await this.#db.get_entries_by_pattern.all({
|
|
208
|
+
run_id: runId,
|
|
209
|
+
path,
|
|
210
|
+
body: KnownStore.#bodyPattern(body),
|
|
211
|
+
limit: null,
|
|
212
|
+
offset: null,
|
|
213
|
+
});
|
|
214
|
+
cost = entries
|
|
215
|
+
.filter((e) => e.fidelity === "archive" || e.fidelity === "index")
|
|
216
|
+
.reduce((sum, e) => sum + (e.tokens_full || 0), 0);
|
|
217
|
+
if (cost > 0) this.#budgetGuard.check(cost, path);
|
|
218
|
+
}
|
|
219
|
+
|
|
124
220
|
await this.#db.promote_by_pattern.run({
|
|
125
221
|
run_id: runId,
|
|
126
222
|
path,
|
|
127
223
|
body: KnownStore.#bodyPattern(body),
|
|
128
224
|
turn,
|
|
129
225
|
});
|
|
226
|
+
this.#emitChanged(runId, path, "promote");
|
|
227
|
+
|
|
228
|
+
if (cost > 0) this.#budgetGuard?.charge(cost);
|
|
130
229
|
}
|
|
131
230
|
|
|
132
231
|
async demoteByPattern(runId, path, body) {
|
|
@@ -135,6 +234,7 @@ export default class KnownStore {
|
|
|
135
234
|
path,
|
|
136
235
|
body: KnownStore.#bodyPattern(body),
|
|
137
236
|
});
|
|
237
|
+
this.#emitChanged(runId, path, "demote");
|
|
138
238
|
}
|
|
139
239
|
|
|
140
240
|
async getEntriesByPattern(runId, path, body, { limit, offset } = {}) {
|
|
@@ -153,30 +253,58 @@ export default class KnownStore {
|
|
|
153
253
|
path,
|
|
154
254
|
body: KnownStore.#bodyPattern(body),
|
|
155
255
|
});
|
|
256
|
+
this.#emitChanged(runId, path, "remove");
|
|
156
257
|
}
|
|
157
258
|
|
|
158
259
|
async updateBodyByPattern(runId, path, body, newBody) {
|
|
260
|
+
let delta = 0;
|
|
261
|
+
if (this.#budgetGuard) {
|
|
262
|
+
const entries = await this.#db.get_entries_by_pattern.all({
|
|
263
|
+
run_id: runId,
|
|
264
|
+
path,
|
|
265
|
+
body: KnownStore.#bodyPattern(body),
|
|
266
|
+
limit: null,
|
|
267
|
+
offset: null,
|
|
268
|
+
});
|
|
269
|
+
const visible = entries.filter((e) =>
|
|
270
|
+
this.#isVisible(e.path, e.fidelity),
|
|
271
|
+
);
|
|
272
|
+
const oldTotal = visible.reduce((sum, e) => sum + (e.tokens || 0), 0);
|
|
273
|
+
const newTokensPer = countTokens(newBody);
|
|
274
|
+
delta = newTokensPer * visible.length - oldTotal;
|
|
275
|
+
if (delta > 0) this.#budgetGuard.check(delta, path);
|
|
276
|
+
}
|
|
277
|
+
|
|
159
278
|
await this.#db.update_body_by_pattern.run({
|
|
160
279
|
run_id: runId,
|
|
161
280
|
path,
|
|
162
281
|
body: KnownStore.#bodyPattern(body),
|
|
163
282
|
new_body: newBody,
|
|
164
283
|
});
|
|
284
|
+
this.#emitChanged(runId, path, "body");
|
|
285
|
+
|
|
286
|
+
if (delta > 0) this.#budgetGuard?.charge(delta);
|
|
165
287
|
}
|
|
166
288
|
|
|
167
|
-
async resolve(runId, path,
|
|
289
|
+
async resolve(runId, path, status, body) {
|
|
290
|
+
const normalized = KnownStore.normalizePath(path);
|
|
168
291
|
await this.#db.resolve_known_entry.run({
|
|
169
292
|
run_id: runId,
|
|
170
|
-
path:
|
|
171
|
-
|
|
293
|
+
path: normalized,
|
|
294
|
+
status,
|
|
172
295
|
body,
|
|
173
296
|
});
|
|
297
|
+
this.#emitChanged(runId, normalized, "resolve");
|
|
174
298
|
}
|
|
175
299
|
|
|
176
300
|
async getLog(runId) {
|
|
177
301
|
return this.#db.get_results.all({ run_id: runId });
|
|
178
302
|
}
|
|
179
303
|
|
|
304
|
+
async getEntries(runId) {
|
|
305
|
+
return this.#db.get_known_entries.all({ run_id: runId });
|
|
306
|
+
}
|
|
307
|
+
|
|
180
308
|
async getFileEntries(runId) {
|
|
181
309
|
return this.#db.get_file_entries.all({ run_id: runId });
|
|
182
310
|
}
|
|
@@ -185,8 +313,11 @@ export default class KnownStore {
|
|
|
185
313
|
return this.#db.get_file_states_by_pattern.all({ run_id: runId, pattern });
|
|
186
314
|
}
|
|
187
315
|
|
|
188
|
-
async hasRejections(runId) {
|
|
189
|
-
const row = await this.#db.has_rejections.get({
|
|
316
|
+
async hasRejections(runId, loopId) {
|
|
317
|
+
const row = await this.#db.has_rejections.get({
|
|
318
|
+
run_id: runId,
|
|
319
|
+
loop_id: loopId,
|
|
320
|
+
});
|
|
190
321
|
return row.count > 0;
|
|
191
322
|
}
|
|
192
323
|
|
|
@@ -218,11 +349,13 @@ export default class KnownStore {
|
|
|
218
349
|
}
|
|
219
350
|
|
|
220
351
|
async setAttributes(runId, path, attrs) {
|
|
352
|
+
const normalized = KnownStore.normalizePath(path);
|
|
221
353
|
await this.#db.update_entry_attributes.run({
|
|
222
354
|
run_id: runId,
|
|
223
|
-
path:
|
|
355
|
+
path: normalized,
|
|
224
356
|
attributes: JSON.stringify(attrs),
|
|
225
357
|
});
|
|
358
|
+
this.#emitChanged(runId, normalized, "attributes");
|
|
226
359
|
}
|
|
227
360
|
|
|
228
361
|
async getState(runId, path) {
|
|
@@ -14,7 +14,10 @@ export default class ProjectAgent {
|
|
|
14
14
|
this.#db = db;
|
|
15
15
|
this.#hooks = hooks;
|
|
16
16
|
this.#llm = new LlmProvider(db);
|
|
17
|
-
this.#knownStore = new KnownStore(db
|
|
17
|
+
this.#knownStore = new KnownStore(db, {
|
|
18
|
+
onChanged: (event) => hooks.entry.changed.emit(event).catch(() => {}),
|
|
19
|
+
});
|
|
20
|
+
this.#knownStore.loadSchemes(db);
|
|
18
21
|
|
|
19
22
|
const turnExecutor = new TurnExecutor(
|
|
20
23
|
db,
|
|
@@ -40,7 +43,7 @@ export default class ProjectAgent {
|
|
|
40
43
|
const projectRow = await this.#db.upsert_project.get({
|
|
41
44
|
name: projectName,
|
|
42
45
|
project_root: projectRoot,
|
|
43
|
-
config_path: configPath
|
|
46
|
+
config_path: configPath ?? null,
|
|
44
47
|
});
|
|
45
48
|
const projectId = projectRow.id;
|
|
46
49
|
|
|
@@ -57,8 +60,6 @@ export default class ProjectAgent {
|
|
|
57
60
|
return this.#knownStore;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
// --- Run operations ---
|
|
61
|
-
|
|
62
63
|
async ask(projectId, model, prompt, run = null, options = {}) {
|
|
63
64
|
return this.#agentLoop.run(
|
|
64
65
|
"ask",
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const MAX_STALLS = Number(process.env.RUMMY_MAX_STALLS) || 3;
|
|
2
2
|
const MAX_REPETITIONS = Number(process.env.RUMMY_MAX_REPETITIONS) || 3;
|
|
3
|
+
const MAX_UPDATE_REPEATS = Number(process.env.RUMMY_MAX_UPDATE_REPEATS) || 3;
|
|
3
4
|
|
|
4
5
|
export default class ResponseHealer {
|
|
5
6
|
#stallCount = 0;
|
|
6
7
|
#lastFingerprint = null;
|
|
7
8
|
#repetitionCount = 0;
|
|
9
|
+
#lastUpdateText = null;
|
|
10
|
+
#updateRepeatCount = 0;
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Heal a missing status tag. Called when the model emits
|
|
@@ -97,7 +100,7 @@ export default class ResponseHealer {
|
|
|
97
100
|
* neither present → warn, increment stall counter, continue
|
|
98
101
|
* stall counter hits MAX_STALLS → force-complete
|
|
99
102
|
*/
|
|
100
|
-
assessProgress({ summaryText, updateText, statusHealed }) {
|
|
103
|
+
assessProgress({ summaryText, updateText, statusHealed, flags }) {
|
|
101
104
|
if (summaryText) {
|
|
102
105
|
this.#stallCount = 0;
|
|
103
106
|
return { continue: false };
|
|
@@ -105,6 +108,21 @@ export default class ResponseHealer {
|
|
|
105
108
|
|
|
106
109
|
if (updateText && !statusHealed) {
|
|
107
110
|
this.#stallCount = 0;
|
|
111
|
+
// Track repeated update text — model stuck declaring readiness
|
|
112
|
+
// But if the model created new entries this turn, it's making
|
|
113
|
+
// progress even if the update text is the same.
|
|
114
|
+
const madeProgress = flags?.hasWrites || flags?.hasReads;
|
|
115
|
+
if (updateText === this.#lastUpdateText && !madeProgress) {
|
|
116
|
+
this.#updateRepeatCount++;
|
|
117
|
+
if (this.#updateRepeatCount >= MAX_UPDATE_REPEATS) {
|
|
118
|
+
const reason = `Same <update/> repeated ${this.#updateRepeatCount} turns: "${updateText.slice(0, 60)}"`;
|
|
119
|
+
console.warn(`[RUMMY] Stalled: ${reason}. Force-completing.`);
|
|
120
|
+
return { continue: false, reason };
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
this.#lastUpdateText = updateText;
|
|
124
|
+
this.#updateRepeatCount = 1;
|
|
125
|
+
}
|
|
108
126
|
return { continue: true };
|
|
109
127
|
}
|
|
110
128
|
|
|
@@ -130,5 +148,7 @@ export default class ResponseHealer {
|
|
|
130
148
|
this.#stallCount = 0;
|
|
131
149
|
this.#lastFingerprint = null;
|
|
132
150
|
this.#repetitionCount = 0;
|
|
151
|
+
this.#lastUpdateText = null;
|
|
152
|
+
this.#updateRepeatCount = 0;
|
|
133
153
|
}
|
|
134
154
|
}
|