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.
@@ -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
- && typeof value.path === "string"
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 && (typeof value.evidence !== "string"
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() || [...objective].length > GOAL_OBJECTIVE_LIMIT
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 && typeof result !== "string"
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 [...objective].length <= GOAL_OBJECTIVE_LIMIT ? objective : undefined;
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
- return { ...current, revision: current.revision + 1, turns: current.turns + 1, updatedAt: now };
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.revision + 1,
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
- ...common,
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: paused.revision + 1, updatedAt: now };
582
+ return { ...paused, revision: nextRevision(current), updatedAt: now };
444
583
  }
445
584
 
446
- export function recordGoalBlockerAudit(
585
+ export function recordGoalDecision(
447
586
  state: Extract<GoalState, { status: "active" }>,
448
- blockerAudit: GoalBlockerAudit,
587
+ decision: GoalPendingDecision,
449
588
  now: number,
450
- ): GoalState {
451
- return { ...state, revision: state.revision + 1, updatedAt: now, blockerAudit };
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.revision + 1,
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 { ...common, status, activeStartedAt: now, ...(blockerAudit === undefined ? {} : { blockerAudit }) };
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
- ...common,
634
+ ...withoutPending,
475
635
  status,
476
- ...(result === undefined ? {} : { result }),
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 { ...common, status, result, ...(blockerAudit === undefined ? {} : { blockerAudit }) };
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 { ...common, status, result };
653
+ return {
654
+ ...withoutPending,
655
+ status,
656
+ result,
657
+ ...(options.decision === undefined ? {} : { lastDecision: options.decision }),
658
+ };
486
659
  }
487
660
  }
@@ -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
  }
@@ -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", `✻ Worked for ${formatWorkedForDuration(data.milliseconds)}`), 1, 0);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "killeros",
3
- "version": "2.1.27",
3
+ "version": "2.1.28",
4
4
  "description": "TUI, goals, and workflow automation for the Pi coding agent",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -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": "coralBright",
40
+ "customMessageLabel": "teal",
40
41
  "toolPendingBg": "surface",
41
42
  "toolSuccessBg": "surface",
42
43
  "toolErrorBg": "surface",