killeros 2.0.5 → 2.0.7
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 +4 -0
- package/README.md +19 -12
- package/killeros/activity.ts +97 -0
- package/killeros/footer.ts +31 -45
- package/killeros/goals.ts +118 -42
- package/killeros/init.ts +23 -2
- package/killeros/question.ts +382 -186
- package/killeros/runtime.ts +9 -0
- package/killeros/shell-ui.ts +14 -68
- package/killeros/variants.ts +74 -32
- package/killeros/worked-for.ts +135 -0
- package/package.json +7 -6
package/killeros/goals.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
2
|
+
import { lstatSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext, type ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
3
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
4
6
|
import { Type } from "typebox";
|
|
@@ -7,10 +9,11 @@ import { BoundedText } from "./bounded-text.ts";
|
|
|
7
9
|
import { formatTime, formatTokens } from "./display.ts";
|
|
8
10
|
import { reportError } from "./errors.ts";
|
|
9
11
|
import { resolvePersonalInstructions } from "./personal-instructions.ts";
|
|
10
|
-
import type { GoalBlockerAudit, GoalRuntime, GoalState, GoalStatus, InitRuntime } from "./runtime.ts";
|
|
12
|
+
import type { GoalBlockerAudit, GoalFileVerification, GoalRuntime, GoalState, GoalStatus, InitRuntime } from "./runtime.ts";
|
|
11
13
|
|
|
12
14
|
const GOAL_ENTRY_TYPE = "killeros-goal";
|
|
13
15
|
const GOAL_CONTINUATION_TYPE = "killeros-goal-continuation";
|
|
16
|
+
const GOAL_UPDATE_TOOL = "killeros_goal_update";
|
|
14
17
|
const GOAL_OBJECTIVE_LIMIT = 4_000;
|
|
15
18
|
const GOAL_VERSION = 1;
|
|
16
19
|
|
|
@@ -29,7 +32,6 @@ interface GoalTransitionOptions {
|
|
|
29
32
|
|
|
30
33
|
interface RestoredGoalState {
|
|
31
34
|
state?: GoalState;
|
|
32
|
-
recoveryProven: boolean;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const GoalUpdateParams = Type.Object({
|
|
@@ -52,6 +54,7 @@ const GoalUpdateParams = Type.Object({
|
|
|
52
54
|
interface GoalUpdateDetails {
|
|
53
55
|
status: "complete" | "blocked" | "blocker-audit";
|
|
54
56
|
evidence: string;
|
|
57
|
+
verification?: "file" | "model-reported";
|
|
55
58
|
blockerKey?: string;
|
|
56
59
|
streak?: number;
|
|
57
60
|
}
|
|
@@ -64,6 +67,41 @@ function finiteNonNegative(value: unknown): value is number {
|
|
|
64
67
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
function isGoalFileVerification(value: unknown): value is GoalFileVerification {
|
|
71
|
+
if (!value || typeof value !== "object") return false;
|
|
72
|
+
const candidate = value as Partial<GoalFileVerification>;
|
|
73
|
+
return candidate.kind === "file"
|
|
74
|
+
&& typeof candidate.path === "string"
|
|
75
|
+
&& candidate.path === candidate.path.trim()
|
|
76
|
+
&& isAbsoluteFilePath(candidate.path);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isAbsoluteFilePath(value: string): boolean {
|
|
80
|
+
if (!value || /^(?:https?|file):\/\//iu.test(value) || /[\\\/]$/u.test(value)) return false;
|
|
81
|
+
return path.isAbsolute(value) || path.win32.isAbsolute(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function inferGoalVerification(objective: string): GoalFileVerification | undefined {
|
|
85
|
+
const destination = /\b(?:create|write|save|generate)\b[^\r\n]{0,160}?\b(?:file|document|markdown|report|spreadsheet|presentation|image)\b\s+(?:to|at|as|destination(?:\s+is)?|output(?:\s+(?:to|at))?)\b\s*(?:`([^`\r\n]+)`|"([^"\r\n]+)"|'([^'\r\n]+)'|([A-Za-z]:\\[^\s,;]+|\/[^\s,;]+))/giu;
|
|
86
|
+
const paths = [...objective.matchAll(destination)]
|
|
87
|
+
.map((match) => (match[1] ?? match[2] ?? match[3] ?? match[4] ?? "").trim())
|
|
88
|
+
.filter(isAbsoluteFilePath);
|
|
89
|
+
const unique = [...new Set(paths)];
|
|
90
|
+
return unique.length === 1 ? { kind: "file", path: unique[0]! } : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function verifyGoalDeliverable(verification: GoalFileVerification): void {
|
|
94
|
+
let artifact: ReturnType<typeof lstatSync>;
|
|
95
|
+
try {
|
|
96
|
+
artifact = lstatSync(verification.path);
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error(`Goal deliverable is not a regular file at the required path: ${verification.path}`);
|
|
99
|
+
}
|
|
100
|
+
if (!artifact.isFile()) {
|
|
101
|
+
throw new Error(`Goal deliverable is not a regular file at the required path: ${verification.path}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
67
105
|
function isGoalBlockerAudit(value: unknown, turns: number, status: GoalStatus): value is GoalBlockerAudit {
|
|
68
106
|
if (!value || typeof value !== "object") return false;
|
|
69
107
|
const candidate = value as Partial<GoalBlockerAudit>;
|
|
@@ -92,6 +130,7 @@ function parseGoalState(value: unknown): GoalState | undefined {
|
|
|
92
130
|
|| !finiteNonNegative(candidate.baselineTokens)
|
|
93
131
|
|| candidate.activeStartedAt !== undefined && !finiteNonNegative(candidate.activeStartedAt)
|
|
94
132
|
|| candidate.result !== undefined && typeof candidate.result !== "string"
|
|
133
|
+
|| candidate.verification !== undefined && !isGoalFileVerification(candidate.verification)
|
|
95
134
|
|| candidate.blockerAudit !== undefined && !isGoalBlockerAudit(candidate.blockerAudit, candidate.turns!, candidate.status)
|
|
96
135
|
|| candidate.resumeAfterManualCompaction !== undefined && candidate.resumeAfterManualCompaction !== true
|
|
97
136
|
|| candidate.resumeAfterManualCompaction === true && candidate.status !== "paused") {
|
|
@@ -112,6 +151,7 @@ function parseGoalState(value: unknown): GoalState | undefined {
|
|
|
112
151
|
result: candidate.result,
|
|
113
152
|
resumeAfterManualCompaction: candidate.resumeAfterManualCompaction,
|
|
114
153
|
blockerAudit: candidate.blockerAudit,
|
|
154
|
+
verification: candidate.verification,
|
|
115
155
|
};
|
|
116
156
|
}
|
|
117
157
|
|
|
@@ -130,19 +170,17 @@ function restoreGoalState(ctx: ExtensionContext): RestoredGoalState {
|
|
|
130
170
|
if (entry?.type !== "custom" || entry.customType !== GOAL_ENTRY_TYPE) continue;
|
|
131
171
|
const data = entry.data as Partial<GoalEntryData> | undefined;
|
|
132
172
|
if (!data || data.version !== GOAL_VERSION || data.state === null) {
|
|
133
|
-
return { state: undefined
|
|
173
|
+
return { state: undefined };
|
|
134
174
|
}
|
|
135
175
|
const restored = parseGoalState(data.state);
|
|
136
|
-
if (!restored) return { state: undefined
|
|
176
|
+
if (!restored) return { state: undefined };
|
|
137
177
|
const state = restored.status === "active"
|
|
138
178
|
? { ...restored, activeStartedAt: Date.now() }
|
|
139
179
|
: { ...restored, activeStartedAt: undefined };
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
&& entries.slice(index + 1).some((candidate) => candidate.type === "compaction");
|
|
143
|
-
return { state, recoveryProven };
|
|
180
|
+
if (state.status === "paused") state.resumeAfterManualCompaction = undefined;
|
|
181
|
+
return { state };
|
|
144
182
|
}
|
|
145
|
-
return { state: undefined
|
|
183
|
+
return { state: undefined };
|
|
146
184
|
}
|
|
147
185
|
|
|
148
186
|
export function goalElapsedMilliseconds(state: GoalState, now = Date.now()): number {
|
|
@@ -173,6 +211,21 @@ function sumGoalTokens(ctx: ExtensionContext): number {
|
|
|
173
211
|
return total;
|
|
174
212
|
}
|
|
175
213
|
|
|
214
|
+
function setGoalUpdateToolActive(pi: ExtensionAPI, active: boolean): void {
|
|
215
|
+
const api = pi as ExtensionAPI & { getActiveTools?: () => string[]; setActiveTools?: (names: string[]) => void };
|
|
216
|
+
if (!api.getActiveTools || !api.setActiveTools) return;
|
|
217
|
+
const activeTools = api.getActiveTools();
|
|
218
|
+
const isActive = activeTools.includes(GOAL_UPDATE_TOOL);
|
|
219
|
+
if (active === isActive) return;
|
|
220
|
+
api.setActiveTools(active
|
|
221
|
+
? [...activeTools, GOAL_UPDATE_TOOL]
|
|
222
|
+
: activeTools.filter((name) => name !== GOAL_UPDATE_TOOL));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function syncGoalUpdateTool(pi: ExtensionAPI, runtime: GoalRuntime): void {
|
|
226
|
+
setGoalUpdateToolActive(pi, runtime.state?.status === "active");
|
|
227
|
+
}
|
|
228
|
+
|
|
176
229
|
function persistGoalState(
|
|
177
230
|
pi: ExtensionAPI,
|
|
178
231
|
runtime: GoalRuntime,
|
|
@@ -182,6 +235,7 @@ function persistGoalState(
|
|
|
182
235
|
const data: GoalEntryData = { version: GOAL_VERSION, event, state: state ?? null };
|
|
183
236
|
pi.appendEntry(GOAL_ENTRY_TYPE, data);
|
|
184
237
|
runtime.state = state;
|
|
238
|
+
syncGoalUpdateTool(pi, runtime);
|
|
185
239
|
runtime.persistenceRetryNeeded = false;
|
|
186
240
|
runtime.requestRender?.();
|
|
187
241
|
}
|
|
@@ -276,6 +330,7 @@ export function pauseGoalAfterFailure(
|
|
|
276
330
|
result: reason,
|
|
277
331
|
resumeAfterManualCompaction: undefined,
|
|
278
332
|
} : undefined;
|
|
333
|
+
syncGoalUpdateTool(pi, runtime);
|
|
279
334
|
runtime.persistenceRetryNeeded = true;
|
|
280
335
|
runtime.continuationScheduled = false;
|
|
281
336
|
runtime.requestRender?.();
|
|
@@ -301,6 +356,7 @@ function pauseGoalForPossibleManualCompaction(
|
|
|
301
356
|
result: reason,
|
|
302
357
|
resumeAfterManualCompaction: true,
|
|
303
358
|
} : undefined;
|
|
359
|
+
syncGoalUpdateTool(pi, runtime);
|
|
304
360
|
runtime.persistenceRetryNeeded = true;
|
|
305
361
|
runtime.continuationScheduled = false;
|
|
306
362
|
runtime.requestRender?.();
|
|
@@ -333,6 +389,34 @@ function recoverGoalAfterManualCompaction(
|
|
|
333
389
|
return true;
|
|
334
390
|
}
|
|
335
391
|
|
|
392
|
+
function beginGoalTurn(
|
|
393
|
+
pi: ExtensionAPI,
|
|
394
|
+
runtime: GoalRuntime,
|
|
395
|
+
ctx: ExtensionContext,
|
|
396
|
+
current: GoalState,
|
|
397
|
+
): GoalState | undefined {
|
|
398
|
+
const now = Date.now();
|
|
399
|
+
const next: GoalState = {
|
|
400
|
+
...current,
|
|
401
|
+
revision: current.revision + 1,
|
|
402
|
+
turns: current.turns + 1,
|
|
403
|
+
updatedAt: now,
|
|
404
|
+
activeStartedAt: current.activeStartedAt ?? now,
|
|
405
|
+
resumeAfterManualCompaction: undefined,
|
|
406
|
+
};
|
|
407
|
+
try {
|
|
408
|
+
persistGoalState(pi, runtime, "turn", next);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
pauseGoalAfterFailure(pi, runtime, ctx, `turn state could not be saved: ${error instanceof Error ? error.message : String(error)}`);
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
runtime.goalTurnInFlight = true;
|
|
414
|
+
runtime.agentEndObserved = false;
|
|
415
|
+
runtime.lastStopReason = undefined;
|
|
416
|
+
runtime.lastError = undefined;
|
|
417
|
+
return next;
|
|
418
|
+
}
|
|
419
|
+
|
|
336
420
|
function scheduleGoalContinuation(
|
|
337
421
|
pi: ExtensionAPI,
|
|
338
422
|
runtime: GoalRuntime,
|
|
@@ -348,16 +432,13 @@ function scheduleGoalContinuation(
|
|
|
348
432
|
|| initState.active
|
|
349
433
|
|| !ctx.isIdle()
|
|
350
434
|
|| ctx.hasPendingMessages()) return false;
|
|
351
|
-
const current = runtime.state;
|
|
352
435
|
runtime.continuationScheduled = true;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
runtime.lastStopReason = undefined;
|
|
356
|
-
runtime.lastError = undefined;
|
|
436
|
+
const next = beginGoalTurn(pi, runtime, ctx, runtime.state);
|
|
437
|
+
if (!next) return false;
|
|
357
438
|
try {
|
|
358
439
|
pi.sendMessage({
|
|
359
440
|
customType: GOAL_CONTINUATION_TYPE,
|
|
360
|
-
content: goalContinuationMessage(
|
|
441
|
+
content: goalContinuationMessage(next, ctx),
|
|
361
442
|
display: false,
|
|
362
443
|
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
363
444
|
return true;
|
|
@@ -440,7 +521,7 @@ export function registerGoal(
|
|
|
440
521
|
});
|
|
441
522
|
|
|
442
523
|
pi.registerTool<typeof GoalUpdateParams, GoalUpdateDetails>({
|
|
443
|
-
name:
|
|
524
|
+
name: GOAL_UPDATE_TOOL,
|
|
444
525
|
label: "Goal update",
|
|
445
526
|
description: "Mark the active KillerOS long-running goal complete after verification, or record the same blocker key on three consecutive goal turns before blocking it.",
|
|
446
527
|
parameters: GoalUpdateParams,
|
|
@@ -453,10 +534,14 @@ export function registerGoal(
|
|
|
453
534
|
const evidence = params.evidence.trim();
|
|
454
535
|
if (!evidence) throw new Error("Goal evidence must not be empty");
|
|
455
536
|
if (params.status === "complete") {
|
|
537
|
+
if (state.verification) verifyGoalDeliverable(state.verification);
|
|
538
|
+
const verification = state.verification ? "file" : "model-reported";
|
|
456
539
|
transitionGoal(pi, runtime, "complete", "complete", evidence, { resetBlockedAudit: true });
|
|
457
540
|
return {
|
|
458
|
-
content: [{ type: "text", text:
|
|
459
|
-
|
|
541
|
+
content: [{ type: "text", text: state.verification
|
|
542
|
+
? `Goal verified complete at ${state.verification.path}: ${evidence}`
|
|
543
|
+
: `Goal marked complete (model-reported): ${evidence}` }],
|
|
544
|
+
details: { status: "complete", evidence, verification },
|
|
460
545
|
};
|
|
461
546
|
}
|
|
462
547
|
if (!runtime.goalTurnInFlight) throw new Error("A blocker audit can only be recorded during an active KillerOS goal turn");
|
|
@@ -491,7 +576,12 @@ export function registerGoal(
|
|
|
491
576
|
renderCall(args, theme) {
|
|
492
577
|
return new Text(`${theme.fg("toolTitle", theme.bold("goal "))}${theme.fg("muted", args.status)}`, 0, 0);
|
|
493
578
|
},
|
|
494
|
-
renderResult(result, options, theme) {
|
|
579
|
+
renderResult(result, options, theme, context) {
|
|
580
|
+
if (context?.isError) {
|
|
581
|
+
const first = result.content[0];
|
|
582
|
+
const message = first?.type === "text" ? first.text : "Goal update failed";
|
|
583
|
+
return new BoundedText(theme.fg("error", message), options.expanded ? undefined : 3);
|
|
584
|
+
}
|
|
495
585
|
const details = result.details;
|
|
496
586
|
if (!details) return new BoundedText(theme.fg("dim", "Goal updated"));
|
|
497
587
|
const label = details.status === "complete" ? "✓ Complete" : details.status === "blocked" ? "! Blocked" : `! Blocker audit ${details.streak}/3`;
|
|
@@ -502,7 +592,8 @@ export function registerGoal(
|
|
|
502
592
|
|
|
503
593
|
const restoreGoal = (ctx: ExtensionContext): void => {
|
|
504
594
|
const restored = restoreGoalState(ctx);
|
|
505
|
-
runtime.state = restored.state;
|
|
595
|
+
runtime.state = isGoalModeSupported(ctx) ? restored.state : undefined;
|
|
596
|
+
syncGoalUpdateTool(pi, runtime);
|
|
506
597
|
runtime.continuationScheduled = false;
|
|
507
598
|
runtime.continuationHeld = false;
|
|
508
599
|
runtime.goalTurnInFlight = false;
|
|
@@ -511,9 +602,7 @@ export function registerGoal(
|
|
|
511
602
|
runtime.lastStopReason = undefined;
|
|
512
603
|
runtime.lastError = undefined;
|
|
513
604
|
runtime.requestRender?.();
|
|
514
|
-
if (
|
|
515
|
-
recoverGoalAfterManualCompaction(pi, runtime, initState, ctx);
|
|
516
|
-
} else if (runtime.state?.status === "active") {
|
|
605
|
+
if (runtime.state?.status === "active") {
|
|
517
606
|
setImmediate(() => scheduleGoalContinuation(pi, runtime, initState, ctx));
|
|
518
607
|
}
|
|
519
608
|
};
|
|
@@ -536,6 +625,7 @@ export function registerGoal(
|
|
|
536
625
|
}
|
|
537
626
|
}
|
|
538
627
|
runtime.state = undefined;
|
|
628
|
+
syncGoalUpdateTool(pi, runtime);
|
|
539
629
|
runtime.continuationScheduled = false;
|
|
540
630
|
runtime.continuationHeld = false;
|
|
541
631
|
runtime.goalTurnInFlight = false;
|
|
@@ -550,25 +640,8 @@ export function registerGoal(
|
|
|
550
640
|
const current = runtime.state;
|
|
551
641
|
if (!isGoalModeSupported(ctx) || !isSavedSession(ctx) || !current || current.status !== "active" || initState.active) return;
|
|
552
642
|
if (runtime.goalTurnInFlight) return { systemPrompt: `${event.systemPrompt}\n\n${goalSystemPrompt(current)}` };
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
...current,
|
|
556
|
-
revision: current.revision + 1,
|
|
557
|
-
turns: current.turns + 1,
|
|
558
|
-
updatedAt: now,
|
|
559
|
-
activeStartedAt: current.activeStartedAt ?? now,
|
|
560
|
-
resumeAfterManualCompaction: undefined,
|
|
561
|
-
};
|
|
562
|
-
try {
|
|
563
|
-
persistGoalState(pi, runtime, "turn", next);
|
|
564
|
-
} catch (error) {
|
|
565
|
-
pauseGoalAfterFailure(pi, runtime, ctx, `turn state could not be saved: ${error instanceof Error ? error.message : String(error)}`);
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
runtime.goalTurnInFlight = true;
|
|
569
|
-
runtime.agentEndObserved = false;
|
|
570
|
-
runtime.lastStopReason = undefined;
|
|
571
|
-
runtime.lastError = undefined;
|
|
643
|
+
const next = beginGoalTurn(pi, runtime, ctx, current);
|
|
644
|
+
if (!next) return;
|
|
572
645
|
return { systemPrompt: `${event.systemPrompt}\n\n${goalSystemPrompt(next)}` };
|
|
573
646
|
});
|
|
574
647
|
|
|
@@ -672,6 +745,7 @@ export function registerGoal(
|
|
|
672
745
|
ctx.ui.notify("Goal pause saved. Goal remains paused. Automatic compaction recovery is off.", "info");
|
|
673
746
|
} catch (error) {
|
|
674
747
|
runtime.state = checkpoint;
|
|
748
|
+
syncGoalUpdateTool(pi, runtime);
|
|
675
749
|
runtime.persistenceRetryNeeded = true;
|
|
676
750
|
runtime.continuationScheduled = false;
|
|
677
751
|
runtime.requestRender?.();
|
|
@@ -790,6 +864,7 @@ export function registerGoal(
|
|
|
790
864
|
activeStartedAt: now,
|
|
791
865
|
blockedAuditStartTurn: current.turns,
|
|
792
866
|
blockerAudit: undefined,
|
|
867
|
+
verification: inferGoalVerification(objective),
|
|
793
868
|
result: undefined,
|
|
794
869
|
resumeAfterManualCompaction: undefined,
|
|
795
870
|
};
|
|
@@ -861,6 +936,7 @@ export function registerGoal(
|
|
|
861
936
|
turns: 0,
|
|
862
937
|
blockedAuditStartTurn: 0,
|
|
863
938
|
baselineTokens: sumGoalTokens(ctx),
|
|
939
|
+
verification: inferGoalVerification(objective),
|
|
864
940
|
};
|
|
865
941
|
try {
|
|
866
942
|
persistGoalState(pi, runtime, unfinished ? "replace" : "set", state);
|
package/killeros/init.ts
CHANGED
|
@@ -133,8 +133,10 @@ export function registerInitCommand(pi: ExtensionAPI, initState: InitRuntime, go
|
|
|
133
133
|
|
|
134
134
|
pi.on("session_start", () => setInitTools(pi, initState, false));
|
|
135
135
|
pi.on("session_shutdown", () => {
|
|
136
|
+
const settle = initState.settle;
|
|
136
137
|
setInitTools(pi, initState, false);
|
|
137
138
|
resetInitRuntime(initState);
|
|
139
|
+
settle?.({ kind: "cancelled" });
|
|
138
140
|
});
|
|
139
141
|
pi.on("before_agent_start", () => {
|
|
140
142
|
if (initState.active) setInitTools(pi, initState, true);
|
|
@@ -160,7 +162,7 @@ export function registerInitCommand(pi: ExtensionAPI, initState: InitRuntime, go
|
|
|
160
162
|
ctx.ui.notify("/init requires interactive TUI mode", "error");
|
|
161
163
|
return;
|
|
162
164
|
}
|
|
163
|
-
if (initState.active) {
|
|
165
|
+
if (initState.active || initState.starting) {
|
|
164
166
|
ctx.ui.notify("/init is already running", "warning");
|
|
165
167
|
return;
|
|
166
168
|
}
|
|
@@ -172,28 +174,45 @@ export function registerInitCommand(pi: ExtensionAPI, initState: InitRuntime, go
|
|
|
172
174
|
ctx.ui.notify("Trust this project before running /init", "error");
|
|
173
175
|
return;
|
|
174
176
|
}
|
|
175
|
-
|
|
177
|
+
const starting = Symbol();
|
|
178
|
+
initState.starting = starting;
|
|
179
|
+
try {
|
|
180
|
+
await ctx.waitForIdle();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (initState.starting !== starting) return;
|
|
183
|
+
initState.starting = undefined;
|
|
184
|
+
reportError(ctx, "/init could not wait for active work", error);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (initState.starting !== starting) return;
|
|
176
188
|
|
|
177
189
|
let projectRoot: string;
|
|
178
190
|
try {
|
|
179
191
|
projectRoot = await fs.realpath(ctx.cwd);
|
|
180
192
|
} catch (error) {
|
|
193
|
+
if (initState.starting !== starting) return;
|
|
194
|
+
initState.starting = undefined;
|
|
181
195
|
reportError(ctx, "/init could not resolve the project root", error);
|
|
182
196
|
return;
|
|
183
197
|
}
|
|
198
|
+
if (initState.starting !== starting) return;
|
|
184
199
|
const targetPath = path.join(projectRoot, "AGENTS.md");
|
|
185
200
|
try {
|
|
186
201
|
const [{ index: evidence }, baseline] = await Promise.all([
|
|
187
202
|
buildInitEvidence(projectRoot),
|
|
188
203
|
captureInitTargetBaseline(targetPath),
|
|
189
204
|
]);
|
|
205
|
+
if (initState.starting !== starting) return;
|
|
190
206
|
initState.active = true;
|
|
191
207
|
initState.projectRoot = projectRoot;
|
|
192
208
|
initState.targetPath = targetPath;
|
|
193
209
|
initState.evidence = evidence;
|
|
194
210
|
initState.baseline = baseline;
|
|
195
211
|
initState.outcome = { kind: "pending" };
|
|
212
|
+
initState.starting = undefined;
|
|
196
213
|
} catch (error) {
|
|
214
|
+
if (initState.starting !== starting) return;
|
|
215
|
+
initState.starting = undefined;
|
|
197
216
|
reportError(ctx, "/init could not capture safe repository evidence", error);
|
|
198
217
|
return;
|
|
199
218
|
}
|
|
@@ -234,6 +253,8 @@ export function registerInitCommand(pi: ExtensionAPI, initState: InitRuntime, go
|
|
|
234
253
|
case "policy-conflict":
|
|
235
254
|
ctx.ui.notify(`/init left AGENTS.md unchanged: ${outcome.reason}`, "warning");
|
|
236
255
|
break;
|
|
256
|
+
case "cancelled":
|
|
257
|
+
break;
|
|
237
258
|
default:
|
|
238
259
|
reportError(ctx, "/init did not generate AGENTS.md", "the model completed without a write or policy-conflict outcome");
|
|
239
260
|
}
|