@hazeljs/cli 1.0.6 → 2.0.1
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/@template-ai-native/README.md +19 -0
- package/@template-ai-native/src/examples/skillgate.example.ts +29 -0
- package/cli-manifest.json +82 -1
- package/dist/commands/agent-templates.d.ts +27 -0
- package/dist/commands/agent-templates.js +480 -0
- package/dist/commands/agent.d.ts +5 -1
- package/dist/commands/agent.js +723 -3
- package/dist/commands/agent.test.d.ts +1 -0
- package/dist/commands/agent.test.js +104 -0
- package/dist/commands/skillgate.d.ts +6 -0
- package/dist/commands/skillgate.js +147 -0
- package/dist/commands/store.d.ts +7 -0
- package/dist/commands/store.js +203 -0
- package/dist/commands/store.test.d.ts +1 -0
- package/dist/commands/store.test.js +120 -0
- package/dist/index.js +4 -0
- package/dist/utils/packages-registry.js +19 -0
- package/package.json +12 -7
package/dist/commands/agent.js
CHANGED
|
@@ -35,13 +35,94 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.registerAgentCommand = registerAgentCommand;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
38
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const agent_templates_1 = require("./agent-templates");
|
|
41
|
+
const DEFAULT_RUN_STORE = path.join('.hazel', 'agent-runs.json');
|
|
42
|
+
const DEFAULT_DURABLE_DIR = path.join('.hazel', 'runs');
|
|
43
|
+
const DEFAULT_TIMELINE = path.join('.hazel', 'runs', 'timeline.jsonl');
|
|
44
|
+
const DEFAULT_PLATFORM_STORE = path.join('.hazel', 'platform', 'resources.json');
|
|
39
45
|
/**
|
|
46
|
+
* `hazel agent new` — scaffold Agent OS / DNA templates (G2 template unification).
|
|
40
47
|
* `hazel agent install <file.dna.json>` — validate / print marketplace install plan.
|
|
41
|
-
*
|
|
48
|
+
* `hazel agent run` — live execute from DNA (AOS-011).
|
|
49
|
+
* `hazel agent apply|get|describe|delete|reconcile|events` — declarative platform resources (local control plane).
|
|
50
|
+
* `hazel agent logs` / `doctor` — timeline + environment checks.
|
|
51
|
+
* `hazel agent runs list|inspect|cancel|resume|approve` — durable store ops.
|
|
42
52
|
*/
|
|
43
53
|
function registerAgentCommand(program) {
|
|
44
|
-
const agent = program
|
|
54
|
+
const agent = program
|
|
55
|
+
.command('agent')
|
|
56
|
+
.description('Agent OS DNA / runtime / marketplace / platform helpers');
|
|
57
|
+
agent
|
|
58
|
+
.command('templates')
|
|
59
|
+
.description('List Agent OS / DNA project templates')
|
|
60
|
+
.option('--json', 'Print JSON')
|
|
61
|
+
.action((opts) => {
|
|
62
|
+
const templates = (0, agent_templates_1.listAgentTemplates)();
|
|
63
|
+
if (opts.json) {
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.log(JSON.stringify({ ok: true, templates }, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.log('\nAgent OS templates (`hazel agent new <name> --template <id>`):\n');
|
|
70
|
+
for (const t of templates) {
|
|
71
|
+
// eslint-disable-next-line no-console
|
|
72
|
+
console.log(` ${t.id.padEnd(12)} ${t.label}`);
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.log(` ${t.description}\n`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
agent
|
|
78
|
+
.command('new')
|
|
79
|
+
.description('Scaffold an Agent OS / DNA project (bare | agent-os | skillgate). DNA = contract; app tools = implementation.')
|
|
80
|
+
.argument('<name>', 'Project directory / package name')
|
|
81
|
+
.option('-t, --template <id>', 'Template: bare | agent-os | skillgate', 'agent-os')
|
|
82
|
+
.option('-d, --dest <dir>', 'Parent directory', '.')
|
|
83
|
+
.option('-f, --force', 'Allow non-empty destination')
|
|
84
|
+
.option('--json', 'Print machine-readable result')
|
|
85
|
+
.action((name, opts) => {
|
|
86
|
+
try {
|
|
87
|
+
const destDir = path.resolve(process.cwd(), opts.dest, name);
|
|
88
|
+
const result = (0, agent_templates_1.scaffoldAgentProject)({
|
|
89
|
+
name,
|
|
90
|
+
destDir,
|
|
91
|
+
template: opts.template,
|
|
92
|
+
force: opts.force,
|
|
93
|
+
});
|
|
94
|
+
const payload = {
|
|
95
|
+
ok: true,
|
|
96
|
+
action: 'agent-new',
|
|
97
|
+
...result,
|
|
98
|
+
next: [
|
|
99
|
+
`cd ${path.relative(process.cwd(), result.path) || '.'}`,
|
|
100
|
+
result.template === 'bare'
|
|
101
|
+
? 'npx hazel agent run dna/agent.marketplace.json "hello"'
|
|
102
|
+
: 'npm install && npm run dev',
|
|
103
|
+
'npx hazel store publish dna/agent.marketplace.json',
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(opts.json
|
|
108
|
+
? JSON.stringify(payload, null, 2)
|
|
109
|
+
: [
|
|
110
|
+
`✓ Created Agent OS project (${result.template})`,
|
|
111
|
+
` ${result.path}`,
|
|
112
|
+
` files: ${result.files.length}`,
|
|
113
|
+
'',
|
|
114
|
+
'Next:',
|
|
115
|
+
...payload.next.map((l) => ` ${l}`),
|
|
116
|
+
'',
|
|
117
|
+
'Note: `hazel agent run` on DNA uses stub tools. Use the app (`npm run dev`) for real @Tool / Skillgate handlers.',
|
|
118
|
+
].join('\n'));
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.error(e instanceof Error ? e.message : e);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
45
126
|
agent
|
|
46
127
|
.command('install')
|
|
47
128
|
.description('Validate a .dna / marketplace JSON package and print install plan')
|
|
@@ -58,7 +139,7 @@ function registerAgentCommand(program) {
|
|
|
58
139
|
agent: pkg.dna.name,
|
|
59
140
|
tools: pkg.dna.tools.map((t) => t.name),
|
|
60
141
|
hasPolicies: Boolean(pkg.dna.policies?.length),
|
|
61
|
-
note: '
|
|
142
|
+
note: 'Validate only. Use hazel store install to materialize into .hazel/agents; call runtime.installAgentPackage(path) to hot-reload',
|
|
62
143
|
}, null, 2));
|
|
63
144
|
if (opts.out) {
|
|
64
145
|
const outPath = path.join(opts.out, `${pkg.dna.name}.marketplace.json`);
|
|
@@ -91,4 +172,643 @@ function registerAgentCommand(program) {
|
|
|
91
172
|
process.exitCode = 1;
|
|
92
173
|
}
|
|
93
174
|
});
|
|
175
|
+
agent
|
|
176
|
+
.command('run')
|
|
177
|
+
.description('Execute an agent from DNA / marketplace package (live CLI run, AOS-011)')
|
|
178
|
+
.argument('<file>', 'Path to .dna.json or marketplace package JSON')
|
|
179
|
+
.argument('[input...]', 'User input (default: hello)')
|
|
180
|
+
.option('--dir <path>', 'Durable store directory', DEFAULT_DURABLE_DIR)
|
|
181
|
+
.option('--mock', 'Use offline mock LLM (no API key)')
|
|
182
|
+
.option('--model <model>', 'Model id for HTTP LLM', process.env.HAZEL_AGENT_MODEL ?? 'gpt-4o-mini')
|
|
183
|
+
.option('--base-url <url>', 'OpenAI-compatible base URL', process.env.OPENAI_BASE_URL)
|
|
184
|
+
.option('--api-key <key>', 'API key (default: OPENAI_API_KEY)')
|
|
185
|
+
.option('--worker-id <id>', 'Worker id for run leases', `cli-${os.hostname()}`)
|
|
186
|
+
.option('--max-steps <n>', 'Max agent steps', '8')
|
|
187
|
+
.option('--json', 'Print full execution result JSON')
|
|
188
|
+
.action(async (file, inputParts, opts) => {
|
|
189
|
+
try {
|
|
190
|
+
const { bootstrapRuntimeFromDna, createHttpLlmProvider, createMockLlmProvider } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
191
|
+
const dnaPath = path.resolve(process.cwd(), file);
|
|
192
|
+
if (!fs.existsSync(dnaPath)) {
|
|
193
|
+
throw new Error(`DNA file not found: ${dnaPath}`);
|
|
194
|
+
}
|
|
195
|
+
const input = inputParts.length ? inputParts.join(' ') : 'hello';
|
|
196
|
+
const storeDir = path.resolve(process.cwd(), opts.dir);
|
|
197
|
+
const apiKey = opts.apiKey ?? process.env.OPENAI_API_KEY;
|
|
198
|
+
const llm = opts.mock || !apiKey
|
|
199
|
+
? createMockLlmProvider(opts.mock
|
|
200
|
+
? 'Mock reply from hazel agent run.'
|
|
201
|
+
: 'No OPENAI_API_KEY — mock reply. Pass --mock to silence this, or set a key.')
|
|
202
|
+
: createHttpLlmProvider({
|
|
203
|
+
apiKey,
|
|
204
|
+
baseUrl: opts.baseUrl,
|
|
205
|
+
model: opts.model,
|
|
206
|
+
});
|
|
207
|
+
const { runtime, dna, store, timelinePath } = bootstrapRuntimeFromDna(dnaPath, {
|
|
208
|
+
llmProvider: llm,
|
|
209
|
+
storeDir,
|
|
210
|
+
durableSuspend: true,
|
|
211
|
+
workerId: opts.workerId,
|
|
212
|
+
stubTools: true,
|
|
213
|
+
});
|
|
214
|
+
const result = await runtime.execute(dna.name, input, {
|
|
215
|
+
maxSteps: Number(opts.maxSteps) || 8,
|
|
216
|
+
});
|
|
217
|
+
const run = store ? await store.runRepository.get(result.executionId) : undefined;
|
|
218
|
+
const summary = {
|
|
219
|
+
agent: dna.name,
|
|
220
|
+
executionId: result.executionId,
|
|
221
|
+
state: result.state,
|
|
222
|
+
response: result.response,
|
|
223
|
+
runStatus: run?.status,
|
|
224
|
+
storeDir,
|
|
225
|
+
timelinePath,
|
|
226
|
+
llm: opts.mock || !apiKey ? 'mock' : 'http',
|
|
227
|
+
};
|
|
228
|
+
// eslint-disable-next-line no-console
|
|
229
|
+
console.log(JSON.stringify(opts.json ? { ...summary, result, run } : summary, null, 2));
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
// eslint-disable-next-line no-console
|
|
233
|
+
console.error(e);
|
|
234
|
+
process.exitCode = 1;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
agent
|
|
238
|
+
.command('logs')
|
|
239
|
+
.description('Show AgentRun timeline JSONL (optionally filter by run id)')
|
|
240
|
+
.option('--timeline <path>', 'Timeline JSONL path', DEFAULT_TIMELINE)
|
|
241
|
+
.option('--run <runId>', 'Filter by execution / run id')
|
|
242
|
+
.option('--agent <name>', 'Filter by agent name')
|
|
243
|
+
.option('--follow', 'Tail new lines (poll)')
|
|
244
|
+
.option('--interval <ms>', 'Follow poll interval', '1000')
|
|
245
|
+
.action(async (opts) => {
|
|
246
|
+
try {
|
|
247
|
+
const { FileTimelineStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
248
|
+
const timelinePath = path.resolve(process.cwd(), opts.timeline);
|
|
249
|
+
const store = new FileTimelineStore(timelinePath);
|
|
250
|
+
const print = () => {
|
|
251
|
+
const steps = store.load({
|
|
252
|
+
executionId: opts.run,
|
|
253
|
+
agentName: opts.agent,
|
|
254
|
+
});
|
|
255
|
+
// eslint-disable-next-line no-console
|
|
256
|
+
console.log(JSON.stringify(steps, null, 2));
|
|
257
|
+
};
|
|
258
|
+
if (!opts.follow) {
|
|
259
|
+
print();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
let lastSize = fs.existsSync(timelinePath) ? fs.statSync(timelinePath).size : 0;
|
|
263
|
+
print();
|
|
264
|
+
const ms = Math.max(200, Number(opts.interval) || 1000);
|
|
265
|
+
// eslint-disable-next-line no-console
|
|
266
|
+
console.error(`Following ${timelinePath} every ${ms}ms (Ctrl+C to stop)…`);
|
|
267
|
+
setInterval(() => {
|
|
268
|
+
if (!fs.existsSync(timelinePath))
|
|
269
|
+
return;
|
|
270
|
+
const size = fs.statSync(timelinePath).size;
|
|
271
|
+
if (size !== lastSize) {
|
|
272
|
+
lastSize = size;
|
|
273
|
+
print();
|
|
274
|
+
}
|
|
275
|
+
}, ms);
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
// eslint-disable-next-line no-console
|
|
279
|
+
console.error(e);
|
|
280
|
+
process.exitCode = 1;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
agent
|
|
284
|
+
.command('doctor')
|
|
285
|
+
.description('Check Agent OS CLI environment (peers, store paths, LLM key)')
|
|
286
|
+
.option('--dir <path>', 'Expected durable store directory', DEFAULT_DURABLE_DIR)
|
|
287
|
+
.action(async (opts) => {
|
|
288
|
+
try {
|
|
289
|
+
const cwd = process.cwd();
|
|
290
|
+
const storeDir = path.resolve(cwd, opts.dir);
|
|
291
|
+
const checks = [];
|
|
292
|
+
checks.push({
|
|
293
|
+
ok: true,
|
|
294
|
+
name: 'node',
|
|
295
|
+
detail: process.version,
|
|
296
|
+
});
|
|
297
|
+
try {
|
|
298
|
+
await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
299
|
+
checks.push({ ok: true, name: '@hazeljs/agent', detail: 'resolvable' });
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
checks.push({
|
|
303
|
+
ok: false,
|
|
304
|
+
name: '@hazeljs/agent',
|
|
305
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
309
|
+
checks.push({
|
|
310
|
+
ok: fs.existsSync(pkgPath),
|
|
311
|
+
name: 'package.json',
|
|
312
|
+
detail: fs.existsSync(pkgPath) ? pkgPath : 'missing in cwd',
|
|
313
|
+
});
|
|
314
|
+
checks.push({
|
|
315
|
+
ok: true,
|
|
316
|
+
name: 'durableStore',
|
|
317
|
+
detail: fs.existsSync(storeDir)
|
|
318
|
+
? `exists: ${storeDir}`
|
|
319
|
+
: `will be created on run: ${storeDir}`,
|
|
320
|
+
});
|
|
321
|
+
const key = process.env.OPENAI_API_KEY;
|
|
322
|
+
checks.push({
|
|
323
|
+
ok: Boolean(key),
|
|
324
|
+
name: 'OPENAI_API_KEY',
|
|
325
|
+
detail: key ? 'set (http LLM available)' : 'unset — use --mock for offline run',
|
|
326
|
+
});
|
|
327
|
+
const failed = checks.filter((c) => !c.ok);
|
|
328
|
+
// eslint-disable-next-line no-console
|
|
329
|
+
console.log(JSON.stringify({
|
|
330
|
+
ok: failed.length === 0 || (failed.length === 1 && failed[0].name === 'OPENAI_API_KEY'),
|
|
331
|
+
checks,
|
|
332
|
+
hints: [
|
|
333
|
+
'hazel agent run ./agent.dna.json "hello" --mock',
|
|
334
|
+
'hazel agent runs list --dir .hazel/runs',
|
|
335
|
+
'hazel agent logs --timeline .hazel/runs/timeline.jsonl',
|
|
336
|
+
],
|
|
337
|
+
}, null, 2));
|
|
338
|
+
if (failed.some((c) => c.name === '@hazeljs/agent'))
|
|
339
|
+
process.exitCode = 1;
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
// eslint-disable-next-line no-console
|
|
343
|
+
console.error(e);
|
|
344
|
+
process.exitCode = 1;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const runs = agent.command('runs').description('Inspect durable AgentRun records (file store)');
|
|
348
|
+
runs
|
|
349
|
+
.command('list')
|
|
350
|
+
.description('List AgentRun records from a FileAgentRunRepository JSON store')
|
|
351
|
+
.option('--store <path>', 'Path to runs JSON', DEFAULT_RUN_STORE)
|
|
352
|
+
.option('--dir <path>', 'Durable store directory (uses runs.json inside)')
|
|
353
|
+
.option('--agent <name>', 'Filter by agent name')
|
|
354
|
+
.option('--status <status>', 'Filter by status')
|
|
355
|
+
.action(async (opts) => {
|
|
356
|
+
try {
|
|
357
|
+
const { FileAgentRunRepository, AgentRunStatus, createDurableRunStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
358
|
+
const repo = opts.dir
|
|
359
|
+
? createDurableRunStore(path.resolve(process.cwd(), opts.dir))
|
|
360
|
+
.runRepository
|
|
361
|
+
: new FileAgentRunRepository(path.resolve(process.cwd(), opts.store));
|
|
362
|
+
const filter = {};
|
|
363
|
+
if (opts.agent)
|
|
364
|
+
filter.agentName = opts.agent;
|
|
365
|
+
if (opts.status) {
|
|
366
|
+
const values = Object.values(AgentRunStatus);
|
|
367
|
+
if (!values.includes(opts.status)) {
|
|
368
|
+
throw new Error(`Unknown status "${opts.status}". Expected one of: ${values.join(', ')}`);
|
|
369
|
+
}
|
|
370
|
+
filter.status = opts.status;
|
|
371
|
+
}
|
|
372
|
+
const list = await repo.list(filter);
|
|
373
|
+
// eslint-disable-next-line no-console
|
|
374
|
+
console.log(JSON.stringify(list.map((r) => ({
|
|
375
|
+
id: r.id,
|
|
376
|
+
agentName: r.agentName,
|
|
377
|
+
status: r.status,
|
|
378
|
+
leaseOwner: r.leaseOwner,
|
|
379
|
+
updatedAt: r.updatedAt,
|
|
380
|
+
})), null, 2));
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
// eslint-disable-next-line no-console
|
|
384
|
+
console.error(e);
|
|
385
|
+
process.exitCode = 1;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
runs
|
|
389
|
+
.command('inspect')
|
|
390
|
+
.description('Show one AgentRun by id')
|
|
391
|
+
.argument('<runId>', 'AgentRun / execution id')
|
|
392
|
+
.option('--store <path>', 'Path to runs JSON', DEFAULT_RUN_STORE)
|
|
393
|
+
.option('--dir <path>', 'Durable store directory')
|
|
394
|
+
.action(async (runId, opts) => {
|
|
395
|
+
try {
|
|
396
|
+
const { FileAgentRunRepository, createDurableRunStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
397
|
+
const repo = opts.dir
|
|
398
|
+
? createDurableRunStore(path.resolve(process.cwd(), opts.dir)).runRepository
|
|
399
|
+
: new FileAgentRunRepository(path.resolve(process.cwd(), opts.store));
|
|
400
|
+
const run = await repo.get(runId);
|
|
401
|
+
if (!run) {
|
|
402
|
+
// eslint-disable-next-line no-console
|
|
403
|
+
console.error(`Run not found: ${runId}`);
|
|
404
|
+
process.exitCode = 1;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
// eslint-disable-next-line no-console
|
|
408
|
+
console.log(JSON.stringify(run, null, 2));
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
// eslint-disable-next-line no-console
|
|
412
|
+
console.error(e);
|
|
413
|
+
process.exitCode = 1;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
runs
|
|
417
|
+
.command('cancel')
|
|
418
|
+
.description('Mark an AgentRun CANCELLED in the file store (does not abort a live worker)')
|
|
419
|
+
.argument('<runId>', 'AgentRun / execution id')
|
|
420
|
+
.option('--store <path>', 'Path to runs JSON', DEFAULT_RUN_STORE)
|
|
421
|
+
.option('--dir <path>', 'Durable store directory')
|
|
422
|
+
.action(async (runId, opts) => {
|
|
423
|
+
try {
|
|
424
|
+
const { FileAgentRunRepository, AgentRunStatus, createDurableRunStore } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
425
|
+
const repo = opts.dir
|
|
426
|
+
? createDurableRunStore(path.resolve(process.cwd(), opts.dir)).runRepository
|
|
427
|
+
: new FileAgentRunRepository(path.resolve(process.cwd(), opts.store));
|
|
428
|
+
const run = await repo.updateStatus(runId, AgentRunStatus.CANCELLED, {
|
|
429
|
+
error: { message: 'Cancelled via hazel agent runs cancel' },
|
|
430
|
+
});
|
|
431
|
+
// eslint-disable-next-line no-console
|
|
432
|
+
console.log(JSON.stringify({ id: run.id, status: run.status }, null, 2));
|
|
433
|
+
}
|
|
434
|
+
catch (e) {
|
|
435
|
+
// eslint-disable-next-line no-console
|
|
436
|
+
console.error(e);
|
|
437
|
+
process.exitCode = 1;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
const resumeAction = async (runId, opts) => {
|
|
441
|
+
try {
|
|
442
|
+
const approved = opts.approve !== false && !opts.reject;
|
|
443
|
+
const { createDurableRunStore, FileAgentRunRepository, FileHumanTaskService, FileCheckpointService, } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
444
|
+
const storePath = path.resolve(process.cwd(), opts.store);
|
|
445
|
+
const isDir = opts.dir || storePath.endsWith('.hazel') || !storePath.endsWith('.json');
|
|
446
|
+
let humanTasks;
|
|
447
|
+
let runsRepo;
|
|
448
|
+
if (opts.dir) {
|
|
449
|
+
const store = createDurableRunStore(path.resolve(process.cwd(), opts.dir));
|
|
450
|
+
humanTasks = store.humanTaskService;
|
|
451
|
+
runsRepo = store.runRepository;
|
|
452
|
+
}
|
|
453
|
+
else if (isDir && !storePath.endsWith('.json')) {
|
|
454
|
+
const store = createDurableRunStore(storePath);
|
|
455
|
+
humanTasks = store.humanTaskService;
|
|
456
|
+
runsRepo = store.runRepository;
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
const dir = path.dirname(storePath);
|
|
460
|
+
runsRepo = new FileAgentRunRepository(storePath);
|
|
461
|
+
humanTasks = new FileHumanTaskService(path.join(dir, 'human-tasks.json'));
|
|
462
|
+
void new FileCheckpointService(path.join(dir, 'checkpoints.json'));
|
|
463
|
+
}
|
|
464
|
+
const run = await runsRepo.get(runId);
|
|
465
|
+
if (!run) {
|
|
466
|
+
// eslint-disable-next-line no-console
|
|
467
|
+
console.error(`Run not found: ${runId}`);
|
|
468
|
+
process.exitCode = 1;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const tasks = await humanTasks.listByRun(runId);
|
|
472
|
+
const pending = tasks.find((t) => t.status === 'pending');
|
|
473
|
+
if (pending) {
|
|
474
|
+
await humanTasks.resolve(pending.id, approved ? 'approved' : 'rejected', opts.by);
|
|
475
|
+
}
|
|
476
|
+
await runsRepo.updateStatus(runId, run.status, {
|
|
477
|
+
metadata: {
|
|
478
|
+
...run.metadata,
|
|
479
|
+
cliDecision: {
|
|
480
|
+
approved,
|
|
481
|
+
by: opts.by,
|
|
482
|
+
at: new Date().toISOString(),
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
// eslint-disable-next-line no-console
|
|
487
|
+
console.log(JSON.stringify({
|
|
488
|
+
runId,
|
|
489
|
+
decision: approved ? 'approved' : 'rejected',
|
|
490
|
+
by: opts.by,
|
|
491
|
+
humanTaskId: pending?.id,
|
|
492
|
+
note: 'Human task updated. Call runtime.approveAndResume(runId, { approved, approvedBy }) in your app to continue the agent.',
|
|
493
|
+
}, null, 2));
|
|
494
|
+
}
|
|
495
|
+
catch (e) {
|
|
496
|
+
// eslint-disable-next-line no-console
|
|
497
|
+
console.error(e);
|
|
498
|
+
process.exitCode = 1;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
runs
|
|
502
|
+
.command('resume')
|
|
503
|
+
.description('Record HITL approve/reject on file store (in-app approveAndResume still required to continue)')
|
|
504
|
+
.argument('<runId>', 'AgentRun / execution id')
|
|
505
|
+
.option('--store <path>', 'Path to runs.json or durable store directory', DEFAULT_RUN_STORE)
|
|
506
|
+
.option('--dir <path>', 'Durable store directory (runs + human-tasks + checkpoints)')
|
|
507
|
+
.option('--approve', 'Approve pending human task (default)')
|
|
508
|
+
.option('--reject', 'Reject pending human task')
|
|
509
|
+
.option('--by <who>', 'Approver identity', 'cli')
|
|
510
|
+
.action(resumeAction);
|
|
511
|
+
runs
|
|
512
|
+
.command('approve')
|
|
513
|
+
.description('Alias for runs resume --approve')
|
|
514
|
+
.argument('<runId>', 'AgentRun / execution id')
|
|
515
|
+
.option('--store <path>', 'Path to runs.json or durable store directory', DEFAULT_RUN_STORE)
|
|
516
|
+
.option('--dir <path>', 'Durable store directory')
|
|
517
|
+
.option('--by <who>', 'Approver identity', 'cli')
|
|
518
|
+
.action(async (runId, opts) => {
|
|
519
|
+
await resumeAction(runId, { ...opts, approve: true });
|
|
520
|
+
});
|
|
521
|
+
agent
|
|
522
|
+
.command('apply')
|
|
523
|
+
.description('Apply declarative Agent OS platform resources (Definition / Deployment / Run)')
|
|
524
|
+
.requiredOption('-f, --file <path>', 'Manifest file (JSON or YAML)')
|
|
525
|
+
.option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
|
|
526
|
+
.option('--registry <path>', 'Local package registry root for packageRef resolution')
|
|
527
|
+
.option('--project <path>', 'Project root for .hazel/agents packageRef resolution', '.')
|
|
528
|
+
.action(async (opts) => {
|
|
529
|
+
try {
|
|
530
|
+
const { createLocalPlatform, defaultRegistryRoot, parsePlatformDocuments } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
531
|
+
const text = fs.readFileSync(path.resolve(opts.file), 'utf8');
|
|
532
|
+
const docs = parsePlatformDocuments(text);
|
|
533
|
+
const projectRoot = path.resolve(opts.project);
|
|
534
|
+
const platform = createLocalPlatform({
|
|
535
|
+
storePath: path.resolve(opts.store),
|
|
536
|
+
projectRoot,
|
|
537
|
+
registryRoot: opts.registry ? path.resolve(opts.registry) : defaultRegistryRoot(),
|
|
538
|
+
});
|
|
539
|
+
const results = [];
|
|
540
|
+
for (const doc of docs) {
|
|
541
|
+
const result = await platform.reconciler.applyResource(doc);
|
|
542
|
+
results.push({
|
|
543
|
+
kind: result.resource.kind,
|
|
544
|
+
name: result.resource.metadata.name,
|
|
545
|
+
namespace: result.resource.metadata.namespace ?? 'default',
|
|
546
|
+
generation: result.resource.metadata.generation,
|
|
547
|
+
ready: result.ready,
|
|
548
|
+
message: result.message,
|
|
549
|
+
conditions: result.resource.status?.conditions,
|
|
550
|
+
resolved: result.resource.status?.backend,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
// eslint-disable-next-line no-console
|
|
554
|
+
console.log(JSON.stringify({ applied: results.length, results }, null, 2));
|
|
555
|
+
if (results.some((r) => !r.ready))
|
|
556
|
+
process.exitCode = 1;
|
|
557
|
+
}
|
|
558
|
+
catch (e) {
|
|
559
|
+
// eslint-disable-next-line no-console
|
|
560
|
+
console.error(e instanceof Error ? e.message : e);
|
|
561
|
+
process.exitCode = 1;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
agent
|
|
565
|
+
.command('get')
|
|
566
|
+
.description('List or get platform resources from the local store')
|
|
567
|
+
.argument('[type]', 'Resource type (e.g. agentdefinitions, agentdeployments)')
|
|
568
|
+
.argument('[name]', 'Resource name')
|
|
569
|
+
.option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
|
|
570
|
+
.option('-n, --namespace <ns>', 'Namespace filter', 'default')
|
|
571
|
+
.option('--all-namespaces', 'List across namespaces')
|
|
572
|
+
.option('--project <path>', 'Project root (durable run correlation)', '.')
|
|
573
|
+
.option('--summary', 'Print secret-safe summaries instead of full resources')
|
|
574
|
+
.action(async (type, name, opts) => {
|
|
575
|
+
try {
|
|
576
|
+
const { createLocalPlatform, parseResourceTypeArg, summarizeResource } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
577
|
+
const platform = createLocalPlatform({
|
|
578
|
+
storePath: path.resolve(opts.store),
|
|
579
|
+
projectRoot: path.resolve(opts.project),
|
|
580
|
+
actor: 'cli',
|
|
581
|
+
});
|
|
582
|
+
let kind;
|
|
583
|
+
let resourceName = name;
|
|
584
|
+
if (type) {
|
|
585
|
+
const parsed = parseResourceTypeArg(name ? `${type}/${name}` : type);
|
|
586
|
+
kind = parsed.kind;
|
|
587
|
+
resourceName = parsed.name ?? name;
|
|
588
|
+
if (parsed.namespace && !opts.allNamespaces) {
|
|
589
|
+
opts.namespace = parsed.namespace;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (kind && resourceName) {
|
|
593
|
+
const found = platform.repo.get(kind, resourceName, opts.namespace);
|
|
594
|
+
if (!found) {
|
|
595
|
+
// eslint-disable-next-line no-console
|
|
596
|
+
console.error(`Not found: ${opts.namespace}/${kind}/${resourceName}`);
|
|
597
|
+
process.exitCode = 1;
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
// eslint-disable-next-line no-console
|
|
601
|
+
console.log(JSON.stringify(opts.summary ? summarizeResource(found) : found, null, 2));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const items = platform.repo.list({
|
|
605
|
+
kind,
|
|
606
|
+
namespace: opts.allNamespaces ? undefined : opts.namespace,
|
|
607
|
+
});
|
|
608
|
+
// eslint-disable-next-line no-console
|
|
609
|
+
console.log(JSON.stringify({
|
|
610
|
+
items: opts.summary
|
|
611
|
+
? items.map(summarizeResource)
|
|
612
|
+
: items.map((r) => ({
|
|
613
|
+
kind: r.kind,
|
|
614
|
+
name: r.metadata.name,
|
|
615
|
+
namespace: r.metadata.namespace ?? 'default',
|
|
616
|
+
generation: r.metadata.generation,
|
|
617
|
+
ready: r.status?.conditions?.find((c) => c.type === 'Ready')?.status,
|
|
618
|
+
})),
|
|
619
|
+
}, null, 2));
|
|
620
|
+
}
|
|
621
|
+
catch (e) {
|
|
622
|
+
// eslint-disable-next-line no-console
|
|
623
|
+
console.error(e instanceof Error ? e.message : e);
|
|
624
|
+
process.exitCode = 1;
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
agent
|
|
628
|
+
.command('describe')
|
|
629
|
+
.description('Describe a platform resource (spec, status, conditions)')
|
|
630
|
+
.argument('<resource>', 'kind/name or namespace/kind/name')
|
|
631
|
+
.option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
|
|
632
|
+
.option('--project <path>', 'Project root (re-correlate durable runs on describe)', '.')
|
|
633
|
+
.option('--refresh', 'Re-reconcile before describe (refresh durable correlation)')
|
|
634
|
+
.action(async (resource, opts) => {
|
|
635
|
+
try {
|
|
636
|
+
const { createLocalPlatform, parseResourceTypeArg } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
637
|
+
const parsed = parseResourceTypeArg(resource);
|
|
638
|
+
if (!parsed.name) {
|
|
639
|
+
throw new Error('describe requires kind/name (e.g. agentdeployment/support)');
|
|
640
|
+
}
|
|
641
|
+
const platform = createLocalPlatform({
|
|
642
|
+
storePath: path.resolve(opts.store),
|
|
643
|
+
projectRoot: path.resolve(opts.project),
|
|
644
|
+
});
|
|
645
|
+
const ns = parsed.namespace ?? 'default';
|
|
646
|
+
if (opts.refresh) {
|
|
647
|
+
if (parsed.kind === 'AgentDeployment') {
|
|
648
|
+
await platform.reconciler.reconcileDeployment(parsed.name, ns);
|
|
649
|
+
}
|
|
650
|
+
else if (parsed.kind === 'AgentRun') {
|
|
651
|
+
await platform.reconciler.reconcileRun(parsed.name, ns);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const found = platform.repo.get(parsed.kind, parsed.name, ns);
|
|
655
|
+
if (!found) {
|
|
656
|
+
// eslint-disable-next-line no-console
|
|
657
|
+
console.error(`Not found: ${ns}/${parsed.kind}/${parsed.name}`);
|
|
658
|
+
process.exitCode = 1;
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const { summarizeResource } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
662
|
+
// eslint-disable-next-line no-console
|
|
663
|
+
console.log(JSON.stringify({
|
|
664
|
+
resource: found,
|
|
665
|
+
summary: summarizeResource(found),
|
|
666
|
+
}, null, 2));
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
// eslint-disable-next-line no-console
|
|
670
|
+
console.error(e instanceof Error ? e.message : e);
|
|
671
|
+
process.exitCode = 1;
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
agent
|
|
675
|
+
.command('delete')
|
|
676
|
+
.description('Delete a platform resource (deployments clean up the local backend)')
|
|
677
|
+
.argument('<resource>', 'kind/name or namespace/kind/name')
|
|
678
|
+
.option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
|
|
679
|
+
.action(async (resource, opts) => {
|
|
680
|
+
try {
|
|
681
|
+
const { createLocalPlatform, parseResourceTypeArg } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
682
|
+
const parsed = parseResourceTypeArg(resource);
|
|
683
|
+
if (!parsed.name) {
|
|
684
|
+
throw new Error('delete requires kind/name (e.g. agentdeployment/support)');
|
|
685
|
+
}
|
|
686
|
+
const platform = createLocalPlatform({
|
|
687
|
+
storePath: path.resolve(opts.store),
|
|
688
|
+
projectRoot: process.cwd(),
|
|
689
|
+
});
|
|
690
|
+
const result = await platform.reconciler.deleteResource({
|
|
691
|
+
kind: parsed.kind,
|
|
692
|
+
name: parsed.name,
|
|
693
|
+
namespace: parsed.namespace ?? 'default',
|
|
694
|
+
});
|
|
695
|
+
// FileResourceRepository auto-persists; keep save() for compatibility
|
|
696
|
+
platform.save();
|
|
697
|
+
// eslint-disable-next-line no-console
|
|
698
|
+
console.log(JSON.stringify({
|
|
699
|
+
deleted: result.deleted,
|
|
700
|
+
kind: parsed.kind,
|
|
701
|
+
name: parsed.name,
|
|
702
|
+
namespace: parsed.namespace ?? 'default',
|
|
703
|
+
backendMessage: result.backendMessage,
|
|
704
|
+
}, null, 2));
|
|
705
|
+
if (!result.deleted)
|
|
706
|
+
process.exitCode = 1;
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
// eslint-disable-next-line no-console
|
|
710
|
+
console.error(e instanceof Error ? e.message : e);
|
|
711
|
+
process.exitCode = 1;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
agent
|
|
715
|
+
.command('reconcile')
|
|
716
|
+
.description('Reconcile all AgentDeployments / AgentRuns in the local platform store (control-plane loop)')
|
|
717
|
+
.option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
|
|
718
|
+
.option('--project <path>', 'Project root for packageRef / durable run correlation', '.')
|
|
719
|
+
.option('--registry <path>', 'Local package registry root for packageRef resolution')
|
|
720
|
+
.option('-n, --namespace <ns>', 'Limit to one namespace')
|
|
721
|
+
.option('--watch', 'Keep reconciling on an interval until interrupted')
|
|
722
|
+
.option('--interval <seconds>', 'Watch interval in seconds (default 5)', '5')
|
|
723
|
+
.action(async (opts) => {
|
|
724
|
+
try {
|
|
725
|
+
const { createLocalPlatform, defaultRegistryRoot, watchLocalPlatform } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
726
|
+
const projectRoot = path.resolve(opts.project);
|
|
727
|
+
const platform = createLocalPlatform({
|
|
728
|
+
storePath: path.resolve(opts.store),
|
|
729
|
+
projectRoot,
|
|
730
|
+
registryRoot: opts.registry ? path.resolve(opts.registry) : defaultRegistryRoot(),
|
|
731
|
+
actor: 'cli',
|
|
732
|
+
});
|
|
733
|
+
const namespace = opts.namespace;
|
|
734
|
+
const printTick = (result, tick) => {
|
|
735
|
+
// eslint-disable-next-line no-console
|
|
736
|
+
console.log(JSON.stringify({
|
|
737
|
+
tick: tick ?? 1,
|
|
738
|
+
ready: result.ready,
|
|
739
|
+
notReady: result.notReady,
|
|
740
|
+
errors: result.errors,
|
|
741
|
+
items: result.results.map((r) => ({
|
|
742
|
+
kind: r.resource.kind,
|
|
743
|
+
name: r.resource.metadata.name,
|
|
744
|
+
namespace: r.resource.metadata.namespace ?? 'default',
|
|
745
|
+
ready: r.ready,
|
|
746
|
+
message: r.message,
|
|
747
|
+
})),
|
|
748
|
+
}, null, 2));
|
|
749
|
+
};
|
|
750
|
+
if (!opts.watch) {
|
|
751
|
+
const result = await platform.reconcileAll({ namespace });
|
|
752
|
+
printTick(result);
|
|
753
|
+
if (result.notReady > 0 || result.errors.length > 0)
|
|
754
|
+
process.exitCode = 1;
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const seconds = Math.max(1, Number(opts.interval) || 5);
|
|
758
|
+
const ac = new AbortController();
|
|
759
|
+
const onSig = () => ac.abort();
|
|
760
|
+
process.on('SIGINT', onSig);
|
|
761
|
+
process.on('SIGTERM', onSig);
|
|
762
|
+
// eslint-disable-next-line no-console
|
|
763
|
+
console.error(`Watching every ${seconds}s (Ctrl+C to stop)…`);
|
|
764
|
+
await watchLocalPlatform(platform, {
|
|
765
|
+
namespace,
|
|
766
|
+
intervalMs: seconds * 1000,
|
|
767
|
+
signal: ac.signal,
|
|
768
|
+
onTick: async (result, tick) => {
|
|
769
|
+
printTick(result, tick);
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
process.off('SIGINT', onSig);
|
|
773
|
+
process.off('SIGTERM', onSig);
|
|
774
|
+
}
|
|
775
|
+
catch (e) {
|
|
776
|
+
// eslint-disable-next-line no-console
|
|
777
|
+
console.error(e instanceof Error ? e.message : e);
|
|
778
|
+
process.exitCode = 1;
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
agent
|
|
782
|
+
.command('events')
|
|
783
|
+
.description('List platform control-plane events (audit log; no secrets)')
|
|
784
|
+
.option('--store <path>', 'Platform resource store path', DEFAULT_PLATFORM_STORE)
|
|
785
|
+
.option('--events <path>', 'Events JSONL path (default: beside store)')
|
|
786
|
+
.option('--type <type>', 'Filter by event type')
|
|
787
|
+
.option('--kind <kind>', 'Filter by resource kind')
|
|
788
|
+
.option('--limit <n>', 'Max events (most recent)', '50')
|
|
789
|
+
.action(async (opts) => {
|
|
790
|
+
try {
|
|
791
|
+
const { createLocalPlatform } = await Promise.resolve().then(() => __importStar(require('@hazeljs/agent')));
|
|
792
|
+
const storePath = path.resolve(opts.store);
|
|
793
|
+
const platform = createLocalPlatform({
|
|
794
|
+
storePath,
|
|
795
|
+
eventsPath: opts.events
|
|
796
|
+
? path.resolve(opts.events)
|
|
797
|
+
: path.join(path.dirname(storePath), 'events.jsonl'),
|
|
798
|
+
actor: 'cli',
|
|
799
|
+
});
|
|
800
|
+
const items = platform.events.list({
|
|
801
|
+
type: opts.type,
|
|
802
|
+
kind: opts.kind,
|
|
803
|
+
limit: Number(opts.limit) || 50,
|
|
804
|
+
});
|
|
805
|
+
// eslint-disable-next-line no-console
|
|
806
|
+
console.log(JSON.stringify({ items }, null, 2));
|
|
807
|
+
}
|
|
808
|
+
catch (e) {
|
|
809
|
+
// eslint-disable-next-line no-console
|
|
810
|
+
console.error(e instanceof Error ? e.message : e);
|
|
811
|
+
process.exitCode = 1;
|
|
812
|
+
}
|
|
813
|
+
});
|
|
94
814
|
}
|