killeros 2.1.27 → 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 +18 -0
- package/README.md +3 -3
- package/killeros/activity.ts +16 -7
- package/killeros/footer.ts +6 -4
- package/killeros/goal-interface.ts +195 -48
- package/killeros/goal-runtime.ts +190 -44
- package/killeros/goal-settlement.ts +129 -30
- package/killeros/goal-state.ts +205 -32
- package/killeros/runtime.ts +54 -0
- package/killeros/worked-for.ts +1 -1
- package/package.json +1 -1
- package/themes/killeros.json +3 -2
package/killeros/goal-state.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import type { Stats } from "node:fs";
|
|
3
3
|
import { lstat, open, type FileHandle } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import type { GoalBlockerAudit, GoalFileBaseline, GoalFileVerification, GoalState, GoalStateCommon, GoalStatus } from "./runtime.ts";
|
|
5
|
+
import type { GoalBlockerAudit, GoalContinueReport, GoalFileBaseline, GoalFileVerification, GoalPendingDecision, GoalState, GoalStateCommon, GoalStatus, GoalTurnDecision, GoalTurnPhase } from "./runtime.ts";
|
|
6
|
+
import { safeTerminalText } from "./safe-terminal-text.ts";
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_GOAL_MAX_TURNS = 20;
|
|
8
9
|
export const GOAL_OBJECTIVE_LIMIT = 4_000;
|
|
10
|
+
export const GOAL_EVIDENCE_LIMIT = 2_000;
|
|
9
11
|
export const GOAL_MAX_TURNS = 10_000;
|
|
10
12
|
export const GOAL_VERSION = 1;
|
|
11
13
|
const FILE_HASH_CHUNK_SIZE = 64 * 1024;
|
|
@@ -18,6 +20,10 @@ export interface GoalTransitionOptions {
|
|
|
18
20
|
resetBlockedAudit?: boolean;
|
|
19
21
|
resumeAfterManualCompaction?: true;
|
|
20
22
|
blockerAudit?: GoalBlockerAudit;
|
|
23
|
+
keepTurnForRecovery?: true;
|
|
24
|
+
resumeInterruptedTurn?: true;
|
|
25
|
+
preserveTurnAuthorization?: true;
|
|
26
|
+
decision?: GoalTurnDecision;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
function isGoalStatus(value: unknown): value is GoalStatus {
|
|
@@ -46,6 +52,79 @@ function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
|
46
52
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47
53
|
}
|
|
48
54
|
|
|
55
|
+
function graphemeCount(value: string): number {
|
|
56
|
+
let length = 0;
|
|
57
|
+
for (const _ of graphemeSegmenter.segment(value)) length += 1;
|
|
58
|
+
return length;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isSafePersistedText(value: unknown, limit: number, required = true): value is string {
|
|
62
|
+
return typeof value === "string"
|
|
63
|
+
&& (required ? Boolean(value.trim()) : true)
|
|
64
|
+
&& value === value.trim()
|
|
65
|
+
&& safeTerminalText(value) === value
|
|
66
|
+
&& graphemeCount(value) <= limit;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Normalizes model or user text before it crosses the persisted goal boundary. */
|
|
70
|
+
export function normalizeGoalText(value: string, limit: number, label: string): string {
|
|
71
|
+
if (graphemeCount(value) > limit) throw new Error(`${label} must not exceed ${limit} characters`);
|
|
72
|
+
const normalized = safeTerminalText(value).trim();
|
|
73
|
+
if (!normalized) throw new Error(`${label} must not be empty`);
|
|
74
|
+
if (graphemeCount(normalized) > limit) throw new Error(`${label} must not exceed ${limit} characters`);
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isGoalTurnPhase(value: unknown): value is GoalTurnPhase {
|
|
79
|
+
return value === "ready" || value === "in-flight" || value === "authorized";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isGoalContinueReport(value: unknown, turns: number): value is GoalContinueReport {
|
|
83
|
+
return isUnknownRecord(value)
|
|
84
|
+
&& safeNonNegativeInteger(value.turn)
|
|
85
|
+
&& value.turn >= 1
|
|
86
|
+
&& value.turn <= turns
|
|
87
|
+
&& isSafePersistedText(value.evidence, GOAL_EVIDENCE_LIMIT)
|
|
88
|
+
&& isSafePersistedText(value.nextAction, GOAL_EVIDENCE_LIMIT);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isGoalPendingDecision(value: unknown, turns: number): value is GoalPendingDecision {
|
|
92
|
+
if (!isUnknownRecord(value)
|
|
93
|
+
|| !safeNonNegativeInteger(value.turn)
|
|
94
|
+
|| value.turn < 1
|
|
95
|
+
|| value.turn > turns
|
|
96
|
+
|| !isSafePersistedText(value.evidence, GOAL_EVIDENCE_LIMIT)) return false;
|
|
97
|
+
if (value.kind === "continue") return isSafePersistedText(value.nextAction, GOAL_EVIDENCE_LIMIT);
|
|
98
|
+
return value.kind === "blocker-audit"
|
|
99
|
+
&& typeof value.blockerKey === "string"
|
|
100
|
+
&& /^[a-z0-9][a-z0-9._-]{0,119}$/u.test(value.blockerKey)
|
|
101
|
+
&& typeof value.streak === "number"
|
|
102
|
+
&& Number.isInteger(value.streak)
|
|
103
|
+
&& value.streak >= 1
|
|
104
|
+
&& value.streak < 3;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isGoalTurnDecision(value: unknown, turns: number): value is GoalTurnDecision {
|
|
108
|
+
if (!isUnknownRecord(value)
|
|
109
|
+
|| !safeNonNegativeInteger(value.turn)
|
|
110
|
+
|| value.turn < 1
|
|
111
|
+
|| value.turn > turns
|
|
112
|
+
|| !isSafePersistedText(value.evidence, GOAL_EVIDENCE_LIMIT)) return false;
|
|
113
|
+
if (value.kind === "continue") return isSafePersistedText(value.nextAction, GOAL_EVIDENCE_LIMIT);
|
|
114
|
+
if (value.kind === "complete") return value.verification === "file" || value.verification === "model-reported";
|
|
115
|
+
return (value.kind === "blocker-audit"
|
|
116
|
+
&& typeof value.blockerKey === "string"
|
|
117
|
+
&& /^[a-z0-9][a-z0-9._-]{0,119}$/u.test(value.blockerKey)
|
|
118
|
+
&& typeof value.streak === "number"
|
|
119
|
+
&& Number.isInteger(value.streak)
|
|
120
|
+
&& value.streak >= 1
|
|
121
|
+
&& value.streak < 3)
|
|
122
|
+
|| (value.kind === "blocked"
|
|
123
|
+
&& typeof value.blockerKey === "string"
|
|
124
|
+
&& /^[a-z0-9][a-z0-9._-]{0,119}$/u.test(value.blockerKey)
|
|
125
|
+
&& value.streak === 3);
|
|
126
|
+
}
|
|
127
|
+
|
|
49
128
|
function isGoalFileBaseline(value: unknown): value is GoalFileBaseline {
|
|
50
129
|
if (!isUnknownRecord(value)) return false;
|
|
51
130
|
if (value.exists === false) {
|
|
@@ -75,8 +154,7 @@ function stripUnquotedPathPunctuation(value: string): string {
|
|
|
75
154
|
function isGoalFileVerification(value: unknown): value is GoalFileVerification {
|
|
76
155
|
return isUnknownRecord(value)
|
|
77
156
|
&& value.kind === "file"
|
|
78
|
-
&&
|
|
79
|
-
&& value.path === value.path.trim()
|
|
157
|
+
&& isSafePersistedText(value.path, GOAL_OBJECTIVE_LIMIT)
|
|
80
158
|
&& isAbsoluteFilePath(value.path)
|
|
81
159
|
&& isGoalFileBaseline(value.baseline);
|
|
82
160
|
}
|
|
@@ -85,22 +163,13 @@ function isMaxTurns(value: unknown): value is number {
|
|
|
85
163
|
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= GOAL_MAX_TURNS;
|
|
86
164
|
}
|
|
87
165
|
|
|
88
|
-
function exceedsBlockerEvidenceLimit(value: string): boolean {
|
|
89
|
-
let length = 0;
|
|
90
|
-
for (const _ of graphemeSegmenter.segment(value)) {
|
|
91
|
-
if (++length > 2_000) return true;
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
166
|
function isGoalBlockerAudit(value: unknown, turns: number, status: GoalStatus): value is GoalBlockerAudit {
|
|
97
167
|
if (!isUnknownRecord(value)
|
|
98
168
|
|| typeof value.key !== "string"
|
|
99
169
|
|| !/^[a-z0-9][a-z0-9._-]{0,119}$/u.test(value.key)
|
|
100
170
|
|| typeof value.streak !== "number" || !Number.isInteger(value.streak) || value.streak < 1 || value.streak > 3
|
|
101
171
|
|| typeof value.lastTurn !== "number" || !Number.isInteger(value.lastTurn) || value.lastTurn < 1 || value.lastTurn > turns
|
|
102
|
-
|| value.evidence !== undefined && (
|
|
103
|
-
|| value.evidence !== value.evidence.trim() || !value.evidence || exceedsBlockerEvidenceLimit(value.evidence))) {
|
|
172
|
+
|| value.evidence !== undefined && !isSafePersistedText(value.evidence, GOAL_EVIDENCE_LIMIT)) {
|
|
104
173
|
return false;
|
|
105
174
|
}
|
|
106
175
|
if (status === "complete") return false;
|
|
@@ -126,10 +195,16 @@ export function parseGoalState(value: unknown): GoalState | undefined {
|
|
|
126
195
|
blockerAudit,
|
|
127
196
|
verification,
|
|
128
197
|
maxTurns,
|
|
198
|
+
turnPhase,
|
|
199
|
+
turnDecision,
|
|
200
|
+
lastDecision,
|
|
201
|
+
lastContinueReport,
|
|
202
|
+
stopReason,
|
|
129
203
|
} = value;
|
|
130
204
|
if (version !== GOAL_VERSION
|
|
131
205
|
|| !incrementableNonNegativeInteger(revision) || revision < 1
|
|
132
|
-
|| typeof objective !== "string" || !objective.trim() ||
|
|
206
|
+
|| typeof objective !== "string" || !objective.trim() || safeTerminalText(objective) !== objective
|
|
207
|
+
|| graphemeCount(objective.trim()) > GOAL_OBJECTIVE_LIMIT
|
|
133
208
|
|| !isGoalStatus(status)
|
|
134
209
|
|| !safeNonNegativeInteger(createdAt)
|
|
135
210
|
|| !safeNonNegativeInteger(updatedAt)
|
|
@@ -138,14 +213,37 @@ export function parseGoalState(value: unknown): GoalState | undefined {
|
|
|
138
213
|
|| blockedAuditStartTurn !== undefined
|
|
139
214
|
&& (!safeNonNegativeInteger(blockedAuditStartTurn) || blockedAuditStartTurn > turns)
|
|
140
215
|
|| !safeNonNegativeInteger(baselineTokens)
|
|
141
|
-
|| result !== undefined &&
|
|
216
|
+
|| result !== undefined && !isSafePersistedText(result, GOAL_EVIDENCE_LIMIT, false)
|
|
142
217
|
|| verification !== undefined && !isGoalFileVerification(verification)
|
|
143
218
|
|| maxTurns !== undefined && !isMaxTurns(maxTurns)
|
|
144
219
|
|| resumeAfterManualCompaction !== undefined && resumeAfterManualCompaction !== true
|
|
145
|
-
|| blockerAudit !== undefined && !isGoalBlockerAudit(blockerAudit, turns, status)
|
|
220
|
+
|| blockerAudit !== undefined && !isGoalBlockerAudit(blockerAudit, turns, status)
|
|
221
|
+
|| turnPhase !== undefined && !isGoalTurnPhase(turnPhase)
|
|
222
|
+
|| turnDecision !== undefined && !isGoalPendingDecision(turnDecision, turns)
|
|
223
|
+
|| lastDecision !== undefined && !isGoalTurnDecision(lastDecision, turns)
|
|
224
|
+
|| lastContinueReport !== undefined && !isGoalContinueReport(lastContinueReport, turns)
|
|
225
|
+
|| stopReason !== undefined && !isSafePersistedText(stopReason, GOAL_EVIDENCE_LIMIT)) {
|
|
146
226
|
return undefined;
|
|
147
227
|
}
|
|
148
228
|
|
|
229
|
+
const pending = turnDecision;
|
|
230
|
+
if (pending !== undefined && pending.turn !== turns) return undefined;
|
|
231
|
+
if (isGoalTurnDecision(lastDecision, turns)) {
|
|
232
|
+
if (lastDecision.kind === "complete") {
|
|
233
|
+
if (status !== "complete") return undefined;
|
|
234
|
+
if ((lastDecision.verification === "file") !== (verification !== undefined)) return undefined;
|
|
235
|
+
}
|
|
236
|
+
if (lastDecision.kind === "blocked" && status !== "blocked") return undefined;
|
|
237
|
+
}
|
|
238
|
+
if (pending !== undefined && turnPhase !== "authorized") return undefined;
|
|
239
|
+
if (turnPhase === "authorized" && pending === undefined) return undefined;
|
|
240
|
+
if (turnPhase === "ready" && pending !== undefined) return undefined;
|
|
241
|
+
if (status === "complete" && (turnPhase !== undefined || pending !== undefined)) return undefined;
|
|
242
|
+
if (status === "blocked" && (turnPhase !== undefined || pending !== undefined)) return undefined;
|
|
243
|
+
if (status === "paused" && turnPhase === "ready" && pending !== undefined) return undefined;
|
|
244
|
+
// A pause reason is only current while paused; on any other status it contradicts the state.
|
|
245
|
+
if (stopReason !== undefined && status !== "paused") return undefined;
|
|
246
|
+
|
|
149
247
|
const common: GoalStateCommon = {
|
|
150
248
|
version: GOAL_VERSION,
|
|
151
249
|
revision,
|
|
@@ -158,6 +256,11 @@ export function parseGoalState(value: unknown): GoalState | undefined {
|
|
|
158
256
|
baselineTokens,
|
|
159
257
|
...(verification === undefined ? {} : { verification }),
|
|
160
258
|
...(maxTurns === undefined ? {} : { maxTurns }),
|
|
259
|
+
...(turnPhase === undefined ? {} : { turnPhase }),
|
|
260
|
+
...(pending === undefined ? {} : { turnDecision: pending }),
|
|
261
|
+
...(lastDecision === undefined ? {} : { lastDecision }),
|
|
262
|
+
...(lastContinueReport === undefined ? {} : { lastContinueReport }),
|
|
263
|
+
...(stopReason === undefined ? {} : { stopReason }),
|
|
161
264
|
};
|
|
162
265
|
switch (status) {
|
|
163
266
|
case "active":
|
|
@@ -339,10 +442,19 @@ export async function verifyGoalDeliverable(verification: GoalFileVerification):
|
|
|
339
442
|
}
|
|
340
443
|
}
|
|
341
444
|
|
|
445
|
+
export function boundGoalText(value: string, limit = GOAL_EVIDENCE_LIMIT): string {
|
|
446
|
+
const safe = safeTerminalText(value).trim();
|
|
447
|
+
return [...graphemeSegmenter.segment(safe)]
|
|
448
|
+
.slice(0, limit)
|
|
449
|
+
.map(({ segment }) => segment)
|
|
450
|
+
.join("")
|
|
451
|
+
.trimEnd();
|
|
452
|
+
}
|
|
453
|
+
|
|
342
454
|
export function validateGoalObjective(input: string): string | undefined {
|
|
343
455
|
const objective = input.trim();
|
|
344
|
-
if (!objective) return undefined;
|
|
345
|
-
return
|
|
456
|
+
if (!objective || safeTerminalText(objective) !== objective) return undefined;
|
|
457
|
+
return graphemeCount(objective) <= GOAL_OBJECTIVE_LIMIT ? objective : undefined;
|
|
346
458
|
}
|
|
347
459
|
|
|
348
460
|
export function goalElapsedMilliseconds(state: GoalState, now: number): number {
|
|
@@ -363,6 +475,11 @@ export function commonGoalState(state: GoalState): GoalStateCommon {
|
|
|
363
475
|
baselineTokens: state.baselineTokens,
|
|
364
476
|
...(state.verification === undefined ? {} : { verification: state.verification }),
|
|
365
477
|
...(state.maxTurns === undefined ? {} : { maxTurns: state.maxTurns }),
|
|
478
|
+
...(state.turnPhase === undefined ? {} : { turnPhase: state.turnPhase }),
|
|
479
|
+
...(state.turnDecision === undefined ? {} : { turnDecision: state.turnDecision }),
|
|
480
|
+
...(state.lastDecision === undefined ? {} : { lastDecision: state.lastDecision }),
|
|
481
|
+
...(state.lastContinueReport === undefined ? {} : { lastContinueReport: state.lastContinueReport }),
|
|
482
|
+
...(state.status === "paused" && state.stopReason !== undefined ? { stopReason: state.stopReason } : {}),
|
|
366
483
|
};
|
|
367
484
|
}
|
|
368
485
|
|
|
@@ -392,16 +509,33 @@ export function createNewGoalState(
|
|
|
392
509
|
turns: 0,
|
|
393
510
|
blockedAuditStartTurn: 0,
|
|
394
511
|
baselineTokens,
|
|
512
|
+
turnPhase: "ready",
|
|
395
513
|
...(verification === undefined ? {} : { verification }),
|
|
396
514
|
...(controls.maxTurns === undefined ? {} : { maxTurns: controls.maxTurns }),
|
|
397
515
|
};
|
|
398
516
|
}
|
|
399
517
|
|
|
518
|
+
function nextRevision(current: { revision: number }): number {
|
|
519
|
+
if (!incrementableNonNegativeInteger(current.revision)) {
|
|
520
|
+
throw new Error("Goal revision cannot advance safely");
|
|
521
|
+
}
|
|
522
|
+
return current.revision + 1;
|
|
523
|
+
}
|
|
524
|
+
|
|
400
525
|
export function beginGoalTurnState(
|
|
401
526
|
current: Extract<GoalState, { status: "active" }>,
|
|
402
527
|
now: number,
|
|
403
528
|
): GoalState {
|
|
404
|
-
|
|
529
|
+
if (!incrementableNonNegativeInteger(current.turns)) throw new Error("Goal turn counter cannot advance safely");
|
|
530
|
+
const { turnDecision: _decision, turnPhase: _phase, ...withoutPending } = current;
|
|
531
|
+
return {
|
|
532
|
+
...withoutPending,
|
|
533
|
+
revision: nextRevision(current),
|
|
534
|
+
turns: current.turns + 1,
|
|
535
|
+
updatedAt: now,
|
|
536
|
+
turnPhase: "in-flight",
|
|
537
|
+
...(current.turnDecision === undefined ? {} : { lastDecision: current.turnDecision }),
|
|
538
|
+
};
|
|
405
539
|
}
|
|
406
540
|
|
|
407
541
|
export function checkpointActiveGoalState(
|
|
@@ -410,7 +544,7 @@ export function checkpointActiveGoalState(
|
|
|
410
544
|
): GoalState {
|
|
411
545
|
return {
|
|
412
546
|
...stopGoalClock(current, now),
|
|
413
|
-
revision: current
|
|
547
|
+
revision: nextRevision(current),
|
|
414
548
|
status: "active",
|
|
415
549
|
updatedAt: now,
|
|
416
550
|
activeStartedAt: now,
|
|
@@ -424,12 +558,17 @@ export function pauseGoalState(
|
|
|
424
558
|
result: string | undefined,
|
|
425
559
|
now: number,
|
|
426
560
|
resumeAfterManualCompaction = false,
|
|
561
|
+
preserveTurnAuthorization = false,
|
|
427
562
|
): GoalState {
|
|
428
563
|
const common = stopGoalClock(current, now);
|
|
564
|
+
const { turnDecision: _decision, turnPhase: _phase, ...withoutPending } = common;
|
|
429
565
|
return {
|
|
430
|
-
...
|
|
566
|
+
...withoutPending,
|
|
431
567
|
status: "paused",
|
|
568
|
+
turnPhase: preserveTurnAuthorization ? current.turnPhase : "ready",
|
|
569
|
+
...(preserveTurnAuthorization && current.turnDecision !== undefined ? { turnDecision: current.turnDecision } : {}),
|
|
432
570
|
...(result === undefined ? {} : { result }),
|
|
571
|
+
...(result === undefined ? {} : { stopReason: boundGoalText(result) }),
|
|
433
572
|
...(current.blockerAudit === undefined ? {} : { blockerAudit: current.blockerAudit }),
|
|
434
573
|
...(resumeAfterManualCompaction ? { resumeAfterManualCompaction: true as const } : {}),
|
|
435
574
|
};
|
|
@@ -440,15 +579,26 @@ export function checkpointPausedGoalState(
|
|
|
440
579
|
now: number,
|
|
441
580
|
): GoalState {
|
|
442
581
|
const { resumeAfterManualCompaction: _resume, ...paused } = current;
|
|
443
|
-
return { ...paused, revision:
|
|
582
|
+
return { ...paused, revision: nextRevision(current), updatedAt: now };
|
|
444
583
|
}
|
|
445
584
|
|
|
446
|
-
export function
|
|
585
|
+
export function recordGoalDecision(
|
|
447
586
|
state: Extract<GoalState, { status: "active" }>,
|
|
448
|
-
|
|
587
|
+
decision: GoalPendingDecision,
|
|
449
588
|
now: number,
|
|
450
|
-
): GoalState {
|
|
451
|
-
|
|
589
|
+
): Extract<GoalState, { status: "active" }> {
|
|
590
|
+
if (decision.turn !== state.turns) throw new Error("Goal decision does not match the active turn");
|
|
591
|
+
return {
|
|
592
|
+
...state,
|
|
593
|
+
revision: nextRevision(state),
|
|
594
|
+
updatedAt: now,
|
|
595
|
+
turnPhase: "authorized",
|
|
596
|
+
turnDecision: decision,
|
|
597
|
+
lastDecision: decision,
|
|
598
|
+
...(decision.kind === "continue"
|
|
599
|
+
? { lastContinueReport: { turn: decision.turn, evidence: decision.evidence, nextAction: decision.nextAction } }
|
|
600
|
+
: {}),
|
|
601
|
+
};
|
|
452
602
|
}
|
|
453
603
|
|
|
454
604
|
export function transitionGoalState(
|
|
@@ -461,27 +611,50 @@ export function transitionGoalState(
|
|
|
461
611
|
const stopped = stopGoalClock(current, now);
|
|
462
612
|
const common: GoalStateCommon = {
|
|
463
613
|
...stopped,
|
|
464
|
-
revision: stopped
|
|
614
|
+
revision: nextRevision(stopped),
|
|
465
615
|
updatedAt: now,
|
|
466
616
|
blockedAuditStartTurn: options.resetBlockedAudit ? stopped.turns : stopped.blockedAuditStartTurn,
|
|
467
617
|
};
|
|
468
618
|
const blockerAudit = options.resetBlockedAudit ? undefined : options.blockerAudit ?? current.blockerAudit;
|
|
619
|
+
const pending = options.preserveTurnAuthorization ? current.turnDecision : undefined;
|
|
620
|
+
const { turnDecision: _decision, turnPhase: _phase, stopReason: _stopReason, ...withoutPending } = common;
|
|
469
621
|
switch (status) {
|
|
470
622
|
case "active":
|
|
471
|
-
return {
|
|
623
|
+
return {
|
|
624
|
+
...withoutPending,
|
|
625
|
+
...(current.status === "blocked" ? { lastDecision: undefined } : {}),
|
|
626
|
+
status,
|
|
627
|
+
activeStartedAt: now,
|
|
628
|
+
turnPhase: pending !== undefined ? "authorized" : options.resumeInterruptedTurn ? "in-flight" : "ready",
|
|
629
|
+
...(pending === undefined ? {} : { turnDecision: pending }),
|
|
630
|
+
...(blockerAudit === undefined ? {} : { blockerAudit }),
|
|
631
|
+
};
|
|
472
632
|
case "paused":
|
|
473
633
|
return {
|
|
474
|
-
...
|
|
634
|
+
...withoutPending,
|
|
475
635
|
status,
|
|
476
|
-
|
|
636
|
+
turnPhase: options.keepTurnForRecovery ? current.turnPhase : "ready",
|
|
637
|
+
...(options.keepTurnForRecovery && current.turnDecision !== undefined ? { turnDecision: current.turnDecision } : {}),
|
|
638
|
+
...(result === undefined ? {} : { result, stopReason: boundGoalText(result) }),
|
|
477
639
|
...(blockerAudit === undefined ? {} : { blockerAudit }),
|
|
478
640
|
...(options.resumeAfterManualCompaction === undefined ? {} : { resumeAfterManualCompaction: true }),
|
|
479
641
|
};
|
|
480
642
|
case "blocked":
|
|
481
643
|
if (result === undefined) throw new Error("A blocked goal requires a result");
|
|
482
|
-
return {
|
|
644
|
+
return {
|
|
645
|
+
...withoutPending,
|
|
646
|
+
status,
|
|
647
|
+
result,
|
|
648
|
+
...(options.decision === undefined ? {} : { lastDecision: options.decision }),
|
|
649
|
+
...(blockerAudit === undefined ? {} : { blockerAudit }),
|
|
650
|
+
};
|
|
483
651
|
case "complete":
|
|
484
652
|
if (result === undefined) throw new Error("A complete goal requires a result");
|
|
485
|
-
return {
|
|
653
|
+
return {
|
|
654
|
+
...withoutPending,
|
|
655
|
+
status,
|
|
656
|
+
result,
|
|
657
|
+
...(options.decision === undefined ? {} : { lastDecision: options.decision }),
|
|
658
|
+
};
|
|
486
659
|
}
|
|
487
660
|
}
|
package/killeros/runtime.ts
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
export type GoalStatus = "active" | "paused" | "blocked" | "complete";
|
|
2
2
|
|
|
3
|
+
export type GoalTurnPhase = "ready" | "in-flight" | "authorized";
|
|
4
|
+
|
|
5
|
+
export interface GoalContinueReport {
|
|
6
|
+
turn: number;
|
|
7
|
+
evidence: string;
|
|
8
|
+
nextAction: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type GoalTurnDecision =
|
|
12
|
+
| {
|
|
13
|
+
kind: "continue";
|
|
14
|
+
turn: number;
|
|
15
|
+
evidence: string;
|
|
16
|
+
nextAction: string;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
kind: "blocker-audit";
|
|
20
|
+
turn: number;
|
|
21
|
+
blockerKey: string;
|
|
22
|
+
streak: number;
|
|
23
|
+
evidence: string;
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
kind: "blocked";
|
|
27
|
+
turn: number;
|
|
28
|
+
blockerKey: string;
|
|
29
|
+
streak: 3;
|
|
30
|
+
evidence: string;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
kind: "complete";
|
|
34
|
+
turn: number;
|
|
35
|
+
evidence: string;
|
|
36
|
+
verification: "file" | "model-reported";
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type GoalPendingDecision = Extract<GoalTurnDecision, { kind: "continue" | "blocker-audit" }>;
|
|
40
|
+
|
|
3
41
|
export interface GoalBlockerAudit {
|
|
4
42
|
key: string;
|
|
5
43
|
streak: number;
|
|
@@ -29,6 +67,11 @@ export interface GoalStateCommon {
|
|
|
29
67
|
baselineTokens: number;
|
|
30
68
|
verification?: GoalFileVerification;
|
|
31
69
|
maxTurns?: number;
|
|
70
|
+
turnPhase?: GoalTurnPhase;
|
|
71
|
+
turnDecision?: GoalPendingDecision;
|
|
72
|
+
lastDecision?: GoalTurnDecision;
|
|
73
|
+
lastContinueReport?: GoalContinueReport;
|
|
74
|
+
stopReason?: string;
|
|
32
75
|
}
|
|
33
76
|
|
|
34
77
|
export type GoalState = GoalStateCommon & (
|
|
@@ -69,6 +112,13 @@ export interface AutomaticGoalCompaction {
|
|
|
69
112
|
pausedRevision: number;
|
|
70
113
|
outcome: AutomaticGoalCompactionOutcome;
|
|
71
114
|
turnSettled: boolean;
|
|
115
|
+
turn: number;
|
|
116
|
+
resumeSameTurn: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface GoalTurnExecution {
|
|
120
|
+
turn: number;
|
|
121
|
+
revision: number;
|
|
72
122
|
}
|
|
73
123
|
|
|
74
124
|
export interface GoalRuntime {
|
|
@@ -77,10 +127,12 @@ export interface GoalRuntime {
|
|
|
77
127
|
continuationHeld: boolean;
|
|
78
128
|
goalTurnInFlight: boolean;
|
|
79
129
|
agentEndObserved: boolean;
|
|
130
|
+
goalTurn?: GoalTurnExecution;
|
|
80
131
|
automaticCompaction?: AutomaticGoalCompaction;
|
|
81
132
|
persistenceRetryNeeded: boolean;
|
|
82
133
|
lastStopReason?: string;
|
|
83
134
|
lastError?: string;
|
|
135
|
+
lifecycleGeneration: number;
|
|
84
136
|
requestRender?: () => void;
|
|
85
137
|
}
|
|
86
138
|
|
|
@@ -90,7 +142,9 @@ export function createGoalRuntime(): GoalRuntime {
|
|
|
90
142
|
continuationHeld: false,
|
|
91
143
|
goalTurnInFlight: false,
|
|
92
144
|
agentEndObserved: false,
|
|
145
|
+
goalTurn: undefined,
|
|
93
146
|
automaticCompaction: undefined,
|
|
94
147
|
persistenceRetryNeeded: false,
|
|
148
|
+
lifecycleGeneration: 0,
|
|
95
149
|
};
|
|
96
150
|
}
|
package/killeros/worked-for.ts
CHANGED
|
@@ -322,7 +322,7 @@ export function registerWorkedFor(
|
|
|
322
322
|
pi.registerEntryRenderer<WorkedForEntryData>(WORKED_FOR_ENTRY_TYPE, (entry, options, theme) => {
|
|
323
323
|
const data = parseWorkedForEntryData(entry.data);
|
|
324
324
|
if (!data) return undefined;
|
|
325
|
-
if (data.version === 1) return new Text(theme.fg("dim",
|
|
325
|
+
if (data.version === 1) return new Text(theme.fg("dim", `Worked for ${formatWorkedForDuration(data.milliseconds)}`), 1, 0);
|
|
326
326
|
if (data.version === 4) return new WorkedForV4Component(data, options.expanded, theme);
|
|
327
327
|
const outcome = OUTCOMES[data.outcome];
|
|
328
328
|
const tokens = data.version === 3 ? ` · ↑ ${formatTokens(data.tokens)} tokens` : "";
|
package/package.json
CHANGED
package/themes/killeros.json
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"success": "#8fa88b",
|
|
17
17
|
"error": "#c8786c",
|
|
18
18
|
"warning": "#bda36c",
|
|
19
|
-
"pink": "#b98aa5"
|
|
19
|
+
"pink": "#b98aa5",
|
|
20
|
+
"teal": "#6FAEB2"
|
|
20
21
|
},
|
|
21
22
|
"colors": {
|
|
22
23
|
"accent": "coral",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"userMessageText": "text",
|
|
37
38
|
"customMessageBg": "surface",
|
|
38
39
|
"customMessageText": "text",
|
|
39
|
-
"customMessageLabel": "
|
|
40
|
+
"customMessageLabel": "teal",
|
|
40
41
|
"toolPendingBg": "surface",
|
|
41
42
|
"toolSuccessBg": "surface",
|
|
42
43
|
"toolErrorBg": "surface",
|