killeros 2.1.26 → 2.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/Killeros.ts +6 -13
- package/README.md +8 -10
- package/killeros/activity.ts +16 -7
- package/killeros/auto-compaction.ts +1 -2
- package/killeros/change-receipt.ts +109 -37
- package/killeros/codex-fast.ts +8 -3
- package/killeros/footer.ts +82 -31
- package/killeros/goal-interface.ts +198 -60
- package/killeros/goal-runtime.ts +192 -49
- package/killeros/goal-settlement.ts +140 -49
- package/killeros/goal-state.ts +250 -34
- package/killeros/handoff.ts +36 -4
- package/killeros/passive-git-status.ts +206 -0
- package/killeros/personal-instructions.ts +2 -3
- package/killeros/runtime.ts +51 -35
- package/killeros/shell-ui.ts +13 -4
- package/killeros/worked-for.ts +4 -3
- package/package.json +1 -1
- package/themes/killeros.json +3 -2
- package/killeros/init-evidence.ts +0 -291
- package/killeros/init-target.ts +0 -309
- package/killeros/init.ts +0 -285
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
// Passive Git inspection disables fsmonitor, known clean/process filters,
|
|
5
|
+
// promisor fetches, and repository-supplied Git executables. Config discovery
|
|
6
|
+
// and status run in separate Git processes, so a filter configured between
|
|
7
|
+
// them would be absent from the safety overrides. Automatic Git children run
|
|
8
|
+
// without PATH resolution, and each scan rejects results when the effective
|
|
9
|
+
// filter set changed. Absolute filter commands remain possible, so product
|
|
10
|
+
// callers run these scans only after project trust is granted.
|
|
11
|
+
//
|
|
12
|
+
// Executable discovery itself is passive: it never starts a command shell,
|
|
13
|
+
// locator process, or other helper executable, and never executes a bare
|
|
14
|
+
// program name. It scans absolute search-path entries for an absolute Git
|
|
15
|
+
// path outside the inspected repository and fails closed when none exists.
|
|
16
|
+
export const PASSIVE_GIT_CONFIG_ARGS = ["config", "--includes", "--null", "--name-only", "--list"] as const;
|
|
17
|
+
|
|
18
|
+
const SAFE_FILTER_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
|
|
19
|
+
|
|
20
|
+
function searchPathEntries(env: NodeJS.ProcessEnv): string[] {
|
|
21
|
+
const values: string[] = [];
|
|
22
|
+
for (const [key, value] of Object.entries(env)) {
|
|
23
|
+
if (key.toLowerCase() === "path" && typeof value === "string") values.push(value);
|
|
24
|
+
}
|
|
25
|
+
const entries: string[] = [];
|
|
26
|
+
for (const value of values) entries.push(...value.split(path.delimiter));
|
|
27
|
+
return entries;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function pathExtensionEntries(env: NodeJS.ProcessEnv): string[] {
|
|
31
|
+
let raw: string | undefined;
|
|
32
|
+
for (const [key, value] of Object.entries(env)) {
|
|
33
|
+
if (key.toLowerCase() === "pathext" && typeof value === "string") {
|
|
34
|
+
raw = value;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
raw ??= ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL";
|
|
39
|
+
return raw.split(";").map((entry) => entry.trim()).filter(Boolean).map((entry) => entry.startsWith(".") ? entry : `.${entry}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function unquoted(entry: string): string {
|
|
43
|
+
if (entry.length >= 2) {
|
|
44
|
+
const first = entry[0];
|
|
45
|
+
const last = entry[entry.length - 1];
|
|
46
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) return entry.slice(1, -1);
|
|
47
|
+
}
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function inspectedRoot(cwd: string): string | undefined {
|
|
52
|
+
if (!cwd || typeof cwd !== "string") return undefined;
|
|
53
|
+
let start: string;
|
|
54
|
+
try {
|
|
55
|
+
start = path.resolve(cwd);
|
|
56
|
+
} catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
let base: string;
|
|
60
|
+
try {
|
|
61
|
+
base = realpathSync(start);
|
|
62
|
+
} catch {
|
|
63
|
+
base = start;
|
|
64
|
+
}
|
|
65
|
+
let current = base;
|
|
66
|
+
for (;;) {
|
|
67
|
+
try {
|
|
68
|
+
if (existsSync(path.join(current, ".git"))) {
|
|
69
|
+
try {
|
|
70
|
+
return realpathSync(current);
|
|
71
|
+
} catch {
|
|
72
|
+
return current;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// Unreadable directory: keep walking toward the filesystem root.
|
|
77
|
+
}
|
|
78
|
+
const parent = path.dirname(current);
|
|
79
|
+
if (parent === current) break;
|
|
80
|
+
current = parent;
|
|
81
|
+
}
|
|
82
|
+
return base;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function insideInspected(candidate: string, root: string): boolean {
|
|
86
|
+
if (process.platform === "win32") {
|
|
87
|
+
const normalizedCandidate = path.win32.normalize(candidate).toLowerCase();
|
|
88
|
+
const normalizedRoot = path.win32.normalize(root).toLowerCase();
|
|
89
|
+
const trimmed = normalizedRoot.length > 3 && normalizedRoot.endsWith(path.win32.sep)
|
|
90
|
+
? normalizedRoot.slice(0, -1)
|
|
91
|
+
: normalizedRoot;
|
|
92
|
+
if (normalizedCandidate === trimmed) return true;
|
|
93
|
+
return normalizedCandidate.startsWith(`${trimmed}${path.win32.sep}`);
|
|
94
|
+
}
|
|
95
|
+
const normalizedCandidate = path.normalize(candidate);
|
|
96
|
+
const normalizedRoot = path.normalize(root);
|
|
97
|
+
const trimmed = normalizedRoot.length > 1 && normalizedRoot.endsWith(path.sep)
|
|
98
|
+
? normalizedRoot.slice(0, -1)
|
|
99
|
+
: normalizedRoot;
|
|
100
|
+
if (normalizedCandidate === trimmed) return true;
|
|
101
|
+
return normalizedCandidate.startsWith(`${trimmed}${path.sep}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Absolute Git binary outside the inspected repository, or undefined when
|
|
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.
|
|
111
|
+
export function passiveGitCommand(cwd: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
112
|
+
const root = inspectedRoot(cwd);
|
|
113
|
+
if (!root) return undefined;
|
|
114
|
+
const entries = searchPathEntries(env);
|
|
115
|
+
if (entries.length === 0) return undefined;
|
|
116
|
+
const windows = process.platform === "win32";
|
|
117
|
+
const baseNames = windows ? ["git", ...pathExtensionEntries(env).map((extension) => `git${extension}`)] : ["git"];
|
|
118
|
+
for (const raw of entries) {
|
|
119
|
+
if (raw === "" || raw.trim() === "") continue;
|
|
120
|
+
const directory = unquoted(raw);
|
|
121
|
+
if (directory === "" || directory.trim() === "") continue;
|
|
122
|
+
if (!path.isAbsolute(directory)) continue;
|
|
123
|
+
for (const base of baseNames) {
|
|
124
|
+
const candidate = path.join(directory, base);
|
|
125
|
+
try {
|
|
126
|
+
if (!statSync(candidate).isFile()) continue;
|
|
127
|
+
} catch {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!windows) {
|
|
131
|
+
try {
|
|
132
|
+
accessSync(candidate, constants.X_OK);
|
|
133
|
+
} catch {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let resolved: string;
|
|
138
|
+
try {
|
|
139
|
+
resolved = realpathSync(candidate);
|
|
140
|
+
} catch {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!path.isAbsolute(resolved)) continue;
|
|
144
|
+
if (insideInspected(resolved, root)) continue;
|
|
145
|
+
return resolved;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Lists effective clean/process filter drivers in discovery order, or
|
|
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.
|
|
191
|
+
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
|
+
};
|
|
197
|
+
let hasPath = false;
|
|
198
|
+
for (const key of Object.keys(env)) {
|
|
199
|
+
if (key.toLowerCase() === "path") {
|
|
200
|
+
env[key] = "";
|
|
201
|
+
hasPath = true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!hasPath) env.PATH = "";
|
|
205
|
+
return env;
|
|
206
|
+
}
|
|
@@ -3,7 +3,6 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { StringDecoder } from "node:string_decoder";
|
|
5
5
|
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import type { InitRuntime } from "./runtime.ts";
|
|
7
6
|
|
|
8
7
|
const PERSONAL_INSTRUCTIONS_FILE = "AGENTS.local.md";
|
|
9
8
|
const PERSONAL_INSTRUCTIONS_LIMIT = 32 * 1024;
|
|
@@ -107,9 +106,9 @@ export function resolvePersonalInstructions(cwd: string): string | undefined {
|
|
|
107
106
|
return `<personal_instructions>\n${content}\n</personal_instructions>`;
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
export function registerPersonalInstructions(pi: ExtensionAPI
|
|
109
|
+
export function registerPersonalInstructions(pi: ExtensionAPI): void {
|
|
111
110
|
pi.on("before_agent_start", (event, ctx) => {
|
|
112
|
-
if (
|
|
111
|
+
if (!ctx.isProjectTrusted()) return;
|
|
113
112
|
const personal = resolvePersonalInstructions(ctx.cwd);
|
|
114
113
|
if (!personal) return;
|
|
115
114
|
return {
|
package/killeros/runtime.ts
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
import type { InitTargetBaseline } from "./init-target.ts";
|
|
1
|
+
export type GoalStatus = "active" | "paused" | "blocked" | "complete";
|
|
3
2
|
|
|
4
|
-
export type
|
|
5
|
-
| { kind: "pending" }
|
|
6
|
-
| { kind: "written"; recoveryPath?: string }
|
|
7
|
-
| { kind: "policy-conflict"; reason: string }
|
|
8
|
-
| { kind: "cancelled" }
|
|
9
|
-
| { kind: "no-outcome" };
|
|
3
|
+
export type GoalTurnPhase = "ready" | "in-flight" | "authorized";
|
|
10
4
|
|
|
11
|
-
export interface
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
projectRoot?: string;
|
|
16
|
-
activeTools?: string[];
|
|
17
|
-
evidence?: InitEvidenceIndex;
|
|
18
|
-
baseline?: InitTargetBaseline;
|
|
19
|
-
outcome: InitOutcome;
|
|
20
|
-
settle?: (outcome: InitOutcome) => void;
|
|
5
|
+
export interface GoalContinueReport {
|
|
6
|
+
turn: number;
|
|
7
|
+
evidence: string;
|
|
8
|
+
nextAction: string;
|
|
21
9
|
}
|
|
22
10
|
|
|
23
|
-
export type
|
|
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" }>;
|
|
24
40
|
|
|
25
41
|
export interface GoalBlockerAudit {
|
|
26
42
|
key: string;
|
|
@@ -51,6 +67,11 @@ export interface GoalStateCommon {
|
|
|
51
67
|
baselineTokens: number;
|
|
52
68
|
verification?: GoalFileVerification;
|
|
53
69
|
maxTurns?: number;
|
|
70
|
+
turnPhase?: GoalTurnPhase;
|
|
71
|
+
turnDecision?: GoalPendingDecision;
|
|
72
|
+
lastDecision?: GoalTurnDecision;
|
|
73
|
+
lastContinueReport?: GoalContinueReport;
|
|
74
|
+
stopReason?: string;
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
export type GoalState = GoalStateCommon & (
|
|
@@ -91,6 +112,13 @@ export interface AutomaticGoalCompaction {
|
|
|
91
112
|
pausedRevision: number;
|
|
92
113
|
outcome: AutomaticGoalCompactionOutcome;
|
|
93
114
|
turnSettled: boolean;
|
|
115
|
+
turn: number;
|
|
116
|
+
resumeSameTurn: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface GoalTurnExecution {
|
|
120
|
+
turn: number;
|
|
121
|
+
revision: number;
|
|
94
122
|
}
|
|
95
123
|
|
|
96
124
|
export interface GoalRuntime {
|
|
@@ -99,36 +127,24 @@ export interface GoalRuntime {
|
|
|
99
127
|
continuationHeld: boolean;
|
|
100
128
|
goalTurnInFlight: boolean;
|
|
101
129
|
agentEndObserved: boolean;
|
|
130
|
+
goalTurn?: GoalTurnExecution;
|
|
102
131
|
automaticCompaction?: AutomaticGoalCompaction;
|
|
103
132
|
persistenceRetryNeeded: boolean;
|
|
104
133
|
lastStopReason?: string;
|
|
105
134
|
lastError?: string;
|
|
135
|
+
lifecycleGeneration: number;
|
|
106
136
|
requestRender?: () => void;
|
|
107
137
|
}
|
|
108
138
|
|
|
109
|
-
export function createInitRuntime(): InitRuntime {
|
|
110
|
-
return { active: false, outcome: { kind: "pending" } };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
139
|
export function createGoalRuntime(): GoalRuntime {
|
|
114
140
|
return {
|
|
115
141
|
continuationScheduled: false,
|
|
116
142
|
continuationHeld: false,
|
|
117
143
|
goalTurnInFlight: false,
|
|
118
144
|
agentEndObserved: false,
|
|
145
|
+
goalTurn: undefined,
|
|
119
146
|
automaticCompaction: undefined,
|
|
120
147
|
persistenceRetryNeeded: false,
|
|
148
|
+
lifecycleGeneration: 0,
|
|
121
149
|
};
|
|
122
150
|
}
|
|
123
|
-
|
|
124
|
-
export function resetInitRuntime(state: InitRuntime): void {
|
|
125
|
-
state.active = false;
|
|
126
|
-
state.starting = undefined;
|
|
127
|
-
state.targetPath = undefined;
|
|
128
|
-
state.projectRoot = undefined;
|
|
129
|
-
state.activeTools = undefined;
|
|
130
|
-
state.evidence = undefined;
|
|
131
|
-
state.baseline = undefined;
|
|
132
|
-
state.outcome = { kind: "pending" };
|
|
133
|
-
state.settle = undefined;
|
|
134
|
-
}
|
package/killeros/shell-ui.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type SlashCommandResolver,
|
|
25
25
|
} from "./commands.ts";
|
|
26
26
|
import { reportError } from "./errors.ts";
|
|
27
|
+
import { passiveGitCommand, passiveGitEnv } from "./passive-git-status.ts";
|
|
27
28
|
|
|
28
29
|
function readPackageVersion(path: string | URL): string | undefined {
|
|
29
30
|
try {
|
|
@@ -47,7 +48,6 @@ const STARTUP_TIPS = [
|
|
|
47
48
|
"Type / to browse every command available in this session.",
|
|
48
49
|
"Run /notification to enable a terminal bell when work settles.",
|
|
49
50
|
"Run /goal <objective> to keep long-running work moving across turns.",
|
|
50
|
-
"Run /init to generate root AGENTS.md from bounded repository evidence.",
|
|
51
51
|
"Run /handoff [focus] to continue work in a fresh linked session.",
|
|
52
52
|
"Run /codex-fast to toggle priority requests for Codex models.",
|
|
53
53
|
"Run /clear to start a fresh session after confirmation.",
|
|
@@ -67,13 +67,22 @@ const EDITOR_SUGGESTIONS = [
|
|
|
67
67
|
'Try "draft an implementation plan for <feature>"',
|
|
68
68
|
] as const;
|
|
69
69
|
|
|
70
|
-
export function resolveGitBranch(cwd: string): Promise<string | undefined> {
|
|
70
|
+
export function resolveGitBranch(cwd: string, trusted = true): Promise<string | undefined> {
|
|
71
|
+
if (!trusted) return Promise.resolve(undefined);
|
|
72
|
+
let gitCommand: string | undefined;
|
|
73
|
+
try {
|
|
74
|
+
gitCommand = passiveGitCommand(cwd);
|
|
75
|
+
} catch {
|
|
76
|
+
return Promise.resolve(undefined);
|
|
77
|
+
}
|
|
78
|
+
if (!gitCommand) return Promise.resolve(undefined);
|
|
71
79
|
return new Promise((resolve) => {
|
|
72
80
|
execFile(
|
|
73
|
-
|
|
81
|
+
gitCommand,
|
|
74
82
|
["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
75
83
|
{
|
|
76
84
|
encoding: "utf8",
|
|
85
|
+
env: passiveGitEnv(),
|
|
77
86
|
maxBuffer: 64 * 1024,
|
|
78
87
|
timeout: 500,
|
|
79
88
|
windowsHide: true,
|
|
@@ -132,7 +141,7 @@ class PiStartupHeader {
|
|
|
132
141
|
this.ctx = ctx;
|
|
133
142
|
this.tip = tip;
|
|
134
143
|
this.tui = tui;
|
|
135
|
-
void resolveGitBranch(ctx.cwd).then((branch) => {
|
|
144
|
+
void resolveGitBranch(ctx.cwd, ctx.isProjectTrusted()).then((branch) => {
|
|
136
145
|
if (this.disposed) return;
|
|
137
146
|
this.branch = branch;
|
|
138
147
|
this.tui.requestRender();
|
package/killeros/worked-for.ts
CHANGED
|
@@ -142,7 +142,8 @@ function parseV4(data: Record<string, unknown>): WorkedForEntryDataV4 | undefine
|
|
|
142
142
|
const checks: CheckAttempt[] = [];
|
|
143
143
|
for (const check of data.checks) {
|
|
144
144
|
if (!record(check) || check.outcome !== "passed" && check.outcome !== "failed") return undefined;
|
|
145
|
-
const label = CHECK_LABELS.find((candidate) => candidate === check.label)
|
|
145
|
+
const label = CHECK_LABELS.find((candidate) => candidate === check.label)
|
|
146
|
+
?? (check.label === "node --test (focused)" ? "node --test (focused)" : undefined);
|
|
146
147
|
if (!label) return undefined;
|
|
147
148
|
checks.push({ label, outcome: check.outcome });
|
|
148
149
|
}
|
|
@@ -321,7 +322,7 @@ export function registerWorkedFor(
|
|
|
321
322
|
pi.registerEntryRenderer<WorkedForEntryData>(WORKED_FOR_ENTRY_TYPE, (entry, options, theme) => {
|
|
322
323
|
const data = parseWorkedForEntryData(entry.data);
|
|
323
324
|
if (!data) return undefined;
|
|
324
|
-
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);
|
|
325
326
|
if (data.version === 4) return new WorkedForV4Component(data, options.expanded, theme);
|
|
326
327
|
const outcome = OUTCOMES[data.outcome];
|
|
327
328
|
const tokens = data.version === 3 ? ` · ↑ ${formatTokens(data.tokens)} tokens` : "";
|
|
@@ -337,7 +338,7 @@ export function registerWorkedFor(
|
|
|
337
338
|
});
|
|
338
339
|
|
|
339
340
|
pi.on("agent_start", async (_event, ctx) => {
|
|
340
|
-
if (ctx.mode !== "tui" || active) return;
|
|
341
|
+
if (ctx.mode !== "tui" || active || !ctx.isProjectTrusted()) return;
|
|
341
342
|
const state: ActiveReceipt = {
|
|
342
343
|
startedAt: now(),
|
|
343
344
|
startedTokens: sessionTokenTotal(ctx),
|
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",
|