@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/AgentLoop.js
CHANGED
|
@@ -32,7 +32,7 @@ export default class AgentLoop {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async #ensureRun(projectId, model, run, options) {
|
|
35
|
-
const
|
|
35
|
+
const _noRepo = options?.noRepo === true;
|
|
36
36
|
const isFork = options?.fork === true;
|
|
37
37
|
const requestedModel = model;
|
|
38
38
|
|
|
@@ -54,6 +54,11 @@ export default class AgentLoop {
|
|
|
54
54
|
new_run_id: runRow.id,
|
|
55
55
|
parent_run_id: existingRun.id,
|
|
56
56
|
});
|
|
57
|
+
await this.#hooks.run.created.emit({
|
|
58
|
+
runId: runRow.id,
|
|
59
|
+
alias,
|
|
60
|
+
forkedFrom: existingRun.id,
|
|
61
|
+
});
|
|
57
62
|
return { runId: runRow.id, alias };
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -87,6 +92,7 @@ export default class AgentLoop {
|
|
|
87
92
|
persona: options?.persona ?? null,
|
|
88
93
|
context_limit: options?.contextLimit ?? null,
|
|
89
94
|
});
|
|
95
|
+
await this.#hooks.run.created.emit({ runId: runRow.id, alias });
|
|
90
96
|
return { runId: runRow.id, alias };
|
|
91
97
|
}
|
|
92
98
|
|
|
@@ -112,14 +118,16 @@ export default class AgentLoop {
|
|
|
112
118
|
if (!project)
|
|
113
119
|
throw new Error(msg("error.project_not_found", { projectId }));
|
|
114
120
|
|
|
115
|
-
const
|
|
121
|
+
const noRepo = options?.noRepo === true;
|
|
122
|
+
const noInteraction = options?.noInteraction === true;
|
|
123
|
+
const noWeb = options?.noWeb === true;
|
|
116
124
|
const requestedModel = model;
|
|
117
125
|
|
|
118
126
|
const runInfo = await this.#ensureRun(projectId, model, run, options);
|
|
119
127
|
if (runInfo.blocked) {
|
|
120
128
|
return {
|
|
121
129
|
run: runInfo.alias,
|
|
122
|
-
status:
|
|
130
|
+
status: 202,
|
|
123
131
|
remainingCount: runInfo.proposed.length,
|
|
124
132
|
proposed: runInfo.proposed,
|
|
125
133
|
};
|
|
@@ -127,16 +135,23 @@ export default class AgentLoop {
|
|
|
127
135
|
|
|
128
136
|
const { runId: currentRunId, alias: currentAlias } = runInfo;
|
|
129
137
|
|
|
130
|
-
await this.#db.
|
|
138
|
+
const loopSeq = await this.#db.next_loop.get({ run_id: currentRunId });
|
|
139
|
+
await this.#db.enqueue_loop.get({
|
|
131
140
|
run_id: currentRunId,
|
|
141
|
+
sequence: loopSeq.sequence,
|
|
132
142
|
mode,
|
|
133
143
|
model: requestedModel,
|
|
134
144
|
prompt: prompt || "",
|
|
135
|
-
config: JSON.stringify({
|
|
145
|
+
config: JSON.stringify({
|
|
146
|
+
noRepo,
|
|
147
|
+
noInteraction,
|
|
148
|
+
noWeb,
|
|
149
|
+
temperature: options?.temperature,
|
|
150
|
+
}),
|
|
136
151
|
});
|
|
137
152
|
|
|
138
153
|
if (this.#activeRuns.has(currentRunId)) {
|
|
139
|
-
return { run: currentAlias, status:
|
|
154
|
+
return { run: currentAlias, status: 100 };
|
|
140
155
|
}
|
|
141
156
|
|
|
142
157
|
return this.#drainQueue(
|
|
@@ -149,36 +164,100 @@ export default class AgentLoop {
|
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
async #drainQueue(currentRunId, currentAlias, projectId, project, options) {
|
|
167
|
+
let panicAttempted = false;
|
|
168
|
+
|
|
152
169
|
while (true) {
|
|
153
|
-
const
|
|
170
|
+
const loop = await this.#db.claim_next_loop.get({
|
|
154
171
|
run_id: currentRunId,
|
|
155
172
|
});
|
|
156
|
-
if (!
|
|
173
|
+
if (!loop) break;
|
|
174
|
+
|
|
175
|
+
const loopConfig = loop.config ? JSON.parse(loop.config) : {};
|
|
176
|
+
const hook =
|
|
177
|
+
loop.mode === "panic"
|
|
178
|
+
? this.#hooks.panic
|
|
179
|
+
: loop.mode === "ask"
|
|
180
|
+
? this.#hooks.ask
|
|
181
|
+
: this.#hooks.act;
|
|
157
182
|
|
|
158
|
-
const promptConfig = queued.config ? JSON.parse(queued.config) : {};
|
|
159
183
|
const result = await this.#executeLoop({
|
|
160
|
-
mode:
|
|
184
|
+
mode: loop.mode,
|
|
161
185
|
project,
|
|
162
186
|
projectId,
|
|
163
187
|
currentRunId,
|
|
164
188
|
currentAlias,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
currentLoopId: loop.id,
|
|
190
|
+
requestedModel: loop.model,
|
|
191
|
+
prompt: loop.prompt,
|
|
192
|
+
noRepo: loopConfig.noRepo || false,
|
|
193
|
+
noInteraction: loopConfig.noInteraction || false,
|
|
194
|
+
noWeb: loopConfig.noWeb || false,
|
|
195
|
+
options: { ...options, temperature: loopConfig.temperature },
|
|
196
|
+
hook,
|
|
170
197
|
});
|
|
171
198
|
|
|
172
|
-
|
|
173
|
-
|
|
199
|
+
if (result.status === 413) {
|
|
200
|
+
await this.#db.complete_loop.run({
|
|
201
|
+
id: loop.id,
|
|
202
|
+
status: 413,
|
|
203
|
+
result: JSON.stringify(result),
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// One panic attempt per drain cycle
|
|
207
|
+
if (loop.mode === "panic" || panicAttempted) {
|
|
208
|
+
return {
|
|
209
|
+
run: currentAlias,
|
|
210
|
+
status: 413,
|
|
211
|
+
error:
|
|
212
|
+
loop.mode === "panic"
|
|
213
|
+
? `Panic mode failed to free enough space (${result.overflow} tokens over).`
|
|
214
|
+
: `Context full (${result.overflow} tokens over).`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
panicAttempted = true;
|
|
219
|
+
|
|
220
|
+
const panicPrompt = this.#hooks.budget.panicPrompt({
|
|
221
|
+
assembledTokens: result.assembledTokens,
|
|
222
|
+
contextSize: result.contextSize,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Enqueue panic loop
|
|
226
|
+
const panicSeq = await this.#db.next_loop.get({ run_id: currentRunId });
|
|
227
|
+
await this.#db.enqueue_loop.get({
|
|
228
|
+
run_id: currentRunId,
|
|
229
|
+
sequence: panicSeq.sequence,
|
|
230
|
+
mode: "panic",
|
|
231
|
+
model: loop.model,
|
|
232
|
+
prompt: panicPrompt,
|
|
233
|
+
config: JSON.stringify({ noRepo: true }),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Re-enqueue the original loop to retry after panic
|
|
237
|
+
const retrySeq = await this.#db.next_loop.get({ run_id: currentRunId });
|
|
238
|
+
await this.#db.enqueue_loop.get({
|
|
239
|
+
run_id: currentRunId,
|
|
240
|
+
sequence: retrySeq.sequence,
|
|
241
|
+
mode: loop.mode,
|
|
242
|
+
model: loop.model,
|
|
243
|
+
prompt: loop.prompt,
|
|
244
|
+
config: loop.config,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
await this.#db.complete_loop.run({
|
|
251
|
+
id: loop.id,
|
|
252
|
+
status: result.status === 202 ? 202 : result.status,
|
|
174
253
|
result: JSON.stringify(result),
|
|
175
254
|
});
|
|
176
255
|
|
|
177
|
-
if (result.status ===
|
|
256
|
+
if (result.status === 202) return result;
|
|
178
257
|
}
|
|
179
258
|
|
|
180
259
|
const runRow = await this.#db.get_run_by_alias.get({ alias: currentAlias });
|
|
181
|
-
return { run: currentAlias, status: runRow?.status
|
|
260
|
+
return { run: currentAlias, status: runRow?.status ?? 200 };
|
|
182
261
|
}
|
|
183
262
|
|
|
184
263
|
async #executeLoop({
|
|
@@ -187,17 +266,20 @@ export default class AgentLoop {
|
|
|
187
266
|
projectId,
|
|
188
267
|
currentRunId,
|
|
189
268
|
currentAlias,
|
|
269
|
+
currentLoopId,
|
|
190
270
|
requestedModel,
|
|
191
271
|
prompt,
|
|
192
|
-
|
|
272
|
+
noRepo,
|
|
273
|
+
noInteraction,
|
|
274
|
+
noWeb,
|
|
193
275
|
options,
|
|
194
276
|
hook,
|
|
195
277
|
}) {
|
|
196
278
|
const runRow = await this.#db.get_run_by_id.get({ id: currentRunId });
|
|
197
|
-
if (runRow.status !==
|
|
279
|
+
if (runRow.status !== 102) {
|
|
198
280
|
await this.#db.update_run_status.run({
|
|
199
281
|
id: currentRunId,
|
|
200
|
-
status:
|
|
282
|
+
status: 102,
|
|
201
283
|
});
|
|
202
284
|
}
|
|
203
285
|
|
|
@@ -207,6 +289,11 @@ export default class AgentLoop {
|
|
|
207
289
|
? Math.min(runRow.context_limit, modelContextSize)
|
|
208
290
|
: modelContextSize;
|
|
209
291
|
|
|
292
|
+
const toolSet = this.#hooks.tools.resolveForLoop(mode, {
|
|
293
|
+
noInteraction,
|
|
294
|
+
noWeb,
|
|
295
|
+
});
|
|
296
|
+
|
|
210
297
|
let loopIteration = 0;
|
|
211
298
|
const MAX_LOOP_ITERATIONS = Number(process.env.RUMMY_MAX_TURNS) || 15;
|
|
212
299
|
const healer = new ResponseHealer();
|
|
@@ -214,16 +301,27 @@ export default class AgentLoop {
|
|
|
214
301
|
const controller = new AbortController();
|
|
215
302
|
this.#activeRuns.set(currentRunId, controller);
|
|
216
303
|
|
|
304
|
+
let _lastAssembledTokens = 0;
|
|
305
|
+
let _panicStrikes = 0;
|
|
306
|
+
let _lastPanicTokens = null;
|
|
307
|
+
|
|
308
|
+
await this.#hooks.loop.started.emit({
|
|
309
|
+
runId: currentRunId,
|
|
310
|
+
loopId: currentLoopId,
|
|
311
|
+
mode,
|
|
312
|
+
prompt,
|
|
313
|
+
});
|
|
314
|
+
|
|
217
315
|
try {
|
|
218
316
|
while (loopIteration < MAX_LOOP_ITERATIONS) {
|
|
219
317
|
if (controller.signal.aborted) {
|
|
220
318
|
await this.#db.update_run_status.run({
|
|
221
319
|
id: currentRunId,
|
|
222
|
-
status:
|
|
320
|
+
status: 499,
|
|
223
321
|
});
|
|
224
322
|
const out = {
|
|
225
323
|
run: currentAlias,
|
|
226
|
-
status:
|
|
324
|
+
status: 499,
|
|
227
325
|
turn: loopIteration,
|
|
228
326
|
};
|
|
229
327
|
await hook.completed.emit({ projectId, ...out });
|
|
@@ -234,6 +332,8 @@ export default class AgentLoop {
|
|
|
234
332
|
let turnPrompt;
|
|
235
333
|
if (loopIteration === 1) {
|
|
236
334
|
turnPrompt = prompt;
|
|
335
|
+
} else if (mode === "panic") {
|
|
336
|
+
turnPrompt = "Continue freeing space. Check <knowns> token counts.";
|
|
237
337
|
} else {
|
|
238
338
|
turnPrompt = this.#buildContinuationPrompt(
|
|
239
339
|
loopIteration,
|
|
@@ -247,14 +347,70 @@ export default class AgentLoop {
|
|
|
247
347
|
projectId,
|
|
248
348
|
currentRunId,
|
|
249
349
|
currentAlias,
|
|
350
|
+
currentLoopId,
|
|
250
351
|
requestedModel,
|
|
251
352
|
loopPrompt: turnPrompt,
|
|
252
|
-
|
|
353
|
+
noRepo,
|
|
354
|
+
toolSet,
|
|
253
355
|
contextSize,
|
|
254
356
|
options: { ...options, isContinuation: loopIteration > 1 },
|
|
255
357
|
signal: controller.signal,
|
|
256
358
|
});
|
|
257
359
|
|
|
360
|
+
// Budget overflow — return 413 to drainQueue for panic mode
|
|
361
|
+
if (result.status === 413) {
|
|
362
|
+
return {
|
|
363
|
+
run: currentAlias,
|
|
364
|
+
status: 413,
|
|
365
|
+
overflow: result.overflow,
|
|
366
|
+
assembledTokens: result.assembledTokens,
|
|
367
|
+
contextSize: result.contextSize,
|
|
368
|
+
turn: result.turn,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
_lastAssembledTokens = result.assembledTokens;
|
|
373
|
+
|
|
374
|
+
// Panic mode: target check + strike counting
|
|
375
|
+
if (mode === "panic") {
|
|
376
|
+
const panicTarget = Math.floor(contextSize * 0.75);
|
|
377
|
+
if (result.assembledTokens <= panicTarget) {
|
|
378
|
+
await this.#db.update_run_status.run({
|
|
379
|
+
id: currentRunId,
|
|
380
|
+
status: 200,
|
|
381
|
+
});
|
|
382
|
+
const out = {
|
|
383
|
+
run: currentAlias,
|
|
384
|
+
status: 200,
|
|
385
|
+
turn: result.turn,
|
|
386
|
+
};
|
|
387
|
+
await hook.completed.emit({ projectId, ...out });
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
if (_lastPanicTokens !== null) {
|
|
391
|
+
if (result.assembledTokens < _lastPanicTokens) {
|
|
392
|
+
_panicStrikes = 0;
|
|
393
|
+
} else {
|
|
394
|
+
_panicStrikes++;
|
|
395
|
+
if (_panicStrikes >= 3) {
|
|
396
|
+
await this.#db.update_run_status.run({
|
|
397
|
+
id: currentRunId,
|
|
398
|
+
status: 200,
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
run: currentAlias,
|
|
402
|
+
status: 413,
|
|
403
|
+
overflow: result.assembledTokens - contextSize,
|
|
404
|
+
assembledTokens: result.assembledTokens,
|
|
405
|
+
contextSize,
|
|
406
|
+
turn: result.turn,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
_lastPanicTokens = result.assembledTokens;
|
|
412
|
+
}
|
|
413
|
+
|
|
258
414
|
const runUsage = await this.#db.get_run_usage.get({
|
|
259
415
|
run_id: currentRunId,
|
|
260
416
|
});
|
|
@@ -265,14 +421,14 @@ export default class AgentLoop {
|
|
|
265
421
|
const unresolved = await this.#knownStore.getUnresolved(currentRunId);
|
|
266
422
|
|
|
267
423
|
const latestSummary = history
|
|
268
|
-
.filter((e) => e.status === "
|
|
424
|
+
.filter((e) => e.status === 200 && e.path?.startsWith("summarize://"))
|
|
269
425
|
.at(-1);
|
|
270
426
|
|
|
271
427
|
await this.#hooks.run.state.emit({
|
|
272
428
|
projectId,
|
|
273
429
|
run: currentAlias,
|
|
274
430
|
turn: result.turn,
|
|
275
|
-
status: unresolved.length > 0 ?
|
|
431
|
+
status: unresolved.length > 0 ? 202 : 102,
|
|
276
432
|
summary: latestSummary?.body || "",
|
|
277
433
|
history,
|
|
278
434
|
unknowns: unknowns.map((u) => ({ path: u.path, body: u.body })),
|
|
@@ -286,6 +442,12 @@ export default class AgentLoop {
|
|
|
286
442
|
model: result.model,
|
|
287
443
|
temperature: result.temperature,
|
|
288
444
|
context_size: result.contextSize,
|
|
445
|
+
context_tokens: (
|
|
446
|
+
await this.#db.get_turn_budget.get({
|
|
447
|
+
run_id: currentRunId,
|
|
448
|
+
turn: result.turn,
|
|
449
|
+
})
|
|
450
|
+
).total,
|
|
289
451
|
prompt_tokens: runUsage.prompt_tokens,
|
|
290
452
|
cached_tokens: runUsage.cached_tokens,
|
|
291
453
|
completion_tokens: runUsage.completion_tokens,
|
|
@@ -301,11 +463,11 @@ export default class AgentLoop {
|
|
|
301
463
|
if (unresolved.length > 0) {
|
|
302
464
|
await this.#db.update_run_status.run({
|
|
303
465
|
id: currentRunId,
|
|
304
|
-
status:
|
|
466
|
+
status: 202,
|
|
305
467
|
});
|
|
306
468
|
const out = {
|
|
307
469
|
run: currentAlias,
|
|
308
|
-
status:
|
|
470
|
+
status: 202,
|
|
309
471
|
turn: result.turn,
|
|
310
472
|
proposed: unresolved,
|
|
311
473
|
};
|
|
@@ -324,11 +486,11 @@ export default class AgentLoop {
|
|
|
324
486
|
if (!repetition.continue) {
|
|
325
487
|
await this.#db.update_run_status.run({
|
|
326
488
|
id: currentRunId,
|
|
327
|
-
status:
|
|
489
|
+
status: 200,
|
|
328
490
|
});
|
|
329
491
|
const out = {
|
|
330
492
|
run: currentAlias,
|
|
331
|
-
status:
|
|
493
|
+
status: 200,
|
|
332
494
|
turn: result.turn,
|
|
333
495
|
reason: repetition.reason,
|
|
334
496
|
};
|
|
@@ -341,11 +503,11 @@ export default class AgentLoop {
|
|
|
341
503
|
|
|
342
504
|
await this.#db.update_run_status.run({
|
|
343
505
|
id: currentRunId,
|
|
344
|
-
status:
|
|
506
|
+
status: 200,
|
|
345
507
|
});
|
|
346
508
|
const out = {
|
|
347
509
|
run: currentAlias,
|
|
348
|
-
status:
|
|
510
|
+
status: 200,
|
|
349
511
|
turn: result.turn,
|
|
350
512
|
};
|
|
351
513
|
await hook.completed.emit({ projectId, ...out });
|
|
@@ -354,11 +516,11 @@ export default class AgentLoop {
|
|
|
354
516
|
|
|
355
517
|
await this.#db.update_run_status.run({
|
|
356
518
|
id: currentRunId,
|
|
357
|
-
status:
|
|
519
|
+
status: 200,
|
|
358
520
|
});
|
|
359
521
|
const out = {
|
|
360
522
|
run: currentAlias,
|
|
361
|
-
status:
|
|
523
|
+
status: 200,
|
|
362
524
|
turn: loopIteration,
|
|
363
525
|
};
|
|
364
526
|
await hook.completed.emit({ projectId, ...out });
|
|
@@ -367,15 +529,15 @@ export default class AgentLoop {
|
|
|
367
529
|
if (controller.signal.aborted) {
|
|
368
530
|
await this.#db.update_run_status.run({
|
|
369
531
|
id: currentRunId,
|
|
370
|
-
status:
|
|
532
|
+
status: 499,
|
|
371
533
|
});
|
|
372
|
-
return { run: currentAlias, status:
|
|
534
|
+
return { run: currentAlias, status: 499, turn: loopIteration };
|
|
373
535
|
}
|
|
374
536
|
console.warn(`[RUMMY] Run failed: ${err.message}`);
|
|
375
537
|
console.warn(`[RUMMY] Stack: ${err.stack}`);
|
|
376
538
|
await this.#db.update_run_status.run({
|
|
377
539
|
id: currentRunId,
|
|
378
|
-
status:
|
|
540
|
+
status: 500,
|
|
379
541
|
});
|
|
380
542
|
try {
|
|
381
543
|
await this.#knownStore.upsert(
|
|
@@ -383,12 +545,13 @@ export default class AgentLoop {
|
|
|
383
545
|
loopIteration,
|
|
384
546
|
`error://${loopIteration}`,
|
|
385
547
|
`${err.message}\n${err.stack}`,
|
|
386
|
-
|
|
548
|
+
500,
|
|
549
|
+
{ loopId: currentLoopId },
|
|
387
550
|
);
|
|
388
551
|
} catch {}
|
|
389
552
|
const out = {
|
|
390
553
|
run: currentAlias,
|
|
391
|
-
status:
|
|
554
|
+
status: 500,
|
|
392
555
|
turn: loopIteration,
|
|
393
556
|
error: err.message,
|
|
394
557
|
};
|
|
@@ -396,6 +559,14 @@ export default class AgentLoop {
|
|
|
396
559
|
return out;
|
|
397
560
|
} finally {
|
|
398
561
|
this.#activeRuns.delete(currentRunId);
|
|
562
|
+
await this.#hooks.loop.completed
|
|
563
|
+
.emit({
|
|
564
|
+
runId: currentRunId,
|
|
565
|
+
loopId: currentLoopId,
|
|
566
|
+
mode,
|
|
567
|
+
turns: loopIteration,
|
|
568
|
+
})
|
|
569
|
+
.catch(() => {});
|
|
399
570
|
}
|
|
400
571
|
}
|
|
401
572
|
|
|
@@ -415,14 +586,14 @@ export default class AgentLoop {
|
|
|
415
586
|
attrs,
|
|
416
587
|
output,
|
|
417
588
|
);
|
|
418
|
-
const
|
|
419
|
-
await this.#knownStore.resolve(runId, path,
|
|
589
|
+
const status = action === "error" ? 500 : 200;
|
|
590
|
+
await this.#knownStore.resolve(runId, path, status, resolvedBody);
|
|
420
591
|
|
|
421
592
|
// Store answer in attributes for ask_user
|
|
422
593
|
if (path.startsWith("ask_user://") && output) {
|
|
423
594
|
const turn = (await this.#db.get_run_by_id.get({ id: runId }))
|
|
424
595
|
.next_turn;
|
|
425
|
-
await this.#knownStore.upsert(runId, turn, path, resolvedBody,
|
|
596
|
+
await this.#knownStore.upsert(runId, turn, path, resolvedBody, status, {
|
|
426
597
|
attributes: { ...attrs, answer: output },
|
|
427
598
|
});
|
|
428
599
|
}
|
|
@@ -441,12 +612,7 @@ export default class AgentLoop {
|
|
|
441
612
|
}
|
|
442
613
|
}
|
|
443
614
|
} else if (action === "reject") {
|
|
444
|
-
await this.#knownStore.resolve(
|
|
445
|
-
runId,
|
|
446
|
-
path,
|
|
447
|
-
"rejected",
|
|
448
|
-
output || "rejected",
|
|
449
|
-
);
|
|
615
|
+
await this.#knownStore.resolve(runId, path, 403, output || "rejected");
|
|
450
616
|
} else {
|
|
451
617
|
throw new Error(msg("error.resolution_invalid", { action }));
|
|
452
618
|
}
|
|
@@ -455,23 +621,46 @@ export default class AgentLoop {
|
|
|
455
621
|
if (unresolved.length > 0) {
|
|
456
622
|
return {
|
|
457
623
|
run: runAlias,
|
|
458
|
-
status:
|
|
624
|
+
status: 202,
|
|
459
625
|
remainingCount: unresolved.length,
|
|
460
626
|
proposed: unresolved,
|
|
461
627
|
};
|
|
462
628
|
}
|
|
463
629
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
630
|
+
// Scope completion checks to the current loop
|
|
631
|
+
const currentLoop = await this.#db.get_current_loop.get({ run_id: runId });
|
|
632
|
+
const loopId = currentLoop?.id ?? null;
|
|
633
|
+
|
|
634
|
+
if (await this.#knownStore.hasRejections(runId, loopId)) {
|
|
635
|
+
if (currentLoop)
|
|
636
|
+
await this.#db.complete_loop.run({
|
|
637
|
+
id: loopId,
|
|
638
|
+
status: 200,
|
|
639
|
+
result: null,
|
|
640
|
+
});
|
|
641
|
+
await this.#db.update_run_status.run({ id: runId, status: 200 });
|
|
642
|
+
return { run: runAlias, status: 200 };
|
|
467
643
|
}
|
|
468
644
|
|
|
469
|
-
const hasSummary = await this.#db.get_latest_summary.get({
|
|
645
|
+
const hasSummary = await this.#db.get_latest_summary.get({
|
|
646
|
+
run_id: runId,
|
|
647
|
+
loop_id: loopId,
|
|
648
|
+
});
|
|
470
649
|
if (hasSummary?.body) {
|
|
471
|
-
|
|
472
|
-
|
|
650
|
+
if (currentLoop)
|
|
651
|
+
await this.#db.complete_loop.run({
|
|
652
|
+
id: loopId,
|
|
653
|
+
status: 200,
|
|
654
|
+
result: null,
|
|
655
|
+
});
|
|
656
|
+
await this.#db.update_run_status.run({ id: runId, status: 200 });
|
|
657
|
+
return { run: runAlias, status: 200 };
|
|
473
658
|
}
|
|
474
659
|
|
|
660
|
+
// No summary and no rejections in this loop — resume it
|
|
661
|
+
const projectId = runRow.project_id;
|
|
662
|
+
const project = await this.#db.get_project_by_id.get({ id: projectId });
|
|
663
|
+
|
|
475
664
|
const latestPrompt = await this.#db.get_latest_prompt.get({
|
|
476
665
|
run_id: runId,
|
|
477
666
|
});
|
|
@@ -479,11 +668,11 @@ export default class AgentLoop {
|
|
|
479
668
|
? JSON.parse(latestPrompt.attributes).mode
|
|
480
669
|
: "ask";
|
|
481
670
|
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
await this.#db.enqueue_prompt.get({
|
|
671
|
+
// Re-enqueue the current loop's prompt to continue it
|
|
672
|
+
const loopSeq = await this.#db.next_loop.get({ run_id: runId });
|
|
673
|
+
await this.#db.enqueue_loop.get({
|
|
486
674
|
run_id: runId,
|
|
675
|
+
sequence: loopSeq.sequence,
|
|
487
676
|
mode: resumeMode,
|
|
488
677
|
model: runRow.model,
|
|
489
678
|
prompt: "",
|
|
@@ -515,24 +704,19 @@ export default class AgentLoop {
|
|
|
515
704
|
runRow.id,
|
|
516
705
|
nextTurn,
|
|
517
706
|
`prompt://${nextTurn}`,
|
|
518
|
-
"",
|
|
519
|
-
"info",
|
|
520
|
-
{ attributes: { mode: "ask" } },
|
|
521
|
-
);
|
|
522
|
-
await this.#knownStore.upsert(
|
|
523
|
-
runRow.id,
|
|
524
|
-
nextTurn,
|
|
525
|
-
`ask://${nextTurn}`,
|
|
526
707
|
message,
|
|
527
|
-
|
|
708
|
+
200,
|
|
709
|
+
{ attributes: { mode: "ask" } },
|
|
528
710
|
);
|
|
529
711
|
|
|
530
712
|
if (this.#activeRuns.has(runRow.id)) {
|
|
531
713
|
return { run: runAlias, status: runRow.status, injected: "next_turn" };
|
|
532
714
|
}
|
|
533
715
|
|
|
534
|
-
await this.#db.
|
|
716
|
+
const injectLoopSeq = await this.#db.next_loop.get({ run_id: runRow.id });
|
|
717
|
+
await this.#db.enqueue_loop.get({
|
|
535
718
|
run_id: runRow.id,
|
|
719
|
+
sequence: injectLoopSeq.sequence,
|
|
536
720
|
mode: "ask",
|
|
537
721
|
model: runRow.model,
|
|
538
722
|
prompt: message,
|
|
@@ -6,17 +6,31 @@
|
|
|
6
6
|
export default class ContextAssembler {
|
|
7
7
|
static async assembleFromTurnContext(
|
|
8
8
|
rows,
|
|
9
|
-
{
|
|
9
|
+
{
|
|
10
|
+
type = "ask",
|
|
11
|
+
systemPrompt = "",
|
|
12
|
+
contextSize = 0,
|
|
13
|
+
demoted = [],
|
|
14
|
+
toolSet = null,
|
|
15
|
+
lastContextTokens = 0,
|
|
16
|
+
} = {},
|
|
10
17
|
hooks,
|
|
11
18
|
) {
|
|
12
19
|
// Find loop boundary from active prompt
|
|
13
20
|
const promptEntry = rows.findLast(
|
|
14
|
-
(r) =>
|
|
15
|
-
r.category === "prompt" && (r.scheme === "ask" || r.scheme === "act"),
|
|
21
|
+
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
16
22
|
);
|
|
17
23
|
const loopStartTurn = promptEntry?.source_turn ?? 0;
|
|
18
24
|
|
|
19
|
-
const ctx = {
|
|
25
|
+
const ctx = {
|
|
26
|
+
rows,
|
|
27
|
+
loopStartTurn,
|
|
28
|
+
type,
|
|
29
|
+
contextSize,
|
|
30
|
+
lastContextTokens,
|
|
31
|
+
demoted,
|
|
32
|
+
toolSet,
|
|
33
|
+
};
|
|
20
34
|
|
|
21
35
|
const system = await hooks.assembly.system.filter(systemPrompt, ctx);
|
|
22
36
|
const user = await hooks.assembly.user.filter("", ctx);
|