@openlife/cli 1.7.6 → 1.7.9

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.
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ // test_agent_creator — verifies the authoring pipeline for standalone agents.
3
+ // Each scenario uses a temp catalog root so we don't touch the maintainer's
4
+ // real `.catalog/agents/`.
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const AgentCreator_1 = require("./orchestrator/AgentCreator");
43
+ function assert(cond, msg) {
44
+ if (!cond) {
45
+ console.error(`❌ ASSERT FAILED: ${msg}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ function tempCatalog() {
50
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openlife-agentcreator-'));
51
+ const agents = path.join(root, 'agents');
52
+ const skills = path.join(root, 'skills');
53
+ const squads = path.join(root, 'squads');
54
+ fs.mkdirSync(agents, { recursive: true });
55
+ fs.mkdirSync(skills, { recursive: true });
56
+ fs.mkdirSync(squads, { recursive: true });
57
+ return { root, agents, skills, squads };
58
+ }
59
+ function cleanup(root) {
60
+ try {
61
+ fs.rmSync(root, { recursive: true, force: true });
62
+ }
63
+ catch { /* best effort */ }
64
+ }
65
+ function scenarioMinimalProposal() {
66
+ const cat = tempCatalog();
67
+ try {
68
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
69
+ const result = creator.create({ id: 'minimal-agent', name: 'Minimal', role: 'specialist' });
70
+ assert(result.ok === true, 'minimal proposal should succeed');
71
+ if (!result.ok)
72
+ return;
73
+ assert(fs.existsSync(result.filePath), 'AGENT.md file written');
74
+ const body = fs.readFileSync(result.filePath, 'utf-8');
75
+ assert(body.includes('id: minimal-agent'), 'frontmatter contains id');
76
+ assert(body.includes('## Goal'), 'has Goal section');
77
+ assert(body.includes('## Commands'), 'has Commands section');
78
+ assert(body.includes('## Dependencies'), 'has Dependencies section');
79
+ assert(body.includes('## Persona'), 'has Persona section');
80
+ assert(body.includes('## Guardrails'), 'has Guardrails section');
81
+ console.log('✅ scenario 1: minimal proposal renders full 5-section AGENT.md');
82
+ }
83
+ finally {
84
+ cleanup(cat.root);
85
+ }
86
+ }
87
+ function scenarioFullProposalWithCrossRefs() {
88
+ const cat = tempCatalog();
89
+ try {
90
+ // Create a referenced skill + squad first so cross-refs resolve.
91
+ fs.mkdirSync(path.join(cat.skills, 'planning-skill'), { recursive: true });
92
+ fs.writeFileSync(path.join(cat.skills, 'planning-skill', 'SKILL.md'), '---\nid: planning-skill\n---\n', 'utf-8');
93
+ fs.mkdirSync(path.join(cat.squads, 'ops-squad'), { recursive: true });
94
+ fs.writeFileSync(path.join(cat.squads, 'ops-squad', 'SQUAD.md'), '---\nid: ops-squad\nname: ops\nstatus: active\n---\n', 'utf-8');
95
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
96
+ const result = creator.create({
97
+ id: 'full-agent',
98
+ name: 'Full Agent',
99
+ role: 'planner',
100
+ domain: 'ops',
101
+ expertise: ['planning', 'routing'],
102
+ primarySkills: ['planning-skill'],
103
+ parentSquad: 'ops-squad',
104
+ whenToUse: 'When operators need a structured plan.',
105
+ identity: 'Methodical planner.',
106
+ style: 'Concrete, step-driven.',
107
+ focus: 'Operational planning.',
108
+ });
109
+ assert(result.ok === true, 'full proposal should succeed');
110
+ if (!result.ok)
111
+ return;
112
+ const validation = creator.validate('full-agent');
113
+ assert(validation.ok === true, `validation should pass, got errors: ${validation.errors.join(',')}`);
114
+ assert(validation.warnings.length === 0, `no warnings expected, got: ${validation.warnings.join(',')}`);
115
+ console.log('✅ scenario 2: full proposal with valid cross-refs passes validation cleanly');
116
+ }
117
+ finally {
118
+ cleanup(cat.root);
119
+ }
120
+ }
121
+ function scenarioOrphanCrossRefsBecomeWarnings() {
122
+ const cat = tempCatalog();
123
+ try {
124
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
125
+ const result = creator.create({
126
+ id: 'orphan-agent',
127
+ name: 'Orphan',
128
+ role: 'specialist',
129
+ primarySkills: ['missing-skill'],
130
+ parentSquad: 'missing-squad',
131
+ });
132
+ assert(result.ok === true, 'creation succeeds even with orphan refs (validation surfaces them later)');
133
+ const v = creator.validate('orphan-agent');
134
+ assert(v.ok === true, 'validation passes — orphans are warnings, not errors');
135
+ assert(v.warnings.length === 2, `expected 2 warnings, got ${v.warnings.length}`);
136
+ assert(v.warnings.some((w) => w.includes('missing-skill')), 'warning mentions missing skill');
137
+ assert(v.warnings.some((w) => w.includes('missing-squad')), 'warning mentions missing squad');
138
+ console.log('✅ scenario 3: orphan skill/squad refs become warnings, not errors');
139
+ }
140
+ finally {
141
+ cleanup(cat.root);
142
+ }
143
+ }
144
+ function scenarioInvalidIdRejected() {
145
+ const cat = tempCatalog();
146
+ try {
147
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
148
+ const result = creator.create({ id: '!invalid id!', name: 'X', role: 'specialist' });
149
+ assert(result.ok === false, 'invalid id should be rejected');
150
+ if (result.ok)
151
+ return;
152
+ assert(result.error === 'invalid_agent_id', `expected invalid_agent_id, got ${result.error}`);
153
+ console.log('✅ scenario 4: invalid id (special chars / spaces) rejected up front');
154
+ }
155
+ finally {
156
+ cleanup(cat.root);
157
+ }
158
+ }
159
+ function scenarioDuplicateAgentRejected() {
160
+ const cat = tempCatalog();
161
+ try {
162
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
163
+ creator.create({ id: 'dup-test', name: 'Dup', role: 'specialist' });
164
+ const second = creator.create({ id: 'dup-test', name: 'Dup2', role: 'specialist' });
165
+ assert(second.ok === false, 'second create should fail');
166
+ if (second.ok)
167
+ return;
168
+ assert(second.error === 'agent_already_exists', `expected agent_already_exists, got ${second.error}`);
169
+ console.log('✅ scenario 5: duplicate agent rejected without overwriting');
170
+ }
171
+ finally {
172
+ cleanup(cat.root);
173
+ }
174
+ }
175
+ function scenarioListAndAnalyze() {
176
+ const cat = tempCatalog();
177
+ try {
178
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
179
+ creator.create({ id: 'agent-a', name: 'A', role: 'specialist', status: 'active' });
180
+ creator.create({ id: 'agent-b', name: 'B', role: 'planner', status: 'draft' });
181
+ const all = creator.list();
182
+ assert(all.length === 2, `expected 2 agents, got ${all.length}`);
183
+ const onlyDraft = creator.list({ status: 'draft' });
184
+ assert(onlyDraft.length === 1, 'filter by status=draft returns 1');
185
+ assert(onlyDraft[0].id === 'agent-b', 'correct one returned');
186
+ const analysis = creator.analyze('agent-a');
187
+ assert(analysis !== null, 'analyze finds existing agent');
188
+ assert(analysis.commandCount >= 1, 'at least one command (help fallback) reported');
189
+ assert(analysis.suggestions.length >= 0, 'suggestions array returned (may be empty)');
190
+ console.log('✅ scenario 6: list + analyze report accurate catalog shape');
191
+ }
192
+ finally {
193
+ cleanup(cat.root);
194
+ }
195
+ }
196
+ function scenarioCustomCommandsAndGuardrails() {
197
+ const cat = tempCatalog();
198
+ try {
199
+ const creator = new AgentCreator_1.AgentCreator({ catalogRoot: cat.agents, skillsCatalogRoot: cat.skills, squadsCatalogRoot: cat.squads });
200
+ const result = creator.create({
201
+ id: 'rich-cmds',
202
+ name: 'Rich',
203
+ role: 'specialist',
204
+ commands: [
205
+ { name: 'plan', description: 'Generate a step-by-step plan' },
206
+ { name: 'review', description: 'Review work against criteria' },
207
+ ],
208
+ guardrails: ['Never push without operator approval', 'Always log audit events'],
209
+ });
210
+ assert(result.ok === true, 'create succeeds');
211
+ if (!result.ok)
212
+ return;
213
+ const body = fs.readFileSync(result.filePath, 'utf-8');
214
+ assert(body.includes('name: plan'), 'custom plan command rendered');
215
+ assert(body.includes('name: review'), 'custom review command rendered');
216
+ assert(body.includes('Never push without operator approval'), 'custom guardrail rendered');
217
+ const analysis = creator.analyze('rich-cmds');
218
+ assert(analysis.commandCount === 2, `expected 2 commands counted, got ${analysis.commandCount}`);
219
+ console.log('✅ scenario 7: custom commands + guardrails flow through to AGENT.md');
220
+ }
221
+ finally {
222
+ cleanup(cat.root);
223
+ }
224
+ }
225
+ function main() {
226
+ console.log('🧪 test_agent_creator — authoring pipeline for standalone agents');
227
+ scenarioMinimalProposal();
228
+ scenarioFullProposalWithCrossRefs();
229
+ scenarioOrphanCrossRefsBecomeWarnings();
230
+ scenarioInvalidIdRejected();
231
+ scenarioDuplicateAgentRejected();
232
+ scenarioListAndAnalyze();
233
+ scenarioCustomCommandsAndGuardrails();
234
+ console.log('');
235
+ console.log('TEST_AGENT_CREATOR_OK');
236
+ }
237
+ main();
@@ -296,7 +296,32 @@ async function scenario10WithApiKeysPersists() {
296
296
  cleanup(root);
297
297
  }
298
298
  }
299
- async function scenario11ProfileBothMapsToAutonomous() {
299
+ async function scenario11AbortDoesNotPersistApiKeys() {
300
+ const root = tempRoot();
301
+ try {
302
+ const provider = new InstallWizard_1.CannedAnswerProvider([
303
+ 0, 0,
304
+ true,
305
+ 'openai-abort-test-key',
306
+ '',
307
+ '',
308
+ '',
309
+ false,
310
+ '',
311
+ false,
312
+ false,
313
+ ]);
314
+ const wizard = new InstallWizard_1.InstallWizard(root, provider);
315
+ const result = await wizard.run();
316
+ assert(result.ok === false, 'scenario11: expected ok=false');
317
+ assert(!fs.existsSync(path.join(root, '.env')), 'scenario11: aborted wizard must not write .env');
318
+ console.log('✅ scenario 11: abort after API-key collection does not persist .env');
319
+ }
320
+ finally {
321
+ cleanup(root);
322
+ }
323
+ }
324
+ async function scenario12ProfileBothMapsToAutonomous() {
300
325
  const root = tempRoot();
301
326
  try {
302
327
  // profile=both(2), claude-code, apiKeys=N, oauth=N, models='', telegram=true (autonomous path),
@@ -327,7 +352,8 @@ async function main() {
327
352
  await scenario8CustomModelChain();
328
353
  await scenario9OutOfAnswersThrows();
329
354
  await scenario10WithApiKeysPersists();
330
- await scenario11ProfileBothMapsToAutonomous();
355
+ await scenario11AbortDoesNotPersistApiKeys();
356
+ await scenario12ProfileBothMapsToAutonomous();
331
357
  console.log('');
332
358
  console.log('TEST_INSTALL_WIZARD_OK');
333
359
  }
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ // test_logs_command — verifies filter / tail / since semantics + duration
3
+ // parser of `openlife logs`.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const LogsCommand_1 = require("./cli/LogsCommand");
42
+ function assert(cond, msg) {
43
+ if (!cond) {
44
+ console.error(`❌ ASSERT FAILED: ${msg}`);
45
+ process.exit(1);
46
+ }
47
+ }
48
+ function tempRoot() {
49
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openlife-logs-'));
50
+ fs.mkdirSync(path.join(root, '.openlife'), { recursive: true });
51
+ return root;
52
+ }
53
+ function cleanup(root) {
54
+ try {
55
+ fs.rmSync(root, { recursive: true, force: true });
56
+ }
57
+ catch {
58
+ // best-effort
59
+ }
60
+ }
61
+ function writeJsonl(root, filename, entries) {
62
+ fs.writeFileSync(path.join(root, '.openlife', filename), entries.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
63
+ }
64
+ function scenarioDurationParser() {
65
+ assert((0, LogsCommand_1.parseDurationToSeconds)('30s') === 30, '30s → 30');
66
+ assert((0, LogsCommand_1.parseDurationToSeconds)('5m') === 300, '5m → 300');
67
+ assert((0, LogsCommand_1.parseDurationToSeconds)('1h') === 3600, '1h → 3600');
68
+ assert((0, LogsCommand_1.parseDurationToSeconds)('2d') === 172800, '2d → 172800');
69
+ assert((0, LogsCommand_1.parseDurationToSeconds)('bogus') === null, 'bogus → null');
70
+ assert((0, LogsCommand_1.parseDurationToSeconds)('-1m') === null, 'negative → null');
71
+ console.log('✅ scenario 1: parseDurationToSeconds covers s/m/h/d + rejects garbage');
72
+ }
73
+ function scenarioTailDefault() {
74
+ const root = tempRoot();
75
+ try {
76
+ const entries = [];
77
+ const now = Date.now();
78
+ for (let i = 0; i < 50; i++) {
79
+ entries.push({ ts: new Date(now - (50 - i) * 1000).toISOString(), summary: `entry ${i}` });
80
+ }
81
+ writeJsonl(root, 'governance-ledger.jsonl', entries);
82
+ const collected = (0, LogsCommand_1.collectLogs)({ root, tail: 5 });
83
+ assert(collected.length === 5, `expected 5 entries, got ${collected.length}`);
84
+ assert(collected[collected.length - 1].parsed.summary === 'entry 49', 'last entry should be the newest');
85
+ console.log('✅ scenario 2: tail=5 returns last 5 chronological entries');
86
+ }
87
+ finally {
88
+ cleanup(root);
89
+ }
90
+ }
91
+ function scenarioFilterBySubsystem() {
92
+ const root = tempRoot();
93
+ try {
94
+ const now = Date.now();
95
+ const govEntries = [
96
+ { ts: new Date(now).toISOString(), summary: 'gov-entry' },
97
+ ];
98
+ const mediaEntries = [
99
+ { ts: new Date(now).toISOString(), summary: 'media-entry' },
100
+ ];
101
+ writeJsonl(root, 'governance-ledger.jsonl', govEntries);
102
+ writeJsonl(root, 'media-routing.log.jsonl', mediaEntries);
103
+ const govOnly = (0, LogsCommand_1.collectLogs)({ root, filter: 'governance' });
104
+ assert(govOnly.length === 1, `expected 1 governance entry, got ${govOnly.length}`);
105
+ assert(govOnly[0].subsystem === 'governance-ledger', 'subsystem should match');
106
+ const mediaOnly = (0, LogsCommand_1.collectLogs)({ root, filter: 'media' });
107
+ assert(mediaOnly.length === 1, `expected 1 media entry, got ${mediaOnly.length}`);
108
+ assert(mediaOnly[0].subsystem === 'media-routing', 'media subsystem name');
109
+ console.log('✅ scenario 3: --filter narrows by subsystem id');
110
+ }
111
+ finally {
112
+ cleanup(root);
113
+ }
114
+ }
115
+ function scenarioSinceFiltersOldEntries() {
116
+ const root = tempRoot();
117
+ try {
118
+ const now = Date.now();
119
+ const entries = [
120
+ { ts: new Date(now - 3600_000).toISOString(), summary: 'one-hour-ago' },
121
+ { ts: new Date(now - 60_000).toISOString(), summary: 'one-minute-ago' },
122
+ { ts: new Date(now - 5_000).toISOString(), summary: 'five-seconds-ago' },
123
+ ];
124
+ writeJsonl(root, 'governance-ledger.jsonl', entries);
125
+ const recent = (0, LogsCommand_1.collectLogs)({ root, since: '5m' });
126
+ assert(recent.length === 2, `expected 2 entries within 5m, got ${recent.length}`);
127
+ assert(!recent.some((e) => e.parsed.summary === 'one-hour-ago'), 'one-hour-ago should be excluded');
128
+ console.log('✅ scenario 4: --since=5m excludes older entries');
129
+ }
130
+ finally {
131
+ cleanup(root);
132
+ }
133
+ }
134
+ function scenarioRenderHumanAndJson() {
135
+ const root = tempRoot();
136
+ try {
137
+ writeJsonl(root, 'governance-ledger.jsonl', [
138
+ { ts: new Date().toISOString(), summary: 'test summary' },
139
+ ]);
140
+ const entries = (0, LogsCommand_1.collectLogs)({ root });
141
+ const human = (0, LogsCommand_1.renderLogsHuman)(entries);
142
+ assert(human.includes('[governance-ledger]'), 'human render includes subsystem');
143
+ assert(human.includes('test summary'), 'human render includes summary');
144
+ const jsonLine = (0, LogsCommand_1.renderLogsJson)(entries);
145
+ const parsed = JSON.parse(jsonLine);
146
+ assert(parsed.subsystem === 'governance-ledger', 'json render preserves subsystem');
147
+ console.log('✅ scenario 5: human + JSON renderers both produce expected shape');
148
+ }
149
+ finally {
150
+ cleanup(root);
151
+ }
152
+ }
153
+ function scenarioNoEntriesGracefullyHandled() {
154
+ const root = tempRoot();
155
+ try {
156
+ const entries = (0, LogsCommand_1.collectLogs)({ root });
157
+ assert(entries.length === 0, 'no jsonl files → empty result');
158
+ const human = (0, LogsCommand_1.renderLogsHuman)(entries);
159
+ assert(human.includes('no entries'), 'human render reports empty');
160
+ console.log('✅ scenario 6: empty state renders helpful message');
161
+ }
162
+ finally {
163
+ cleanup(root);
164
+ }
165
+ }
166
+ function main() {
167
+ console.log('🧪 test_logs_command — jsonl viewer');
168
+ scenarioDurationParser();
169
+ scenarioTailDefault();
170
+ scenarioFilterBySubsystem();
171
+ scenarioSinceFiltersOldEntries();
172
+ scenarioRenderHumanAndJson();
173
+ scenarioNoEntriesGracefullyHandled();
174
+ console.log('');
175
+ console.log('TEST_LOGS_COMMAND_OK');
176
+ }
177
+ main();
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ // test_status_command — verifies the shape and overall-derivation logic of
3
+ // `openlife status`. Uses a temp .openlife/ scratch dir so we don't rely on
4
+ // (or pollute) the maintainer's local runtime state.
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs = __importStar(require("fs"));
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const StatusCommand_1 = require("./cli/StatusCommand");
43
+ function assert(cond, msg) {
44
+ if (!cond) {
45
+ console.error(`❌ ASSERT FAILED: ${msg}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ function tempRoot() {
50
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openlife-status-'));
51
+ fs.mkdirSync(path.join(root, '.openlife'), { recursive: true });
52
+ return root;
53
+ }
54
+ function cleanup(root) {
55
+ try {
56
+ fs.rmSync(root, { recursive: true, force: true });
57
+ }
58
+ catch {
59
+ // best-effort
60
+ }
61
+ }
62
+ function writeJson(root, filename, payload) {
63
+ fs.writeFileSync(path.join(root, '.openlife', filename), JSON.stringify(payload, null, 2), 'utf-8');
64
+ }
65
+ function writeJsonl(root, filename, entries) {
66
+ fs.writeFileSync(path.join(root, '.openlife', filename), entries.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf-8');
67
+ }
68
+ function scenarioEmpty() {
69
+ const root = tempRoot();
70
+ try {
71
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
72
+ assert(report.overall === 'down', 'empty state should be down (no heartbeat, no executors)');
73
+ assert(report.heartbeat.present === false, 'no heartbeat in empty state');
74
+ assert(report.governance.ledgerPresent === false, 'no governance ledger in empty state');
75
+ assert(report.executors.length === 0, 'no executors in empty state');
76
+ assert(report.notes.includes('no_heartbeat'), 'should note no_heartbeat');
77
+ console.log('✅ scenario 1: empty state → overall=down with no_heartbeat note');
78
+ }
79
+ finally {
80
+ cleanup(root);
81
+ }
82
+ }
83
+ function scenarioHealthy() {
84
+ const root = tempRoot();
85
+ try {
86
+ const now = Date.now();
87
+ writeJson(root, 'heartbeat.json', { pid: 12345, host: 'test-host', ts: now, uptime_s: 100, startedAt: now - 100_000 });
88
+ writeJson(root, 'runtime-policy-status.json', {
89
+ executors: {
90
+ openai: { executor: 'openai', available: true, reason: 'ok', updatedAt: new Date(now).toISOString() },
91
+ anthropic: { executor: 'anthropic', available: true, reason: 'ok', updatedAt: new Date(now).toISOString() },
92
+ },
93
+ });
94
+ writeJsonl(root, 'governance-ledger.jsonl', [
95
+ { index: 1, ts: new Date(now).toISOString(), entryHash: 'aaa' },
96
+ { index: 2, ts: new Date(now).toISOString(), entryHash: 'bbb' },
97
+ ]);
98
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
99
+ assert(report.overall === 'healthy', `expected healthy, got ${report.overall} (notes: ${report.notes.join(',')})`);
100
+ assert(report.heartbeat.fresh === true, 'fresh heartbeat expected');
101
+ assert(report.executors.length === 2, 'two executors expected');
102
+ assert(report.executors.every((e) => e.available), 'all executors available');
103
+ assert(report.governance.entryCount === 2, 'two ledger entries expected');
104
+ assert(report.governance.lastEntryHash === 'bbb', 'last entry hash is bbb');
105
+ console.log('✅ scenario 2: healthy heartbeat + all executors + ledger → overall=healthy');
106
+ }
107
+ finally {
108
+ cleanup(root);
109
+ }
110
+ }
111
+ function scenarioDegradedOneExecutorDown() {
112
+ const root = tempRoot();
113
+ try {
114
+ const now = Date.now();
115
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: now, uptime_s: 1, startedAt: now });
116
+ writeJson(root, 'runtime-policy-status.json', {
117
+ executors: {
118
+ openai: { executor: 'openai', available: true, reason: 'ok' },
119
+ anthropic: { executor: 'anthropic', available: false, reason: 'auth expired', category: 'auth' },
120
+ },
121
+ });
122
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
123
+ assert(report.overall === 'degraded', `expected degraded, got ${report.overall}`);
124
+ assert(report.notes.some((n) => n.startsWith('executors_down:')), 'should note specific executors down');
125
+ console.log('✅ scenario 3: one executor down → overall=degraded');
126
+ }
127
+ finally {
128
+ cleanup(root);
129
+ }
130
+ }
131
+ function scenarioDownAllExecutorsDown() {
132
+ const root = tempRoot();
133
+ try {
134
+ const now = Date.now();
135
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: now, uptime_s: 1, startedAt: now });
136
+ writeJson(root, 'runtime-policy-status.json', {
137
+ executors: {
138
+ openai: { executor: 'openai', available: false, reason: 'rate limit' },
139
+ anthropic: { executor: 'anthropic', available: false, reason: 'auth expired' },
140
+ },
141
+ });
142
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
143
+ assert(report.overall === 'down', `expected down, got ${report.overall}`);
144
+ assert(report.notes.includes('all_executors_down'), 'should note all_executors_down');
145
+ console.log('✅ scenario 4: all executors down → overall=down');
146
+ }
147
+ finally {
148
+ cleanup(root);
149
+ }
150
+ }
151
+ function scenarioStaleHeartbeat() {
152
+ const root = tempRoot();
153
+ try {
154
+ const staleTs = Date.now() - (StatusCommand_1.HEARTBEAT_FRESH_S + 30) * 1000;
155
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: staleTs, uptime_s: 9999, startedAt: staleTs - 1000 });
156
+ writeJson(root, 'runtime-policy-status.json', {
157
+ executors: { openai: { executor: 'openai', available: true, reason: 'ok' } },
158
+ });
159
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
160
+ assert(report.overall === 'degraded', `expected degraded for stale beat, got ${report.overall}`);
161
+ assert(report.heartbeat.fresh === false, 'stale heartbeat is not fresh');
162
+ assert(report.notes.some((n) => n.startsWith('heartbeat_warning_')), 'should warn about stale heartbeat');
163
+ console.log('✅ scenario 5: stale heartbeat → overall=degraded with heartbeat_warning note');
164
+ }
165
+ finally {
166
+ cleanup(root);
167
+ }
168
+ }
169
+ function scenarioDeadHeartbeat() {
170
+ const root = tempRoot();
171
+ try {
172
+ const deadTs = Date.now() - (StatusCommand_1.HEARTBEAT_STALE_S + 60) * 1000;
173
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: deadTs, uptime_s: 9999, startedAt: deadTs - 1000 });
174
+ writeJson(root, 'runtime-policy-status.json', {
175
+ executors: { openai: { executor: 'openai', available: true, reason: 'ok' } },
176
+ });
177
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
178
+ assert(report.overall === 'down', `expected down for dead beat, got ${report.overall}`);
179
+ assert(report.notes.some((n) => n.startsWith('heartbeat_stale_')), 'should note heartbeat_stale_<age>s');
180
+ console.log('✅ scenario 6: dead heartbeat → overall=down');
181
+ }
182
+ finally {
183
+ cleanup(root);
184
+ }
185
+ }
186
+ function scenarioCorruptLedgerHandled() {
187
+ const root = tempRoot();
188
+ try {
189
+ const now = Date.now();
190
+ writeJson(root, 'heartbeat.json', { pid: 1, host: 'h', ts: now, uptime_s: 1, startedAt: now });
191
+ writeJson(root, 'runtime-policy-status.json', {
192
+ executors: { openai: { executor: 'openai', available: true, reason: 'ok' } },
193
+ });
194
+ // Write a corrupt last line — must not throw.
195
+ fs.writeFileSync(path.join(root, '.openlife', 'governance-ledger.jsonl'), '{"index":1,"entryHash":"good","ts":"' + new Date(now).toISOString() + '"}\n{not-valid-json\n', 'utf-8');
196
+ const report = (0, StatusCommand_1.buildStatusReport)(root);
197
+ assert(report.governance.entryCount === 2, 'should count both lines (parseability separate from count)');
198
+ // Last entry was corrupt → lastEntryHash undefined; gracefully degraded, doesn't throw.
199
+ assert(report.governance.lastEntryHash === undefined, 'corrupt last line yields no entryHash');
200
+ console.log('✅ scenario 7: corrupt ledger line handled gracefully');
201
+ }
202
+ finally {
203
+ cleanup(root);
204
+ }
205
+ }
206
+ function main() {
207
+ console.log('🧪 test_status_command — runtime state aggregator');
208
+ scenarioEmpty();
209
+ scenarioHealthy();
210
+ scenarioDegradedOneExecutorDown();
211
+ scenarioDownAllExecutorsDown();
212
+ scenarioStaleHeartbeat();
213
+ scenarioDeadHeartbeat();
214
+ scenarioCorruptLedgerHandled();
215
+ console.log('');
216
+ console.log('TEST_STATUS_COMMAND_OK');
217
+ }
218
+ main();