killeros 2.1.27 → 2.1.29
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 +32 -0
- package/README.md +8 -4
- package/killeros/activity.ts +16 -7
- package/killeros/auto-compaction.ts +58 -7
- package/killeros/change-receipt.ts +37 -130
- package/killeros/commands.ts +1 -0
- package/killeros/footer.ts +48 -77
- package/killeros/goal-interface.ts +195 -48
- package/killeros/goal-runtime.ts +191 -45
- package/killeros/goal-settlement.ts +129 -30
- package/killeros/goal-state.ts +205 -32
- package/killeros/passive-git-status.ts +212 -72
- package/killeros/runtime.ts +54 -0
- package/killeros/worked-for.ts +1 -1
- package/package.json +1 -1
- package/themes/killeros.json +3 -2
|
@@ -1,21 +1,40 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { accessSync, constants, existsSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { lstat, open, readlink } from "node:fs/promises";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
const WORKTREE_CONTENT_LIMIT = 128 * 1024 * 1024;
|
|
7
|
+
const PASSIVE_METADATA_ARGS = ["-c", "core.fsmonitor=false"] as const;
|
|
8
|
+
|
|
9
|
+
export type PassiveGitRunner = (args: readonly string[]) => Promise<Buffer>;
|
|
10
|
+
|
|
11
|
+
export type PassiveWorktreeFile = {
|
|
12
|
+
path: string;
|
|
13
|
+
headMode?: string;
|
|
14
|
+
headObjectId?: string;
|
|
15
|
+
indexMode?: string;
|
|
16
|
+
indexObjectId?: string;
|
|
17
|
+
mode?: string;
|
|
18
|
+
content?: Buffer;
|
|
19
|
+
contentObjectId?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type PassiveWorktreeSnapshot = {
|
|
23
|
+
head: string;
|
|
24
|
+
files: Map<string, PassiveWorktreeFile>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type IndexEntry = {
|
|
28
|
+
mode: string;
|
|
29
|
+
objectId: string;
|
|
30
|
+
eolAttributes: string;
|
|
31
|
+
ctimeNanoseconds: bigint;
|
|
32
|
+
mtimeNanoseconds: bigint;
|
|
33
|
+
size: bigint;
|
|
34
|
+
skipWorktree: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type TreeEntry = { mode: string; objectId: string };
|
|
19
38
|
|
|
20
39
|
function searchPathEntries(env: NodeJS.ProcessEnv): string[] {
|
|
21
40
|
const values: string[] = [];
|
|
@@ -101,13 +120,7 @@ function insideInspected(candidate: string, root: string): boolean {
|
|
|
101
120
|
return normalizedCandidate.startsWith(`${trimmed}${path.sep}`);
|
|
102
121
|
}
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
// no safe candidate exists. Never starts a helper process and never
|
|
106
|
-
// returns a bare command name, so opening a repository cannot execute a
|
|
107
|
-
// repository-local locator or Git executable. Empty and relative
|
|
108
|
-
// search-path entries are ignored because they can resolve against the
|
|
109
|
-
// current directory. A candidate that resolves through a link returns its
|
|
110
|
-
// final path only when that path is also outside the repository.
|
|
123
|
+
/** Returns an absolute Git binary outside the inspected repository without starting a locator process. */
|
|
111
124
|
export function passiveGitCommand(cwd: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
112
125
|
const root = inspectedRoot(cwd);
|
|
113
126
|
if (!root) return undefined;
|
|
@@ -118,8 +131,7 @@ export function passiveGitCommand(cwd: string, env: NodeJS.ProcessEnv = process.
|
|
|
118
131
|
for (const raw of entries) {
|
|
119
132
|
if (raw === "" || raw.trim() === "") continue;
|
|
120
133
|
const directory = unquoted(raw);
|
|
121
|
-
if (directory === "" || directory.trim() === "") continue;
|
|
122
|
-
if (!path.isAbsolute(directory)) continue;
|
|
134
|
+
if (directory === "" || directory.trim() === "" || !path.isAbsolute(directory)) continue;
|
|
123
135
|
for (const base of baseNames) {
|
|
124
136
|
const candidate = path.join(directory, base);
|
|
125
137
|
try {
|
|
@@ -140,60 +152,15 @@ export function passiveGitCommand(cwd: string, env: NodeJS.ProcessEnv = process.
|
|
|
140
152
|
} catch {
|
|
141
153
|
continue;
|
|
142
154
|
}
|
|
143
|
-
if (
|
|
144
|
-
if (insideInspected(resolved, root)) continue;
|
|
145
|
-
return resolved;
|
|
155
|
+
if (path.isAbsolute(resolved) && !insideInspected(resolved, root)) return resolved;
|
|
146
156
|
}
|
|
147
157
|
}
|
|
148
158
|
return undefined;
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
// undefined when discovery output is incomplete or names a driver the
|
|
153
|
-
// safety overrides cannot represent.
|
|
154
|
-
export function passiveFilterNames(config: string): string[] | undefined {
|
|
155
|
-
const records = config.split("\0");
|
|
156
|
-
if (records.at(-1) !== "") return undefined;
|
|
157
|
-
const names = new Set<string>();
|
|
158
|
-
for (const key of records) {
|
|
159
|
-
if (!key) continue;
|
|
160
|
-
const name = /^filter\.(.*)\.(?:clean|process)$/us.exec(key)?.[1];
|
|
161
|
-
if (name === undefined) continue;
|
|
162
|
-
if (!SAFE_FILTER_NAME.test(name)) return undefined;
|
|
163
|
-
names.add(name);
|
|
164
|
-
}
|
|
165
|
-
return [...names];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Builds safe overrides from null-delimited `git config --name-only --list`
|
|
169
|
-
// output. Returns undefined when output is incomplete or names an unsafe
|
|
170
|
-
// filter, so the caller skips the status call.
|
|
171
|
-
export function passiveStatusSafetyArgs(config: string): string[] | undefined {
|
|
172
|
-
const names = passiveFilterNames(config);
|
|
173
|
-
if (!names) return undefined;
|
|
174
|
-
return ["-c", "core.fsmonitor=false", ...names.flatMap((name) => ["-c", `filter.${name}.clean=`, "-c", `filter.${name}.process=`, "-c", `filter.${name}.required=false`])];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function samePassiveFilters(before: string, after: string): boolean {
|
|
178
|
-
const earlier = passiveFilterNames(before);
|
|
179
|
-
const later = passiveFilterNames(after);
|
|
180
|
-
if (!earlier || !later) return false;
|
|
181
|
-
if (earlier.length !== later.length) return false;
|
|
182
|
-
const ordered = [...later].sort();
|
|
183
|
-
return [...earlier].sort().every((name, index) => name === ordered[index]);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Environment for automatic Git children: no optional locks, no lazy fetch
|
|
187
|
-
// from a promisor remote, and no PATH so a filter command that becomes
|
|
188
|
-
// effective after discovery cannot resolve a bare command name. Absolute
|
|
189
|
-
// filter paths are still possible; trusted-project callers detect mid-scan
|
|
190
|
-
// config changes with samePassiveFilters and skip those results.
|
|
161
|
+
/** Environment for automatic Git children, with locks, lazy fetches, and PATH command resolution disabled. */
|
|
191
162
|
export function passiveGitEnv(base: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
192
|
-
const env: NodeJS.ProcessEnv = {
|
|
193
|
-
...base,
|
|
194
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
195
|
-
GIT_NO_LAZY_FETCH: "1",
|
|
196
|
-
};
|
|
163
|
+
const env: NodeJS.ProcessEnv = { ...base, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1" };
|
|
197
164
|
let hasPath = false;
|
|
198
165
|
for (const key of Object.keys(env)) {
|
|
199
166
|
if (key.toLowerCase() === "path") {
|
|
@@ -204,3 +171,176 @@ export function passiveGitEnv(base: NodeJS.ProcessEnv = process.env): NodeJS.Pro
|
|
|
204
171
|
if (!hasPath) env.PATH = "";
|
|
205
172
|
return env;
|
|
206
173
|
}
|
|
174
|
+
|
|
175
|
+
function decode(buffer: Buffer): string {
|
|
176
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function parseIndex(output: Buffer): Map<string, IndexEntry> {
|
|
180
|
+
const text = decode(output);
|
|
181
|
+
const pattern = /([0-7]{6}) ([0-9a-f]{40}(?:[0-9a-f]{24})?) ([0-3])\t([^\t]*)\t([^\0]*)\0 ctime: (\d+):(\d+)\n mtime: (\d+):(\d+)\n dev: \d+\tino: \d+\n uid: \d+\tgid: \d+\n size: (\d+)\tflags: ([0-9a-f]+)\n/gu;
|
|
182
|
+
const entries = new Map<string, IndexEntry>();
|
|
183
|
+
let end = 0;
|
|
184
|
+
for (const match of text.matchAll(pattern)) {
|
|
185
|
+
if (match.index !== end) throw new Error("invalid index metadata");
|
|
186
|
+
end = match.index + match[0].length;
|
|
187
|
+
const [, mode, objectId, stage, eolAttributes, filePath, ctimeSeconds, ctimeNanoseconds, mtimeSeconds, mtimeNanoseconds, size, flags] = match;
|
|
188
|
+
if (!mode || !objectId || !stage || eolAttributes === undefined || filePath === undefined || !ctimeSeconds || !ctimeNanoseconds || !mtimeSeconds || !mtimeNanoseconds || !size || !flags || stage !== "0") {
|
|
189
|
+
throw new Error("unsupported index entry");
|
|
190
|
+
}
|
|
191
|
+
entries.set(filePath, {
|
|
192
|
+
mode,
|
|
193
|
+
objectId,
|
|
194
|
+
eolAttributes: eolAttributes.trim(),
|
|
195
|
+
ctimeNanoseconds: BigInt(ctimeSeconds) * 1_000_000_000n + BigInt(ctimeNanoseconds),
|
|
196
|
+
mtimeNanoseconds: BigInt(mtimeSeconds) * 1_000_000_000n + BigInt(mtimeNanoseconds),
|
|
197
|
+
size: BigInt(size),
|
|
198
|
+
skipWorktree: (Number.parseInt(flags, 16) & 0x40000000) !== 0,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (end !== text.length) throw new Error("incomplete index metadata");
|
|
202
|
+
return entries;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function parseTree(output: Buffer): Map<string, TreeEntry> {
|
|
206
|
+
const entries = new Map<string, TreeEntry>();
|
|
207
|
+
for (const record of decode(output).split("\0")) {
|
|
208
|
+
if (!record) continue;
|
|
209
|
+
const match = /^([0-7]{6}) (?:blob|commit) ([0-9a-f]{40}(?:[0-9a-f]{24})?)\t([^\0]+)$/u.exec(record);
|
|
210
|
+
if (!match?.[1] || !match[2] || !match[3]) throw new Error("invalid tree entry");
|
|
211
|
+
entries.set(match[3], { mode: match[1], objectId: match[2] });
|
|
212
|
+
}
|
|
213
|
+
return entries;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parsePaths(output: Buffer): string[] {
|
|
217
|
+
const records = decode(output).split("\0");
|
|
218
|
+
if (records.at(-1) !== "") throw new Error("incomplete path list");
|
|
219
|
+
return records.slice(0, -1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function readFileState(root: string, entry: IndexEntry | undefined, filePath: string, remaining: number): Promise<{ mode: string; content?: Buffer; statsMatch: boolean }> {
|
|
223
|
+
const absolutePath = path.join(root, ...filePath.split("/"));
|
|
224
|
+
const stats = await lstat(absolutePath, { bigint: true });
|
|
225
|
+
const mode = stats.isSymbolicLink() ? "120000" : stats.isFile() ? stats.mode & 0o111n ? "100755" : "100644" : "";
|
|
226
|
+
if (!mode) throw new Error("unsupported worktree entry");
|
|
227
|
+
const statsMatch = entry !== undefined
|
|
228
|
+
&& entry.mode === mode
|
|
229
|
+
&& entry.size === stats.size
|
|
230
|
+
&& entry.ctimeNanoseconds === stats.ctimeNs
|
|
231
|
+
&& entry.mtimeNanoseconds === stats.mtimeNs;
|
|
232
|
+
if (statsMatch) return { mode, statsMatch };
|
|
233
|
+
if (!stats.isSymbolicLink() && stats.size > BigInt(remaining)) throw new Error("worktree content limit exceeded");
|
|
234
|
+
const content = stats.isSymbolicLink() ? Buffer.from(await readlink(absolutePath)) : await readBounded(absolutePath, remaining);
|
|
235
|
+
return { mode, content, statsMatch };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function readBounded(filePath: string, limit: number): Promise<Buffer> {
|
|
239
|
+
const handle = await open(filePath, "r");
|
|
240
|
+
try {
|
|
241
|
+
const stats = await handle.stat();
|
|
242
|
+
if (!stats.isFile() || stats.size > limit) throw new Error("worktree content limit exceeded");
|
|
243
|
+
const content = Buffer.alloc(stats.size + 1);
|
|
244
|
+
let length = 0;
|
|
245
|
+
while (length < content.length) {
|
|
246
|
+
const { bytesRead } = await handle.read(content, length, content.length - length, length);
|
|
247
|
+
if (bytesRead === 0) break;
|
|
248
|
+
length += bytesRead;
|
|
249
|
+
}
|
|
250
|
+
if (length > stats.size) throw new Error("worktree changed while reading");
|
|
251
|
+
return content.subarray(0, length);
|
|
252
|
+
} finally {
|
|
253
|
+
await handle.close();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function blobObjectId(content: Buffer, length: number): string {
|
|
258
|
+
const algorithm = length === 64 ? "sha256" : "sha1";
|
|
259
|
+
return createHash(algorithm).update(`blob ${content.length}\0`).update(content).digest("hex");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function matchesIndex(content: Buffer, entry: IndexEntry, autoCrlf: string): boolean {
|
|
263
|
+
if (blobObjectId(content, entry.objectId.length) === entry.objectId) return true;
|
|
264
|
+
if (entry.mode === "120000" || !content.includes(Buffer.from("\r\n"))) return false;
|
|
265
|
+
const attributes = entry.eolAttributes;
|
|
266
|
+
if (attributes === "-text") return false;
|
|
267
|
+
const automatic = attributes.startsWith("text=auto") || !attributes;
|
|
268
|
+
if (!attributes && !["true", "input"].includes(autoCrlf)) return false;
|
|
269
|
+
if (automatic) {
|
|
270
|
+
// Match Git's convert_is_binary heuristic, including its terminal EOF exception.
|
|
271
|
+
let printable = 0;
|
|
272
|
+
let nonprintable = 0;
|
|
273
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
274
|
+
const byte = content[index];
|
|
275
|
+
if (byte === 0 || byte === 13 && content[index + 1] !== 10) return false;
|
|
276
|
+
if (byte === 13 || byte === 10 || byte === 26 && index === content.length - 1) continue;
|
|
277
|
+
if (byte === 127 || byte < 32 && ![8, 9, 12, 27].includes(byte)) nonprintable += 1;
|
|
278
|
+
else printable += 1;
|
|
279
|
+
}
|
|
280
|
+
if (Math.floor(printable / 128) < nonprintable) return false;
|
|
281
|
+
}
|
|
282
|
+
const normalized = Buffer.from(content.toString("latin1").replaceAll("\r\n", "\n"), "latin1");
|
|
283
|
+
return blobObjectId(normalized, entry.objectId.length) === entry.objectId;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Reads index and tree metadata with Git, then inspects worktree files with Node.
|
|
288
|
+
* No Git command in this path asks Git to convert worktree content, so clean and process filters cannot start.
|
|
289
|
+
*/
|
|
290
|
+
export async function inspectWorktreeWithoutFilters(root: string, runGit: PassiveGitRunner): Promise<PassiveWorktreeSnapshot> {
|
|
291
|
+
const [indexOutput, untrackedOutput, resolvedHead, autoCrlfOutput] = await Promise.all([
|
|
292
|
+
runGit([...PASSIVE_METADATA_ARGS, "ls-files", "--cached", "--debug", "-z", "--full-name",
|
|
293
|
+
"--format=%(objectmode) %(objectname) %(stage)%x09%(eolattr)%x09%(path)"]),
|
|
294
|
+
runGit([...PASSIVE_METADATA_ARGS, "ls-files", "--others", "--exclude-standard", "-z", "--full-name"]),
|
|
295
|
+
runGit(["rev-parse", "--verify", "HEAD"]).then(decode, () => ""),
|
|
296
|
+
runGit(["config", "--type=bool-or-str", "--default", "false", "--get", "core.autocrlf"]),
|
|
297
|
+
]);
|
|
298
|
+
const autoCrlf = decode(autoCrlfOutput).trim().toLowerCase();
|
|
299
|
+
const head = resolvedHead.trim();
|
|
300
|
+
if (head && !/^[0-9a-f]{40}(?:[0-9a-f]{24})?$/u.test(head)) throw new Error("invalid HEAD");
|
|
301
|
+
const index = parseIndex(indexOutput);
|
|
302
|
+
const tree = head ? parseTree(await runGit(["ls-tree", "-r", "-z", "--full-tree", head])) : new Map<string, TreeEntry>();
|
|
303
|
+
const files = new Map<string, PassiveWorktreeFile>();
|
|
304
|
+
let contentBytes = 0;
|
|
305
|
+
|
|
306
|
+
for (const filePath of new Set([...tree.keys(), ...index.keys()])) {
|
|
307
|
+
const headEntry = tree.get(filePath);
|
|
308
|
+
const indexEntry = index.get(filePath);
|
|
309
|
+
if (headEntry?.mode === "160000" || indexEntry?.mode === "160000") continue;
|
|
310
|
+
const staged = headEntry?.mode !== indexEntry?.mode || headEntry?.objectId !== indexEntry?.objectId;
|
|
311
|
+
let mode: string | undefined;
|
|
312
|
+
let content: Buffer | undefined;
|
|
313
|
+
let worktreeChanged = false;
|
|
314
|
+
if (indexEntry && !indexEntry.skipWorktree) {
|
|
315
|
+
try {
|
|
316
|
+
const state = await readFileState(root, indexEntry, filePath, WORKTREE_CONTENT_LIMIT - contentBytes);
|
|
317
|
+
mode = process.platform === "win32" && indexEntry.mode !== "120000" ? indexEntry.mode : state.mode;
|
|
318
|
+
content = state.content;
|
|
319
|
+
if (content) contentBytes += content.length;
|
|
320
|
+
worktreeChanged = !state.statsMatch && (mode !== indexEntry.mode || !content || !matchesIndex(content, indexEntry, autoCrlf));
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") worktreeChanged = true;
|
|
323
|
+
else throw error;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (!staged && !worktreeChanged) continue;
|
|
327
|
+
files.set(filePath, {
|
|
328
|
+
path: filePath,
|
|
329
|
+
headMode: headEntry?.mode,
|
|
330
|
+
headObjectId: headEntry?.objectId,
|
|
331
|
+
indexMode: indexEntry?.mode,
|
|
332
|
+
indexObjectId: indexEntry?.objectId,
|
|
333
|
+
mode: worktreeChanged ? mode : indexEntry?.mode,
|
|
334
|
+
...(worktreeChanged && content ? { content } : indexEntry ? { contentObjectId: indexEntry.objectId } : {}),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
for (const filePath of parsePaths(untrackedOutput)) {
|
|
339
|
+
const state = await readFileState(root, undefined, filePath, WORKTREE_CONTENT_LIMIT - contentBytes);
|
|
340
|
+
if (!state.content) throw new Error("missing untracked content");
|
|
341
|
+
contentBytes += state.content.length;
|
|
342
|
+
const previous = files.get(filePath);
|
|
343
|
+
files.set(filePath, { ...previous, path: filePath, mode: state.mode, content: state.content, contentObjectId: undefined });
|
|
344
|
+
}
|
|
345
|
+
return { head: head || "(initial)", files };
|
|
346
|
+
}
|
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",
|