@dudousxd/nestjs-agent-opencode 1.0.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 +88 -0
- package/dist/durable/index.cjs +613 -39
- 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 +613 -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 +595 -26
- 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 +594 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/durable/index.cjs
CHANGED
|
@@ -2009,6 +2009,37 @@ var OpenCodeEventHub = class _OpenCodeEventHub {
|
|
|
2009
2009
|
}
|
|
2010
2010
|
};
|
|
2011
2011
|
|
|
2012
|
+
// src/turn-limits.ts
|
|
2013
|
+
var STOPPED_ERROR_CODE = "opencode_stopped";
|
|
2014
|
+
function stopErrorText(stop) {
|
|
2015
|
+
switch (stop.reason) {
|
|
2016
|
+
case "steps":
|
|
2017
|
+
return `The turn was stopped after ${stop.counts.steps - 1} steps, its limit.`;
|
|
2018
|
+
case "compactions":
|
|
2019
|
+
return `The turn was stopped after ${stop.counts.compactions - 1} compactions, its limit.`;
|
|
2020
|
+
case "wall-clock":
|
|
2021
|
+
return "The turn was stopped: it ran longer than allowed.";
|
|
2022
|
+
default:
|
|
2023
|
+
return "The turn was stopped.";
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
__name(stopErrorText, "stopErrorText");
|
|
2027
|
+
function cleanLimits(limits) {
|
|
2028
|
+
const ok = /* @__PURE__ */ __name((n) => typeof n === "number" && Number.isFinite(n) && n >= 0, "ok");
|
|
2029
|
+
return {
|
|
2030
|
+
...ok(limits?.steps) ? {
|
|
2031
|
+
steps: limits.steps
|
|
2032
|
+
} : {},
|
|
2033
|
+
...ok(limits?.compactions) ? {
|
|
2034
|
+
compactions: limits.compactions
|
|
2035
|
+
} : {},
|
|
2036
|
+
...ok(limits?.wallClockMs) ? {
|
|
2037
|
+
wallClockMs: limits.wallClockMs
|
|
2038
|
+
} : {}
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
__name(cleanLimits, "cleanLimits");
|
|
2042
|
+
|
|
2012
2043
|
// src/turns.ts
|
|
2013
2044
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
2014
2045
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2038,6 +2069,16 @@ function _ts_param2(paramIndex, decorator) {
|
|
|
2038
2069
|
__name(_ts_param2, "_ts_param");
|
|
2039
2070
|
var DEFAULT_TURN_TIMEOUT_MS = 30 * 6e4;
|
|
2040
2071
|
var DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
2072
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 5e3;
|
|
2073
|
+
var PARKED_RECHECKS_MS = [
|
|
2074
|
+
250,
|
|
2075
|
+
1e3,
|
|
2076
|
+
3e3,
|
|
2077
|
+
1e4,
|
|
2078
|
+
3e4
|
|
2079
|
+
];
|
|
2080
|
+
var PARKED_READ_GAP_MS = 250;
|
|
2081
|
+
var RELEASED_USAGE_MS = 10 * 6e4;
|
|
2041
2082
|
var OpenCodeTurns = class {
|
|
2042
2083
|
static {
|
|
2043
2084
|
__name(this, "OpenCodeTurns");
|
|
@@ -2061,6 +2102,21 @@ var OpenCodeTurns = class {
|
|
|
2061
2102
|
spent = /* @__PURE__ */ new Set();
|
|
2062
2103
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
2063
2104
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
2105
|
+
/**
|
|
2106
|
+
* What a turn let go of here had spent, for a settle that follows here (a cancel in this process
|
|
2107
|
+
* that settles after the turn was released). Dropped when the run settles here, or after
|
|
2108
|
+
* {@link RELEASED_USAGE_MS} when it settles elsewhere, so no run is kept for good.
|
|
2109
|
+
*/
|
|
2110
|
+
releasedUsage = /* @__PURE__ */ new Map();
|
|
2111
|
+
keepReleasedUsage(runId, usage) {
|
|
2112
|
+
this.releasedUsage.set(runId, usage);
|
|
2113
|
+
setTimeout(() => {
|
|
2114
|
+
if (this.releasedUsage.get(runId) === usage) this.releasedUsage.delete(runId);
|
|
2115
|
+
}, RELEASED_USAGE_MS).unref?.();
|
|
2116
|
+
}
|
|
2117
|
+
/** The turns this process executes, and which turn owns each session (`server|session`). */
|
|
2118
|
+
watches = /* @__PURE__ */ new Map();
|
|
2119
|
+
owners = /* @__PURE__ */ new Map();
|
|
2064
2120
|
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
2065
2121
|
this.host = host;
|
|
2066
2122
|
this.sessions = sessions;
|
|
@@ -2073,6 +2129,10 @@ var OpenCodeTurns = class {
|
|
|
2073
2129
|
this.memory = memory;
|
|
2074
2130
|
this.queue = queue;
|
|
2075
2131
|
this.toolsTokens = toolsTokens2;
|
|
2132
|
+
const hookTimeoutMs = settings.hookTimeoutMs;
|
|
2133
|
+
if (hookTimeoutMs !== void 0 && !(Number.isFinite(hookTimeoutMs) && hookTimeoutMs > 0)) {
|
|
2134
|
+
throw new Error(`hookTimeoutMs must be a positive number of milliseconds, not ${hookTimeoutMs}`);
|
|
2135
|
+
}
|
|
2076
2136
|
this.prices = new import_nestjs_agent_core5.ModelPriceResolver({
|
|
2077
2137
|
pricingStore,
|
|
2078
2138
|
catalog: agentOptions?.priceCatalog,
|
|
@@ -2344,8 +2404,12 @@ var OpenCodeTurns = class {
|
|
|
2344
2404
|
}
|
|
2345
2405
|
}
|
|
2346
2406
|
onApplicationShutdown() {
|
|
2407
|
+
for (const runId of [
|
|
2408
|
+
...this.watches.keys()
|
|
2409
|
+
]) this.release(runId, "released");
|
|
2347
2410
|
for (const live of this.live.values()) live.stop();
|
|
2348
2411
|
this.live.clear();
|
|
2412
|
+
this.owners.clear();
|
|
2349
2413
|
this.events.close();
|
|
2350
2414
|
}
|
|
2351
2415
|
// ---- begin ---------------------------------------------------------------------------------
|
|
@@ -2355,7 +2419,8 @@ var OpenCodeTurns = class {
|
|
|
2355
2419
|
* durable `begin` re-run after a crash): the run's user message is written once.
|
|
2356
2420
|
*/
|
|
2357
2421
|
async begin(runId, input) {
|
|
2358
|
-
const
|
|
2422
|
+
const startedAt = this.watches.get(runId)?.startedAt ?? Date.now();
|
|
2423
|
+
const { earlier, rerun } = await this.prepareThread(runId, input);
|
|
2359
2424
|
const server = await this.host.server(input.actor);
|
|
2360
2425
|
const known = await this.sessions.get(input.threadId);
|
|
2361
2426
|
const reusable = known !== null && known.serverKey === server.key && known.bootId === server.bootId && (await this.host.reuse?.({
|
|
@@ -2401,7 +2466,12 @@ var OpenCodeTurns = class {
|
|
|
2401
2466
|
created
|
|
2402
2467
|
});
|
|
2403
2468
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2404
|
-
|
|
2469
|
+
const begun = this.limitsFor(input).wallClockMs !== void 0 ? {
|
|
2470
|
+
...handle,
|
|
2471
|
+
startedAt
|
|
2472
|
+
} : handle;
|
|
2473
|
+
await this.adopt(runId, input, begun, rerun, void 0, startedAt);
|
|
2474
|
+
return begun;
|
|
2405
2475
|
}
|
|
2406
2476
|
async createSession(runId, input, server, earlier, note) {
|
|
2407
2477
|
const { client } = server;
|
|
@@ -2471,10 +2541,16 @@ ${transcript}` : "",
|
|
|
2471
2541
|
}
|
|
2472
2542
|
const firstDropped = messages[lastUser + 1];
|
|
2473
2543
|
if (firstDropped !== void 0) await this.store.truncateFrom(input.threadId, firstDropped.id);
|
|
2474
|
-
return
|
|
2544
|
+
return {
|
|
2545
|
+
earlier: messages.slice(0, Math.max(lastUser, 0)),
|
|
2546
|
+
rerun: false
|
|
2547
|
+
};
|
|
2475
2548
|
}
|
|
2476
2549
|
const mine = messages.findIndex((m) => m.role === "user" && m.runId === runId);
|
|
2477
|
-
if (mine >= 0) return
|
|
2550
|
+
if (mine >= 0) return {
|
|
2551
|
+
earlier: messages.slice(0, mine),
|
|
2552
|
+
rerun: true
|
|
2553
|
+
};
|
|
2478
2554
|
await this.store.appendMessage({
|
|
2479
2555
|
threadId: input.threadId,
|
|
2480
2556
|
role: "user",
|
|
@@ -2490,7 +2566,10 @@ ${transcript}` : "",
|
|
|
2490
2566
|
if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
|
|
2491
2567
|
await this.store.setTitle(input.threadId, titleOf(input.userText));
|
|
2492
2568
|
}
|
|
2493
|
-
return
|
|
2569
|
+
return {
|
|
2570
|
+
earlier: messages,
|
|
2571
|
+
rerun: false
|
|
2572
|
+
};
|
|
2494
2573
|
}
|
|
2495
2574
|
/** Rewind the session to before its last user message, so the regenerated answer replaces it. */
|
|
2496
2575
|
async revertLastExchange(client, sessionId) {
|
|
@@ -2706,6 +2785,7 @@ ${body}
|
|
|
2706
2785
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2707
2786
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2708
2787
|
async prompt(runId, input, handle, spent) {
|
|
2788
|
+
await this.adopt(runId, input, handle, true);
|
|
2709
2789
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2710
2790
|
try {
|
|
2711
2791
|
const prompt = await this.host.promptFor?.({
|
|
@@ -2748,7 +2828,24 @@ ${body}
|
|
|
2748
2828
|
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2749
2829
|
*/
|
|
2750
2830
|
async observe(runId, input, handle, spent, progress) {
|
|
2831
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2751
2832
|
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2833
|
+
watch.parkedOn = void 0;
|
|
2834
|
+
watch.parkedEnd = void 0;
|
|
2835
|
+
watch.series = false;
|
|
2836
|
+
watch.observing = true;
|
|
2837
|
+
const wall = this.watchClock(watch);
|
|
2838
|
+
try {
|
|
2839
|
+
if (watch.pending !== void 0) await this.requestStop(runId, watch.pending);
|
|
2840
|
+
const milestone = this.marked(watch, await this.observeLive(runId, input, handle, live));
|
|
2841
|
+
if (milestone.kind === "ask") watch.parkedOn = milestone.ask.id;
|
|
2842
|
+
return milestone;
|
|
2843
|
+
} finally {
|
|
2844
|
+
watch.observing = false;
|
|
2845
|
+
if (wall !== void 0) clearTimeout(wall);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
async observeLive(runId, input, handle, live) {
|
|
2752
2849
|
const { turn, client } = live;
|
|
2753
2850
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2754
2851
|
let done = false;
|
|
@@ -2788,19 +2885,64 @@ ${body}
|
|
|
2788
2885
|
* to go on.
|
|
2789
2886
|
*/
|
|
2790
2887
|
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2888
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2889
|
+
watch.parkedOn = void 0;
|
|
2890
|
+
watch.parkedEnd = void 0;
|
|
2891
|
+
watch.series = false;
|
|
2892
|
+
const stopping = watch.pending !== void 0 || watch.stop !== void 0;
|
|
2893
|
+
const answer = stopping ? refusalOf(ask) : reply;
|
|
2791
2894
|
const server = await this.host.server(input.actor);
|
|
2792
2895
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2793
|
-
return this.
|
|
2896
|
+
if (stopping) return this.stopAfterRestart(watch, runId, input, handle, ask, answer, spent);
|
|
2897
|
+
return this.continueAfterRestart(runId, input, server, ask, answer, spent, handle.startedAt);
|
|
2794
2898
|
}
|
|
2795
2899
|
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2796
|
-
await live.turn.decide(ask,
|
|
2900
|
+
await live.turn.decide(ask, answer);
|
|
2797
2901
|
return handle;
|
|
2798
2902
|
}
|
|
2799
|
-
|
|
2903
|
+
/**
|
|
2904
|
+
* A stopping turn whose OpenCode server restarted while it waited: there is no session left to
|
|
2905
|
+
* abort, and none is opened or prompted. The ask is settled as refused and the turn ends stopped.
|
|
2906
|
+
*/
|
|
2907
|
+
async stopAfterRestart(watch, runId, input, handle, ask, answer, spent) {
|
|
2908
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2909
|
+
await live.turn.decide(ask, answer, {
|
|
2910
|
+
tellOpenCode: false
|
|
2911
|
+
});
|
|
2912
|
+
const reason = watch.pending ?? watch.stop?.reason ?? "stopped";
|
|
2913
|
+
if (watch.stop === void 0) {
|
|
2914
|
+
const stop = {
|
|
2915
|
+
reason,
|
|
2916
|
+
counts: this.counts(watch)
|
|
2917
|
+
};
|
|
2918
|
+
watch.stop = stop;
|
|
2919
|
+
this.hook("onStop", {
|
|
2920
|
+
turnId: runId,
|
|
2921
|
+
reason,
|
|
2922
|
+
counts: stop.counts
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
watch.aborted = true;
|
|
2926
|
+
watch.pending = void 0;
|
|
2927
|
+
live.turn.handle({
|
|
2928
|
+
type: "session.execution.interrupted",
|
|
2929
|
+
data: {
|
|
2930
|
+
sessionID: handle.sessionId
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
return handle;
|
|
2934
|
+
}
|
|
2935
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent, startedAt) {
|
|
2800
2936
|
const approved = "approved" in reply && reply.approved === true;
|
|
2801
2937
|
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.`;
|
|
2802
2938
|
const thread = await this.store.getThread(input.threadId);
|
|
2803
|
-
const
|
|
2939
|
+
const created = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2940
|
+
const handle = {
|
|
2941
|
+
...created,
|
|
2942
|
+
...startedAt !== void 0 ? {
|
|
2943
|
+
startedAt
|
|
2944
|
+
} : {}
|
|
2945
|
+
};
|
|
2804
2946
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2805
2947
|
this.drop(runId);
|
|
2806
2948
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
@@ -2885,7 +3027,11 @@ ${body}
|
|
|
2885
3027
|
this.grants.set(runId, grants);
|
|
2886
3028
|
}, "onGranted")
|
|
2887
3029
|
});
|
|
2888
|
-
const
|
|
3030
|
+
const sessionId = handle.sessionId;
|
|
3031
|
+
const stop = await this.events.listen(server.key, server.client, sessionId, (event) => {
|
|
3032
|
+
turn.handle(event);
|
|
3033
|
+
this.sawEvent(runId, sessionId, event);
|
|
3034
|
+
});
|
|
2889
3035
|
const live = {
|
|
2890
3036
|
turn,
|
|
2891
3037
|
input,
|
|
@@ -2894,6 +3040,15 @@ ${body}
|
|
|
2894
3040
|
stop
|
|
2895
3041
|
};
|
|
2896
3042
|
this.live.set(runId, live);
|
|
3043
|
+
this.owners.set(ownerKey(handle), runId);
|
|
3044
|
+
const watch = this.watches.get(runId);
|
|
3045
|
+
if (watch !== void 0 && watch.handle.sessionId !== sessionId) {
|
|
3046
|
+
watch.handle = {
|
|
3047
|
+
...handle,
|
|
3048
|
+
startedAt: watch.startedAt
|
|
3049
|
+
};
|
|
3050
|
+
watch.ending = false;
|
|
3051
|
+
}
|
|
2897
3052
|
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2898
3053
|
return live;
|
|
2899
3054
|
}
|
|
@@ -2902,11 +3057,46 @@ ${body}
|
|
|
2902
3057
|
this.live.get(runId)?.stop();
|
|
2903
3058
|
this.live.delete(runId);
|
|
2904
3059
|
}
|
|
2905
|
-
/**
|
|
3060
|
+
/**
|
|
3061
|
+
* Stop following a run and drop the approvals it was granted: it is over. Returns the turn this
|
|
3062
|
+
* process executed, now settled (no stop reaches it any more), for its `onTurnEnd`.
|
|
3063
|
+
*/
|
|
2906
3064
|
forget(runId) {
|
|
2907
3065
|
this.drop(runId);
|
|
3066
|
+
this.releasedUsage.delete(runId);
|
|
2908
3067
|
for (const grant of this.grants.get(runId) ?? []) this.spent.delete(grant.id);
|
|
2909
3068
|
this.grants.delete(runId);
|
|
3069
|
+
const watch = this.watches.get(runId);
|
|
3070
|
+
if (watch === void 0) return void 0;
|
|
3071
|
+
watch.settled = true;
|
|
3072
|
+
this.watches.delete(runId);
|
|
3073
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3074
|
+
return watch;
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* A run that ended without settling here (cancelled while this process executed it; whoever
|
|
3078
|
+
* cancelled settled it): stop following it, and end the turn this process executed.
|
|
3079
|
+
*/
|
|
3080
|
+
async abandon(runId) {
|
|
3081
|
+
const watch = this.forget(runId);
|
|
3082
|
+
if (watch !== void 0) this.ended(watch, "interrupted", void 0);
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Let go of a turn this process executed without settling it here: it was cancelled while parked
|
|
3086
|
+
* (its session ended under it, `interrupted`), another worker took it over, or this process shuts
|
|
3087
|
+
* down (`released`). The per-turn state goes, `onTurnEnd` fires once, and a later stop of the turn
|
|
3088
|
+
* here is ignored. Nothing settles the run here afterwards: whoever answered or cancelled it does.
|
|
3089
|
+
*/
|
|
3090
|
+
release(runId, status) {
|
|
3091
|
+
const watch = this.watches.get(runId);
|
|
3092
|
+
if (watch === void 0) return;
|
|
3093
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3094
|
+
if (seen !== void 0) this.keepReleasedUsage(runId, seen);
|
|
3095
|
+
this.drop(runId);
|
|
3096
|
+
watch.settled = true;
|
|
3097
|
+
this.watches.delete(runId);
|
|
3098
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3099
|
+
this.ended(watch, status, void 0);
|
|
2910
3100
|
}
|
|
2911
3101
|
// ---- settle --------------------------------------------------------------------------------
|
|
2912
3102
|
/** A person was asked something: tell the host (cards in other channels, wake-ups). */
|
|
@@ -2979,7 +3169,7 @@ ${body}
|
|
|
2979
3169
|
* two milestones.
|
|
2980
3170
|
*/
|
|
2981
3171
|
spentBy(runId, spent) {
|
|
2982
|
-
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3172
|
+
const seen = this.live.get(runId)?.turn.totalUsage() ?? this.releasedUsage.get(runId);
|
|
2983
3173
|
if (spent === void 0) return seen ?? emptyUsage();
|
|
2984
3174
|
if (seen === void 0) return spent;
|
|
2985
3175
|
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
@@ -2989,13 +3179,13 @@ ${body}
|
|
|
2989
3179
|
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2990
3180
|
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2991
3181
|
*/
|
|
2992
|
-
async settle(runId, input,
|
|
3182
|
+
async settle(runId, input, ended, durationMs, spent) {
|
|
3183
|
+
const outcome = this.withStop(ended, this.watches.get(runId));
|
|
2993
3184
|
if (outcome.status === "failed") {
|
|
2994
|
-
|
|
2995
|
-
return;
|
|
3185
|
+
return this.settleFailed(runId, input, outcome.error, durationMs, spent, outcome.stop);
|
|
2996
3186
|
}
|
|
2997
3187
|
const usage = this.spentBy(runId, spent);
|
|
2998
|
-
this.forget(runId);
|
|
3188
|
+
const watch = this.forget(runId);
|
|
2999
3189
|
const writer = await this.sink.open(runId);
|
|
3000
3190
|
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
3001
3191
|
await this.amend(writer, result);
|
|
@@ -3030,22 +3220,30 @@ ${body}
|
|
|
3030
3220
|
});
|
|
3031
3221
|
}
|
|
3032
3222
|
await this.settled(result);
|
|
3223
|
+
if (watch !== void 0) this.ended(watch, outcome.status, outcome.status === "succeeded" ? outcome.stop : void 0);
|
|
3224
|
+
return outcome;
|
|
3033
3225
|
}
|
|
3034
|
-
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
3226
|
+
async settleFailed(runId, input, error, durationMs = 0, spent, stop) {
|
|
3035
3227
|
const usage = this.spentBy(runId, spent);
|
|
3036
|
-
this.forget(runId);
|
|
3228
|
+
const watch = this.forget(runId);
|
|
3229
|
+
const stopped = stop ?? watch?.stop;
|
|
3037
3230
|
const writer = await this.sink.open(runId);
|
|
3038
3231
|
const result = await this.runResult(runId, input, {
|
|
3039
3232
|
status: "failed",
|
|
3040
|
-
error
|
|
3233
|
+
error,
|
|
3234
|
+
...stopped !== void 0 ? {
|
|
3235
|
+
stop: stopped
|
|
3236
|
+
} : {}
|
|
3041
3237
|
}, durationMs, usage);
|
|
3042
3238
|
const worded = (await this.amend(writer, result)).error;
|
|
3043
|
-
const message = worded ?? (0, import_nestjs_agent_core5.streamFailure)(new Error(error)).message;
|
|
3044
|
-
|
|
3239
|
+
const message = worded ?? (stopped !== void 0 ? error : (0, import_nestjs_agent_core5.streamFailure)(new Error(error)).message);
|
|
3240
|
+
const code = stopped !== void 0 ? STOPPED_ERROR_CODE : "opencode_failed";
|
|
3241
|
+
if (stopped !== void 0) this.logger.log(`agent run ${runId} stopped: ${stopped.reason}`);
|
|
3242
|
+
else this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
3045
3243
|
(0, import_nestjs_agent_core5.publishAgentRunFailed)({
|
|
3046
3244
|
runId,
|
|
3047
3245
|
threadId: input.threadId,
|
|
3048
|
-
code
|
|
3246
|
+
code,
|
|
3049
3247
|
message: error,
|
|
3050
3248
|
durationMs,
|
|
3051
3249
|
context: (0, import_nestjs_agent_core5.agentRunContext)(input)
|
|
@@ -3054,23 +3252,370 @@ ${body}
|
|
|
3054
3252
|
runId,
|
|
3055
3253
|
status: "failed",
|
|
3056
3254
|
durationMs,
|
|
3057
|
-
errorCode:
|
|
3255
|
+
errorCode: code,
|
|
3058
3256
|
errorMessage: error
|
|
3059
3257
|
});
|
|
3060
3258
|
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, import_nestjs_agent_core5.RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
|
|
3061
3259
|
await this.handoff(writer, input, runId, "failed", error);
|
|
3062
3260
|
await (0, import_nestjs_agent_core5.releaseThreadRun)(this.store, input.threadId, runId);
|
|
3063
3261
|
await writer.fail({
|
|
3064
|
-
code
|
|
3262
|
+
code,
|
|
3065
3263
|
message
|
|
3066
3264
|
});
|
|
3067
3265
|
await this.settled({
|
|
3068
3266
|
...result,
|
|
3069
3267
|
outcome: {
|
|
3070
3268
|
status: "failed",
|
|
3071
|
-
error: worded ?? error
|
|
3269
|
+
error: worded ?? error,
|
|
3270
|
+
...stopped !== void 0 ? {
|
|
3271
|
+
stop: stopped
|
|
3272
|
+
} : {}
|
|
3273
|
+
}
|
|
3274
|
+
});
|
|
3275
|
+
if (watch !== void 0) this.ended(watch, "failed", stopped);
|
|
3276
|
+
return {
|
|
3277
|
+
status: "failed",
|
|
3278
|
+
error,
|
|
3279
|
+
...stopped !== void 0 ? {
|
|
3280
|
+
stop: stopped
|
|
3281
|
+
} : {}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
// ---- limits, stops and the turn's lifecycle -------------------------------------------------
|
|
3285
|
+
/**
|
|
3286
|
+
* Stop a turn's own work: abort its session so OpenCode starts no new round, only while the turn
|
|
3287
|
+
* still owns the session (a thread's session is reused by its next turn) — checked immediately
|
|
3288
|
+
* before the abort. Acts in the process executing the turn; see {@link OpenCodeStopResult}. The
|
|
3289
|
+
* turn ends failed with `outcome.stop` (code `opencode_stopped`), and `onStop` is called.
|
|
3290
|
+
*/
|
|
3291
|
+
stopTurn(turnId, reason) {
|
|
3292
|
+
return this.requestStop(turnId, reason);
|
|
3293
|
+
}
|
|
3294
|
+
/** The turn this process executes, made known here on its first step in this process. */
|
|
3295
|
+
async adopt(runId, input, handle, resumed, counts, startedAt) {
|
|
3296
|
+
const known = this.watches.get(runId);
|
|
3297
|
+
if (known !== void 0) return known;
|
|
3298
|
+
const watch = {
|
|
3299
|
+
runId,
|
|
3300
|
+
input,
|
|
3301
|
+
handle,
|
|
3302
|
+
limits: this.limitsFor(input),
|
|
3303
|
+
startedAt: handle.startedAt ?? startedAt ?? Date.now(),
|
|
3304
|
+
steps: counts?.steps ?? 0,
|
|
3305
|
+
compactions: counts?.compactions ?? 0,
|
|
3306
|
+
observing: false,
|
|
3307
|
+
ending: false,
|
|
3308
|
+
settled: false,
|
|
3309
|
+
aborted: false,
|
|
3310
|
+
checking: false,
|
|
3311
|
+
lastRead: 0
|
|
3312
|
+
};
|
|
3313
|
+
this.watches.set(runId, watch);
|
|
3314
|
+
await this.hook("onTurnStart", {
|
|
3315
|
+
turnId: runId,
|
|
3316
|
+
threadId: input.threadId,
|
|
3317
|
+
sessionId: handle.sessionId,
|
|
3318
|
+
resumed
|
|
3319
|
+
});
|
|
3320
|
+
return watch;
|
|
3321
|
+
}
|
|
3322
|
+
limitsFor(input) {
|
|
3323
|
+
const limits = this.settings.limits;
|
|
3324
|
+
return cleanLimits(typeof limits === "function" ? limits(input) : limits);
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* One event of the turn's session: counted for the limits, and told to `onTurnEvent`. While the
|
|
3328
|
+
* turn is parked on a person here, it may also say the turn went on without this process
|
|
3329
|
+
* ({@link wentOnElsewhere}).
|
|
3330
|
+
*/
|
|
3331
|
+
sawEvent(runId, sessionId, event) {
|
|
3332
|
+
if (sessionOf(event) !== sessionId) return;
|
|
3333
|
+
const watch = this.watches.get(runId);
|
|
3334
|
+
if (watch === void 0 || watch.settled || watch.handle.sessionId !== sessionId) return;
|
|
3335
|
+
if (watch.parkedOn !== void 0 && this.wentOnElsewhere(watch, event)) return;
|
|
3336
|
+
let reached;
|
|
3337
|
+
if (event.type.startsWith("session.execution.")) watch.ending = true;
|
|
3338
|
+
else if (event.type === "session.step.started") {
|
|
3339
|
+
watch.steps += 1;
|
|
3340
|
+
if (passed(watch.steps, watch.limits.steps)) reached = "steps";
|
|
3341
|
+
} else if (event.type === "session.compaction.started") {
|
|
3342
|
+
watch.compactions += 1;
|
|
3343
|
+
if (passed(watch.compactions, watch.limits.compactions)) reached = "compactions";
|
|
3344
|
+
}
|
|
3345
|
+
this.hook("onTurnEvent", {
|
|
3346
|
+
turnId: runId,
|
|
3347
|
+
type: event.type,
|
|
3348
|
+
sessionId
|
|
3349
|
+
});
|
|
3350
|
+
if (reached !== void 0) void this.limitStop(runId, reached);
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Whether a turn parked here went on without this process, which then lets it go. While a tool of
|
|
3354
|
+
* the step waits on a person, OpenCode keeps running the step's other tools, so their events say
|
|
3355
|
+
* nothing. What does: a new round (`session.step.started`, which cannot begin while the step
|
|
3356
|
+
* waits), or the store showing the parked ask decided by someone else (another worker answered
|
|
3357
|
+
* it, or a cancel elsewhere settled it) — this process clears the park before it answers. When
|
|
3358
|
+
* the session's execution ends while parked, the store is asked again for a while, as a cancel
|
|
3359
|
+
* elsewhere settles the ask just after it interrupted the session; with the ask still open, the
|
|
3360
|
+
* turn stays here for its answer.
|
|
3361
|
+
*/
|
|
3362
|
+
wentOnElsewhere(watch, event) {
|
|
3363
|
+
if (event.type === "session.step.started") {
|
|
3364
|
+
this.release(watch.runId, "released");
|
|
3365
|
+
return true;
|
|
3366
|
+
}
|
|
3367
|
+
const ended = event.type.startsWith("session.execution.");
|
|
3368
|
+
if (ended) watch.parkedEnd = event.type === "session.execution.interrupted" ? "interrupted" : "released";
|
|
3369
|
+
this.checkParked(watch, ended);
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Ask the store whether the parked ask was decided elsewhere, and let the turn go when it was —
|
|
3374
|
+
* one read at a time, at most one every {@link PARKED_READ_GAP_MS}. `series`: the session's
|
|
3375
|
+
* execution ended, so the store is asked again over {@link parkedRechecksMs}. An execution that
|
|
3376
|
+
* was interrupted under a parked turn means a cancel elsewhere: when the store cannot say (it has
|
|
3377
|
+
* no `toolCallApproval`, the read fails, the ask is not on file) past the first recheck, or the
|
|
3378
|
+
* rechecks run out with the ask still open, the turn is let go as `interrupted` anyway.
|
|
3379
|
+
*/
|
|
3380
|
+
checkParked(watch, series) {
|
|
3381
|
+
if (series) watch.series = true;
|
|
3382
|
+
if (watch.checking) {
|
|
3383
|
+
watch.again = true;
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3386
|
+
watch.checking = true;
|
|
3387
|
+
void this.runParkedChecks(watch);
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* The checks of {@link checkParked}. `checking` is cleared in the same step the loop returns in,
|
|
3391
|
+
* so a check asked for afterwards starts a new loop; one asked for while it ran (`again`) but not
|
|
3392
|
+
* read because the loop returned starts one too.
|
|
3393
|
+
*/
|
|
3394
|
+
async runParkedChecks(watch) {
|
|
3395
|
+
try {
|
|
3396
|
+
await this.parkedChecks(watch);
|
|
3397
|
+
} finally {
|
|
3398
|
+
watch.checking = false;
|
|
3399
|
+
if (watch.again && !watch.settled && watch.parkedOn !== void 0) {
|
|
3400
|
+
watch.checking = true;
|
|
3401
|
+
void this.runParkedChecks(watch);
|
|
3072
3402
|
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
async parkedChecks(watch) {
|
|
3406
|
+
const ask = watch.parkedOn;
|
|
3407
|
+
const gone = /* @__PURE__ */ __name(() => watch.settled || ask === void 0 || watch.parkedOn !== ask, "gone");
|
|
3408
|
+
let step = 0;
|
|
3409
|
+
for (; ; ) {
|
|
3410
|
+
watch.again = false;
|
|
3411
|
+
const wait = watch.lastRead + PARKED_READ_GAP_MS - Date.now();
|
|
3412
|
+
if (wait > 0) await sleep(wait);
|
|
3413
|
+
if (gone()) return;
|
|
3414
|
+
const answer = await this.parkedAsk(ask);
|
|
3415
|
+
watch.lastRead = Date.now();
|
|
3416
|
+
if (gone()) return;
|
|
3417
|
+
if (answer === "decided") {
|
|
3418
|
+
this.release(watch.runId, watch.parkedEnd ?? "released");
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
if (watch.series) {
|
|
3422
|
+
const cancelled = watch.parkedEnd === "interrupted";
|
|
3423
|
+
if (cancelled && answer === "unknown" && step > 0) {
|
|
3424
|
+
this.release(watch.runId, "interrupted");
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
const gap = this.parkedRechecksMs[step];
|
|
3428
|
+
step += 1;
|
|
3429
|
+
if (gap === void 0) {
|
|
3430
|
+
if (cancelled) this.release(watch.runId, "interrupted");
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
await sleep(gap);
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
if (!watch.again) return;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
/** What the store says of an ask: decided, still open, or nothing it can tell. */
|
|
3440
|
+
async parkedAsk(id) {
|
|
3441
|
+
if (this.store.toolCallApproval === void 0) return "unknown";
|
|
3442
|
+
try {
|
|
3443
|
+
const state = await this.store.toolCallApproval(id);
|
|
3444
|
+
if (state == null) return "unknown";
|
|
3445
|
+
return state.status === "pending_approval" ? "open" : "decided";
|
|
3446
|
+
} catch {
|
|
3447
|
+
return "unknown";
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* The waits between the store's rereads of a parked ask after its execution ended (cumulative
|
|
3452
|
+
* 0.25, 1.25, 4.25, 14.25 and 44.25 seconds after the first). Overridden by tests only.
|
|
3453
|
+
*
|
|
3454
|
+
* @internal
|
|
3455
|
+
*/
|
|
3456
|
+
parkedRechecksMs = PARKED_RECHECKS_MS;
|
|
3457
|
+
/** A limit's stop: what it did is the engine's to log, there is no caller to tell. */
|
|
3458
|
+
async limitStop(runId, reason) {
|
|
3459
|
+
const result = await this.requestStop(runId, reason);
|
|
3460
|
+
if (result === "unconfirmed") this.logger.warn(`run ${runId}: passed its ${reason} limit, but its owner could not be read`);
|
|
3461
|
+
}
|
|
3462
|
+
/** The wall-clock bound of an observation: a timer for what is left of it, if anything. */
|
|
3463
|
+
watchClock(watch) {
|
|
3464
|
+
const max = watch.limits.wallClockMs;
|
|
3465
|
+
if (max === void 0) return void 0;
|
|
3466
|
+
const left = watch.startedAt + max - Date.now();
|
|
3467
|
+
if (left <= 0) {
|
|
3468
|
+
watch.pending ??= "wall-clock";
|
|
3469
|
+
return void 0;
|
|
3470
|
+
}
|
|
3471
|
+
const timer = setTimeout(() => void this.limitStop(watch.runId, "wall-clock"), left);
|
|
3472
|
+
timer.unref?.();
|
|
3473
|
+
return timer;
|
|
3474
|
+
}
|
|
3475
|
+
counts(watch) {
|
|
3476
|
+
return {
|
|
3477
|
+
steps: watch.steps,
|
|
3478
|
+
compactions: watch.compactions,
|
|
3479
|
+
elapsedMs: Date.now() - watch.startedAt
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
async requestStop(runId, reason) {
|
|
3483
|
+
const watch = this.watches.get(runId);
|
|
3484
|
+
if (watch === void 0 || watch.settled || watch.stop !== void 0) return "ignored";
|
|
3485
|
+
const live = this.live.get(runId);
|
|
3486
|
+
if (watch.parkedOn !== void 0) {
|
|
3487
|
+
watch.pending ??= reason;
|
|
3488
|
+
return "pending";
|
|
3489
|
+
}
|
|
3490
|
+
if (watch.ending || live?.turn.finished === true) return this.landed(watch, reason);
|
|
3491
|
+
if (!watch.observing || live === void 0) {
|
|
3492
|
+
watch.pending ??= reason;
|
|
3493
|
+
return "pending";
|
|
3494
|
+
}
|
|
3495
|
+
watch.pending = void 0;
|
|
3496
|
+
let active;
|
|
3497
|
+
try {
|
|
3498
|
+
active = await this.activeRun(watch.input.threadId);
|
|
3499
|
+
} catch (error) {
|
|
3500
|
+
this.logger.warn(`run ${runId}: could not read the thread's run: ${errorText(error, "error")}`);
|
|
3501
|
+
return "unconfirmed";
|
|
3502
|
+
}
|
|
3503
|
+
if (watch.settled || watch.stop !== void 0 || this.live.get(runId) !== live) return "ignored";
|
|
3504
|
+
if (active !== null && active !== runId) return "ignored";
|
|
3505
|
+
if (this.owners.get(ownerKey(watch.handle)) !== runId) return "ignored";
|
|
3506
|
+
if (watch.ending || live.turn.finished) return this.landed(watch, reason);
|
|
3507
|
+
const stop = {
|
|
3508
|
+
reason,
|
|
3509
|
+
counts: this.counts(watch)
|
|
3510
|
+
};
|
|
3511
|
+
watch.stop = stop;
|
|
3512
|
+
watch.aborted = true;
|
|
3513
|
+
try {
|
|
3514
|
+
await live.client.session.interrupt({
|
|
3515
|
+
sessionID: watch.handle.sessionId
|
|
3516
|
+
});
|
|
3517
|
+
} catch (error) {
|
|
3518
|
+
this.logger.warn(`run ${runId}: could not abort the session: ${errorText(error, "error")}`);
|
|
3519
|
+
}
|
|
3520
|
+
this.hook("onStop", {
|
|
3521
|
+
turnId: runId,
|
|
3522
|
+
reason,
|
|
3523
|
+
counts: stop.counts
|
|
3073
3524
|
});
|
|
3525
|
+
return "aborted";
|
|
3526
|
+
}
|
|
3527
|
+
/** A stop that arrived while the answer settles: recorded and reported, the answer kept. */
|
|
3528
|
+
landed(watch, reason) {
|
|
3529
|
+
const stop = {
|
|
3530
|
+
reason,
|
|
3531
|
+
counts: this.counts(watch)
|
|
3532
|
+
};
|
|
3533
|
+
watch.stop = stop;
|
|
3534
|
+
watch.pending = void 0;
|
|
3535
|
+
this.hook("onStop", {
|
|
3536
|
+
turnId: watch.runId,
|
|
3537
|
+
reason,
|
|
3538
|
+
counts: stop.counts
|
|
3539
|
+
});
|
|
3540
|
+
return "settling";
|
|
3541
|
+
}
|
|
3542
|
+
/** The milestone as the runner journals it: the limits' counts on an ask, the stop on an end. */
|
|
3543
|
+
marked(watch, milestone) {
|
|
3544
|
+
if (milestone.kind === "finished") return {
|
|
3545
|
+
...milestone,
|
|
3546
|
+
outcome: this.withStop(milestone.outcome, watch)
|
|
3547
|
+
};
|
|
3548
|
+
if (milestone.progress === void 0) return milestone;
|
|
3549
|
+
const counts = {
|
|
3550
|
+
steps: watch.steps,
|
|
3551
|
+
compactions: watch.compactions
|
|
3552
|
+
};
|
|
3553
|
+
return {
|
|
3554
|
+
...milestone,
|
|
3555
|
+
progress: {
|
|
3556
|
+
...milestone.progress,
|
|
3557
|
+
counts
|
|
3558
|
+
}
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* How a turn a stop landed on ends. An answer that settled anyway is kept, with the stop on it; an
|
|
3563
|
+
* execution that failed on its own keeps its error, with the stop on it; an execution the stop
|
|
3564
|
+
* aborted ends failed with the stop's own text. One interrupted by a person's cancel stays so.
|
|
3565
|
+
*/
|
|
3566
|
+
withStop(outcome, watch) {
|
|
3567
|
+
const stop = watch?.stop;
|
|
3568
|
+
if (stop === void 0) return outcome;
|
|
3569
|
+
if (outcome.status !== "interrupted") return outcome.stop !== void 0 ? outcome : {
|
|
3570
|
+
...outcome,
|
|
3571
|
+
stop
|
|
3572
|
+
};
|
|
3573
|
+
if (!watch?.aborted) return outcome;
|
|
3574
|
+
return {
|
|
3575
|
+
status: "failed",
|
|
3576
|
+
error: stopErrorText(stop),
|
|
3577
|
+
stop
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
3580
|
+
/** `onTurnEnd`, once per turn this process executed; not waited for. */
|
|
3581
|
+
ended(watch, status, stop) {
|
|
3582
|
+
this.hook("onTurnEnd", {
|
|
3583
|
+
turnId: watch.runId,
|
|
3584
|
+
status,
|
|
3585
|
+
...stop !== void 0 ? {
|
|
3586
|
+
stop
|
|
3587
|
+
} : {}
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
/**
|
|
3591
|
+
* A lifecycle hook, from the settings and the host, called at once. The promise settles when both
|
|
3592
|
+
* have, or after `hookTimeoutMs` (default 5 s) — only `onTurnStart` is waited for. Errors are
|
|
3593
|
+
* logged, never the turn's.
|
|
3594
|
+
*/
|
|
3595
|
+
hook(name, event) {
|
|
3596
|
+
const calls = [];
|
|
3597
|
+
for (const target of [
|
|
3598
|
+
this.settings,
|
|
3599
|
+
this.host
|
|
3600
|
+
]) {
|
|
3601
|
+
const fn = target[name];
|
|
3602
|
+
if (typeof fn !== "function") continue;
|
|
3603
|
+
calls.push(Promise.resolve().then(() => fn.call(target, event)).catch((error) => this.logger.warn(`${name} failed: ${errorText(error, "error")}`)));
|
|
3604
|
+
}
|
|
3605
|
+
if (calls.length === 0) return Promise.resolve();
|
|
3606
|
+
const ms = this.settings.hookTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
|
|
3607
|
+
let timer;
|
|
3608
|
+
const late = new Promise((resolve) => {
|
|
3609
|
+
timer = setTimeout(() => {
|
|
3610
|
+
this.logger.warn(`${name} took longer than ${ms}ms; the turn went on`);
|
|
3611
|
+
resolve();
|
|
3612
|
+
}, ms);
|
|
3613
|
+
timer.unref?.();
|
|
3614
|
+
});
|
|
3615
|
+
return Promise.race([
|
|
3616
|
+
Promise.all(calls).then(() => void 0),
|
|
3617
|
+
late
|
|
3618
|
+
]).finally(() => clearTimeout(timer));
|
|
3074
3619
|
}
|
|
3075
3620
|
/** Starts the next queued message of the thread; set by the runner (it owns `start`). */
|
|
3076
3621
|
startNext;
|
|
@@ -3133,6 +3678,28 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
3133
3678
|
Object
|
|
3134
3679
|
])
|
|
3135
3680
|
], OpenCodeTurns);
|
|
3681
|
+
function passed(count, limit) {
|
|
3682
|
+
return limit !== void 0 && count > limit;
|
|
3683
|
+
}
|
|
3684
|
+
__name(passed, "passed");
|
|
3685
|
+
function sleep(ms) {
|
|
3686
|
+
return new Promise((resolve) => setTimeout(resolve, ms).unref?.());
|
|
3687
|
+
}
|
|
3688
|
+
__name(sleep, "sleep");
|
|
3689
|
+
function refusalOf(ask) {
|
|
3690
|
+
return ask.kind === "approval" ? {
|
|
3691
|
+
approved: false,
|
|
3692
|
+
reason: "The turn was stopped."
|
|
3693
|
+
} : {
|
|
3694
|
+
answers: {},
|
|
3695
|
+
skipped: true
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
__name(refusalOf, "refusalOf");
|
|
3699
|
+
function ownerKey(handle) {
|
|
3700
|
+
return `${handle.serverKey}|${handle.sessionId}`;
|
|
3701
|
+
}
|
|
3702
|
+
__name(ownerKey, "ownerKey");
|
|
3136
3703
|
function titleOf(text) {
|
|
3137
3704
|
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
3138
3705
|
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
|
|
@@ -3306,7 +3873,7 @@ var OpenCodeAgentRunner = class {
|
|
|
3306
3873
|
await this.turns.interrupt(input).catch(() => void 0);
|
|
3307
3874
|
await this.turns.settle(runId, input, {
|
|
3308
3875
|
status: "interrupted"
|
|
3309
|
-
}, Date.now() - started);
|
|
3876
|
+
}, Date.now() - started, spent);
|
|
3310
3877
|
return;
|
|
3311
3878
|
}
|
|
3312
3879
|
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
@@ -3500,14 +4067,14 @@ var OpenCodeRunWorkflow = class {
|
|
|
3500
4067
|
spent = addUsage(spent, milestone.usage);
|
|
3501
4068
|
const total = spent;
|
|
3502
4069
|
if (milestone.kind === "finished") {
|
|
3503
|
-
await ctx.localStep("finish", async () => {
|
|
3504
|
-
if (await this.cancelled(ctx.runId))
|
|
3505
|
-
|
|
3506
|
-
|
|
4070
|
+
const settled = await ctx.localStep("finish", async () => {
|
|
4071
|
+
if (await this.cancelled(ctx.runId)) {
|
|
4072
|
+
await this.turns.abandon(ctx.runId);
|
|
4073
|
+
return milestone.outcome;
|
|
4074
|
+
}
|
|
4075
|
+
return this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
|
|
3507
4076
|
});
|
|
3508
|
-
return
|
|
3509
|
-
outcome: milestone.outcome.status
|
|
3510
|
-
};
|
|
4077
|
+
return resultOf(settled === true ? milestone.outcome : settled);
|
|
3511
4078
|
}
|
|
3512
4079
|
let reply;
|
|
3513
4080
|
try {
|
|
@@ -3528,13 +4095,10 @@ var OpenCodeRunWorkflow = class {
|
|
|
3528
4095
|
}
|
|
3529
4096
|
} catch (error) {
|
|
3530
4097
|
if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
|
|
3531
|
-
await ctx.localStep("fail", async () =>
|
|
3532
|
-
|
|
3533
|
-
return true;
|
|
3534
|
-
});
|
|
3535
|
-
return {
|
|
4098
|
+
const settled = await ctx.localStep("fail", async () => this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent));
|
|
4099
|
+
return settled === true ? {
|
|
3536
4100
|
outcome: "failed"
|
|
3537
|
-
};
|
|
4101
|
+
} : resultOf(settled);
|
|
3538
4102
|
}
|
|
3539
4103
|
}
|
|
3540
4104
|
async cancelled(runId) {
|
|
@@ -3560,6 +4124,16 @@ OpenCodeRunWorkflow = _ts_decorate4([
|
|
|
3560
4124
|
typeof RunGateway === "undefined" ? Object : RunGateway
|
|
3561
4125
|
])
|
|
3562
4126
|
], OpenCodeRunWorkflow);
|
|
4127
|
+
function resultOf(outcome) {
|
|
4128
|
+
const stop = outcome.status === "interrupted" ? void 0 : outcome.stop;
|
|
4129
|
+
return {
|
|
4130
|
+
outcome: outcome.status,
|
|
4131
|
+
...stop !== void 0 ? {
|
|
4132
|
+
stop: stop.reason
|
|
4133
|
+
} : {}
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
__name(resultOf, "resultOf");
|
|
3563
4137
|
|
|
3564
4138
|
// src/durable/runner.ts
|
|
3565
4139
|
function _ts_decorate5(decorators, target, key, desc) {
|