@kky42/pi-goal 1.0.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/CHANGELOG.md +112 -0
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/package.json +73 -0
- package/src/commands.ts +107 -0
- package/src/continuation-scheduler.ts +174 -0
- package/src/format.ts +232 -0
- package/src/goal-accounting.ts +128 -0
- package/src/goal-persistence.ts +73 -0
- package/src/goal-runtime-agent-handlers.ts +51 -0
- package/src/goal-runtime-controller.ts +162 -0
- package/src/goal-runtime-event-handler-types.ts +166 -0
- package/src/goal-runtime-event-handlers.ts +31 -0
- package/src/goal-runtime-event-utils.ts +93 -0
- package/src/goal-runtime-events.ts +24 -0
- package/src/goal-runtime-input-context-handlers.ts +144 -0
- package/src/goal-runtime-session-handlers.ts +131 -0
- package/src/goal-runtime-state.ts +22 -0
- package/src/goal-runtime-status.ts +62 -0
- package/src/goal-runtime-turn-handlers.ts +66 -0
- package/src/goal-state-controller.ts +210 -0
- package/src/goal-transition-effects.ts +91 -0
- package/src/goal-transition.ts +396 -0
- package/src/index.ts +9 -0
- package/src/prompts.ts +170 -0
- package/src/queued-goal-messages.ts +166 -0
- package/src/queued-goal-work.ts +96 -0
- package/src/recovery-adapters.ts +66 -0
- package/src/recovery-machine.ts +196 -0
- package/src/recovery-phase.ts +95 -0
- package/src/recovery-runtime.ts +97 -0
- package/src/recovery.ts +151 -0
- package/src/runtime-config.ts +7 -0
- package/src/stale-queued-work-guard.ts +114 -0
- package/src/stale-queued-work-obligations.ts +291 -0
- package/src/stale-queued-work-reducer.ts +483 -0
- package/src/stale-queued-work-terminal-cleanup.ts +84 -0
- package/src/stale-queued-work-types.ts +81 -0
- package/src/state.ts +404 -0
- package/src/tools.ts +101 -0
- package/src/types.ts +60 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import {
|
|
2
|
+
consumeAbortingAgentEnd,
|
|
3
|
+
consumePendingStaleAgentEnd,
|
|
4
|
+
dropActiveObligations,
|
|
5
|
+
markAllObligationsOlder,
|
|
6
|
+
obligationsForStaleAbort,
|
|
7
|
+
setAnonymousMatching,
|
|
8
|
+
} from "./stale-queued-work-obligations.js";
|
|
9
|
+
import {
|
|
10
|
+
awaitingFromCleanup,
|
|
11
|
+
cloneTerminalCleanup,
|
|
12
|
+
consumePendingStaleTurnEnd,
|
|
13
|
+
noteTerminalEvents,
|
|
14
|
+
resolveLifecycleAfterTerminalCleanup,
|
|
15
|
+
terminalCleanupHasPending,
|
|
16
|
+
} from "./stale-queued-work-terminal-cleanup.js";
|
|
17
|
+
import type {
|
|
18
|
+
AgentEndMessage,
|
|
19
|
+
StaleQueuedWorkEvent,
|
|
20
|
+
StaleQueuedWorkLifecycleKind,
|
|
21
|
+
StaleQueuedWorkPlan,
|
|
22
|
+
StaleQueuedWorkState,
|
|
23
|
+
StaleQueuedWorkTransitionResult,
|
|
24
|
+
TerminalCleanup,
|
|
25
|
+
} from "./stale-queued-work-types.js";
|
|
26
|
+
|
|
27
|
+
export type {
|
|
28
|
+
AgentEndMessage,
|
|
29
|
+
StaleQueuedWorkEffect,
|
|
30
|
+
StaleQueuedWorkEvent,
|
|
31
|
+
StaleQueuedWorkLifecycleKind,
|
|
32
|
+
StaleQueuedWorkPlan,
|
|
33
|
+
StaleQueuedWorkState,
|
|
34
|
+
StaleQueuedWorkTransitionResult,
|
|
35
|
+
} from "./stale-queued-work-types.js";
|
|
36
|
+
|
|
37
|
+
function emptyPlan(): StaleQueuedWorkPlan {
|
|
38
|
+
return { skip: false, effects: [] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function clearAccountingAbortRefreshPlan(): StaleQueuedWorkPlan {
|
|
42
|
+
return {
|
|
43
|
+
skip: false,
|
|
44
|
+
effects: [{ type: "clearAccounting" }, { type: "abort" }, { type: "refreshUi" }],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function skipClearAccountingRefreshPlan(): StaleQueuedWorkPlan {
|
|
49
|
+
return {
|
|
50
|
+
skip: true,
|
|
51
|
+
effects: [{ type: "clearAccounting" }, { type: "refreshUi" }],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function skipRefreshPlan(): StaleQueuedWorkPlan {
|
|
56
|
+
return { skip: true, effects: [{ type: "refreshUi" }] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function transition(
|
|
60
|
+
state: StaleQueuedWorkState,
|
|
61
|
+
plan: StaleQueuedWorkTransitionResult["plan"],
|
|
62
|
+
): StaleQueuedWorkTransitionResult {
|
|
63
|
+
return { state, plan };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function lifecycleKindFromState(
|
|
67
|
+
state: StaleQueuedWorkState,
|
|
68
|
+
): StaleQueuedWorkLifecycleKind {
|
|
69
|
+
return state.kind;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cloneState(state: StaleQueuedWorkState): StaleQueuedWorkState {
|
|
73
|
+
switch (state.kind) {
|
|
74
|
+
case "idle":
|
|
75
|
+
return { kind: "idle" };
|
|
76
|
+
case "observingTurn":
|
|
77
|
+
return {
|
|
78
|
+
kind: "observingTurn",
|
|
79
|
+
staleGoalIds: new Set(state.staleGoalIds),
|
|
80
|
+
hasRunnableWork: state.hasRunnableWork,
|
|
81
|
+
...(state.terminalCleanup
|
|
82
|
+
? { terminalCleanup: cloneTerminalCleanup(state.terminalCleanup) }
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
case "abortingTurn":
|
|
86
|
+
return {
|
|
87
|
+
kind: "abortingTurn",
|
|
88
|
+
activeTurnIndex: state.activeTurnIndex,
|
|
89
|
+
terminalCleanup: cloneTerminalCleanup(state.terminalCleanup),
|
|
90
|
+
};
|
|
91
|
+
case "awaitingTerminalCleanup":
|
|
92
|
+
return {
|
|
93
|
+
kind: "awaitingTerminalCleanup",
|
|
94
|
+
terminalCleanup: cloneTerminalCleanup(state.terminalCleanup),
|
|
95
|
+
};
|
|
96
|
+
default: {
|
|
97
|
+
const _exhaustive: never = state;
|
|
98
|
+
return _exhaustive;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function beginObservingFromIdleOrAwaiting(
|
|
104
|
+
state: Extract<StaleQueuedWorkState, { kind: "idle" | "awaitingTerminalCleanup" }>,
|
|
105
|
+
): Extract<StaleQueuedWorkState, { kind: "observingTurn" }> {
|
|
106
|
+
return {
|
|
107
|
+
kind: "observingTurn",
|
|
108
|
+
staleGoalIds: new Set(),
|
|
109
|
+
hasRunnableWork: false,
|
|
110
|
+
...(state.kind === "awaitingTerminalCleanup"
|
|
111
|
+
? { terminalCleanup: state.terminalCleanup }
|
|
112
|
+
: {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function finishObservingTurn(
|
|
117
|
+
state: Extract<StaleQueuedWorkState, { kind: "observingTurn" }>,
|
|
118
|
+
): StaleQueuedWorkState {
|
|
119
|
+
if (state.terminalCleanup && terminalCleanupHasPending(state.terminalCleanup)) {
|
|
120
|
+
return {
|
|
121
|
+
kind: "awaitingTerminalCleanup",
|
|
122
|
+
terminalCleanup: state.terminalCleanup,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return { kind: "idle" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function resolveCleanupAfterTerminalEvent(
|
|
129
|
+
cleanup: TerminalCleanup,
|
|
130
|
+
observing: Extract<StaleQueuedWorkState, { kind: "observingTurn" }> | null,
|
|
131
|
+
): StaleQueuedWorkState {
|
|
132
|
+
return resolveLifecycleAfterTerminalCleanup(cleanup, observing);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function reduceObservingContextAbort(
|
|
136
|
+
state: Extract<StaleQueuedWorkState, { kind: "observingTurn" }>,
|
|
137
|
+
currentTurnIndex: number | null,
|
|
138
|
+
): StaleQueuedWorkTransitionResult {
|
|
139
|
+
if (state.staleGoalIds.size === 0 || state.hasRunnableWork) {
|
|
140
|
+
if (!state.terminalCleanup) {
|
|
141
|
+
return transition(state, null);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
setAnonymousMatching(state.terminalCleanup.pendingAgentEndObligations, false);
|
|
145
|
+
return transition(
|
|
146
|
+
{
|
|
147
|
+
kind: "awaitingTerminalCleanup",
|
|
148
|
+
terminalCleanup: state.terminalCleanup,
|
|
149
|
+
},
|
|
150
|
+
null,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const pendingTurnEndIndexes = new Set(state.terminalCleanup?.pendingTurnEndIndexes ?? []);
|
|
155
|
+
const pendingAgentEndObligations = [
|
|
156
|
+
...(state.terminalCleanup?.pendingAgentEndObligations ?? []),
|
|
157
|
+
];
|
|
158
|
+
markAllObligationsOlder({ pendingTurnEndIndexes, pendingAgentEndObligations });
|
|
159
|
+
setAnonymousMatching(pendingAgentEndObligations, true);
|
|
160
|
+
noteTerminalEvents(pendingTurnEndIndexes, currentTurnIndex);
|
|
161
|
+
|
|
162
|
+
return transition(
|
|
163
|
+
{
|
|
164
|
+
kind: "abortingTurn",
|
|
165
|
+
activeTurnIndex: currentTurnIndex,
|
|
166
|
+
terminalCleanup: {
|
|
167
|
+
pendingTurnEndIndexes,
|
|
168
|
+
pendingAgentEndObligations: [
|
|
169
|
+
...pendingAgentEndObligations,
|
|
170
|
+
...obligationsForStaleAbort(state.staleGoalIds, "active"),
|
|
171
|
+
],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
clearAccountingAbortRefreshPlan(),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function consumeCleanupTurnEnd(
|
|
179
|
+
cleanup: TerminalCleanup,
|
|
180
|
+
turnIndex: number | null,
|
|
181
|
+
): boolean {
|
|
182
|
+
return consumePendingStaleTurnEnd(cleanup, turnIndex);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function consumeCleanupAgentEnd(
|
|
186
|
+
cleanup: TerminalCleanup,
|
|
187
|
+
messages: AgentEndMessage[],
|
|
188
|
+
): boolean {
|
|
189
|
+
return consumePendingStaleAgentEnd(cleanup, messages);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function releaseAbortingTurn(
|
|
193
|
+
state: Extract<StaleQueuedWorkState, { kind: "abortingTurn" }>,
|
|
194
|
+
includeRefresh: boolean,
|
|
195
|
+
): StaleQueuedWorkTransitionResult {
|
|
196
|
+
const cleanup = cloneTerminalCleanup(state.terminalCleanup);
|
|
197
|
+
const nextState = awaitingFromCleanup(cleanup);
|
|
198
|
+
const effects: StaleQueuedWorkPlan["effects"] = terminalCleanupHasPending(cleanup)
|
|
199
|
+
? includeRefresh
|
|
200
|
+
? [{ type: "clearAccounting" }, { type: "refreshUi" }]
|
|
201
|
+
: [{ type: "clearAccounting" }]
|
|
202
|
+
: includeRefresh
|
|
203
|
+
? [{ type: "refreshUi" }]
|
|
204
|
+
: [];
|
|
205
|
+
return transition(nextState, { skip: false, effects });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function finishActiveAbortingLifecycle(
|
|
209
|
+
state: Extract<StaleQueuedWorkState, { kind: "abortingTurn" }>,
|
|
210
|
+
): StaleQueuedWorkTransitionResult {
|
|
211
|
+
const cleanup = cloneTerminalCleanup(state.terminalCleanup);
|
|
212
|
+
dropActiveObligations(cleanup);
|
|
213
|
+
const nextState: StaleQueuedWorkState = terminalCleanupHasPending(cleanup)
|
|
214
|
+
? { kind: "awaitingTerminalCleanup", terminalCleanup: cleanup }
|
|
215
|
+
: { kind: "idle" };
|
|
216
|
+
return transition(nextState, skipClearAccountingRefreshPlan());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
type EventDefaultAction = "emptyPlan" | "noPlan" | "handled";
|
|
220
|
+
type LifecycleEventDefaults = Record<StaleQueuedWorkEvent["type"], EventDefaultAction>;
|
|
221
|
+
|
|
222
|
+
const IDLE_EVENT_DEFAULTS = {
|
|
223
|
+
runnableWorkStarted: "handled",
|
|
224
|
+
staleWorkStarted: "handled",
|
|
225
|
+
contextAbort: "noPlan",
|
|
226
|
+
userInputClearAbort: "emptyPlan",
|
|
227
|
+
extensionContinuationClearAbort: "emptyPlan",
|
|
228
|
+
beforeAgentStartClearAbort: "emptyPlan",
|
|
229
|
+
turnStart: "emptyPlan",
|
|
230
|
+
toolExecutionEnd: "emptyPlan",
|
|
231
|
+
sessionBeforeCompact: "emptyPlan",
|
|
232
|
+
sessionCompact: "emptyPlan",
|
|
233
|
+
turnEnd: "emptyPlan",
|
|
234
|
+
agentEnd: "emptyPlan",
|
|
235
|
+
sessionShutdown: "emptyPlan",
|
|
236
|
+
} as const satisfies LifecycleEventDefaults;
|
|
237
|
+
|
|
238
|
+
const OBSERVING_TURN_EVENT_DEFAULTS = {
|
|
239
|
+
runnableWorkStarted: "handled",
|
|
240
|
+
staleWorkStarted: "handled",
|
|
241
|
+
contextAbort: "handled",
|
|
242
|
+
userInputClearAbort: "emptyPlan",
|
|
243
|
+
extensionContinuationClearAbort: "emptyPlan",
|
|
244
|
+
beforeAgentStartClearAbort: "emptyPlan",
|
|
245
|
+
turnStart: "handled",
|
|
246
|
+
toolExecutionEnd: "emptyPlan",
|
|
247
|
+
sessionBeforeCompact: "emptyPlan",
|
|
248
|
+
sessionCompact: "emptyPlan",
|
|
249
|
+
turnEnd: "handled",
|
|
250
|
+
agentEnd: "handled",
|
|
251
|
+
sessionShutdown: "handled",
|
|
252
|
+
} as const satisfies LifecycleEventDefaults;
|
|
253
|
+
|
|
254
|
+
const ABORTING_TURN_EVENT_DEFAULTS = {
|
|
255
|
+
runnableWorkStarted: "emptyPlan",
|
|
256
|
+
staleWorkStarted: "emptyPlan",
|
|
257
|
+
contextAbort: "handled",
|
|
258
|
+
userInputClearAbort: "handled",
|
|
259
|
+
extensionContinuationClearAbort: "handled",
|
|
260
|
+
beforeAgentStartClearAbort: "handled",
|
|
261
|
+
turnStart: "handled",
|
|
262
|
+
toolExecutionEnd: "handled",
|
|
263
|
+
sessionBeforeCompact: "handled",
|
|
264
|
+
sessionCompact: "handled",
|
|
265
|
+
turnEnd: "handled",
|
|
266
|
+
agentEnd: "handled",
|
|
267
|
+
sessionShutdown: "handled",
|
|
268
|
+
} as const satisfies LifecycleEventDefaults;
|
|
269
|
+
|
|
270
|
+
const AWAITING_TERMINAL_CLEANUP_EVENT_DEFAULTS = {
|
|
271
|
+
runnableWorkStarted: "handled",
|
|
272
|
+
staleWorkStarted: "handled",
|
|
273
|
+
contextAbort: "noPlan",
|
|
274
|
+
userInputClearAbort: "emptyPlan",
|
|
275
|
+
extensionContinuationClearAbort: "emptyPlan",
|
|
276
|
+
beforeAgentStartClearAbort: "emptyPlan",
|
|
277
|
+
turnStart: "emptyPlan",
|
|
278
|
+
toolExecutionEnd: "emptyPlan",
|
|
279
|
+
sessionBeforeCompact: "emptyPlan",
|
|
280
|
+
sessionCompact: "emptyPlan",
|
|
281
|
+
turnEnd: "handled",
|
|
282
|
+
agentEnd: "handled",
|
|
283
|
+
sessionShutdown: "handled",
|
|
284
|
+
} as const satisfies LifecycleEventDefaults;
|
|
285
|
+
|
|
286
|
+
function applyDefaultTransition(
|
|
287
|
+
state: StaleQueuedWorkState,
|
|
288
|
+
event: StaleQueuedWorkEvent,
|
|
289
|
+
defaults: LifecycleEventDefaults,
|
|
290
|
+
lifecycle: StaleQueuedWorkLifecycleKind,
|
|
291
|
+
): StaleQueuedWorkTransitionResult {
|
|
292
|
+
switch (defaults[event.type]) {
|
|
293
|
+
case "emptyPlan":
|
|
294
|
+
return transition(state, emptyPlan());
|
|
295
|
+
case "noPlan":
|
|
296
|
+
return transition(state, null);
|
|
297
|
+
case "handled":
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Missing stale queued-work reducer handler for ${event.type} in ${lifecycle}`,
|
|
300
|
+
);
|
|
301
|
+
default:
|
|
302
|
+
throw new Error(
|
|
303
|
+
`Unknown stale queued-work default action for ${event.type} in ${lifecycle}`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function reduceIdleState(
|
|
309
|
+
draft: Extract<StaleQueuedWorkState, { kind: "idle" }>,
|
|
310
|
+
event: StaleQueuedWorkEvent,
|
|
311
|
+
): StaleQueuedWorkTransitionResult {
|
|
312
|
+
switch (event.type) {
|
|
313
|
+
case "runnableWorkStarted": {
|
|
314
|
+
const next = beginObservingFromIdleOrAwaiting(draft);
|
|
315
|
+
next.hasRunnableWork = true;
|
|
316
|
+
return transition(next, emptyPlan());
|
|
317
|
+
}
|
|
318
|
+
case "staleWorkStarted": {
|
|
319
|
+
const next = beginObservingFromIdleOrAwaiting(draft);
|
|
320
|
+
next.staleGoalIds.add(event.goalId);
|
|
321
|
+
return transition(next, emptyPlan());
|
|
322
|
+
}
|
|
323
|
+
default:
|
|
324
|
+
return applyDefaultTransition(draft, event, IDLE_EVENT_DEFAULTS, draft.kind);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function reduceObservingTurnState(
|
|
329
|
+
draft: Extract<StaleQueuedWorkState, { kind: "observingTurn" }>,
|
|
330
|
+
event: StaleQueuedWorkEvent,
|
|
331
|
+
): StaleQueuedWorkTransitionResult {
|
|
332
|
+
switch (event.type) {
|
|
333
|
+
case "runnableWorkStarted":
|
|
334
|
+
return transition({ ...draft, hasRunnableWork: true }, emptyPlan());
|
|
335
|
+
case "staleWorkStarted":
|
|
336
|
+
draft.staleGoalIds.add(event.goalId);
|
|
337
|
+
return transition(draft, emptyPlan());
|
|
338
|
+
case "contextAbort":
|
|
339
|
+
return reduceObservingContextAbort(draft, event.currentTurnIndex);
|
|
340
|
+
case "turnStart":
|
|
341
|
+
return transition(finishObservingTurn(draft), emptyPlan());
|
|
342
|
+
case "turnEnd": {
|
|
343
|
+
if (!draft.terminalCleanup || !consumeCleanupTurnEnd(draft.terminalCleanup, event.turnIndex)) {
|
|
344
|
+
return transition(draft, emptyPlan());
|
|
345
|
+
}
|
|
346
|
+
return transition(
|
|
347
|
+
resolveCleanupAfterTerminalEvent(draft.terminalCleanup, draft),
|
|
348
|
+
skipRefreshPlan(),
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
case "agentEnd": {
|
|
352
|
+
if (!draft.terminalCleanup || !consumeCleanupAgentEnd(draft.terminalCleanup, event.messages)) {
|
|
353
|
+
return transition(draft, emptyPlan());
|
|
354
|
+
}
|
|
355
|
+
return transition(
|
|
356
|
+
resolveCleanupAfterTerminalEvent(draft.terminalCleanup, draft),
|
|
357
|
+
skipRefreshPlan(),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
case "sessionShutdown":
|
|
361
|
+
return transition({ kind: "idle" }, emptyPlan());
|
|
362
|
+
default:
|
|
363
|
+
return applyDefaultTransition(draft, event, OBSERVING_TURN_EVENT_DEFAULTS, draft.kind);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function reduceAbortingTurnState(
|
|
368
|
+
draft: Extract<StaleQueuedWorkState, { kind: "abortingTurn" }>,
|
|
369
|
+
event: StaleQueuedWorkEvent,
|
|
370
|
+
): StaleQueuedWorkTransitionResult {
|
|
371
|
+
switch (event.type) {
|
|
372
|
+
case "contextAbort":
|
|
373
|
+
return transition(draft, clearAccountingAbortRefreshPlan());
|
|
374
|
+
case "userInputClearAbort":
|
|
375
|
+
return releaseAbortingTurn(draft, true);
|
|
376
|
+
case "extensionContinuationClearAbort":
|
|
377
|
+
case "beforeAgentStartClearAbort":
|
|
378
|
+
case "turnStart":
|
|
379
|
+
return releaseAbortingTurn(draft, false);
|
|
380
|
+
case "toolExecutionEnd":
|
|
381
|
+
case "sessionBeforeCompact":
|
|
382
|
+
case "sessionCompact":
|
|
383
|
+
return transition(draft, skipClearAccountingRefreshPlan());
|
|
384
|
+
case "turnEnd": {
|
|
385
|
+
if (event.turnIndex !== null && draft.activeTurnIndex === event.turnIndex) {
|
|
386
|
+
draft.terminalCleanup.pendingTurnEndIndexes.delete(event.turnIndex);
|
|
387
|
+
return transition(draft, skipClearAccountingRefreshPlan());
|
|
388
|
+
}
|
|
389
|
+
if (consumeCleanupTurnEnd(draft.terminalCleanup, event.turnIndex)) {
|
|
390
|
+
return transition(draft, skipRefreshPlan());
|
|
391
|
+
}
|
|
392
|
+
return transition(draft, emptyPlan());
|
|
393
|
+
}
|
|
394
|
+
case "agentEnd": {
|
|
395
|
+
const result = consumeAbortingAgentEnd(draft, event.messages);
|
|
396
|
+
if (result.consumedActive) {
|
|
397
|
+
return finishActiveAbortingLifecycle(draft);
|
|
398
|
+
}
|
|
399
|
+
if (result.consumedOlder) {
|
|
400
|
+
return transition(draft, skipRefreshPlan());
|
|
401
|
+
}
|
|
402
|
+
if (result.activePending) {
|
|
403
|
+
return transition(draft, emptyPlan());
|
|
404
|
+
}
|
|
405
|
+
return finishActiveAbortingLifecycle(draft);
|
|
406
|
+
}
|
|
407
|
+
case "sessionShutdown":
|
|
408
|
+
return transition({ kind: "idle" }, { skip: false, effects: [{ type: "clearAccounting" }] });
|
|
409
|
+
default:
|
|
410
|
+
return applyDefaultTransition(draft, event, ABORTING_TURN_EVENT_DEFAULTS, draft.kind);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function reduceAwaitingTerminalCleanupState(
|
|
415
|
+
draft: Extract<StaleQueuedWorkState, { kind: "awaitingTerminalCleanup" }>,
|
|
416
|
+
event: StaleQueuedWorkEvent,
|
|
417
|
+
): StaleQueuedWorkTransitionResult {
|
|
418
|
+
switch (event.type) {
|
|
419
|
+
case "runnableWorkStarted": {
|
|
420
|
+
const next = beginObservingFromIdleOrAwaiting(draft);
|
|
421
|
+
next.hasRunnableWork = true;
|
|
422
|
+
return transition(next, emptyPlan());
|
|
423
|
+
}
|
|
424
|
+
case "staleWorkStarted": {
|
|
425
|
+
const next = beginObservingFromIdleOrAwaiting(draft);
|
|
426
|
+
next.staleGoalIds.add(event.goalId);
|
|
427
|
+
return transition(next, emptyPlan());
|
|
428
|
+
}
|
|
429
|
+
case "turnEnd": {
|
|
430
|
+
if (!consumeCleanupTurnEnd(draft.terminalCleanup, event.turnIndex)) {
|
|
431
|
+
return transition(draft, emptyPlan());
|
|
432
|
+
}
|
|
433
|
+
return transition(
|
|
434
|
+
resolveCleanupAfterTerminalEvent(draft.terminalCleanup, null),
|
|
435
|
+
skipRefreshPlan(),
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
case "agentEnd": {
|
|
439
|
+
if (!consumeCleanupAgentEnd(draft.terminalCleanup, event.messages)) {
|
|
440
|
+
return transition(draft, emptyPlan());
|
|
441
|
+
}
|
|
442
|
+
return transition(
|
|
443
|
+
resolveCleanupAfterTerminalEvent(draft.terminalCleanup, null),
|
|
444
|
+
skipRefreshPlan(),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
case "sessionShutdown":
|
|
448
|
+
return transition({ kind: "idle" }, emptyPlan());
|
|
449
|
+
default:
|
|
450
|
+
return applyDefaultTransition(
|
|
451
|
+
draft,
|
|
452
|
+
event,
|
|
453
|
+
AWAITING_TERMINAL_CLEANUP_EVENT_DEFAULTS,
|
|
454
|
+
draft.kind,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function reduceStaleQueuedWork(
|
|
460
|
+
state: StaleQueuedWorkState,
|
|
461
|
+
event: StaleQueuedWorkEvent,
|
|
462
|
+
): StaleQueuedWorkTransitionResult {
|
|
463
|
+
const draft = cloneState(state);
|
|
464
|
+
|
|
465
|
+
switch (draft.kind) {
|
|
466
|
+
case "idle":
|
|
467
|
+
return reduceIdleState(draft, event);
|
|
468
|
+
case "observingTurn":
|
|
469
|
+
return reduceObservingTurnState(draft, event);
|
|
470
|
+
case "abortingTurn":
|
|
471
|
+
return reduceAbortingTurnState(draft, event);
|
|
472
|
+
case "awaitingTerminalCleanup":
|
|
473
|
+
return reduceAwaitingTerminalCleanupState(draft, event);
|
|
474
|
+
default: {
|
|
475
|
+
const _exhaustive: never = draft;
|
|
476
|
+
return _exhaustive;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function createInitialStaleQueuedWorkState(): StaleQueuedWorkState {
|
|
482
|
+
return { kind: "idle" };
|
|
483
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { markAllObligationsOlder } from "./stale-queued-work-obligations.js";
|
|
2
|
+
import type {
|
|
3
|
+
ObservingTurnState,
|
|
4
|
+
StaleQueuedWorkState,
|
|
5
|
+
TerminalCleanup,
|
|
6
|
+
} from "./stale-queued-work-types.js";
|
|
7
|
+
|
|
8
|
+
export function terminalCleanupHasPending(cleanup: TerminalCleanup): boolean {
|
|
9
|
+
return cleanup.pendingTurnEndIndexes.size > 0 || cleanup.pendingAgentEndObligations.length > 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function cloneTerminalCleanup(cleanup: TerminalCleanup): TerminalCleanup {
|
|
13
|
+
return {
|
|
14
|
+
pendingTurnEndIndexes: new Set(cleanup.pendingTurnEndIndexes),
|
|
15
|
+
pendingAgentEndObligations: cleanup.pendingAgentEndObligations.map((obligation) => ({
|
|
16
|
+
goalIds: new Set(obligation.goalIds),
|
|
17
|
+
acceptsAnonymous: obligation.acceptsAnonymous,
|
|
18
|
+
phase: obligation.phase,
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function noteTerminalEvents(
|
|
24
|
+
pendingTurnEndIndexes: Set<number>,
|
|
25
|
+
currentTurnIndex: number | null,
|
|
26
|
+
): void {
|
|
27
|
+
if (currentTurnIndex !== null) {
|
|
28
|
+
pendingTurnEndIndexes.add(currentTurnIndex);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function terminalCleanupFromObserving(
|
|
33
|
+
state: ObservingTurnState,
|
|
34
|
+
): { cleanup: TerminalCleanup; observing: ObservingTurnState } | null {
|
|
35
|
+
if (state.terminalCleanup === undefined) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return { cleanup: state.terminalCleanup, observing: state };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveLifecycleAfterTerminalCleanup(
|
|
42
|
+
cleanup: TerminalCleanup,
|
|
43
|
+
observing: ObservingTurnState | null,
|
|
44
|
+
): StaleQueuedWorkState {
|
|
45
|
+
const hasPending = terminalCleanupHasPending(cleanup);
|
|
46
|
+
|
|
47
|
+
if (observing) {
|
|
48
|
+
if (hasPending) {
|
|
49
|
+
return { ...observing, terminalCleanup: cleanup };
|
|
50
|
+
}
|
|
51
|
+
const { terminalCleanup: _removed, ...withoutCleanup } = observing;
|
|
52
|
+
return withoutCleanup;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasPending) {
|
|
56
|
+
return {
|
|
57
|
+
kind: "awaitingTerminalCleanup",
|
|
58
|
+
terminalCleanup: cleanup,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { kind: "idle" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function awaitingFromCleanup(cleanup: TerminalCleanup): StaleQueuedWorkState {
|
|
65
|
+
markAllObligationsOlder(cleanup);
|
|
66
|
+
if (!terminalCleanupHasPending(cleanup)) {
|
|
67
|
+
return { kind: "idle" };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
kind: "awaitingTerminalCleanup",
|
|
71
|
+
terminalCleanup: cleanup,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function consumePendingStaleTurnEnd(
|
|
76
|
+
cleanup: TerminalCleanup,
|
|
77
|
+
turnIndex: number | null,
|
|
78
|
+
): boolean {
|
|
79
|
+
if (turnIndex === null || !cleanup.pendingTurnEndIndexes.has(turnIndex)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
cleanup.pendingTurnEndIndexes.delete(turnIndex);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type StaleQueuedWorkEffect =
|
|
2
|
+
| { type: "clearAccounting" }
|
|
3
|
+
| { type: "refreshUi" }
|
|
4
|
+
| { type: "abort" };
|
|
5
|
+
|
|
6
|
+
export type StaleQueuedWorkPlan = {
|
|
7
|
+
skip: boolean;
|
|
8
|
+
effects: StaleQueuedWorkEffect[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type StaleQueuedWorkLifecycleKind =
|
|
12
|
+
| "idle"
|
|
13
|
+
| "observingTurn"
|
|
14
|
+
| "abortingTurn"
|
|
15
|
+
| "awaitingTerminalCleanup";
|
|
16
|
+
|
|
17
|
+
export type TerminalObligationPhase = "older" | "active";
|
|
18
|
+
|
|
19
|
+
/** One stale abort's pending agent_end: match goalIds, or id-less when acceptsAnonymous. */
|
|
20
|
+
export type AgentEndObligation = {
|
|
21
|
+
goalIds: Set<string>;
|
|
22
|
+
acceptsAnonymous: boolean;
|
|
23
|
+
phase: TerminalObligationPhase;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type TerminalCleanup = {
|
|
27
|
+
pendingTurnEndIndexes: Set<number>;
|
|
28
|
+
pendingAgentEndObligations: AgentEndObligation[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ObservingTurnState = {
|
|
32
|
+
kind: "observingTurn";
|
|
33
|
+
staleGoalIds: Set<string>;
|
|
34
|
+
hasRunnableWork: boolean;
|
|
35
|
+
terminalCleanup?: TerminalCleanup;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type AbortingTurnState = {
|
|
39
|
+
kind: "abortingTurn";
|
|
40
|
+
activeTurnIndex: number | null;
|
|
41
|
+
terminalCleanup: TerminalCleanup;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type AwaitingTerminalCleanupState = {
|
|
45
|
+
kind: "awaitingTerminalCleanup";
|
|
46
|
+
terminalCleanup: TerminalCleanup;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type StaleQueuedWorkState =
|
|
50
|
+
| { kind: "idle" }
|
|
51
|
+
| ObservingTurnState
|
|
52
|
+
| AbortingTurnState
|
|
53
|
+
| AwaitingTerminalCleanupState;
|
|
54
|
+
|
|
55
|
+
export type AgentEndMessage = {
|
|
56
|
+
role: string;
|
|
57
|
+
customType?: string;
|
|
58
|
+
details?: unknown;
|
|
59
|
+
content?: unknown;
|
|
60
|
+
stopReason?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type StaleQueuedWorkEvent =
|
|
64
|
+
| { type: "runnableWorkStarted" }
|
|
65
|
+
| { type: "staleWorkStarted"; goalId: string }
|
|
66
|
+
| { type: "contextAbort"; currentTurnIndex: number | null }
|
|
67
|
+
| { type: "userInputClearAbort" }
|
|
68
|
+
| { type: "extensionContinuationClearAbort" }
|
|
69
|
+
| { type: "beforeAgentStartClearAbort" }
|
|
70
|
+
| { type: "turnStart" }
|
|
71
|
+
| { type: "toolExecutionEnd" }
|
|
72
|
+
| { type: "sessionBeforeCompact" }
|
|
73
|
+
| { type: "sessionCompact" }
|
|
74
|
+
| { type: "turnEnd"; turnIndex: number | null }
|
|
75
|
+
| { type: "agentEnd"; messages: AgentEndMessage[] }
|
|
76
|
+
| { type: "sessionShutdown" };
|
|
77
|
+
|
|
78
|
+
export type StaleQueuedWorkTransitionResult = {
|
|
79
|
+
state: StaleQueuedWorkState;
|
|
80
|
+
plan: StaleQueuedWorkPlan | null;
|
|
81
|
+
};
|