@dudousxd/nestjs-agent-opencode 0.4.0 → 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/README.md +10 -3
- package/dist/durable/index.cjs +272 -13
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +3 -2
- package/dist/durable/index.d.ts +3 -2
- package/dist/durable/index.js +273 -14
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-BNNxQAFV.d.cts → engine-BX6MLzxs.d.cts} +102 -8
- package/dist/{engine-BNNxQAFV.d.ts → engine-BX6MLzxs.d.ts} +102 -8
- package/dist/index.cjs +268 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +268 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -94,8 +94,9 @@ the dashboard and `GET /quota` see an OpenCode turn like any other.
|
|
|
94
94
|
the turn's last message) is added up from the turn's own calls, not read back from the store: each
|
|
95
95
|
milestone carries what was spent since the last one, and a durable run journals it with the
|
|
96
96
|
milestone, so a run resumed in another process (an approval answered after a restart) still reports
|
|
97
|
-
the whole run, cache tokens included. A process that dies while OpenCode works
|
|
98
|
-
saw
|
|
97
|
+
the whole run, cache tokens included. A process that dies while OpenCode works does not lose the
|
|
98
|
+
calls it saw: the `observe` re-run reads each step's usage back from the session's messages (see
|
|
99
|
+
below).
|
|
99
100
|
|
|
100
101
|
## Durable turns
|
|
101
102
|
|
|
@@ -109,7 +110,13 @@ AgentModule.forRoot({ engine: openCodeDurable({ host: MyOpenCodeHost, sessions:
|
|
|
109
110
|
Every step of a turn is checkpointed (`begin → prompt → observe → [wait for a person → reply →
|
|
110
111
|
observe]* → finish`) and a person's decision is a durable signal: a turn waiting on an approval
|
|
111
112
|
survives restarts and is resumed by whichever process gets the decision. If OpenCode restarted
|
|
112
|
-
meanwhile, a new session is opened with the conversation and the decision.
|
|
113
|
+
meanwhile, a new session is opened with the conversation and the decision. If the API restarted
|
|
114
|
+
while OpenCode worked (its `observe` step re-runs), the rebuilt turn catches up on what OpenCode
|
|
115
|
+
asked meanwhile and, when the execution ends, on what it said: the text, tool calls and results and
|
|
116
|
+
each step's usage are read back from the session's messages after the turn's prompt (the prompt
|
|
117
|
+
carries the run id in its `metadata`), less what was already taken before the restart (journaled
|
|
118
|
+
with each ask). A session that left no answer at all fails the turn (`LOST_ANSWER_ERROR`) instead
|
|
119
|
+
of settling it as an empty success. It needs the client's `message.list`. `openCode()` runs the
|
|
113
120
|
same steps in memory (single replica). Several processes need a cross-process sink and a persistent
|
|
114
121
|
`OpenCodeSessionStore`.
|
|
115
122
|
|
package/dist/durable/index.cjs
CHANGED
|
@@ -285,6 +285,9 @@ function subUsage(a, b) {
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
__name(subUsage, "subUsage");
|
|
288
|
+
var LOST_ANSWER_ERROR = "The answer was lost while the agent restarted; ask again.";
|
|
289
|
+
var RECOVER_PAGES = 10;
|
|
290
|
+
var RECOVER_PAGE_SIZE = 100;
|
|
288
291
|
var ASK_TOOL_NAME = "ask";
|
|
289
292
|
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
290
293
|
"error",
|
|
@@ -373,6 +376,14 @@ var OpenCodeTurn = class {
|
|
|
373
376
|
wroteMessage = false;
|
|
374
377
|
ended = false;
|
|
375
378
|
stepError;
|
|
379
|
+
/** What this turn object took from the session's assistant messages (see {@link TurnProgress}). */
|
|
380
|
+
took = {
|
|
381
|
+
text: /* @__PURE__ */ new Map(),
|
|
382
|
+
reasoning: /* @__PURE__ */ new Map(),
|
|
383
|
+
called: /* @__PURE__ */ new Set(),
|
|
384
|
+
settled: /* @__PURE__ */ new Set(),
|
|
385
|
+
steps: /* @__PURE__ */ new Set()
|
|
386
|
+
};
|
|
376
387
|
constructor(a) {
|
|
377
388
|
this.a = a;
|
|
378
389
|
this.model = modelOfLabel(a.input.model);
|
|
@@ -390,6 +401,30 @@ var OpenCodeTurn = class {
|
|
|
390
401
|
tokens: info.tokens
|
|
391
402
|
};
|
|
392
403
|
}
|
|
404
|
+
/** What the turn took so far: carried from before this turn object, plus what it took. */
|
|
405
|
+
progress() {
|
|
406
|
+
const before = this.a.progress;
|
|
407
|
+
const merged = /* @__PURE__ */ __name((carried, own) => {
|
|
408
|
+
const out = {
|
|
409
|
+
...carried ?? {}
|
|
410
|
+
};
|
|
411
|
+
for (const [id, n] of own) out[id] = (out[id] ?? 0) + n;
|
|
412
|
+
return out;
|
|
413
|
+
}, "merged");
|
|
414
|
+
const union = /* @__PURE__ */ __name((carried, own) => [
|
|
415
|
+
.../* @__PURE__ */ new Set([
|
|
416
|
+
...carried ?? [],
|
|
417
|
+
...own
|
|
418
|
+
])
|
|
419
|
+
], "union");
|
|
420
|
+
return {
|
|
421
|
+
text: merged(before?.text, this.took.text),
|
|
422
|
+
reasoning: merged(before?.reasoning, this.took.reasoning),
|
|
423
|
+
called: union(before?.called, this.took.called),
|
|
424
|
+
settled: union(before?.settled, this.took.settled),
|
|
425
|
+
steps: union(before?.steps, this.took.steps)
|
|
426
|
+
};
|
|
427
|
+
}
|
|
393
428
|
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
394
429
|
totalUsage() {
|
|
395
430
|
return addUsage(this.a.carried, this.spent);
|
|
@@ -480,7 +515,11 @@ var OpenCodeTurn = class {
|
|
|
480
515
|
this.reported = {
|
|
481
516
|
...this.spent
|
|
482
517
|
};
|
|
483
|
-
const milestone = {
|
|
518
|
+
const milestone = reached.kind === "ask" ? {
|
|
519
|
+
...reached,
|
|
520
|
+
usage,
|
|
521
|
+
progress: this.progress()
|
|
522
|
+
} : {
|
|
484
523
|
...reached,
|
|
485
524
|
usage
|
|
486
525
|
};
|
|
@@ -535,6 +574,7 @@ var OpenCodeTurn = class {
|
|
|
535
574
|
innerSettled: /* @__PURE__ */ new Set()
|
|
536
575
|
};
|
|
537
576
|
this.tools.set(id, state);
|
|
577
|
+
this.took.called.add(id);
|
|
538
578
|
this.segment.calls.push({
|
|
539
579
|
id,
|
|
540
580
|
name,
|
|
@@ -662,6 +702,7 @@ var OpenCodeTurn = class {
|
|
|
662
702
|
case "session.text.delta": {
|
|
663
703
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
664
704
|
if (!delta) return;
|
|
705
|
+
this.count(this.took.text, d.assistantMessageID, delta.length);
|
|
665
706
|
this.endReasoning();
|
|
666
707
|
if (!this.sawText && !delta.trim()) return;
|
|
667
708
|
await this.step();
|
|
@@ -683,6 +724,7 @@ ${delta}` : delta;
|
|
|
683
724
|
case "session.reasoning.delta": {
|
|
684
725
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
685
726
|
if (!delta) return;
|
|
727
|
+
this.count(this.took.reasoning, d.assistantMessageID, delta.length);
|
|
686
728
|
this.reasoningSince ??= Date.now();
|
|
687
729
|
await this.step();
|
|
688
730
|
this.segment.reasoning += delta;
|
|
@@ -737,6 +779,7 @@ ${delta}` : delta;
|
|
|
737
779
|
}
|
|
738
780
|
case "session.step.ended":
|
|
739
781
|
this.endReasoning();
|
|
782
|
+
if (typeof d.assistantMessageID === "string") this.took.steps.add(d.assistantMessageID);
|
|
740
783
|
await this.stepEnded(d);
|
|
741
784
|
return;
|
|
742
785
|
case "session.step.failed":
|
|
@@ -777,10 +820,16 @@ ${delta}` : delta;
|
|
|
777
820
|
return;
|
|
778
821
|
}
|
|
779
822
|
}
|
|
823
|
+
/** Characters taken from an assistant message's text or reasoning. */
|
|
824
|
+
count(into, messageId, n) {
|
|
825
|
+
if (typeof messageId !== "string") return;
|
|
826
|
+
into.set(messageId, (into.get(messageId) ?? 0) + n);
|
|
827
|
+
}
|
|
780
828
|
async settleTool(id, ok, error = "failed") {
|
|
781
829
|
const state = this.tool(id);
|
|
782
830
|
if (state.settled) return;
|
|
783
831
|
state.settled = true;
|
|
832
|
+
this.took.settled.add(id);
|
|
784
833
|
if (ok) {
|
|
785
834
|
this.segment.results.push({
|
|
786
835
|
id,
|
|
@@ -1330,9 +1379,171 @@ ${delta}` : delta;
|
|
|
1330
1379
|
cost: cost < 1e-9 ? 0 : cost
|
|
1331
1380
|
}, this.compacted ? "history_summary" : "title");
|
|
1332
1381
|
}
|
|
1333
|
-
|
|
1382
|
+
/**
|
|
1383
|
+
* The session's messages since this turn's prompt, oldest first: everything after its newest user
|
|
1384
|
+
* message. `undefined` when they cannot be read (no `message.list`, no prompt within reach); empty
|
|
1385
|
+
* when the newest prompt is another run's (this turn's never reached the session).
|
|
1386
|
+
*/
|
|
1387
|
+
async messagesSincePrompt() {
|
|
1388
|
+
const messages = this.a.client.message;
|
|
1389
|
+
if (messages === void 0) return void 0;
|
|
1390
|
+
const since = [];
|
|
1391
|
+
let cursor;
|
|
1392
|
+
for (let page = 0; page < RECOVER_PAGES; page += 1) {
|
|
1393
|
+
const listed = await messages.list({
|
|
1394
|
+
sessionID: this.a.sessionId,
|
|
1395
|
+
order: "desc",
|
|
1396
|
+
limit: RECOVER_PAGE_SIZE,
|
|
1397
|
+
...cursor !== void 0 ? {
|
|
1398
|
+
cursor
|
|
1399
|
+
} : {}
|
|
1400
|
+
});
|
|
1401
|
+
for (const message of listed.data) {
|
|
1402
|
+
if (message.type === "user") {
|
|
1403
|
+
const runId = message.metadata?.runId;
|
|
1404
|
+
if (typeof runId === "string" && runId !== this.a.runId) return [];
|
|
1405
|
+
return since.reverse();
|
|
1406
|
+
}
|
|
1407
|
+
since.push(message);
|
|
1408
|
+
}
|
|
1409
|
+
const next = listed.cursor?.next;
|
|
1410
|
+
if (typeof next !== "string" || next.length === 0 || listed.data.length === 0) break;
|
|
1411
|
+
cursor = next;
|
|
1412
|
+
}
|
|
1413
|
+
return void 0;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* A turn rebuilt after a restart heard only what the session said once it re-attached: what it
|
|
1417
|
+
* said in between — read back from the session's messages since the prompt, less what an earlier
|
|
1418
|
+
* process already took ({@link TurnProgress}) and what this one heard — is taken now, as its
|
|
1419
|
+
* events would have been: text, reasoning, tool calls and results, each step's usage. A session
|
|
1420
|
+
* that left nothing for the turn fails it ({@link LOST_ANSWER_ERROR}) rather than reading as an
|
|
1421
|
+
* empty answer.
|
|
1422
|
+
*/
|
|
1423
|
+
async recover(outcome) {
|
|
1424
|
+
const messages = await this.messagesSincePrompt().catch((error) => {
|
|
1425
|
+
this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session messages")}`);
|
|
1426
|
+
return void 0;
|
|
1427
|
+
});
|
|
1428
|
+
const before = this.a.progress;
|
|
1429
|
+
const text = [];
|
|
1430
|
+
let reasoning = "";
|
|
1431
|
+
let lastError;
|
|
1432
|
+
for (const message of messages ?? []) {
|
|
1433
|
+
if (message.type !== "assistant") continue;
|
|
1434
|
+
const id = message.id;
|
|
1435
|
+
const parts = message.content ?? [];
|
|
1436
|
+
text.push(...missed(parts, "text", before?.text[id] ?? 0, this.took.text.get(id) ?? 0));
|
|
1437
|
+
reasoning += missed(parts, "reasoning", before?.reasoning[id] ?? 0, this.took.reasoning.get(id) ?? 0).map((piece) => piece.text).join("");
|
|
1438
|
+
for (const part of parts) if (part.type === "tool") await this.missedTool(part);
|
|
1439
|
+
if (message.tokens !== void 0 && !before?.steps.includes(id) && !this.took.steps.has(id)) {
|
|
1440
|
+
this.took.steps.add(id);
|
|
1441
|
+
const model = message.model;
|
|
1442
|
+
if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
|
|
1443
|
+
providerID: model.providerID,
|
|
1444
|
+
id: model.id
|
|
1445
|
+
};
|
|
1446
|
+
await this.spend({
|
|
1447
|
+
tokens: message.tokens,
|
|
1448
|
+
cost: message.cost
|
|
1449
|
+
}, "chat");
|
|
1450
|
+
}
|
|
1451
|
+
if (message.error?.message) lastError = message.error.message;
|
|
1452
|
+
}
|
|
1453
|
+
const recovered = text.map((piece) => piece.text).join("\n\n").trimStart();
|
|
1454
|
+
if (recovered.trim()) {
|
|
1455
|
+
await this.step();
|
|
1456
|
+
const joint = this.segment.text && !text.at(-1)?.continues ? "\n\n" : "";
|
|
1457
|
+
this.segment.text = `${recovered}${joint}${this.segment.text}`;
|
|
1458
|
+
this.sawText = true;
|
|
1459
|
+
await this.write({
|
|
1460
|
+
kind: "text",
|
|
1461
|
+
text: recovered
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
if (reasoning) {
|
|
1465
|
+
await this.step();
|
|
1466
|
+
this.segment.reasoning = `${reasoning}${this.segment.reasoning}`;
|
|
1467
|
+
await this.write({
|
|
1468
|
+
kind: "reasoning",
|
|
1469
|
+
text: reasoning
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
if (outcome.status !== "succeeded") return outcome;
|
|
1473
|
+
const idle = (messages ?? []).filter((m) => m.type === "idle").at(-1);
|
|
1474
|
+
if (idle?.outcome === "failed") return {
|
|
1475
|
+
status: "failed",
|
|
1476
|
+
error: lastError ?? this.stepError ?? "the execution failed"
|
|
1477
|
+
};
|
|
1478
|
+
if (idle?.outcome === "interrupted") return {
|
|
1479
|
+
status: "interrupted"
|
|
1480
|
+
};
|
|
1481
|
+
const s = this.segment;
|
|
1482
|
+
const said = s.text || s.reasoning || s.calls.length > 0 || s.ui.length > 0;
|
|
1483
|
+
const progress = this.progress();
|
|
1484
|
+
const tookAny = Object.keys(progress.text).length > 0 || progress.called.length > 0 || progress.steps.length > 0;
|
|
1485
|
+
if (!said && !this.wroteMessage && !tookAny) return {
|
|
1486
|
+
status: "failed",
|
|
1487
|
+
error: LOST_ANSWER_ERROR
|
|
1488
|
+
};
|
|
1489
|
+
return outcome;
|
|
1490
|
+
}
|
|
1491
|
+
/** A tool part of a message the turn did not hear: its call, its result, whichever it lacks. */
|
|
1492
|
+
async missedTool(part) {
|
|
1493
|
+
const id = part.id;
|
|
1494
|
+
const name = part.name ?? "tool";
|
|
1495
|
+
if (id === void 0 || HIDDEN_TOOLS.has(name)) return;
|
|
1496
|
+
const state = part.state ?? {};
|
|
1497
|
+
const before = this.a.progress;
|
|
1498
|
+
if (!before?.called.includes(id) && !this.tools.has(id)) {
|
|
1499
|
+
await this.step();
|
|
1500
|
+
this.setInput(id, state.input, name);
|
|
1501
|
+
await this.makeAvailable(id);
|
|
1502
|
+
}
|
|
1503
|
+
const ok = state.status === "completed";
|
|
1504
|
+
if (!ok && state.status !== "error" || before?.settled.includes(id)) return;
|
|
1505
|
+
const error = errorText(state.error, "tool failed");
|
|
1506
|
+
if (this.tools.has(id)) {
|
|
1507
|
+
await this.settleTool(id, ok, error);
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
this.took.settled.add(id);
|
|
1511
|
+
const result = ok ? {
|
|
1512
|
+
id,
|
|
1513
|
+
name,
|
|
1514
|
+
output: {
|
|
1515
|
+
ok: true
|
|
1516
|
+
}
|
|
1517
|
+
} : {
|
|
1518
|
+
id,
|
|
1519
|
+
name,
|
|
1520
|
+
output: {
|
|
1521
|
+
ok: false
|
|
1522
|
+
},
|
|
1523
|
+
error
|
|
1524
|
+
};
|
|
1525
|
+
const messageId = await this.messageOf(id);
|
|
1526
|
+
if (messageId !== void 0) await this.addResult(messageId, result);
|
|
1527
|
+
else this.segment.results.push(result);
|
|
1528
|
+
await this.write(ok ? {
|
|
1529
|
+
kind: "tool-output",
|
|
1530
|
+
id,
|
|
1531
|
+
output: {
|
|
1532
|
+
ok: true
|
|
1533
|
+
}
|
|
1534
|
+
} : {
|
|
1535
|
+
kind: "tool-output-error",
|
|
1536
|
+
id,
|
|
1537
|
+
error
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
async end(ended) {
|
|
1334
1541
|
if (this.ended) return;
|
|
1335
1542
|
this.ended = true;
|
|
1543
|
+
const outcome = this.a.resumed === true ? await this.recover(ended).catch((error) => {
|
|
1544
|
+
this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not recover the turn")}`);
|
|
1545
|
+
return ended;
|
|
1546
|
+
}) : ended;
|
|
1336
1547
|
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1337
1548
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1338
1549
|
await this.flush(true);
|
|
@@ -1349,6 +1560,26 @@ ${delta}` : delta;
|
|
|
1349
1560
|
});
|
|
1350
1561
|
}
|
|
1351
1562
|
};
|
|
1563
|
+
function missed(parts, type, head, tail) {
|
|
1564
|
+
const texts = parts.flatMap((p) => p.type === type && typeof p.text === "string" ? [
|
|
1565
|
+
p.text
|
|
1566
|
+
] : []);
|
|
1567
|
+
const total = texts.reduce((n, t) => n + t.length, 0);
|
|
1568
|
+
const end = total - tail;
|
|
1569
|
+
const out = [];
|
|
1570
|
+
let at = 0;
|
|
1571
|
+
for (const t of texts) {
|
|
1572
|
+
const from = Math.max(head, at);
|
|
1573
|
+
const to = Math.min(end, at + t.length);
|
|
1574
|
+
if (to > from) out.push({
|
|
1575
|
+
text: t.slice(from - at, to - at),
|
|
1576
|
+
continues: to < at + t.length
|
|
1577
|
+
});
|
|
1578
|
+
at += t.length;
|
|
1579
|
+
}
|
|
1580
|
+
return out;
|
|
1581
|
+
}
|
|
1582
|
+
__name(missed, "missed");
|
|
1352
1583
|
function modelOfLabel(label) {
|
|
1353
1584
|
const slash = label?.indexOf("/") ?? -1;
|
|
1354
1585
|
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
@@ -2494,7 +2725,11 @@ ${body}
|
|
|
2494
2725
|
text: prompt.text,
|
|
2495
2726
|
...prompt.files?.length ? {
|
|
2496
2727
|
files: prompt.files
|
|
2497
|
-
} : {}
|
|
2728
|
+
} : {},
|
|
2729
|
+
// Marks the prompt as this run's: a turn resumed after a restart reads what came after it.
|
|
2730
|
+
metadata: {
|
|
2731
|
+
runId
|
|
2732
|
+
}
|
|
2498
2733
|
});
|
|
2499
2734
|
} catch (error) {
|
|
2500
2735
|
live.turn.fail(`OpenCode refused the message: ${error.message}`);
|
|
@@ -2507,9 +2742,11 @@ ${body}
|
|
|
2507
2742
|
*
|
|
2508
2743
|
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2509
2744
|
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2745
|
+
* `progress` is what the turn took before (its last `ask` milestone's): a turn rebuilt here reads
|
|
2746
|
+
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2510
2747
|
*/
|
|
2511
|
-
async observe(runId, input, handle, spent) {
|
|
2512
|
-
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2748
|
+
async observe(runId, input, handle, spent, progress) {
|
|
2749
|
+
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2513
2750
|
const { turn, client } = live;
|
|
2514
2751
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2515
2752
|
let done = false;
|
|
@@ -2548,12 +2785,12 @@ ${body}
|
|
|
2548
2785
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2549
2786
|
* to go on.
|
|
2550
2787
|
*/
|
|
2551
|
-
async reply(runId, input, handle, ask, reply, spent) {
|
|
2788
|
+
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2552
2789
|
const server = await this.host.server(input.actor);
|
|
2553
2790
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2554
2791
|
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2555
2792
|
}
|
|
2556
|
-
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2793
|
+
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2557
2794
|
await live.turn.decide(ask, reply);
|
|
2558
2795
|
return handle;
|
|
2559
2796
|
}
|
|
@@ -2571,7 +2808,10 @@ ${body}
|
|
|
2571
2808
|
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2572
2809
|
await live.client.session.prompt({
|
|
2573
2810
|
sessionID: handle.sessionId,
|
|
2574
|
-
text: note
|
|
2811
|
+
text: note,
|
|
2812
|
+
metadata: {
|
|
2813
|
+
runId
|
|
2814
|
+
}
|
|
2575
2815
|
});
|
|
2576
2816
|
return handle;
|
|
2577
2817
|
}
|
|
@@ -2595,9 +2835,10 @@ ${body}
|
|
|
2595
2835
|
sessionID: known.sessionId
|
|
2596
2836
|
});
|
|
2597
2837
|
}
|
|
2598
|
-
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2838
|
+
async ensureLive(runId, input, handle, catchUp, spent, progress) {
|
|
2599
2839
|
const current = this.live.get(runId);
|
|
2600
2840
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2841
|
+
const resumed = catchUp && current === void 0;
|
|
2601
2842
|
current?.stop();
|
|
2602
2843
|
const server = await this.host.server(input.actor);
|
|
2603
2844
|
const deps = this.deps.forAgent(input.agentName);
|
|
@@ -2628,6 +2869,10 @@ ${body}
|
|
|
2628
2869
|
...spent !== void 0 ? {
|
|
2629
2870
|
carried: spent
|
|
2630
2871
|
} : {},
|
|
2872
|
+
...progress !== void 0 ? {
|
|
2873
|
+
progress
|
|
2874
|
+
} : {},
|
|
2875
|
+
resumed,
|
|
2631
2876
|
logger: this.logger,
|
|
2632
2877
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2633
2878
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2647,7 +2892,7 @@ ${body}
|
|
|
2647
2892
|
stop
|
|
2648
2893
|
};
|
|
2649
2894
|
this.live.set(runId, live);
|
|
2650
|
-
if (
|
|
2895
|
+
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2651
2896
|
return live;
|
|
2652
2897
|
}
|
|
2653
2898
|
/** Stop following a run in this process. */
|
|
@@ -2761,6 +3006,12 @@ ${body}
|
|
|
2761
3006
|
kind: "cancelled"
|
|
2762
3007
|
}));
|
|
2763
3008
|
await writer.end();
|
|
3009
|
+
(0, import_nestjs_agent_core4.publishAgentRunCancelled)({
|
|
3010
|
+
runId,
|
|
3011
|
+
threadId: input.threadId,
|
|
3012
|
+
durationMs,
|
|
3013
|
+
context: (0, import_nestjs_agent_core4.agentRunContext)(input)
|
|
3014
|
+
});
|
|
2764
3015
|
await this.store.recordRunEnd?.({
|
|
2765
3016
|
runId,
|
|
2766
3017
|
status: "cancelled",
|
|
@@ -2791,12 +3042,16 @@ ${body}
|
|
|
2791
3042
|
this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
2792
3043
|
(0, import_nestjs_agent_core4.publishAgentRunFailed)({
|
|
2793
3044
|
runId,
|
|
3045
|
+
threadId: input.threadId,
|
|
2794
3046
|
code: "opencode_failed",
|
|
2795
|
-
message: error
|
|
3047
|
+
message: error,
|
|
3048
|
+
durationMs,
|
|
3049
|
+
context: (0, import_nestjs_agent_core4.agentRunContext)(input)
|
|
2796
3050
|
});
|
|
2797
3051
|
await this.store.recordRunEnd?.({
|
|
2798
3052
|
runId,
|
|
2799
3053
|
status: "failed",
|
|
3054
|
+
durationMs,
|
|
2800
3055
|
errorCode: "opencode_failed",
|
|
2801
3056
|
errorMessage: error
|
|
2802
3057
|
});
|
|
@@ -3225,6 +3480,7 @@ var OpenCodeRunWorkflow = class {
|
|
|
3225
3480
|
}
|
|
3226
3481
|
async run(ctx, input) {
|
|
3227
3482
|
let spent = emptyUsage();
|
|
3483
|
+
let progress;
|
|
3228
3484
|
try {
|
|
3229
3485
|
const begun = await ctx.localStep("begin", async () => ({
|
|
3230
3486
|
handle: await this.turns.begin(ctx.runId, input),
|
|
@@ -3237,7 +3493,8 @@ var OpenCodeRunWorkflow = class {
|
|
|
3237
3493
|
});
|
|
3238
3494
|
for (let n = 0; ; n += 1) {
|
|
3239
3495
|
const before = spent;
|
|
3240
|
-
const
|
|
3496
|
+
const taken = progress;
|
|
3497
|
+
const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before, taken));
|
|
3241
3498
|
spent = addUsage(spent, milestone.usage);
|
|
3242
3499
|
const total = spent;
|
|
3243
3500
|
if (milestone.kind === "finished") {
|
|
@@ -3263,7 +3520,9 @@ var OpenCodeRunWorkflow = class {
|
|
|
3263
3520
|
};
|
|
3264
3521
|
}
|
|
3265
3522
|
const ask = milestone.ask;
|
|
3266
|
-
|
|
3523
|
+
progress = milestone.progress ?? progress;
|
|
3524
|
+
const tookSoFar = progress;
|
|
3525
|
+
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total, tookSoFar));
|
|
3267
3526
|
}
|
|
3268
3527
|
} catch (error) {
|
|
3269
3528
|
if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
|