killeros 2.1.29 → 2.1.31

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.
@@ -1,150 +1,150 @@
1
- export type GoalStatus = "active" | "paused" | "blocked" | "complete";
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
-
41
- export interface GoalBlockerAudit {
42
- key: string;
43
- streak: number;
44
- lastTurn: number;
45
- evidence?: string;
46
- }
47
-
48
- export type GoalFileBaseline =
49
- | { exists: false }
50
- | { exists: true; size: number; mtimeMs: number; contentHash?: string | null };
51
-
52
- export interface GoalFileVerification {
53
- kind: "file";
54
- path: string;
55
- baseline: GoalFileBaseline;
56
- }
57
-
58
- export interface GoalStateCommon {
59
- version: 1;
60
- revision: number;
61
- objective: string;
62
- createdAt: number;
63
- updatedAt: number;
64
- activeMilliseconds: number;
65
- turns: number;
66
- blockedAuditStartTurn: number;
67
- baselineTokens: number;
68
- verification?: GoalFileVerification;
69
- maxTurns?: number;
70
- turnPhase?: GoalTurnPhase;
71
- turnDecision?: GoalPendingDecision;
72
- lastDecision?: GoalTurnDecision;
73
- lastContinueReport?: GoalContinueReport;
74
- stopReason?: string;
75
- }
76
-
77
- export type GoalState = GoalStateCommon & (
78
- | {
79
- status: "active";
80
- activeStartedAt: number;
81
- result?: string;
82
- blockerAudit?: GoalBlockerAudit;
83
- resumeAfterManualCompaction?: never;
84
- }
85
- | {
86
- status: "paused";
87
- activeStartedAt?: never;
88
- result?: string;
89
- blockerAudit?: GoalBlockerAudit;
90
- resumeAfterManualCompaction?: true;
91
- }
92
- | {
93
- status: "blocked";
94
- activeStartedAt?: never;
95
- result: string;
96
- blockerAudit?: GoalBlockerAudit;
97
- resumeAfterManualCompaction?: never;
98
- }
99
- | {
100
- status: "complete";
101
- activeStartedAt?: never;
102
- result: string;
103
- blockerAudit?: never;
104
- resumeAfterManualCompaction?: never;
105
- }
106
- );
107
-
108
- /** Pi request outcomes for goal recovery: awaiting result, compacted, or rejected as session-too-small. */
109
- export type AutomaticGoalCompactionOutcome = "pending" | "completed" | "skipped";
110
-
111
- export interface AutomaticGoalCompaction {
112
- pausedRevision: number;
113
- outcome: AutomaticGoalCompactionOutcome;
114
- turnSettled: boolean;
115
- turn: number;
116
- resumeSameTurn: boolean;
117
- }
118
-
119
- export interface GoalTurnExecution {
120
- turn: number;
121
- revision: number;
122
- }
123
-
124
- export interface GoalRuntime {
125
- state?: GoalState;
126
- continuationScheduled: boolean;
127
- continuationHeld: boolean;
128
- goalTurnInFlight: boolean;
129
- agentEndObserved: boolean;
130
- goalTurn?: GoalTurnExecution;
131
- automaticCompaction?: AutomaticGoalCompaction;
132
- persistenceRetryNeeded: boolean;
133
- lastStopReason?: string;
134
- lastError?: string;
135
- lifecycleGeneration: number;
136
- requestRender?: () => void;
137
- }
138
-
139
- export function createGoalRuntime(): GoalRuntime {
140
- return {
141
- continuationScheduled: false,
142
- continuationHeld: false,
143
- goalTurnInFlight: false,
144
- agentEndObserved: false,
145
- goalTurn: undefined,
146
- automaticCompaction: undefined,
147
- persistenceRetryNeeded: false,
148
- lifecycleGeneration: 0,
149
- };
150
- }
1
+ export type GoalStatus = "active" | "paused" | "blocked" | "complete";
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
+
41
+ export interface GoalBlockerAudit {
42
+ key: string;
43
+ streak: number;
44
+ lastTurn: number;
45
+ evidence?: string;
46
+ }
47
+
48
+ export type GoalFileBaseline =
49
+ | { exists: false }
50
+ | { exists: true; size: number; mtimeMs: number; contentHash?: string | null };
51
+
52
+ export interface GoalFileVerification {
53
+ kind: "file";
54
+ path: string;
55
+ baseline: GoalFileBaseline;
56
+ }
57
+
58
+ export interface GoalStateCommon {
59
+ version: 1;
60
+ revision: number;
61
+ objective: string;
62
+ createdAt: number;
63
+ updatedAt: number;
64
+ activeMilliseconds: number;
65
+ turns: number;
66
+ blockedAuditStartTurn: number;
67
+ baselineTokens: number;
68
+ verification?: GoalFileVerification;
69
+ maxTurns?: number;
70
+ turnPhase?: GoalTurnPhase;
71
+ turnDecision?: GoalPendingDecision;
72
+ lastDecision?: GoalTurnDecision;
73
+ lastContinueReport?: GoalContinueReport;
74
+ stopReason?: string;
75
+ }
76
+
77
+ export type GoalState = GoalStateCommon & (
78
+ | {
79
+ status: "active";
80
+ activeStartedAt: number;
81
+ result?: string;
82
+ blockerAudit?: GoalBlockerAudit;
83
+ resumeAfterManualCompaction?: never;
84
+ }
85
+ | {
86
+ status: "paused";
87
+ activeStartedAt?: never;
88
+ result?: string;
89
+ blockerAudit?: GoalBlockerAudit;
90
+ resumeAfterManualCompaction?: true;
91
+ }
92
+ | {
93
+ status: "blocked";
94
+ activeStartedAt?: never;
95
+ result: string;
96
+ blockerAudit?: GoalBlockerAudit;
97
+ resumeAfterManualCompaction?: never;
98
+ }
99
+ | {
100
+ status: "complete";
101
+ activeStartedAt?: never;
102
+ result: string;
103
+ blockerAudit?: never;
104
+ resumeAfterManualCompaction?: never;
105
+ }
106
+ );
107
+
108
+ /** Pi request outcomes for goal recovery: awaiting result, compacted, or rejected as session-too-small. */
109
+ export type AutomaticGoalCompactionOutcome = "pending" | "completed" | "skipped";
110
+
111
+ export interface AutomaticGoalCompaction {
112
+ pausedRevision: number;
113
+ outcome: AutomaticGoalCompactionOutcome;
114
+ turnSettled: boolean;
115
+ turn: number;
116
+ resumeSameTurn: boolean;
117
+ }
118
+
119
+ export interface GoalTurnExecution {
120
+ turn: number;
121
+ revision: number;
122
+ }
123
+
124
+ export interface GoalRuntime {
125
+ state?: GoalState;
126
+ continuationScheduled: boolean;
127
+ continuationHeld: boolean;
128
+ goalTurnInFlight: boolean;
129
+ agentEndObserved: boolean;
130
+ goalTurn?: GoalTurnExecution;
131
+ automaticCompaction?: AutomaticGoalCompaction;
132
+ persistenceRetryNeeded: boolean;
133
+ lastStopReason?: string;
134
+ lastError?: string;
135
+ lifecycleGeneration: number;
136
+ requestRender?: () => void;
137
+ }
138
+
139
+ export function createGoalRuntime(): GoalRuntime {
140
+ return {
141
+ continuationScheduled: false,
142
+ continuationHeld: false,
143
+ goalTurnInFlight: false,
144
+ agentEndObserved: false,
145
+ goalTurn: undefined,
146
+ automaticCompaction: undefined,
147
+ persistenceRetryNeeded: false,
148
+ lifecycleGeneration: 0,
149
+ };
150
+ }
@@ -1,19 +1,19 @@
1
- const SECRET_PATTERNS = [
2
- /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/u,
3
- /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/u,
4
- /\bAIza[0-9A-Za-z_-]{35}\b/u,
5
- /\bgh(?:p|o|u|s|r)_[A-Za-z0-9]{36,255}\b/u,
6
- /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/u,
7
- /\bglpat-[A-Za-z0-9_-]{20,255}\b/u,
8
- /\bnpm_[A-Za-z0-9]{20,255}\b/u,
9
- /\bsk-(?:ant-|proj-|svcacct-)?[A-Za-z0-9_-]{20,255}\b/u,
10
- /\bsk_live_[A-Za-z0-9]{16,255}\b/u,
11
- /\bxox[baprs]-[A-Za-z0-9-]{10,255}\b/u,
12
- /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:[^/\s@]+@/iu,
13
- /^[\t ]*["']?[\w.-]*(?:api_key|apikey|password|passwd|secret|token)[\w.-]*["']?[\t ]*(?:=|:)[\t ]*(?:"[^"\r\n]+"|'[^'\r\n]+'|[^\s#][^\r\n]*)/imu,
14
- ] as const;
15
-
16
- /** Detects high-confidence credential material without returning the matched value. */
17
- export function containsLikelySecret(text: string): boolean {
18
- return SECRET_PATTERNS.some((pattern) => pattern.test(text));
19
- }
1
+ const SECRET_PATTERNS = [
2
+ /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/u,
3
+ /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/u,
4
+ /\bAIza[0-9A-Za-z_-]{35}\b/u,
5
+ /\bgh(?:p|o|u|s|r)_[A-Za-z0-9]{36,255}\b/u,
6
+ /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/u,
7
+ /\bglpat-[A-Za-z0-9_-]{20,255}\b/u,
8
+ /\bnpm_[A-Za-z0-9]{20,255}\b/u,
9
+ /\bsk-(?:ant-|proj-|svcacct-)?[A-Za-z0-9_-]{20,255}\b/u,
10
+ /\bsk_live_[A-Za-z0-9]{16,255}\b/u,
11
+ /\bxox[baprs]-[A-Za-z0-9-]{10,255}\b/u,
12
+ /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:[^/\s@]+@/iu,
13
+ /^[\t ]*["']?[\w.-]*(?:api_key|apikey|password|passwd|secret|token)[\w.-]*["']?[\t ]*(?:=|:)[\t ]*(?:"[^"\r\n]+"|'[^'\r\n]+'|[^\s#][^\r\n]*)/imu,
14
+ ] as const;
15
+
16
+ /** Detects high-confidence credential material without returning the matched value. */
17
+ export function containsLikelySecret(text: string): boolean {
18
+ return SECRET_PATTERNS.some((pattern) => pattern.test(text));
19
+ }
@@ -1,50 +1,141 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
- import { hasErrorCode } from "./errors.ts";
6
-
7
- export type KillerosSettings = Record<string, unknown>;
8
-
9
- export interface KillerosSettingsStore {
10
- load(): KillerosSettings;
11
- update(patch: Readonly<Record<string, unknown>>): void;
12
- }
13
-
14
- function isSettings(value: unknown): value is KillerosSettings {
15
- return typeof value === "object" && value !== null && !Array.isArray(value);
16
- }
17
-
18
- function readStoredSettings(settingsPath: string): KillerosSettings {
19
- try {
20
- const parsed: unknown = JSON.parse(readFileSync(settingsPath, "utf8"));
21
- if (!isSettings(parsed)) throw new Error("KillerOS settings must contain a JSON object");
22
- return parsed;
23
- } catch (error) {
24
- if (hasErrorCode(error, "ENOENT")) return {};
25
- throw error;
26
- }
27
- }
28
-
29
- export function createKillerosSettingsStore(
30
- settingsPath = join(getAgentDir(), "killeros.json"),
31
- ): KillerosSettingsStore {
32
- return {
33
- load: () => readStoredSettings(settingsPath),
34
- update: (patch) => {
35
- const current = readStoredSettings(settingsPath);
36
- mkdirSync(dirname(settingsPath), { recursive: true });
37
- const temporaryPath = `${settingsPath}.${process.pid}.${randomUUID()}.tmp`;
38
- try {
39
- writeFileSync(
40
- temporaryPath,
41
- `${JSON.stringify({ ...current, ...patch }, null, 2)}\n`,
42
- { encoding: "utf8", mode: 0o600 },
43
- );
44
- renameSync(temporaryPath, settingsPath);
45
- } finally {
46
- rmSync(temporaryPath, { force: true });
47
- }
48
- },
49
- };
50
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { linkSync, mkdirSync, readFileSync, renameSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
+ import { hasErrorCode } from "./errors.ts";
6
+
7
+ export type KillerosSettings = Record<string, unknown>;
8
+
9
+ export interface KillerosSettingsStore {
10
+ load(): KillerosSettings;
11
+ update(patch: Readonly<Record<string, unknown>>): void;
12
+ }
13
+
14
+ function isSettings(value: unknown): value is KillerosSettings {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+
18
+ function readStoredSettings(settingsPath: string): KillerosSettings {
19
+ try {
20
+ const parsed: unknown = JSON.parse(readFileSync(settingsPath, "utf8"));
21
+ if (!isSettings(parsed)) throw new Error("KillerOS settings must contain a JSON object");
22
+ return parsed;
23
+ } catch (error) {
24
+ if (hasErrorCode(error, "ENOENT")) return {};
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ const LOCK_RETRY_MILLISECONDS = 10;
30
+ const LOCK_TIMEOUT_MILLISECONDS = 10_000;
31
+
32
+ function readLockOwnerPid(lockPath: string): number | undefined {
33
+ try {
34
+ const value: unknown = JSON.parse(readFileSync(lockPath, "utf8"));
35
+ if (!isSettings(value) || typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 0) return undefined;
36
+ return value.pid;
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ }
41
+
42
+ function isProcessRunning(pid: number): boolean {
43
+ try {
44
+ process.kill(pid, 0);
45
+ return true;
46
+ } catch (error) {
47
+ return !hasErrorCode(error, "ESRCH");
48
+ }
49
+ }
50
+
51
+ function moveLockAside(lockPath: string): boolean {
52
+ const stalePath = `${lockPath}.${process.pid}.${randomUUID()}.stale`;
53
+ const deadline = Date.now() + LOCK_TIMEOUT_MILLISECONDS;
54
+ while (true) {
55
+ try {
56
+ renameSync(lockPath, stalePath);
57
+ break;
58
+ } catch (error) {
59
+ if (hasErrorCode(error, "ENOENT")) return false;
60
+ if (
61
+ Date.now() >= deadline
62
+ || !["EACCES", "EBUSY", "EPERM"].some((code) => hasErrorCode(error, code))
63
+ ) throw error;
64
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, LOCK_RETRY_MILLISECONDS);
65
+ }
66
+ }
67
+ rmSync(stalePath, { force: true });
68
+ return true;
69
+ }
70
+
71
+ function recoverSettingsLock(lockPath: string, expectedPid: number): boolean {
72
+ const recoveryPath = `${lockPath}.recovery`;
73
+ try {
74
+ mkdirSync(recoveryPath);
75
+ } catch (error) {
76
+ if (hasErrorCode(error, "EEXIST")) return false;
77
+ throw error;
78
+ }
79
+ try {
80
+ // Recheck under exclusive recovery ownership; another waiter may have replaced the lock.
81
+ return readLockOwnerPid(lockPath) === expectedPid && !isProcessRunning(expectedPid) && moveLockAside(lockPath);
82
+ } finally {
83
+ // ponytail: a crash during recovery leaves .recovery for manual removal; never steal it and risk lost writes.
84
+ rmdirSync(recoveryPath);
85
+ }
86
+ }
87
+
88
+ function acquireSettingsLock(settingsPath: string): () => void {
89
+ const lockPath = `${settingsPath}.lock`;
90
+ const ownerPath = `${lockPath}.${process.pid}.${randomUUID()}.tmp`;
91
+ const deadline = Date.now() + LOCK_TIMEOUT_MILLISECONDS;
92
+ writeFileSync(ownerPath, JSON.stringify({ pid: process.pid }), { encoding: "utf8", mode: 0o600, flag: "wx" });
93
+ try {
94
+ while (true) {
95
+ try {
96
+ linkSync(ownerPath, lockPath);
97
+ break;
98
+ } catch (error) {
99
+ if (!hasErrorCode(error, "EEXIST")) throw error;
100
+ const currentOwnerPid = readLockOwnerPid(lockPath);
101
+ if (currentOwnerPid && !isProcessRunning(currentOwnerPid) && recoverSettingsLock(lockPath, currentOwnerPid)) continue;
102
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for KillerOS settings lock: ${lockPath}`);
103
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, LOCK_RETRY_MILLISECONDS);
104
+ }
105
+ }
106
+ } finally {
107
+ rmSync(ownerPath, { force: true });
108
+ }
109
+
110
+ return () => {
111
+ moveLockAside(lockPath);
112
+ };
113
+ }
114
+
115
+ export function createKillerosSettingsStore(
116
+ settingsPath = join(getAgentDir(), "killeros.json"),
117
+ ): KillerosSettingsStore {
118
+ return {
119
+ load: () => readStoredSettings(settingsPath),
120
+ update: (patch) => {
121
+ mkdirSync(dirname(settingsPath), { recursive: true });
122
+ const releaseLock = acquireSettingsLock(settingsPath);
123
+ const temporaryPath = `${settingsPath}.${process.pid}.${randomUUID()}.tmp`;
124
+ try {
125
+ const current = readStoredSettings(settingsPath);
126
+ writeFileSync(
127
+ temporaryPath,
128
+ `${JSON.stringify({ ...current, ...patch }, null, 2)}\n`,
129
+ { encoding: "utf8", mode: 0o600 },
130
+ );
131
+ renameSync(temporaryPath, settingsPath);
132
+ } finally {
133
+ try {
134
+ rmSync(temporaryPath, { force: true });
135
+ } finally {
136
+ releaseLock();
137
+ }
138
+ }
139
+ },
140
+ };
141
+ }