killeros 2.1.26 → 2.1.28
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 +38 -0
- package/Killeros.ts +6 -13
- package/README.md +8 -10
- package/killeros/activity.ts +16 -7
- package/killeros/auto-compaction.ts +1 -2
- package/killeros/change-receipt.ts +109 -37
- package/killeros/codex-fast.ts +8 -3
- package/killeros/footer.ts +82 -31
- package/killeros/goal-interface.ts +198 -60
- package/killeros/goal-runtime.ts +192 -49
- package/killeros/goal-settlement.ts +140 -49
- package/killeros/goal-state.ts +250 -34
- package/killeros/handoff.ts +36 -4
- package/killeros/passive-git-status.ts +206 -0
- package/killeros/personal-instructions.ts +2 -3
- package/killeros/runtime.ts +51 -35
- package/killeros/shell-ui.ts +13 -4
- package/killeros/worked-for.ts +4 -3
- package/package.json +1 -1
- package/themes/killeros.json +3 -2
- package/killeros/init-evidence.ts +0 -291
- package/killeros/init-target.ts +0 -309
- package/killeros/init.ts +0 -285
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AutoCompactionGoalHandlers } from "./auto-compaction.ts";
|
|
3
3
|
import { reportError } from "./errors.ts";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
isGoalModeSupported,
|
|
6
|
+
isSavedSession,
|
|
7
|
+
pauseGoalAfterFailure,
|
|
8
|
+
pauseGoalAtTurnLimit,
|
|
9
|
+
resumeInterruptedGoalTurn,
|
|
10
|
+
scheduleGoalContinuation,
|
|
11
|
+
syncGoalUpdateTool,
|
|
12
|
+
transitionGoal,
|
|
13
|
+
} from "./goal-runtime.ts";
|
|
14
|
+
import { boundGoalText, pauseGoalState } from "./goal-state.ts";
|
|
15
|
+
import type { GoalRuntime, GoalState } from "./runtime.ts";
|
|
7
16
|
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
8
17
|
|
|
9
18
|
function pauseGoalForPossibleManualCompaction(
|
|
@@ -13,14 +22,16 @@ function pauseGoalForPossibleManualCompaction(
|
|
|
13
22
|
reason: string,
|
|
14
23
|
): void {
|
|
15
24
|
if (runtime.state?.status !== "active") return;
|
|
16
|
-
const safeReason =
|
|
25
|
+
const safeReason = boundGoalText(reason) || "the agent turn was aborted";
|
|
17
26
|
try {
|
|
18
27
|
transitionGoal(pi, runtime, "error", "paused", safeReason, {
|
|
19
28
|
resumeAfterManualCompaction: true,
|
|
29
|
+
keepTurnForRecovery: true,
|
|
30
|
+
preserveTurnAuthorization: true,
|
|
20
31
|
});
|
|
21
32
|
} catch {
|
|
22
33
|
const current = runtime.state;
|
|
23
|
-
runtime.state = current ? pauseGoalState(current, safeReason, Date.now(), true) : undefined;
|
|
34
|
+
runtime.state = current ? pauseGoalState(current, safeReason, Date.now(), true, true) : undefined;
|
|
24
35
|
syncGoalUpdateTool(pi, runtime);
|
|
25
36
|
runtime.persistenceRetryNeeded = true;
|
|
26
37
|
runtime.continuationScheduled = false;
|
|
@@ -28,7 +39,7 @@ function pauseGoalForPossibleManualCompaction(
|
|
|
28
39
|
runtime.requestRender?.();
|
|
29
40
|
}
|
|
30
41
|
ctx.ui.notify(
|
|
31
|
-
"Goal paused because the turn was aborted. If /compact is running, KillerOS will resume after Pi saves the summary. Run /goal pause to keep it paused.",
|
|
42
|
+
"Goal paused because the turn was aborted. If /compact is running, KillerOS will resume the same goal turn after Pi saves the summary. Run /goal pause to keep it paused.",
|
|
32
43
|
"warning",
|
|
33
44
|
);
|
|
34
45
|
}
|
|
@@ -36,14 +47,20 @@ function pauseGoalForPossibleManualCompaction(
|
|
|
36
47
|
function recoverGoalAfterManualCompaction(
|
|
37
48
|
pi: ExtensionAPI,
|
|
38
49
|
runtime: GoalRuntime,
|
|
39
|
-
initState: InitRuntime,
|
|
40
50
|
ctx: ExtensionContext,
|
|
41
51
|
): boolean {
|
|
42
52
|
if (runtime.state?.status !== "paused"
|
|
43
|
-
|| runtime.state.resumeAfterManualCompaction !== true
|
|
44
|
-
|
|
53
|
+
|| runtime.state.resumeAfterManualCompaction !== true) return false;
|
|
54
|
+
const state = runtime.state;
|
|
55
|
+
const sameTurn = state.turnDecision === undefined;
|
|
56
|
+
const turn = runtime.goalTurn?.turn ?? state.turns;
|
|
57
|
+
const generation = runtime.lifecycleGeneration;
|
|
58
|
+
let resumed: GoalState;
|
|
45
59
|
try {
|
|
46
|
-
transitionGoal(pi, runtime, "resume", "active", undefined, {
|
|
60
|
+
resumed = transitionGoal(pi, runtime, "resume", "active", undefined, {
|
|
61
|
+
...(sameTurn ? { resumeInterruptedTurn: true as const } : {}),
|
|
62
|
+
preserveTurnAuthorization: true,
|
|
63
|
+
});
|
|
47
64
|
} catch (error) {
|
|
48
65
|
runtime.persistenceRetryNeeded = true;
|
|
49
66
|
reportError(ctx, "Manual compaction succeeded, but the goal could not be resumed", error);
|
|
@@ -51,63 +68,93 @@ function recoverGoalAfterManualCompaction(
|
|
|
51
68
|
}
|
|
52
69
|
runtime.continuationScheduled = false;
|
|
53
70
|
runtime.automaticCompaction = undefined;
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
if (sameTurn) {
|
|
72
|
+
runtime.goalTurn = { turn, revision: runtime.state?.revision ?? state.revision };
|
|
73
|
+
runtime.goalTurnInFlight = true;
|
|
74
|
+
runtime.agentEndObserved = false;
|
|
75
|
+
if (ctx.mode !== "tui") ctx.ui.notify("Manual compaction complete. The interrupted goal turn resumed.", "info");
|
|
76
|
+
setImmediate(() => {
|
|
77
|
+
if (runtime.lifecycleGeneration !== generation || runtime.state !== resumed) return;
|
|
78
|
+
resumeInterruptedGoalTurn(pi, runtime, ctx);
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
runtime.goalTurn = undefined;
|
|
82
|
+
runtime.goalTurnInFlight = false;
|
|
83
|
+
if (scheduleGoalContinuation(pi, runtime, ctx) && ctx.mode !== "tui") ctx.ui.notify("Manual compaction complete. Goal resumed.", "info");
|
|
84
|
+
}
|
|
56
85
|
return true;
|
|
57
86
|
}
|
|
58
87
|
|
|
59
|
-
/** Resumes the paused revision after
|
|
88
|
+
/** Resumes the paused revision only after compaction and the interrupted turn have both settled. */
|
|
60
89
|
function finalizeAutomaticCompaction(
|
|
61
90
|
pi: ExtensionAPI,
|
|
62
91
|
runtime: GoalRuntime,
|
|
63
|
-
initState: InitRuntime,
|
|
64
92
|
ctx: ExtensionContext,
|
|
65
93
|
): void {
|
|
66
94
|
const recovery = runtime.automaticCompaction;
|
|
67
95
|
if (!recovery || recovery.outcome === "pending" || !recovery.turnSettled) return;
|
|
68
|
-
const skipped = recovery.outcome === "skipped";
|
|
69
|
-
runtime.automaticCompaction = undefined;
|
|
70
96
|
if (runtime.state?.status !== "paused"
|
|
71
|
-
|| runtime.state.revision !== recovery.pausedRevision
|
|
72
|
-
|
|
97
|
+
|| runtime.state.revision !== recovery.pausedRevision) {
|
|
98
|
+
runtime.automaticCompaction = undefined;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const state = runtime.state;
|
|
102
|
+
const sameTurn = recovery.resumeSameTurn;
|
|
103
|
+
const generation = runtime.lifecycleGeneration;
|
|
104
|
+
runtime.automaticCompaction = undefined;
|
|
105
|
+
let resumed: GoalState;
|
|
73
106
|
try {
|
|
74
|
-
transitionGoal(pi, runtime, "resume", "active", undefined, {
|
|
107
|
+
resumed = transitionGoal(pi, runtime, "resume", "active", undefined, {
|
|
108
|
+
...(sameTurn ? { resumeInterruptedTurn: true as const } : {}),
|
|
109
|
+
...(!sameTurn && state.turnDecision !== undefined ? { preserveTurnAuthorization: true as const } : {}),
|
|
110
|
+
});
|
|
75
111
|
} catch (error) {
|
|
76
112
|
runtime.persistenceRetryNeeded = true;
|
|
77
|
-
reportError(ctx,
|
|
78
|
-
? "Automatic compaction
|
|
113
|
+
reportError(ctx, sameTurn
|
|
114
|
+
? "Automatic compaction succeeded, but the interrupted goal turn could not be resumed"
|
|
79
115
|
: "Automatic compaction succeeded, but the goal could not be resumed", error);
|
|
80
116
|
return;
|
|
81
117
|
}
|
|
82
118
|
runtime.continuationScheduled = false;
|
|
83
|
-
|
|
119
|
+
if (sameTurn) {
|
|
120
|
+
runtime.goalTurn = { turn: recovery.turn, revision: runtime.state?.revision ?? state.revision };
|
|
121
|
+
runtime.goalTurnInFlight = false;
|
|
122
|
+
runtime.agentEndObserved = false;
|
|
123
|
+
setImmediate(() => {
|
|
124
|
+
if (runtime.lifecycleGeneration !== generation
|
|
125
|
+
|| runtime.state !== resumed
|
|
126
|
+
|| runtime.goalTurn?.turn !== recovery.turn) return;
|
|
127
|
+
runtime.goalTurnInFlight = true;
|
|
128
|
+
resumeInterruptedGoalTurn(pi, runtime, ctx);
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
runtime.goalTurn = undefined;
|
|
133
|
+
runtime.goalTurnInFlight = false;
|
|
134
|
+
if (pauseGoalAtTurnLimit(pi, runtime, ctx)) return;
|
|
135
|
+
setImmediate(() => scheduleGoalContinuation(pi, runtime, ctx, { generation, state: resumed }));
|
|
84
136
|
}
|
|
85
137
|
|
|
86
|
-
/** Records Pi's successful compaction callback and attempts guarded recovery. */
|
|
87
138
|
function completeAutomaticCompaction(
|
|
88
139
|
pi: ExtensionAPI,
|
|
89
140
|
runtime: GoalRuntime,
|
|
90
|
-
initState: InitRuntime,
|
|
91
141
|
ctx: ExtensionContext,
|
|
92
142
|
): void {
|
|
93
143
|
if (!runtime.automaticCompaction) return;
|
|
94
144
|
runtime.automaticCompaction.outcome = "completed";
|
|
95
|
-
finalizeAutomaticCompaction(pi, runtime,
|
|
145
|
+
finalizeAutomaticCompaction(pi, runtime, ctx);
|
|
96
146
|
}
|
|
97
147
|
|
|
98
|
-
/** Records Pi's expected session-too-small rejection and resumes without claiming compaction succeeded. */
|
|
99
148
|
function skipAutomaticCompaction(
|
|
100
149
|
pi: ExtensionAPI,
|
|
101
150
|
runtime: GoalRuntime,
|
|
102
|
-
initState: InitRuntime,
|
|
103
151
|
ctx: ExtensionContext,
|
|
104
152
|
): void {
|
|
105
153
|
if (!runtime.automaticCompaction) return;
|
|
106
154
|
runtime.automaticCompaction.outcome = "skipped";
|
|
107
|
-
finalizeAutomaticCompaction(pi, runtime,
|
|
155
|
+
finalizeAutomaticCompaction(pi, runtime, ctx);
|
|
108
156
|
}
|
|
109
157
|
|
|
110
|
-
/** Consumes automatic recovery and records its failure on the eligible paused goal. */
|
|
111
158
|
function stopAutomaticCompactionRecovery(
|
|
112
159
|
pi: ExtensionAPI,
|
|
113
160
|
runtime: GoalRuntime,
|
|
@@ -116,13 +163,17 @@ function stopAutomaticCompactionRecovery(
|
|
|
116
163
|
): void {
|
|
117
164
|
const recovery = runtime.automaticCompaction;
|
|
118
165
|
runtime.automaticCompaction = undefined;
|
|
119
|
-
const safeReason =
|
|
166
|
+
const safeReason = boundGoalText(reason) || "automatic compaction failed";
|
|
120
167
|
if (runtime.state?.status !== "paused" || runtime.state.revision !== recovery?.pausedRevision) return;
|
|
121
168
|
try {
|
|
122
169
|
transitionGoal(pi, runtime, "error", "paused", safeReason);
|
|
123
170
|
} catch {
|
|
124
|
-
runtime.state =
|
|
171
|
+
runtime.state = pauseGoalState(runtime.state, safeReason, Date.now());
|
|
172
|
+
syncGoalUpdateTool(pi, runtime);
|
|
125
173
|
runtime.persistenceRetryNeeded = true;
|
|
174
|
+
runtime.continuationScheduled = false;
|
|
175
|
+
runtime.goalTurn = undefined;
|
|
176
|
+
runtime.goalTurnInFlight = false;
|
|
126
177
|
runtime.requestRender?.();
|
|
127
178
|
}
|
|
128
179
|
ctx.ui.notify(
|
|
@@ -131,7 +182,6 @@ function stopAutomaticCompactionRecovery(
|
|
|
131
182
|
);
|
|
132
183
|
}
|
|
133
184
|
|
|
134
|
-
/** Leaves the goal paused when Pi rejects automatic compaction. */
|
|
135
185
|
function failAutomaticCompaction(
|
|
136
186
|
pi: ExtensionAPI,
|
|
137
187
|
runtime: GoalRuntime,
|
|
@@ -148,15 +198,32 @@ function failAutomaticCompaction(
|
|
|
148
198
|
stopAutomaticCompactionRecovery(pi, runtime, ctx, `automatic compaction failed: ${reason}`);
|
|
149
199
|
}
|
|
150
200
|
|
|
201
|
+
function settleGoalTurn(
|
|
202
|
+
pi: ExtensionAPI,
|
|
203
|
+
runtime: GoalRuntime,
|
|
204
|
+
ctx: ExtensionContext,
|
|
205
|
+
settledTurn: number,
|
|
206
|
+
): void {
|
|
207
|
+
const state = runtime.state;
|
|
208
|
+
if (state?.status !== "active") return;
|
|
209
|
+
const decision = state.turnDecision;
|
|
210
|
+
if (!decision || decision.turn !== settledTurn) {
|
|
211
|
+
pauseGoalAfterFailure(pi, runtime, ctx, "no turn decision");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (pauseGoalAtTurnLimit(pi, runtime, ctx)) return;
|
|
215
|
+
scheduleGoalContinuation(pi, runtime, ctx);
|
|
216
|
+
}
|
|
217
|
+
|
|
151
218
|
export function registerGoalSettlement(
|
|
152
219
|
pi: ExtensionAPI,
|
|
153
220
|
runtime: GoalRuntime,
|
|
154
|
-
initState: InitRuntime,
|
|
155
221
|
): AutoCompactionGoalHandlers {
|
|
156
222
|
pi.on("agent_settled", (_event, ctx) => {
|
|
157
223
|
const wasGoalTurn = runtime.goalTurnInFlight;
|
|
158
224
|
const continuationWasScheduled = runtime.continuationScheduled;
|
|
159
225
|
const agentEndObserved = runtime.agentEndObserved;
|
|
226
|
+
const settledTurn = runtime.goalTurn?.turn;
|
|
160
227
|
runtime.goalTurnInFlight = false;
|
|
161
228
|
runtime.agentEndObserved = false;
|
|
162
229
|
runtime.continuationScheduled = false;
|
|
@@ -166,6 +233,12 @@ export function registerGoalSettlement(
|
|
|
166
233
|
const error = safeTerminalText(runtime.lastError ?? "");
|
|
167
234
|
runtime.lastStopReason = undefined;
|
|
168
235
|
runtime.lastError = undefined;
|
|
236
|
+
if (runtime.automaticCompaction.turnSettled
|
|
237
|
+
&& (settledTurn === undefined || settledTurn === runtime.automaticCompaction.turn)) {
|
|
238
|
+
// The interrupted turn already settled; a repeated settlement carries no new
|
|
239
|
+
// information, so keep waiting for compaction instead of failing the recovery.
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
169
242
|
const expectedInterruption = stopReason === "aborted"
|
|
170
243
|
|| stopReason === "error" && error === "This operation was aborted";
|
|
171
244
|
if (!wasGoalTurn || !agentEndObserved) {
|
|
@@ -181,19 +254,29 @@ export function registerGoalSettlement(
|
|
|
181
254
|
);
|
|
182
255
|
return;
|
|
183
256
|
}
|
|
184
|
-
runtime.automaticCompaction.
|
|
185
|
-
|
|
257
|
+
if (settledTurn !== undefined && runtime.automaticCompaction.turn === settledTurn) {
|
|
258
|
+
runtime.automaticCompaction.turnSettled = true;
|
|
259
|
+
} else {
|
|
260
|
+
stopAutomaticCompactionRecovery(pi, runtime, ctx, "the goal turn identity changed during compaction");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
finalizeAutomaticCompaction(pi, runtime, ctx);
|
|
186
264
|
return;
|
|
187
265
|
}
|
|
188
266
|
|
|
189
|
-
if (!wasGoalTurn || runtime.state?.status !== "active"
|
|
190
|
-
if (continuationWasScheduled && runtime.state?.status === "active"
|
|
267
|
+
if (!wasGoalTurn || runtime.state?.status !== "active") {
|
|
268
|
+
if (continuationWasScheduled && runtime.state?.status === "active") {
|
|
191
269
|
pauseGoalAfterFailure(pi, runtime, ctx, "the goal continuation ended before an agent turn started");
|
|
192
|
-
} else if (runtime.state?.status === "active"
|
|
193
|
-
|
|
270
|
+
} else if (runtime.state?.status === "active"
|
|
271
|
+
&& (runtime.state.turnPhase === "ready" || runtime.state.turnPhase === "authorized")) {
|
|
272
|
+
scheduleGoalContinuation(pi, runtime, ctx);
|
|
194
273
|
}
|
|
195
274
|
return;
|
|
196
275
|
}
|
|
276
|
+
if (settledTurn === undefined) {
|
|
277
|
+
pauseGoalAfterFailure(pi, runtime, ctx, "the goal turn identity was lost");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
197
280
|
if (!agentEndObserved) {
|
|
198
281
|
pauseGoalAfterFailure(pi, runtime, ctx, "the goal turn ended without an agent result");
|
|
199
282
|
return;
|
|
@@ -214,44 +297,52 @@ export function registerGoalSettlement(
|
|
|
214
297
|
}
|
|
215
298
|
runtime.lastStopReason = undefined;
|
|
216
299
|
runtime.lastError = undefined;
|
|
217
|
-
|
|
218
|
-
scheduleGoalContinuation(pi, runtime, initState, ctx);
|
|
300
|
+
settleGoalTurn(pi, runtime, ctx, settledTurn);
|
|
219
301
|
});
|
|
220
302
|
|
|
221
303
|
pi.on("session_compact", (event, ctx) => {
|
|
222
304
|
if (runtime.automaticCompaction !== undefined) return;
|
|
223
305
|
if (event.reason !== "manual") return;
|
|
224
|
-
recoverGoalAfterManualCompaction(pi, runtime,
|
|
306
|
+
recoverGoalAfterManualCompaction(pi, runtime, ctx);
|
|
225
307
|
});
|
|
226
308
|
|
|
227
309
|
return {
|
|
228
310
|
isActive: (ctx: ExtensionContext): boolean => isGoalModeSupported(ctx)
|
|
229
311
|
&& isSavedSession(ctx)
|
|
230
|
-
&& runtime.state?.status === "active"
|
|
231
|
-
&& !initState.active,
|
|
312
|
+
&& runtime.state?.status === "active",
|
|
232
313
|
onRequested: (): void => {
|
|
233
314
|
if (runtime.state?.status !== "active") return;
|
|
315
|
+
const current = runtime.state;
|
|
316
|
+
// A turn that already accepted continue or a blocker audit owns a pending
|
|
317
|
+
// authorization; recovery must take the next-turn path, never resume the decided turn.
|
|
318
|
+
const resumeSameTurn = runtime.goalTurnInFlight && current.turnDecision === undefined;
|
|
234
319
|
try {
|
|
235
|
-
const paused = transitionGoal(pi, runtime, "pause", "paused"
|
|
320
|
+
const paused = transitionGoal(pi, runtime, "pause", "paused", undefined, {
|
|
321
|
+
keepTurnForRecovery: true,
|
|
322
|
+
preserveTurnAuthorization: true,
|
|
323
|
+
});
|
|
236
324
|
runtime.automaticCompaction = {
|
|
237
325
|
pausedRevision: paused.revision,
|
|
238
326
|
outcome: "pending",
|
|
239
327
|
turnSettled: false,
|
|
328
|
+
turn: current.turns,
|
|
329
|
+
resumeSameTurn,
|
|
240
330
|
};
|
|
241
331
|
} catch (error) {
|
|
242
|
-
const
|
|
243
|
-
const reason = safeTerminalText(`automatic compaction pause could not be saved: ${error instanceof Error ? error.message : String(error)}`);
|
|
332
|
+
const reason = boundGoalText(`automatic compaction pause could not be saved: ${error instanceof Error ? error.message : String(error)}`);
|
|
244
333
|
runtime.state = current ? pauseGoalState(current, reason, Date.now()) : undefined;
|
|
245
334
|
syncGoalUpdateTool(pi, runtime);
|
|
246
335
|
runtime.persistenceRetryNeeded = true;
|
|
247
336
|
runtime.continuationScheduled = false;
|
|
248
337
|
runtime.automaticCompaction = undefined;
|
|
338
|
+
runtime.goalTurn = undefined;
|
|
339
|
+
runtime.goalTurnInFlight = false;
|
|
249
340
|
runtime.requestRender?.();
|
|
250
341
|
throw error;
|
|
251
342
|
}
|
|
252
343
|
},
|
|
253
|
-
onCompleted: (ctx: ExtensionContext): void => completeAutomaticCompaction(pi, runtime,
|
|
344
|
+
onCompleted: (ctx: ExtensionContext): void => completeAutomaticCompaction(pi, runtime, ctx),
|
|
254
345
|
onFailed: (ctx: ExtensionContext, error: unknown): void => failAutomaticCompaction(pi, runtime, ctx, error),
|
|
255
|
-
onSkipped: (ctx: ExtensionContext): void => skipAutomaticCompaction(pi, runtime,
|
|
346
|
+
onSkipped: (ctx: ExtensionContext): void => skipAutomaticCompaction(pi, runtime, ctx),
|
|
256
347
|
};
|
|
257
348
|
}
|