@dudousxd/nestjs-agent-opencode 0.5.0 → 1.1.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 +89 -1
- package/dist/durable/index.cjs +673 -97
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +11 -5
- package/dist/durable/index.d.ts +11 -5
- package/dist/durable/index.js +615 -39
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-BX6MLzxs.d.cts → engine-yFh3eD8j.d.cts} +241 -7
- package/dist/{engine-BX6MLzxs.d.ts → engine-yFh3eD8j.d.ts} +241 -7
- package/dist/index.cjs +653 -82
- 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 +596 -26
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/durable/index.js
CHANGED
|
@@ -19,6 +19,7 @@ var InMemoryOpenCodeSessionStore = class {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
// src/mcp.controller.ts
|
|
22
|
+
import { CanAnyone } from "@dudousxd/nestjs-agent-core";
|
|
22
23
|
import { Body, Controller, Delete, Get, Inject, Post, Req, Res } from "@nestjs/common";
|
|
23
24
|
|
|
24
25
|
// src/tokens.ts
|
|
@@ -115,6 +116,7 @@ _ts_decorate([
|
|
|
115
116
|
_ts_metadata("design:returntype", void 0)
|
|
116
117
|
], OpenCodeMcpController.prototype, "delete", null);
|
|
117
118
|
OpenCodeMcpController = _ts_decorate([
|
|
119
|
+
CanAnyone(),
|
|
118
120
|
Controller("opencode/mcp"),
|
|
119
121
|
_ts_param(0, Inject(OPENCODE_MCP_ENDPOINT)),
|
|
120
122
|
_ts_metadata("design:type", Function),
|
|
@@ -1979,6 +1981,37 @@ var OpenCodeEventHub = class _OpenCodeEventHub {
|
|
|
1979
1981
|
}
|
|
1980
1982
|
};
|
|
1981
1983
|
|
|
1984
|
+
// src/turn-limits.ts
|
|
1985
|
+
var STOPPED_ERROR_CODE = "opencode_stopped";
|
|
1986
|
+
function stopErrorText(stop) {
|
|
1987
|
+
switch (stop.reason) {
|
|
1988
|
+
case "steps":
|
|
1989
|
+
return `The turn was stopped after ${stop.counts.steps - 1} steps, its limit.`;
|
|
1990
|
+
case "compactions":
|
|
1991
|
+
return `The turn was stopped after ${stop.counts.compactions - 1} compactions, its limit.`;
|
|
1992
|
+
case "wall-clock":
|
|
1993
|
+
return "The turn was stopped: it ran longer than allowed.";
|
|
1994
|
+
default:
|
|
1995
|
+
return "The turn was stopped.";
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
__name(stopErrorText, "stopErrorText");
|
|
1999
|
+
function cleanLimits(limits) {
|
|
2000
|
+
const ok = /* @__PURE__ */ __name((n) => typeof n === "number" && Number.isFinite(n) && n >= 0, "ok");
|
|
2001
|
+
return {
|
|
2002
|
+
...ok(limits?.steps) ? {
|
|
2003
|
+
steps: limits.steps
|
|
2004
|
+
} : {},
|
|
2005
|
+
...ok(limits?.compactions) ? {
|
|
2006
|
+
compactions: limits.compactions
|
|
2007
|
+
} : {},
|
|
2008
|
+
...ok(limits?.wallClockMs) ? {
|
|
2009
|
+
wallClockMs: limits.wallClockMs
|
|
2010
|
+
} : {}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
__name(cleanLimits, "cleanLimits");
|
|
2014
|
+
|
|
1982
2015
|
// src/turns.ts
|
|
1983
2016
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
1984
2017
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2008,6 +2041,16 @@ function _ts_param2(paramIndex, decorator) {
|
|
|
2008
2041
|
__name(_ts_param2, "_ts_param");
|
|
2009
2042
|
var DEFAULT_TURN_TIMEOUT_MS = 30 * 6e4;
|
|
2010
2043
|
var DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
2044
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 5e3;
|
|
2045
|
+
var PARKED_RECHECKS_MS = [
|
|
2046
|
+
250,
|
|
2047
|
+
1e3,
|
|
2048
|
+
3e3,
|
|
2049
|
+
1e4,
|
|
2050
|
+
3e4
|
|
2051
|
+
];
|
|
2052
|
+
var PARKED_READ_GAP_MS = 250;
|
|
2053
|
+
var RELEASED_USAGE_MS = 10 * 6e4;
|
|
2011
2054
|
var OpenCodeTurns = class {
|
|
2012
2055
|
static {
|
|
2013
2056
|
__name(this, "OpenCodeTurns");
|
|
@@ -2031,6 +2074,21 @@ var OpenCodeTurns = class {
|
|
|
2031
2074
|
spent = /* @__PURE__ */ new Set();
|
|
2032
2075
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
2033
2076
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
2077
|
+
/**
|
|
2078
|
+
* What a turn let go of here had spent, for a settle that follows here (a cancel in this process
|
|
2079
|
+
* that settles after the turn was released). Dropped when the run settles here, or after
|
|
2080
|
+
* {@link RELEASED_USAGE_MS} when it settles elsewhere, so no run is kept for good.
|
|
2081
|
+
*/
|
|
2082
|
+
releasedUsage = /* @__PURE__ */ new Map();
|
|
2083
|
+
keepReleasedUsage(runId, usage) {
|
|
2084
|
+
this.releasedUsage.set(runId, usage);
|
|
2085
|
+
setTimeout(() => {
|
|
2086
|
+
if (this.releasedUsage.get(runId) === usage) this.releasedUsage.delete(runId);
|
|
2087
|
+
}, RELEASED_USAGE_MS).unref?.();
|
|
2088
|
+
}
|
|
2089
|
+
/** The turns this process executes, and which turn owns each session (`server|session`). */
|
|
2090
|
+
watches = /* @__PURE__ */ new Map();
|
|
2091
|
+
owners = /* @__PURE__ */ new Map();
|
|
2034
2092
|
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
2035
2093
|
this.host = host;
|
|
2036
2094
|
this.sessions = sessions;
|
|
@@ -2043,6 +2101,10 @@ var OpenCodeTurns = class {
|
|
|
2043
2101
|
this.memory = memory;
|
|
2044
2102
|
this.queue = queue;
|
|
2045
2103
|
this.toolsTokens = toolsTokens2;
|
|
2104
|
+
const hookTimeoutMs = settings.hookTimeoutMs;
|
|
2105
|
+
if (hookTimeoutMs !== void 0 && !(Number.isFinite(hookTimeoutMs) && hookTimeoutMs > 0)) {
|
|
2106
|
+
throw new Error(`hookTimeoutMs must be a positive number of milliseconds, not ${hookTimeoutMs}`);
|
|
2107
|
+
}
|
|
2046
2108
|
this.prices = new ModelPriceResolver({
|
|
2047
2109
|
pricingStore,
|
|
2048
2110
|
catalog: agentOptions?.priceCatalog,
|
|
@@ -2314,8 +2376,12 @@ var OpenCodeTurns = class {
|
|
|
2314
2376
|
}
|
|
2315
2377
|
}
|
|
2316
2378
|
onApplicationShutdown() {
|
|
2379
|
+
for (const runId of [
|
|
2380
|
+
...this.watches.keys()
|
|
2381
|
+
]) this.release(runId, "released");
|
|
2317
2382
|
for (const live of this.live.values()) live.stop();
|
|
2318
2383
|
this.live.clear();
|
|
2384
|
+
this.owners.clear();
|
|
2319
2385
|
this.events.close();
|
|
2320
2386
|
}
|
|
2321
2387
|
// ---- begin ---------------------------------------------------------------------------------
|
|
@@ -2325,7 +2391,8 @@ var OpenCodeTurns = class {
|
|
|
2325
2391
|
* durable `begin` re-run after a crash): the run's user message is written once.
|
|
2326
2392
|
*/
|
|
2327
2393
|
async begin(runId, input) {
|
|
2328
|
-
const
|
|
2394
|
+
const startedAt = this.watches.get(runId)?.startedAt ?? Date.now();
|
|
2395
|
+
const { earlier, rerun } = await this.prepareThread(runId, input);
|
|
2329
2396
|
const server = await this.host.server(input.actor);
|
|
2330
2397
|
const known = await this.sessions.get(input.threadId);
|
|
2331
2398
|
const reusable = known !== null && known.serverKey === server.key && known.bootId === server.bootId && (await this.host.reuse?.({
|
|
@@ -2371,7 +2438,12 @@ var OpenCodeTurns = class {
|
|
|
2371
2438
|
created
|
|
2372
2439
|
});
|
|
2373
2440
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2374
|
-
|
|
2441
|
+
const begun = this.limitsFor(input).wallClockMs !== void 0 ? {
|
|
2442
|
+
...handle,
|
|
2443
|
+
startedAt
|
|
2444
|
+
} : handle;
|
|
2445
|
+
await this.adopt(runId, input, begun, rerun, void 0, startedAt);
|
|
2446
|
+
return begun;
|
|
2375
2447
|
}
|
|
2376
2448
|
async createSession(runId, input, server, earlier, note) {
|
|
2377
2449
|
const { client } = server;
|
|
@@ -2441,10 +2513,16 @@ ${transcript}` : "",
|
|
|
2441
2513
|
}
|
|
2442
2514
|
const firstDropped = messages[lastUser + 1];
|
|
2443
2515
|
if (firstDropped !== void 0) await this.store.truncateFrom(input.threadId, firstDropped.id);
|
|
2444
|
-
return
|
|
2516
|
+
return {
|
|
2517
|
+
earlier: messages.slice(0, Math.max(lastUser, 0)),
|
|
2518
|
+
rerun: false
|
|
2519
|
+
};
|
|
2445
2520
|
}
|
|
2446
2521
|
const mine = messages.findIndex((m) => m.role === "user" && m.runId === runId);
|
|
2447
|
-
if (mine >= 0) return
|
|
2522
|
+
if (mine >= 0) return {
|
|
2523
|
+
earlier: messages.slice(0, mine),
|
|
2524
|
+
rerun: true
|
|
2525
|
+
};
|
|
2448
2526
|
await this.store.appendMessage({
|
|
2449
2527
|
threadId: input.threadId,
|
|
2450
2528
|
role: "user",
|
|
@@ -2460,7 +2538,10 @@ ${transcript}` : "",
|
|
|
2460
2538
|
if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
|
|
2461
2539
|
await this.store.setTitle(input.threadId, titleOf(input.userText));
|
|
2462
2540
|
}
|
|
2463
|
-
return
|
|
2541
|
+
return {
|
|
2542
|
+
earlier: messages,
|
|
2543
|
+
rerun: false
|
|
2544
|
+
};
|
|
2464
2545
|
}
|
|
2465
2546
|
/** Rewind the session to before its last user message, so the regenerated answer replaces it. */
|
|
2466
2547
|
async revertLastExchange(client, sessionId) {
|
|
@@ -2676,6 +2757,7 @@ ${body}
|
|
|
2676
2757
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2677
2758
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2678
2759
|
async prompt(runId, input, handle, spent) {
|
|
2760
|
+
await this.adopt(runId, input, handle, true);
|
|
2679
2761
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2680
2762
|
try {
|
|
2681
2763
|
const prompt = await this.host.promptFor?.({
|
|
@@ -2718,7 +2800,24 @@ ${body}
|
|
|
2718
2800
|
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2719
2801
|
*/
|
|
2720
2802
|
async observe(runId, input, handle, spent, progress) {
|
|
2803
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2721
2804
|
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2805
|
+
watch.parkedOn = void 0;
|
|
2806
|
+
watch.parkedEnd = void 0;
|
|
2807
|
+
watch.series = false;
|
|
2808
|
+
watch.observing = true;
|
|
2809
|
+
const wall = this.watchClock(watch);
|
|
2810
|
+
try {
|
|
2811
|
+
if (watch.pending !== void 0) await this.requestStop(runId, watch.pending);
|
|
2812
|
+
const milestone = this.marked(watch, await this.observeLive(runId, input, handle, live));
|
|
2813
|
+
if (milestone.kind === "ask") watch.parkedOn = milestone.ask.id;
|
|
2814
|
+
return milestone;
|
|
2815
|
+
} finally {
|
|
2816
|
+
watch.observing = false;
|
|
2817
|
+
if (wall !== void 0) clearTimeout(wall);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
async observeLive(runId, input, handle, live) {
|
|
2722
2821
|
const { turn, client } = live;
|
|
2723
2822
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2724
2823
|
let done = false;
|
|
@@ -2758,19 +2857,64 @@ ${body}
|
|
|
2758
2857
|
* to go on.
|
|
2759
2858
|
*/
|
|
2760
2859
|
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2860
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2861
|
+
watch.parkedOn = void 0;
|
|
2862
|
+
watch.parkedEnd = void 0;
|
|
2863
|
+
watch.series = false;
|
|
2864
|
+
const stopping = watch.pending !== void 0 || watch.stop !== void 0;
|
|
2865
|
+
const answer = stopping ? refusalOf(ask) : reply;
|
|
2761
2866
|
const server = await this.host.server(input.actor);
|
|
2762
2867
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2763
|
-
return this.
|
|
2868
|
+
if (stopping) return this.stopAfterRestart(watch, runId, input, handle, ask, answer, spent);
|
|
2869
|
+
return this.continueAfterRestart(runId, input, server, ask, answer, spent, handle.startedAt);
|
|
2764
2870
|
}
|
|
2765
2871
|
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2766
|
-
await live.turn.decide(ask,
|
|
2872
|
+
await live.turn.decide(ask, answer);
|
|
2767
2873
|
return handle;
|
|
2768
2874
|
}
|
|
2769
|
-
|
|
2875
|
+
/**
|
|
2876
|
+
* A stopping turn whose OpenCode server restarted while it waited: there is no session left to
|
|
2877
|
+
* abort, and none is opened or prompted. The ask is settled as refused and the turn ends stopped.
|
|
2878
|
+
*/
|
|
2879
|
+
async stopAfterRestart(watch, runId, input, handle, ask, answer, spent) {
|
|
2880
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2881
|
+
await live.turn.decide(ask, answer, {
|
|
2882
|
+
tellOpenCode: false
|
|
2883
|
+
});
|
|
2884
|
+
const reason = watch.pending ?? watch.stop?.reason ?? "stopped";
|
|
2885
|
+
if (watch.stop === void 0) {
|
|
2886
|
+
const stop = {
|
|
2887
|
+
reason,
|
|
2888
|
+
counts: this.counts(watch)
|
|
2889
|
+
};
|
|
2890
|
+
watch.stop = stop;
|
|
2891
|
+
this.hook("onStop", {
|
|
2892
|
+
turnId: runId,
|
|
2893
|
+
reason,
|
|
2894
|
+
counts: stop.counts
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
watch.aborted = true;
|
|
2898
|
+
watch.pending = void 0;
|
|
2899
|
+
live.turn.handle({
|
|
2900
|
+
type: "session.execution.interrupted",
|
|
2901
|
+
data: {
|
|
2902
|
+
sessionID: handle.sessionId
|
|
2903
|
+
}
|
|
2904
|
+
});
|
|
2905
|
+
return handle;
|
|
2906
|
+
}
|
|
2907
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent, startedAt) {
|
|
2770
2908
|
const approved = "approved" in reply && reply.approved === true;
|
|
2771
2909
|
const note = ask.kind === "approval" ? approved ? `The user approved the action ${ask.action} you asked permission for. Continue the task.` : `The user did not approve the action ${ask.action}. Continue without it and explain.` : `The user answered your question: ${JSON.stringify("answers" in reply ? reply.answers : {})}. Continue.`;
|
|
2772
2910
|
const thread = await this.store.getThread(input.threadId);
|
|
2773
|
-
const
|
|
2911
|
+
const created = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2912
|
+
const handle = {
|
|
2913
|
+
...created,
|
|
2914
|
+
...startedAt !== void 0 ? {
|
|
2915
|
+
startedAt
|
|
2916
|
+
} : {}
|
|
2917
|
+
};
|
|
2774
2918
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2775
2919
|
this.drop(runId);
|
|
2776
2920
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
@@ -2855,7 +2999,11 @@ ${body}
|
|
|
2855
2999
|
this.grants.set(runId, grants);
|
|
2856
3000
|
}, "onGranted")
|
|
2857
3001
|
});
|
|
2858
|
-
const
|
|
3002
|
+
const sessionId = handle.sessionId;
|
|
3003
|
+
const stop = await this.events.listen(server.key, server.client, sessionId, (event) => {
|
|
3004
|
+
turn.handle(event);
|
|
3005
|
+
this.sawEvent(runId, sessionId, event);
|
|
3006
|
+
});
|
|
2859
3007
|
const live = {
|
|
2860
3008
|
turn,
|
|
2861
3009
|
input,
|
|
@@ -2864,6 +3012,15 @@ ${body}
|
|
|
2864
3012
|
stop
|
|
2865
3013
|
};
|
|
2866
3014
|
this.live.set(runId, live);
|
|
3015
|
+
this.owners.set(ownerKey(handle), runId);
|
|
3016
|
+
const watch = this.watches.get(runId);
|
|
3017
|
+
if (watch !== void 0 && watch.handle.sessionId !== sessionId) {
|
|
3018
|
+
watch.handle = {
|
|
3019
|
+
...handle,
|
|
3020
|
+
startedAt: watch.startedAt
|
|
3021
|
+
};
|
|
3022
|
+
watch.ending = false;
|
|
3023
|
+
}
|
|
2867
3024
|
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2868
3025
|
return live;
|
|
2869
3026
|
}
|
|
@@ -2872,11 +3029,46 @@ ${body}
|
|
|
2872
3029
|
this.live.get(runId)?.stop();
|
|
2873
3030
|
this.live.delete(runId);
|
|
2874
3031
|
}
|
|
2875
|
-
/**
|
|
3032
|
+
/**
|
|
3033
|
+
* Stop following a run and drop the approvals it was granted: it is over. Returns the turn this
|
|
3034
|
+
* process executed, now settled (no stop reaches it any more), for its `onTurnEnd`.
|
|
3035
|
+
*/
|
|
2876
3036
|
forget(runId) {
|
|
2877
3037
|
this.drop(runId);
|
|
3038
|
+
this.releasedUsage.delete(runId);
|
|
2878
3039
|
for (const grant of this.grants.get(runId) ?? []) this.spent.delete(grant.id);
|
|
2879
3040
|
this.grants.delete(runId);
|
|
3041
|
+
const watch = this.watches.get(runId);
|
|
3042
|
+
if (watch === void 0) return void 0;
|
|
3043
|
+
watch.settled = true;
|
|
3044
|
+
this.watches.delete(runId);
|
|
3045
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3046
|
+
return watch;
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* A run that ended without settling here (cancelled while this process executed it; whoever
|
|
3050
|
+
* cancelled settled it): stop following it, and end the turn this process executed.
|
|
3051
|
+
*/
|
|
3052
|
+
async abandon(runId) {
|
|
3053
|
+
const watch = this.forget(runId);
|
|
3054
|
+
if (watch !== void 0) this.ended(watch, "interrupted", void 0);
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Let go of a turn this process executed without settling it here: it was cancelled while parked
|
|
3058
|
+
* (its session ended under it, `interrupted`), another worker took it over, or this process shuts
|
|
3059
|
+
* down (`released`). The per-turn state goes, `onTurnEnd` fires once, and a later stop of the turn
|
|
3060
|
+
* here is ignored. Nothing settles the run here afterwards: whoever answered or cancelled it does.
|
|
3061
|
+
*/
|
|
3062
|
+
release(runId, status) {
|
|
3063
|
+
const watch = this.watches.get(runId);
|
|
3064
|
+
if (watch === void 0) return;
|
|
3065
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3066
|
+
if (seen !== void 0) this.keepReleasedUsage(runId, seen);
|
|
3067
|
+
this.drop(runId);
|
|
3068
|
+
watch.settled = true;
|
|
3069
|
+
this.watches.delete(runId);
|
|
3070
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3071
|
+
this.ended(watch, status, void 0);
|
|
2880
3072
|
}
|
|
2881
3073
|
// ---- settle --------------------------------------------------------------------------------
|
|
2882
3074
|
/** A person was asked something: tell the host (cards in other channels, wake-ups). */
|
|
@@ -2949,7 +3141,7 @@ ${body}
|
|
|
2949
3141
|
* two milestones.
|
|
2950
3142
|
*/
|
|
2951
3143
|
spentBy(runId, spent) {
|
|
2952
|
-
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3144
|
+
const seen = this.live.get(runId)?.turn.totalUsage() ?? this.releasedUsage.get(runId);
|
|
2953
3145
|
if (spent === void 0) return seen ?? emptyUsage();
|
|
2954
3146
|
if (seen === void 0) return spent;
|
|
2955
3147
|
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
@@ -2959,13 +3151,13 @@ ${body}
|
|
|
2959
3151
|
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2960
3152
|
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2961
3153
|
*/
|
|
2962
|
-
async settle(runId, input,
|
|
3154
|
+
async settle(runId, input, ended, durationMs, spent) {
|
|
3155
|
+
const outcome = this.withStop(ended, this.watches.get(runId));
|
|
2963
3156
|
if (outcome.status === "failed") {
|
|
2964
|
-
|
|
2965
|
-
return;
|
|
3157
|
+
return this.settleFailed(runId, input, outcome.error, durationMs, spent, outcome.stop);
|
|
2966
3158
|
}
|
|
2967
3159
|
const usage = this.spentBy(runId, spent);
|
|
2968
|
-
this.forget(runId);
|
|
3160
|
+
const watch = this.forget(runId);
|
|
2969
3161
|
const writer = await this.sink.open(runId);
|
|
2970
3162
|
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2971
3163
|
await this.amend(writer, result);
|
|
@@ -3000,22 +3192,30 @@ ${body}
|
|
|
3000
3192
|
});
|
|
3001
3193
|
}
|
|
3002
3194
|
await this.settled(result);
|
|
3195
|
+
if (watch !== void 0) this.ended(watch, outcome.status, outcome.status === "succeeded" ? outcome.stop : void 0);
|
|
3196
|
+
return outcome;
|
|
3003
3197
|
}
|
|
3004
|
-
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
3198
|
+
async settleFailed(runId, input, error, durationMs = 0, spent, stop) {
|
|
3005
3199
|
const usage = this.spentBy(runId, spent);
|
|
3006
|
-
this.forget(runId);
|
|
3200
|
+
const watch = this.forget(runId);
|
|
3201
|
+
const stopped = stop ?? watch?.stop;
|
|
3007
3202
|
const writer = await this.sink.open(runId);
|
|
3008
3203
|
const result = await this.runResult(runId, input, {
|
|
3009
3204
|
status: "failed",
|
|
3010
|
-
error
|
|
3205
|
+
error,
|
|
3206
|
+
...stopped !== void 0 ? {
|
|
3207
|
+
stop: stopped
|
|
3208
|
+
} : {}
|
|
3011
3209
|
}, durationMs, usage);
|
|
3012
3210
|
const worded = (await this.amend(writer, result)).error;
|
|
3013
|
-
const message = worded ?? streamFailure(new Error(error)).message;
|
|
3014
|
-
|
|
3211
|
+
const message = worded ?? (stopped !== void 0 ? error : streamFailure(new Error(error)).message);
|
|
3212
|
+
const code = stopped !== void 0 ? STOPPED_ERROR_CODE : "opencode_failed";
|
|
3213
|
+
if (stopped !== void 0) this.logger.log(`agent run ${runId} stopped: ${stopped.reason}`);
|
|
3214
|
+
else this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
3015
3215
|
publishAgentRunFailed({
|
|
3016
3216
|
runId,
|
|
3017
3217
|
threadId: input.threadId,
|
|
3018
|
-
code
|
|
3218
|
+
code,
|
|
3019
3219
|
message: error,
|
|
3020
3220
|
durationMs,
|
|
3021
3221
|
context: agentRunContext(input)
|
|
@@ -3024,23 +3224,370 @@ ${body}
|
|
|
3024
3224
|
runId,
|
|
3025
3225
|
status: "failed",
|
|
3026
3226
|
durationMs,
|
|
3027
|
-
errorCode:
|
|
3227
|
+
errorCode: code,
|
|
3028
3228
|
errorMessage: error
|
|
3029
3229
|
});
|
|
3030
3230
|
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
|
|
3031
3231
|
await this.handoff(writer, input, runId, "failed", error);
|
|
3032
3232
|
await releaseThreadRun(this.store, input.threadId, runId);
|
|
3033
3233
|
await writer.fail({
|
|
3034
|
-
code
|
|
3234
|
+
code,
|
|
3035
3235
|
message
|
|
3036
3236
|
});
|
|
3037
3237
|
await this.settled({
|
|
3038
3238
|
...result,
|
|
3039
3239
|
outcome: {
|
|
3040
3240
|
status: "failed",
|
|
3041
|
-
error: worded ?? error
|
|
3241
|
+
error: worded ?? error,
|
|
3242
|
+
...stopped !== void 0 ? {
|
|
3243
|
+
stop: stopped
|
|
3244
|
+
} : {}
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
if (watch !== void 0) this.ended(watch, "failed", stopped);
|
|
3248
|
+
return {
|
|
3249
|
+
status: "failed",
|
|
3250
|
+
error,
|
|
3251
|
+
...stopped !== void 0 ? {
|
|
3252
|
+
stop: stopped
|
|
3253
|
+
} : {}
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
// ---- limits, stops and the turn's lifecycle -------------------------------------------------
|
|
3257
|
+
/**
|
|
3258
|
+
* Stop a turn's own work: abort its session so OpenCode starts no new round, only while the turn
|
|
3259
|
+
* still owns the session (a thread's session is reused by its next turn) — checked immediately
|
|
3260
|
+
* before the abort. Acts in the process executing the turn; see {@link OpenCodeStopResult}. The
|
|
3261
|
+
* turn ends failed with `outcome.stop` (code `opencode_stopped`), and `onStop` is called.
|
|
3262
|
+
*/
|
|
3263
|
+
stopTurn(turnId, reason) {
|
|
3264
|
+
return this.requestStop(turnId, reason);
|
|
3265
|
+
}
|
|
3266
|
+
/** The turn this process executes, made known here on its first step in this process. */
|
|
3267
|
+
async adopt(runId, input, handle, resumed, counts, startedAt) {
|
|
3268
|
+
const known = this.watches.get(runId);
|
|
3269
|
+
if (known !== void 0) return known;
|
|
3270
|
+
const watch = {
|
|
3271
|
+
runId,
|
|
3272
|
+
input,
|
|
3273
|
+
handle,
|
|
3274
|
+
limits: this.limitsFor(input),
|
|
3275
|
+
startedAt: handle.startedAt ?? startedAt ?? Date.now(),
|
|
3276
|
+
steps: counts?.steps ?? 0,
|
|
3277
|
+
compactions: counts?.compactions ?? 0,
|
|
3278
|
+
observing: false,
|
|
3279
|
+
ending: false,
|
|
3280
|
+
settled: false,
|
|
3281
|
+
aborted: false,
|
|
3282
|
+
checking: false,
|
|
3283
|
+
lastRead: 0
|
|
3284
|
+
};
|
|
3285
|
+
this.watches.set(runId, watch);
|
|
3286
|
+
await this.hook("onTurnStart", {
|
|
3287
|
+
turnId: runId,
|
|
3288
|
+
threadId: input.threadId,
|
|
3289
|
+
sessionId: handle.sessionId,
|
|
3290
|
+
resumed
|
|
3291
|
+
});
|
|
3292
|
+
return watch;
|
|
3293
|
+
}
|
|
3294
|
+
limitsFor(input) {
|
|
3295
|
+
const limits = this.settings.limits;
|
|
3296
|
+
return cleanLimits(typeof limits === "function" ? limits(input) : limits);
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* One event of the turn's session: counted for the limits, and told to `onTurnEvent`. While the
|
|
3300
|
+
* turn is parked on a person here, it may also say the turn went on without this process
|
|
3301
|
+
* ({@link wentOnElsewhere}).
|
|
3302
|
+
*/
|
|
3303
|
+
sawEvent(runId, sessionId, event) {
|
|
3304
|
+
if (sessionOf(event) !== sessionId) return;
|
|
3305
|
+
const watch = this.watches.get(runId);
|
|
3306
|
+
if (watch === void 0 || watch.settled || watch.handle.sessionId !== sessionId) return;
|
|
3307
|
+
if (watch.parkedOn !== void 0 && this.wentOnElsewhere(watch, event)) return;
|
|
3308
|
+
let reached;
|
|
3309
|
+
if (event.type.startsWith("session.execution.")) watch.ending = true;
|
|
3310
|
+
else if (event.type === "session.step.started") {
|
|
3311
|
+
watch.steps += 1;
|
|
3312
|
+
if (passed(watch.steps, watch.limits.steps)) reached = "steps";
|
|
3313
|
+
} else if (event.type === "session.compaction.started") {
|
|
3314
|
+
watch.compactions += 1;
|
|
3315
|
+
if (passed(watch.compactions, watch.limits.compactions)) reached = "compactions";
|
|
3316
|
+
}
|
|
3317
|
+
this.hook("onTurnEvent", {
|
|
3318
|
+
turnId: runId,
|
|
3319
|
+
type: event.type,
|
|
3320
|
+
sessionId
|
|
3321
|
+
});
|
|
3322
|
+
if (reached !== void 0) void this.limitStop(runId, reached);
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Whether a turn parked here went on without this process, which then lets it go. While a tool of
|
|
3326
|
+
* the step waits on a person, OpenCode keeps running the step's other tools, so their events say
|
|
3327
|
+
* nothing. What does: a new round (`session.step.started`, which cannot begin while the step
|
|
3328
|
+
* waits), or the store showing the parked ask decided by someone else (another worker answered
|
|
3329
|
+
* it, or a cancel elsewhere settled it) — this process clears the park before it answers. When
|
|
3330
|
+
* the session's execution ends while parked, the store is asked again for a while, as a cancel
|
|
3331
|
+
* elsewhere settles the ask just after it interrupted the session; with the ask still open, the
|
|
3332
|
+
* turn stays here for its answer.
|
|
3333
|
+
*/
|
|
3334
|
+
wentOnElsewhere(watch, event) {
|
|
3335
|
+
if (event.type === "session.step.started") {
|
|
3336
|
+
this.release(watch.runId, "released");
|
|
3337
|
+
return true;
|
|
3338
|
+
}
|
|
3339
|
+
const ended = event.type.startsWith("session.execution.");
|
|
3340
|
+
if (ended) watch.parkedEnd = event.type === "session.execution.interrupted" ? "interrupted" : "released";
|
|
3341
|
+
this.checkParked(watch, ended);
|
|
3342
|
+
return false;
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Ask the store whether the parked ask was decided elsewhere, and let the turn go when it was —
|
|
3346
|
+
* one read at a time, at most one every {@link PARKED_READ_GAP_MS}. `series`: the session's
|
|
3347
|
+
* execution ended, so the store is asked again over {@link parkedRechecksMs}. An execution that
|
|
3348
|
+
* was interrupted under a parked turn means a cancel elsewhere: when the store cannot say (it has
|
|
3349
|
+
* no `toolCallApproval`, the read fails, the ask is not on file) past the first recheck, or the
|
|
3350
|
+
* rechecks run out with the ask still open, the turn is let go as `interrupted` anyway.
|
|
3351
|
+
*/
|
|
3352
|
+
checkParked(watch, series) {
|
|
3353
|
+
if (series) watch.series = true;
|
|
3354
|
+
if (watch.checking) {
|
|
3355
|
+
watch.again = true;
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
watch.checking = true;
|
|
3359
|
+
void this.runParkedChecks(watch);
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* The checks of {@link checkParked}. `checking` is cleared in the same step the loop returns in,
|
|
3363
|
+
* so a check asked for afterwards starts a new loop; one asked for while it ran (`again`) but not
|
|
3364
|
+
* read because the loop returned starts one too.
|
|
3365
|
+
*/
|
|
3366
|
+
async runParkedChecks(watch) {
|
|
3367
|
+
try {
|
|
3368
|
+
await this.parkedChecks(watch);
|
|
3369
|
+
} finally {
|
|
3370
|
+
watch.checking = false;
|
|
3371
|
+
if (watch.again && !watch.settled && watch.parkedOn !== void 0) {
|
|
3372
|
+
watch.checking = true;
|
|
3373
|
+
void this.runParkedChecks(watch);
|
|
3042
3374
|
}
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
async parkedChecks(watch) {
|
|
3378
|
+
const ask = watch.parkedOn;
|
|
3379
|
+
const gone = /* @__PURE__ */ __name(() => watch.settled || ask === void 0 || watch.parkedOn !== ask, "gone");
|
|
3380
|
+
let step = 0;
|
|
3381
|
+
for (; ; ) {
|
|
3382
|
+
watch.again = false;
|
|
3383
|
+
const wait = watch.lastRead + PARKED_READ_GAP_MS - Date.now();
|
|
3384
|
+
if (wait > 0) await sleep(wait);
|
|
3385
|
+
if (gone()) return;
|
|
3386
|
+
const answer = await this.parkedAsk(ask);
|
|
3387
|
+
watch.lastRead = Date.now();
|
|
3388
|
+
if (gone()) return;
|
|
3389
|
+
if (answer === "decided") {
|
|
3390
|
+
this.release(watch.runId, watch.parkedEnd ?? "released");
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
if (watch.series) {
|
|
3394
|
+
const cancelled = watch.parkedEnd === "interrupted";
|
|
3395
|
+
if (cancelled && answer === "unknown" && step > 0) {
|
|
3396
|
+
this.release(watch.runId, "interrupted");
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
const gap = this.parkedRechecksMs[step];
|
|
3400
|
+
step += 1;
|
|
3401
|
+
if (gap === void 0) {
|
|
3402
|
+
if (cancelled) this.release(watch.runId, "interrupted");
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
await sleep(gap);
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
if (!watch.again) return;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
/** What the store says of an ask: decided, still open, or nothing it can tell. */
|
|
3412
|
+
async parkedAsk(id) {
|
|
3413
|
+
if (this.store.toolCallApproval === void 0) return "unknown";
|
|
3414
|
+
try {
|
|
3415
|
+
const state = await this.store.toolCallApproval(id);
|
|
3416
|
+
if (state == null) return "unknown";
|
|
3417
|
+
return state.status === "pending_approval" ? "open" : "decided";
|
|
3418
|
+
} catch {
|
|
3419
|
+
return "unknown";
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* The waits between the store's rereads of a parked ask after its execution ended (cumulative
|
|
3424
|
+
* 0.25, 1.25, 4.25, 14.25 and 44.25 seconds after the first). Overridden by tests only.
|
|
3425
|
+
*
|
|
3426
|
+
* @internal
|
|
3427
|
+
*/
|
|
3428
|
+
parkedRechecksMs = PARKED_RECHECKS_MS;
|
|
3429
|
+
/** A limit's stop: what it did is the engine's to log, there is no caller to tell. */
|
|
3430
|
+
async limitStop(runId, reason) {
|
|
3431
|
+
const result = await this.requestStop(runId, reason);
|
|
3432
|
+
if (result === "unconfirmed") this.logger.warn(`run ${runId}: passed its ${reason} limit, but its owner could not be read`);
|
|
3433
|
+
}
|
|
3434
|
+
/** The wall-clock bound of an observation: a timer for what is left of it, if anything. */
|
|
3435
|
+
watchClock(watch) {
|
|
3436
|
+
const max = watch.limits.wallClockMs;
|
|
3437
|
+
if (max === void 0) return void 0;
|
|
3438
|
+
const left = watch.startedAt + max - Date.now();
|
|
3439
|
+
if (left <= 0) {
|
|
3440
|
+
watch.pending ??= "wall-clock";
|
|
3441
|
+
return void 0;
|
|
3442
|
+
}
|
|
3443
|
+
const timer = setTimeout(() => void this.limitStop(watch.runId, "wall-clock"), left);
|
|
3444
|
+
timer.unref?.();
|
|
3445
|
+
return timer;
|
|
3446
|
+
}
|
|
3447
|
+
counts(watch) {
|
|
3448
|
+
return {
|
|
3449
|
+
steps: watch.steps,
|
|
3450
|
+
compactions: watch.compactions,
|
|
3451
|
+
elapsedMs: Date.now() - watch.startedAt
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
async requestStop(runId, reason) {
|
|
3455
|
+
const watch = this.watches.get(runId);
|
|
3456
|
+
if (watch === void 0 || watch.settled || watch.stop !== void 0) return "ignored";
|
|
3457
|
+
const live = this.live.get(runId);
|
|
3458
|
+
if (watch.parkedOn !== void 0) {
|
|
3459
|
+
watch.pending ??= reason;
|
|
3460
|
+
return "pending";
|
|
3461
|
+
}
|
|
3462
|
+
if (watch.ending || live?.turn.finished === true) return this.landed(watch, reason);
|
|
3463
|
+
if (!watch.observing || live === void 0) {
|
|
3464
|
+
watch.pending ??= reason;
|
|
3465
|
+
return "pending";
|
|
3466
|
+
}
|
|
3467
|
+
watch.pending = void 0;
|
|
3468
|
+
let active;
|
|
3469
|
+
try {
|
|
3470
|
+
active = await this.activeRun(watch.input.threadId);
|
|
3471
|
+
} catch (error) {
|
|
3472
|
+
this.logger.warn(`run ${runId}: could not read the thread's run: ${errorText(error, "error")}`);
|
|
3473
|
+
return "unconfirmed";
|
|
3474
|
+
}
|
|
3475
|
+
if (watch.settled || watch.stop !== void 0 || this.live.get(runId) !== live) return "ignored";
|
|
3476
|
+
if (active !== null && active !== runId) return "ignored";
|
|
3477
|
+
if (this.owners.get(ownerKey(watch.handle)) !== runId) return "ignored";
|
|
3478
|
+
if (watch.ending || live.turn.finished) return this.landed(watch, reason);
|
|
3479
|
+
const stop = {
|
|
3480
|
+
reason,
|
|
3481
|
+
counts: this.counts(watch)
|
|
3482
|
+
};
|
|
3483
|
+
watch.stop = stop;
|
|
3484
|
+
watch.aborted = true;
|
|
3485
|
+
try {
|
|
3486
|
+
await live.client.session.interrupt({
|
|
3487
|
+
sessionID: watch.handle.sessionId
|
|
3488
|
+
});
|
|
3489
|
+
} catch (error) {
|
|
3490
|
+
this.logger.warn(`run ${runId}: could not abort the session: ${errorText(error, "error")}`);
|
|
3491
|
+
}
|
|
3492
|
+
this.hook("onStop", {
|
|
3493
|
+
turnId: runId,
|
|
3494
|
+
reason,
|
|
3495
|
+
counts: stop.counts
|
|
3043
3496
|
});
|
|
3497
|
+
return "aborted";
|
|
3498
|
+
}
|
|
3499
|
+
/** A stop that arrived while the answer settles: recorded and reported, the answer kept. */
|
|
3500
|
+
landed(watch, reason) {
|
|
3501
|
+
const stop = {
|
|
3502
|
+
reason,
|
|
3503
|
+
counts: this.counts(watch)
|
|
3504
|
+
};
|
|
3505
|
+
watch.stop = stop;
|
|
3506
|
+
watch.pending = void 0;
|
|
3507
|
+
this.hook("onStop", {
|
|
3508
|
+
turnId: watch.runId,
|
|
3509
|
+
reason,
|
|
3510
|
+
counts: stop.counts
|
|
3511
|
+
});
|
|
3512
|
+
return "settling";
|
|
3513
|
+
}
|
|
3514
|
+
/** The milestone as the runner journals it: the limits' counts on an ask, the stop on an end. */
|
|
3515
|
+
marked(watch, milestone) {
|
|
3516
|
+
if (milestone.kind === "finished") return {
|
|
3517
|
+
...milestone,
|
|
3518
|
+
outcome: this.withStop(milestone.outcome, watch)
|
|
3519
|
+
};
|
|
3520
|
+
if (milestone.progress === void 0) return milestone;
|
|
3521
|
+
const counts = {
|
|
3522
|
+
steps: watch.steps,
|
|
3523
|
+
compactions: watch.compactions
|
|
3524
|
+
};
|
|
3525
|
+
return {
|
|
3526
|
+
...milestone,
|
|
3527
|
+
progress: {
|
|
3528
|
+
...milestone.progress,
|
|
3529
|
+
counts
|
|
3530
|
+
}
|
|
3531
|
+
};
|
|
3532
|
+
}
|
|
3533
|
+
/**
|
|
3534
|
+
* How a turn a stop landed on ends. An answer that settled anyway is kept, with the stop on it; an
|
|
3535
|
+
* execution that failed on its own keeps its error, with the stop on it; an execution the stop
|
|
3536
|
+
* aborted ends failed with the stop's own text. One interrupted by a person's cancel stays so.
|
|
3537
|
+
*/
|
|
3538
|
+
withStop(outcome, watch) {
|
|
3539
|
+
const stop = watch?.stop;
|
|
3540
|
+
if (stop === void 0) return outcome;
|
|
3541
|
+
if (outcome.status !== "interrupted") return outcome.stop !== void 0 ? outcome : {
|
|
3542
|
+
...outcome,
|
|
3543
|
+
stop
|
|
3544
|
+
};
|
|
3545
|
+
if (!watch?.aborted) return outcome;
|
|
3546
|
+
return {
|
|
3547
|
+
status: "failed",
|
|
3548
|
+
error: stopErrorText(stop),
|
|
3549
|
+
stop
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
/** `onTurnEnd`, once per turn this process executed; not waited for. */
|
|
3553
|
+
ended(watch, status, stop) {
|
|
3554
|
+
this.hook("onTurnEnd", {
|
|
3555
|
+
turnId: watch.runId,
|
|
3556
|
+
status,
|
|
3557
|
+
...stop !== void 0 ? {
|
|
3558
|
+
stop
|
|
3559
|
+
} : {}
|
|
3560
|
+
});
|
|
3561
|
+
}
|
|
3562
|
+
/**
|
|
3563
|
+
* A lifecycle hook, from the settings and the host, called at once. The promise settles when both
|
|
3564
|
+
* have, or after `hookTimeoutMs` (default 5 s) — only `onTurnStart` is waited for. Errors are
|
|
3565
|
+
* logged, never the turn's.
|
|
3566
|
+
*/
|
|
3567
|
+
hook(name, event) {
|
|
3568
|
+
const calls = [];
|
|
3569
|
+
for (const target of [
|
|
3570
|
+
this.settings,
|
|
3571
|
+
this.host
|
|
3572
|
+
]) {
|
|
3573
|
+
const fn = target[name];
|
|
3574
|
+
if (typeof fn !== "function") continue;
|
|
3575
|
+
calls.push(Promise.resolve().then(() => fn.call(target, event)).catch((error) => this.logger.warn(`${name} failed: ${errorText(error, "error")}`)));
|
|
3576
|
+
}
|
|
3577
|
+
if (calls.length === 0) return Promise.resolve();
|
|
3578
|
+
const ms = this.settings.hookTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
|
|
3579
|
+
let timer;
|
|
3580
|
+
const late = new Promise((resolve) => {
|
|
3581
|
+
timer = setTimeout(() => {
|
|
3582
|
+
this.logger.warn(`${name} took longer than ${ms}ms; the turn went on`);
|
|
3583
|
+
resolve();
|
|
3584
|
+
}, ms);
|
|
3585
|
+
timer.unref?.();
|
|
3586
|
+
});
|
|
3587
|
+
return Promise.race([
|
|
3588
|
+
Promise.all(calls).then(() => void 0),
|
|
3589
|
+
late
|
|
3590
|
+
]).finally(() => clearTimeout(timer));
|
|
3044
3591
|
}
|
|
3045
3592
|
/** Starts the next queued message of the thread; set by the runner (it owns `start`). */
|
|
3046
3593
|
startNext;
|
|
@@ -3103,6 +3650,28 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
3103
3650
|
Object
|
|
3104
3651
|
])
|
|
3105
3652
|
], OpenCodeTurns);
|
|
3653
|
+
function passed(count, limit) {
|
|
3654
|
+
return limit !== void 0 && count > limit;
|
|
3655
|
+
}
|
|
3656
|
+
__name(passed, "passed");
|
|
3657
|
+
function sleep(ms) {
|
|
3658
|
+
return new Promise((resolve) => setTimeout(resolve, ms).unref?.());
|
|
3659
|
+
}
|
|
3660
|
+
__name(sleep, "sleep");
|
|
3661
|
+
function refusalOf(ask) {
|
|
3662
|
+
return ask.kind === "approval" ? {
|
|
3663
|
+
approved: false,
|
|
3664
|
+
reason: "The turn was stopped."
|
|
3665
|
+
} : {
|
|
3666
|
+
answers: {},
|
|
3667
|
+
skipped: true
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
__name(refusalOf, "refusalOf");
|
|
3671
|
+
function ownerKey(handle) {
|
|
3672
|
+
return `${handle.serverKey}|${handle.sessionId}`;
|
|
3673
|
+
}
|
|
3674
|
+
__name(ownerKey, "ownerKey");
|
|
3106
3675
|
function titleOf(text) {
|
|
3107
3676
|
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
3108
3677
|
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
|
|
@@ -3276,7 +3845,7 @@ var OpenCodeAgentRunner = class {
|
|
|
3276
3845
|
await this.turns.interrupt(input).catch(() => void 0);
|
|
3277
3846
|
await this.turns.settle(runId, input, {
|
|
3278
3847
|
status: "interrupted"
|
|
3279
|
-
}, Date.now() - started);
|
|
3848
|
+
}, Date.now() - started, spent);
|
|
3280
3849
|
return;
|
|
3281
3850
|
}
|
|
3282
3851
|
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
@@ -3470,14 +4039,14 @@ var OpenCodeRunWorkflow = class {
|
|
|
3470
4039
|
spent = addUsage(spent, milestone.usage);
|
|
3471
4040
|
const total = spent;
|
|
3472
4041
|
if (milestone.kind === "finished") {
|
|
3473
|
-
await ctx.localStep("finish", async () => {
|
|
3474
|
-
if (await this.cancelled(ctx.runId))
|
|
3475
|
-
|
|
3476
|
-
|
|
4042
|
+
const settled = await ctx.localStep("finish", async () => {
|
|
4043
|
+
if (await this.cancelled(ctx.runId)) {
|
|
4044
|
+
await this.turns.abandon(ctx.runId);
|
|
4045
|
+
return milestone.outcome;
|
|
4046
|
+
}
|
|
4047
|
+
return this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
|
|
3477
4048
|
});
|
|
3478
|
-
return
|
|
3479
|
-
outcome: milestone.outcome.status
|
|
3480
|
-
};
|
|
4049
|
+
return resultOf(settled === true ? milestone.outcome : settled);
|
|
3481
4050
|
}
|
|
3482
4051
|
let reply;
|
|
3483
4052
|
try {
|
|
@@ -3498,13 +4067,10 @@ var OpenCodeRunWorkflow = class {
|
|
|
3498
4067
|
}
|
|
3499
4068
|
} catch (error) {
|
|
3500
4069
|
if (isWorkflowControlFlowSignal(error)) throw error;
|
|
3501
|
-
await ctx.localStep("fail", async () =>
|
|
3502
|
-
|
|
3503
|
-
return true;
|
|
3504
|
-
});
|
|
3505
|
-
return {
|
|
4070
|
+
const settled = await ctx.localStep("fail", async () => this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent));
|
|
4071
|
+
return settled === true ? {
|
|
3506
4072
|
outcome: "failed"
|
|
3507
|
-
};
|
|
4073
|
+
} : resultOf(settled);
|
|
3508
4074
|
}
|
|
3509
4075
|
}
|
|
3510
4076
|
async cancelled(runId) {
|
|
@@ -3530,6 +4096,16 @@ OpenCodeRunWorkflow = _ts_decorate4([
|
|
|
3530
4096
|
typeof RunGateway === "undefined" ? Object : RunGateway
|
|
3531
4097
|
])
|
|
3532
4098
|
], OpenCodeRunWorkflow);
|
|
4099
|
+
function resultOf(outcome) {
|
|
4100
|
+
const stop = outcome.status === "interrupted" ? void 0 : outcome.stop;
|
|
4101
|
+
return {
|
|
4102
|
+
outcome: outcome.status,
|
|
4103
|
+
...stop !== void 0 ? {
|
|
4104
|
+
stop: stop.reason
|
|
4105
|
+
} : {}
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
__name(resultOf, "resultOf");
|
|
3533
4109
|
|
|
3534
4110
|
// src/durable/runner.ts
|
|
3535
4111
|
function _ts_decorate5(decorators, target, key, desc) {
|