@dudousxd/nestjs-agent-opencode 0.3.0 → 0.4.1
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 +283 -14
- 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 +284 -15
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-Cp5uysaN.d.cts → engine-BX6MLzxs.d.cts} +109 -10
- package/dist/{engine-Cp5uysaN.d.ts → engine-BX6MLzxs.d.ts} +109 -10
- package/dist/index.cjs +279 -12
- 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 +279 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var index_exports = {};
|
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
FormAnswerError: () => FormAnswerError,
|
|
25
25
|
InMemoryOpenCodeSessionStore: () => InMemoryOpenCodeSessionStore,
|
|
26
|
+
LOST_ANSWER_ERROR: () => LOST_ANSWER_ERROR,
|
|
26
27
|
OPENCODE_HOST: () => OPENCODE_HOST,
|
|
27
28
|
OPENCODE_MCP_ENDPOINT: () => OPENCODE_MCP_ENDPOINT,
|
|
28
29
|
OPENCODE_OPTIONS: () => OPENCODE_OPTIONS,
|
|
@@ -195,6 +196,7 @@ OpenCodeMcpController = _ts_decorate([
|
|
|
195
196
|
// src/mcp.ts
|
|
196
197
|
var import_node_crypto = require("crypto");
|
|
197
198
|
var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
|
|
199
|
+
var import_genui = require("@dudousxd/nestjs-agent-core/genui");
|
|
198
200
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
199
201
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
200
202
|
var import_zod_json_schema_compat = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js");
|
|
@@ -325,6 +327,9 @@ function subUsage(a, b) {
|
|
|
325
327
|
};
|
|
326
328
|
}
|
|
327
329
|
__name(subUsage, "subUsage");
|
|
330
|
+
var LOST_ANSWER_ERROR = "The answer was lost while the agent restarted; ask again.";
|
|
331
|
+
var RECOVER_PAGES = 10;
|
|
332
|
+
var RECOVER_PAGE_SIZE = 100;
|
|
328
333
|
var ASK_TOOL_NAME = "ask";
|
|
329
334
|
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
330
335
|
"error",
|
|
@@ -413,6 +418,14 @@ var OpenCodeTurn = class {
|
|
|
413
418
|
wroteMessage = false;
|
|
414
419
|
ended = false;
|
|
415
420
|
stepError;
|
|
421
|
+
/** What this turn object took from the session's assistant messages (see {@link TurnProgress}). */
|
|
422
|
+
took = {
|
|
423
|
+
text: /* @__PURE__ */ new Map(),
|
|
424
|
+
reasoning: /* @__PURE__ */ new Map(),
|
|
425
|
+
called: /* @__PURE__ */ new Set(),
|
|
426
|
+
settled: /* @__PURE__ */ new Set(),
|
|
427
|
+
steps: /* @__PURE__ */ new Set()
|
|
428
|
+
};
|
|
416
429
|
constructor(a) {
|
|
417
430
|
this.a = a;
|
|
418
431
|
this.model = modelOfLabel(a.input.model);
|
|
@@ -430,6 +443,30 @@ var OpenCodeTurn = class {
|
|
|
430
443
|
tokens: info.tokens
|
|
431
444
|
};
|
|
432
445
|
}
|
|
446
|
+
/** What the turn took so far: carried from before this turn object, plus what it took. */
|
|
447
|
+
progress() {
|
|
448
|
+
const before = this.a.progress;
|
|
449
|
+
const merged = /* @__PURE__ */ __name((carried, own) => {
|
|
450
|
+
const out = {
|
|
451
|
+
...carried ?? {}
|
|
452
|
+
};
|
|
453
|
+
for (const [id, n] of own) out[id] = (out[id] ?? 0) + n;
|
|
454
|
+
return out;
|
|
455
|
+
}, "merged");
|
|
456
|
+
const union = /* @__PURE__ */ __name((carried, own) => [
|
|
457
|
+
.../* @__PURE__ */ new Set([
|
|
458
|
+
...carried ?? [],
|
|
459
|
+
...own
|
|
460
|
+
])
|
|
461
|
+
], "union");
|
|
462
|
+
return {
|
|
463
|
+
text: merged(before?.text, this.took.text),
|
|
464
|
+
reasoning: merged(before?.reasoning, this.took.reasoning),
|
|
465
|
+
called: union(before?.called, this.took.called),
|
|
466
|
+
settled: union(before?.settled, this.took.settled),
|
|
467
|
+
steps: union(before?.steps, this.took.steps)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
433
470
|
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
434
471
|
totalUsage() {
|
|
435
472
|
return addUsage(this.a.carried, this.spent);
|
|
@@ -520,7 +557,11 @@ var OpenCodeTurn = class {
|
|
|
520
557
|
this.reported = {
|
|
521
558
|
...this.spent
|
|
522
559
|
};
|
|
523
|
-
const milestone = {
|
|
560
|
+
const milestone = reached.kind === "ask" ? {
|
|
561
|
+
...reached,
|
|
562
|
+
usage,
|
|
563
|
+
progress: this.progress()
|
|
564
|
+
} : {
|
|
524
565
|
...reached,
|
|
525
566
|
usage
|
|
526
567
|
};
|
|
@@ -575,6 +616,7 @@ var OpenCodeTurn = class {
|
|
|
575
616
|
innerSettled: /* @__PURE__ */ new Set()
|
|
576
617
|
};
|
|
577
618
|
this.tools.set(id, state);
|
|
619
|
+
this.took.called.add(id);
|
|
578
620
|
this.segment.calls.push({
|
|
579
621
|
id,
|
|
580
622
|
name,
|
|
@@ -702,6 +744,7 @@ var OpenCodeTurn = class {
|
|
|
702
744
|
case "session.text.delta": {
|
|
703
745
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
704
746
|
if (!delta) return;
|
|
747
|
+
this.count(this.took.text, d.assistantMessageID, delta.length);
|
|
705
748
|
this.endReasoning();
|
|
706
749
|
if (!this.sawText && !delta.trim()) return;
|
|
707
750
|
await this.step();
|
|
@@ -723,6 +766,7 @@ ${delta}` : delta;
|
|
|
723
766
|
case "session.reasoning.delta": {
|
|
724
767
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
725
768
|
if (!delta) return;
|
|
769
|
+
this.count(this.took.reasoning, d.assistantMessageID, delta.length);
|
|
726
770
|
this.reasoningSince ??= Date.now();
|
|
727
771
|
await this.step();
|
|
728
772
|
this.segment.reasoning += delta;
|
|
@@ -777,6 +821,7 @@ ${delta}` : delta;
|
|
|
777
821
|
}
|
|
778
822
|
case "session.step.ended":
|
|
779
823
|
this.endReasoning();
|
|
824
|
+
if (typeof d.assistantMessageID === "string") this.took.steps.add(d.assistantMessageID);
|
|
780
825
|
await this.stepEnded(d);
|
|
781
826
|
return;
|
|
782
827
|
case "session.step.failed":
|
|
@@ -817,10 +862,16 @@ ${delta}` : delta;
|
|
|
817
862
|
return;
|
|
818
863
|
}
|
|
819
864
|
}
|
|
865
|
+
/** Characters taken from an assistant message's text or reasoning. */
|
|
866
|
+
count(into, messageId, n) {
|
|
867
|
+
if (typeof messageId !== "string") return;
|
|
868
|
+
into.set(messageId, (into.get(messageId) ?? 0) + n);
|
|
869
|
+
}
|
|
820
870
|
async settleTool(id, ok, error = "failed") {
|
|
821
871
|
const state = this.tool(id);
|
|
822
872
|
if (state.settled) return;
|
|
823
873
|
state.settled = true;
|
|
874
|
+
this.took.settled.add(id);
|
|
824
875
|
if (ok) {
|
|
825
876
|
this.segment.results.push({
|
|
826
877
|
id,
|
|
@@ -1370,9 +1421,171 @@ ${delta}` : delta;
|
|
|
1370
1421
|
cost: cost < 1e-9 ? 0 : cost
|
|
1371
1422
|
}, this.compacted ? "history_summary" : "title");
|
|
1372
1423
|
}
|
|
1373
|
-
|
|
1424
|
+
/**
|
|
1425
|
+
* The session's messages since this turn's prompt, oldest first: everything after its newest user
|
|
1426
|
+
* message. `undefined` when they cannot be read (no `message.list`, no prompt within reach); empty
|
|
1427
|
+
* when the newest prompt is another run's (this turn's never reached the session).
|
|
1428
|
+
*/
|
|
1429
|
+
async messagesSincePrompt() {
|
|
1430
|
+
const messages = this.a.client.message;
|
|
1431
|
+
if (messages === void 0) return void 0;
|
|
1432
|
+
const since = [];
|
|
1433
|
+
let cursor;
|
|
1434
|
+
for (let page = 0; page < RECOVER_PAGES; page += 1) {
|
|
1435
|
+
const listed = await messages.list({
|
|
1436
|
+
sessionID: this.a.sessionId,
|
|
1437
|
+
order: "desc",
|
|
1438
|
+
limit: RECOVER_PAGE_SIZE,
|
|
1439
|
+
...cursor !== void 0 ? {
|
|
1440
|
+
cursor
|
|
1441
|
+
} : {}
|
|
1442
|
+
});
|
|
1443
|
+
for (const message of listed.data) {
|
|
1444
|
+
if (message.type === "user") {
|
|
1445
|
+
const runId = message.metadata?.runId;
|
|
1446
|
+
if (typeof runId === "string" && runId !== this.a.runId) return [];
|
|
1447
|
+
return since.reverse();
|
|
1448
|
+
}
|
|
1449
|
+
since.push(message);
|
|
1450
|
+
}
|
|
1451
|
+
const next = listed.cursor?.next;
|
|
1452
|
+
if (typeof next !== "string" || next.length === 0 || listed.data.length === 0) break;
|
|
1453
|
+
cursor = next;
|
|
1454
|
+
}
|
|
1455
|
+
return void 0;
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* A turn rebuilt after a restart heard only what the session said once it re-attached: what it
|
|
1459
|
+
* said in between — read back from the session's messages since the prompt, less what an earlier
|
|
1460
|
+
* process already took ({@link TurnProgress}) and what this one heard — is taken now, as its
|
|
1461
|
+
* events would have been: text, reasoning, tool calls and results, each step's usage. A session
|
|
1462
|
+
* that left nothing for the turn fails it ({@link LOST_ANSWER_ERROR}) rather than reading as an
|
|
1463
|
+
* empty answer.
|
|
1464
|
+
*/
|
|
1465
|
+
async recover(outcome) {
|
|
1466
|
+
const messages = await this.messagesSincePrompt().catch((error) => {
|
|
1467
|
+
this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session messages")}`);
|
|
1468
|
+
return void 0;
|
|
1469
|
+
});
|
|
1470
|
+
const before = this.a.progress;
|
|
1471
|
+
const text = [];
|
|
1472
|
+
let reasoning = "";
|
|
1473
|
+
let lastError;
|
|
1474
|
+
for (const message of messages ?? []) {
|
|
1475
|
+
if (message.type !== "assistant") continue;
|
|
1476
|
+
const id = message.id;
|
|
1477
|
+
const parts = message.content ?? [];
|
|
1478
|
+
text.push(...missed(parts, "text", before?.text[id] ?? 0, this.took.text.get(id) ?? 0));
|
|
1479
|
+
reasoning += missed(parts, "reasoning", before?.reasoning[id] ?? 0, this.took.reasoning.get(id) ?? 0).map((piece) => piece.text).join("");
|
|
1480
|
+
for (const part of parts) if (part.type === "tool") await this.missedTool(part);
|
|
1481
|
+
if (message.tokens !== void 0 && !before?.steps.includes(id) && !this.took.steps.has(id)) {
|
|
1482
|
+
this.took.steps.add(id);
|
|
1483
|
+
const model = message.model;
|
|
1484
|
+
if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
|
|
1485
|
+
providerID: model.providerID,
|
|
1486
|
+
id: model.id
|
|
1487
|
+
};
|
|
1488
|
+
await this.spend({
|
|
1489
|
+
tokens: message.tokens,
|
|
1490
|
+
cost: message.cost
|
|
1491
|
+
}, "chat");
|
|
1492
|
+
}
|
|
1493
|
+
if (message.error?.message) lastError = message.error.message;
|
|
1494
|
+
}
|
|
1495
|
+
const recovered = text.map((piece) => piece.text).join("\n\n").trimStart();
|
|
1496
|
+
if (recovered.trim()) {
|
|
1497
|
+
await this.step();
|
|
1498
|
+
const joint = this.segment.text && !text.at(-1)?.continues ? "\n\n" : "";
|
|
1499
|
+
this.segment.text = `${recovered}${joint}${this.segment.text}`;
|
|
1500
|
+
this.sawText = true;
|
|
1501
|
+
await this.write({
|
|
1502
|
+
kind: "text",
|
|
1503
|
+
text: recovered
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
if (reasoning) {
|
|
1507
|
+
await this.step();
|
|
1508
|
+
this.segment.reasoning = `${reasoning}${this.segment.reasoning}`;
|
|
1509
|
+
await this.write({
|
|
1510
|
+
kind: "reasoning",
|
|
1511
|
+
text: reasoning
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
if (outcome.status !== "succeeded") return outcome;
|
|
1515
|
+
const idle = (messages ?? []).filter((m) => m.type === "idle").at(-1);
|
|
1516
|
+
if (idle?.outcome === "failed") return {
|
|
1517
|
+
status: "failed",
|
|
1518
|
+
error: lastError ?? this.stepError ?? "the execution failed"
|
|
1519
|
+
};
|
|
1520
|
+
if (idle?.outcome === "interrupted") return {
|
|
1521
|
+
status: "interrupted"
|
|
1522
|
+
};
|
|
1523
|
+
const s = this.segment;
|
|
1524
|
+
const said = s.text || s.reasoning || s.calls.length > 0 || s.ui.length > 0;
|
|
1525
|
+
const progress = this.progress();
|
|
1526
|
+
const tookAny = Object.keys(progress.text).length > 0 || progress.called.length > 0 || progress.steps.length > 0;
|
|
1527
|
+
if (!said && !this.wroteMessage && !tookAny) return {
|
|
1528
|
+
status: "failed",
|
|
1529
|
+
error: LOST_ANSWER_ERROR
|
|
1530
|
+
};
|
|
1531
|
+
return outcome;
|
|
1532
|
+
}
|
|
1533
|
+
/** A tool part of a message the turn did not hear: its call, its result, whichever it lacks. */
|
|
1534
|
+
async missedTool(part) {
|
|
1535
|
+
const id = part.id;
|
|
1536
|
+
const name = part.name ?? "tool";
|
|
1537
|
+
if (id === void 0 || HIDDEN_TOOLS.has(name)) return;
|
|
1538
|
+
const state = part.state ?? {};
|
|
1539
|
+
const before = this.a.progress;
|
|
1540
|
+
if (!before?.called.includes(id) && !this.tools.has(id)) {
|
|
1541
|
+
await this.step();
|
|
1542
|
+
this.setInput(id, state.input, name);
|
|
1543
|
+
await this.makeAvailable(id);
|
|
1544
|
+
}
|
|
1545
|
+
const ok = state.status === "completed";
|
|
1546
|
+
if (!ok && state.status !== "error" || before?.settled.includes(id)) return;
|
|
1547
|
+
const error = errorText(state.error, "tool failed");
|
|
1548
|
+
if (this.tools.has(id)) {
|
|
1549
|
+
await this.settleTool(id, ok, error);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
this.took.settled.add(id);
|
|
1553
|
+
const result = ok ? {
|
|
1554
|
+
id,
|
|
1555
|
+
name,
|
|
1556
|
+
output: {
|
|
1557
|
+
ok: true
|
|
1558
|
+
}
|
|
1559
|
+
} : {
|
|
1560
|
+
id,
|
|
1561
|
+
name,
|
|
1562
|
+
output: {
|
|
1563
|
+
ok: false
|
|
1564
|
+
},
|
|
1565
|
+
error
|
|
1566
|
+
};
|
|
1567
|
+
const messageId = await this.messageOf(id);
|
|
1568
|
+
if (messageId !== void 0) await this.addResult(messageId, result);
|
|
1569
|
+
else this.segment.results.push(result);
|
|
1570
|
+
await this.write(ok ? {
|
|
1571
|
+
kind: "tool-output",
|
|
1572
|
+
id,
|
|
1573
|
+
output: {
|
|
1574
|
+
ok: true
|
|
1575
|
+
}
|
|
1576
|
+
} : {
|
|
1577
|
+
kind: "tool-output-error",
|
|
1578
|
+
id,
|
|
1579
|
+
error
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
async end(ended) {
|
|
1374
1583
|
if (this.ended) return;
|
|
1375
1584
|
this.ended = true;
|
|
1585
|
+
const outcome = this.a.resumed === true ? await this.recover(ended).catch((error) => {
|
|
1586
|
+
this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not recover the turn")}`);
|
|
1587
|
+
return ended;
|
|
1588
|
+
}) : ended;
|
|
1376
1589
|
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1377
1590
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1378
1591
|
await this.flush(true);
|
|
@@ -1389,6 +1602,26 @@ ${delta}` : delta;
|
|
|
1389
1602
|
});
|
|
1390
1603
|
}
|
|
1391
1604
|
};
|
|
1605
|
+
function missed(parts, type, head, tail) {
|
|
1606
|
+
const texts = parts.flatMap((p) => p.type === type && typeof p.text === "string" ? [
|
|
1607
|
+
p.text
|
|
1608
|
+
] : []);
|
|
1609
|
+
const total = texts.reduce((n, t) => n + t.length, 0);
|
|
1610
|
+
const end = total - tail;
|
|
1611
|
+
const out = [];
|
|
1612
|
+
let at = 0;
|
|
1613
|
+
for (const t of texts) {
|
|
1614
|
+
const from = Math.max(head, at);
|
|
1615
|
+
const to = Math.min(end, at + t.length);
|
|
1616
|
+
if (to > from) out.push({
|
|
1617
|
+
text: t.slice(from - at, to - at),
|
|
1618
|
+
continues: to < at + t.length
|
|
1619
|
+
});
|
|
1620
|
+
at += t.length;
|
|
1621
|
+
}
|
|
1622
|
+
return out;
|
|
1623
|
+
}
|
|
1624
|
+
__name(missed, "missed");
|
|
1392
1625
|
function modelOfLabel(label) {
|
|
1393
1626
|
const slash = label?.indexOf("/") ?? -1;
|
|
1394
1627
|
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
@@ -1515,6 +1748,8 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1515
1748
|
}
|
|
1516
1749
|
turns;
|
|
1517
1750
|
tokens;
|
|
1751
|
+
/** Genui telemetry of the UI calls OpenCode makes (its code mode calls tools here, not in the loop). */
|
|
1752
|
+
genui = new import_nestjs_agent_core3.GenuiRunRecorder();
|
|
1518
1753
|
constructor(turns, tokens) {
|
|
1519
1754
|
this.turns = turns;
|
|
1520
1755
|
this.tokens = tokens;
|
|
@@ -1646,7 +1881,7 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1646
1881
|
if (spec.kind === "action" && !await this.turns.spendApproval(turn.runId, name, turn.input)) {
|
|
1647
1882
|
throw new OpenCodeToolRefusedError(`"${name}" is an action and nobody approved this call; ask for the permission first.`);
|
|
1648
1883
|
}
|
|
1649
|
-
|
|
1884
|
+
const invoke = /* @__PURE__ */ __name(() => registry.invoke(name, args ?? {}, {
|
|
1650
1885
|
actor,
|
|
1651
1886
|
threadId: turn.ctx.threadId,
|
|
1652
1887
|
runId: turn.runId,
|
|
@@ -1663,7 +1898,24 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1663
1898
|
} : {}
|
|
1664
1899
|
}, this.turns.rolesPolicyFor(turn.input), allowed !== void 0 ? {
|
|
1665
1900
|
allowedTools: allowed
|
|
1666
|
-
} : {});
|
|
1901
|
+
} : {}), "invoke");
|
|
1902
|
+
return this.genui.track({
|
|
1903
|
+
store: this.turns.store,
|
|
1904
|
+
marker: spec.genui,
|
|
1905
|
+
runId: turn.runId,
|
|
1906
|
+
threadId: turn.ctx.threadId,
|
|
1907
|
+
actorRef: actor.id,
|
|
1908
|
+
...turn.input.agentName !== void 0 ? {
|
|
1909
|
+
agentName: turn.input.agentName
|
|
1910
|
+
} : {},
|
|
1911
|
+
channel: (0, import_genui.turnChannel)(turn.input.pageContext),
|
|
1912
|
+
modelId: turn.input.model ?? "opencode",
|
|
1913
|
+
call: {
|
|
1914
|
+
id: `mcp:${turn.runId}:${requestId}`,
|
|
1915
|
+
name,
|
|
1916
|
+
input: args ?? {}
|
|
1917
|
+
}
|
|
1918
|
+
}, invoke);
|
|
1667
1919
|
}
|
|
1668
1920
|
/** `remember`: one fact, at the actor's own scope only — as the loop serves it. */
|
|
1669
1921
|
async remember(turn, args) {
|
|
@@ -2515,7 +2767,11 @@ ${body}
|
|
|
2515
2767
|
text: prompt.text,
|
|
2516
2768
|
...prompt.files?.length ? {
|
|
2517
2769
|
files: prompt.files
|
|
2518
|
-
} : {}
|
|
2770
|
+
} : {},
|
|
2771
|
+
// Marks the prompt as this run's: a turn resumed after a restart reads what came after it.
|
|
2772
|
+
metadata: {
|
|
2773
|
+
runId
|
|
2774
|
+
}
|
|
2519
2775
|
});
|
|
2520
2776
|
} catch (error) {
|
|
2521
2777
|
live.turn.fail(`OpenCode refused the message: ${error.message}`);
|
|
@@ -2528,9 +2784,11 @@ ${body}
|
|
|
2528
2784
|
*
|
|
2529
2785
|
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2530
2786
|
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2787
|
+
* `progress` is what the turn took before (its last `ask` milestone's): a turn rebuilt here reads
|
|
2788
|
+
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2531
2789
|
*/
|
|
2532
|
-
async observe(runId, input, handle, spent) {
|
|
2533
|
-
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2790
|
+
async observe(runId, input, handle, spent, progress) {
|
|
2791
|
+
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2534
2792
|
const { turn, client } = live;
|
|
2535
2793
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2536
2794
|
let done = false;
|
|
@@ -2569,12 +2827,12 @@ ${body}
|
|
|
2569
2827
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2570
2828
|
* to go on.
|
|
2571
2829
|
*/
|
|
2572
|
-
async reply(runId, input, handle, ask, reply, spent) {
|
|
2830
|
+
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2573
2831
|
const server = await this.host.server(input.actor);
|
|
2574
2832
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2575
2833
|
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2576
2834
|
}
|
|
2577
|
-
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2835
|
+
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2578
2836
|
await live.turn.decide(ask, reply);
|
|
2579
2837
|
return handle;
|
|
2580
2838
|
}
|
|
@@ -2592,7 +2850,10 @@ ${body}
|
|
|
2592
2850
|
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2593
2851
|
await live.client.session.prompt({
|
|
2594
2852
|
sessionID: handle.sessionId,
|
|
2595
|
-
text: note
|
|
2853
|
+
text: note,
|
|
2854
|
+
metadata: {
|
|
2855
|
+
runId
|
|
2856
|
+
}
|
|
2596
2857
|
});
|
|
2597
2858
|
return handle;
|
|
2598
2859
|
}
|
|
@@ -2616,9 +2877,10 @@ ${body}
|
|
|
2616
2877
|
sessionID: known.sessionId
|
|
2617
2878
|
});
|
|
2618
2879
|
}
|
|
2619
|
-
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2880
|
+
async ensureLive(runId, input, handle, catchUp, spent, progress) {
|
|
2620
2881
|
const current = this.live.get(runId);
|
|
2621
2882
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2883
|
+
const resumed = catchUp && current === void 0;
|
|
2622
2884
|
current?.stop();
|
|
2623
2885
|
const server = await this.host.server(input.actor);
|
|
2624
2886
|
const deps = this.deps.forAgent(input.agentName);
|
|
@@ -2649,6 +2911,10 @@ ${body}
|
|
|
2649
2911
|
...spent !== void 0 ? {
|
|
2650
2912
|
carried: spent
|
|
2651
2913
|
} : {},
|
|
2914
|
+
...progress !== void 0 ? {
|
|
2915
|
+
progress
|
|
2916
|
+
} : {},
|
|
2917
|
+
resumed,
|
|
2652
2918
|
logger: this.logger,
|
|
2653
2919
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2654
2920
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2668,7 +2934,7 @@ ${body}
|
|
|
2668
2934
|
stop
|
|
2669
2935
|
};
|
|
2670
2936
|
this.live.set(runId, live);
|
|
2671
|
-
if (
|
|
2937
|
+
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2672
2938
|
return live;
|
|
2673
2939
|
}
|
|
2674
2940
|
/** Stop following a run in this process. */
|
|
@@ -3211,6 +3477,7 @@ __name(openCodeProviders, "openCodeProviders");
|
|
|
3211
3477
|
0 && (module.exports = {
|
|
3212
3478
|
FormAnswerError,
|
|
3213
3479
|
InMemoryOpenCodeSessionStore,
|
|
3480
|
+
LOST_ANSWER_ERROR,
|
|
3214
3481
|
OPENCODE_HOST,
|
|
3215
3482
|
OPENCODE_MCP_ENDPOINT,
|
|
3216
3483
|
OPENCODE_OPTIONS,
|