@yansirplus/cli 0.5.17 → 0.5.18

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.
Files changed (52) hide show
  1. package/README.md +12 -6
  2. package/agent-catalog/agentOS/SKILL.md +22 -0
  3. package/agent-catalog/agentOS/references/agent/decision-graph.json +530 -0
  4. package/agent-catalog/agentOS/references/agent/errors.json +497 -0
  5. package/agent-catalog/agentOS/references/agent/invariant-matrix.json +337 -0
  6. package/agent-catalog/agentOS/references/agent/primitives.json +989 -0
  7. package/agent-catalog/agentOS/references/agent/recipes.json +109 -0
  8. package/agent-catalog/agentOS/references/agent/start-here.md +25 -0
  9. package/agent-catalog/agentOS/references/package-map.md +72 -0
  10. package/agent-catalog/agentOS/references/provenance.json +251 -0
  11. package/agent-catalog/agentOS/references/public-api/cli.md +20 -0
  12. package/agent-catalog/agentOS/references/public-api/client.md +88 -0
  13. package/agent-catalog/agentOS/references/public-api/core.md +1817 -0
  14. package/agent-catalog/agentOS/references/public-api/runtime.md +794 -0
  15. package/dist/build/agent-authoring/config.d.ts +20 -5
  16. package/dist/build/agent-authoring/config.js +132 -32
  17. package/dist/build/agent-authoring/manifest-compiler.d.ts +131 -2
  18. package/dist/build/agent-authoring/manifest-compiler.js +630 -8
  19. package/dist/build/agent-authoring/shared.d.ts +2 -0
  20. package/dist/build/agent-authoring/shared.js +2 -0
  21. package/dist/build/agent-authoring/static-target.d.ts +6 -3
  22. package/dist/build/agent-authoring/static-target.js +1807 -286
  23. package/dist/build/agent-authoring.d.ts +3 -3
  24. package/dist/build/agent-authoring.js +1 -1
  25. package/dist/build/build-cli.d.ts +1 -1
  26. package/dist/build/build-cli.js +1614 -26
  27. package/dist/check/algorithmic/client-boundary-checks.mjs +3 -34
  28. package/dist/check/algorithmic/convergence-smoke-checks.mjs +652 -6
  29. package/dist/check/algorithmic/distribution-checks.mjs +8 -7
  30. package/dist/check/algorithmic/package-boundary-checks.mjs +3 -2
  31. package/dist/check/algorithmic/repo-surface-checks.mjs +55 -1
  32. package/dist/check/algorithmic/static-target-checks.mjs +83 -5
  33. package/dist/check/algorithmic-checks.mjs +10 -17
  34. package/dist/check/default-gate.mjs +3 -3
  35. package/dist/check/effect-scan-gate.mjs +121 -0
  36. package/dist/check/package-graph.mjs +2 -32
  37. package/dist/consumer-overlay.mjs +802 -0
  38. package/dist/lib/public-api-model.mjs +19 -0
  39. package/dist/lib/repo-source-files.mjs +26 -0
  40. package/dist/lib/ts-module-loader.mjs +44 -0
  41. package/dist/lib/workspace-manifest.mjs +77 -0
  42. package/dist/main.mjs +151 -21
  43. package/package.json +8 -4
  44. package/dist/check/check-coverage.mjs +0 -231
  45. package/dist/generate/generate-agent-docs.mjs +0 -435
  46. package/dist/generate/generate-carrier-reference.mjs +0 -514
  47. package/dist/generate/generate-docs.mjs +0 -345
  48. package/dist/generate/generate-effect-skill-manifests.mjs +0 -193
  49. package/dist/generate/project-docs-site.mjs +0 -190
  50. package/dist/lib/boundary-rules.mjs +0 -63
  51. package/dist/lib/capability-routes.mjs +0 -354
  52. package/dist/lib/projection-sink.mjs +0 -113
@@ -1,8 +1,14 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
  import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import http from "node:http";
3
4
  import path from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { compileAgentTree, decodeAgentOsConfig, linkWorkspaceStaticTarget, normalizeAgentOsConfig, } from "./agent-authoring.js";
7
+ import { llmMaterialEnvBindings, } from "./agent-authoring/config.js";
8
+ import { importBundledModule } from "../lib/ts-module-loader.mjs";
9
+ import { WORKSPACE_AGENT_COMMAND } from "@yansirplus/core/workspace-agent";
10
+ import { preflightOpenAiCompatibleProviderMaterial, } from "@yansirplus/runtime/llm-effect-ai/openai-compatible";
11
+ import { parseEvalConfig, parseEvalDefinition, } from "@yansirplus/evals";
6
12
  const parseBuildArgs = (rawArgs) => {
7
13
  const args = {
8
14
  cwd: process.cwd(),
@@ -35,20 +41,275 @@ const parseBuildArgs = (rawArgs) => {
35
41
  }
36
42
  return args;
37
43
  };
44
+ const parseInfoArgs = (rawArgs) => {
45
+ const args = {
46
+ cwd: process.cwd(),
47
+ config: "agentos.config.jsonc",
48
+ json: false,
49
+ };
50
+ for (let index = 0; index < rawArgs.length; index += 1) {
51
+ const arg = rawArgs[index];
52
+ switch (arg) {
53
+ case "--cwd":
54
+ if (rawArgs[index + 1] === undefined)
55
+ throw new Error("--cwd requires a value");
56
+ args.cwd = rawArgs[index + 1];
57
+ index += 1;
58
+ break;
59
+ case "--config":
60
+ if (rawArgs[index + 1] === undefined)
61
+ throw new Error("--config requires a value");
62
+ args.config = rawArgs[index + 1];
63
+ index += 1;
64
+ break;
65
+ case "--json":
66
+ args.json = true;
67
+ break;
68
+ default:
69
+ throw new Error(`unexpected argument ${arg}`);
70
+ }
71
+ }
72
+ return args;
73
+ };
74
+ const parsePort = (value) => {
75
+ if (value === undefined)
76
+ throw new Error("--port requires a value");
77
+ const parsed = Number(value);
78
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
79
+ throw new Error("--port must be an integer between 0 and 65535");
80
+ }
81
+ return parsed;
82
+ };
83
+ const parseServeArgs = (rawArgs) => {
84
+ const args = {
85
+ cwd: process.cwd(),
86
+ config: "agentos.config.jsonc",
87
+ host: "127.0.0.1",
88
+ port: 8787,
89
+ llm: "config",
90
+ llmResponse: "ok",
91
+ json: false,
92
+ };
93
+ for (let index = 0; index < rawArgs.length; index += 1) {
94
+ const arg = rawArgs[index];
95
+ switch (arg) {
96
+ case "--cwd":
97
+ if (rawArgs[index + 1] === undefined)
98
+ throw new Error("--cwd requires a value");
99
+ args.cwd = rawArgs[index + 1];
100
+ index += 1;
101
+ break;
102
+ case "--config":
103
+ if (rawArgs[index + 1] === undefined)
104
+ throw new Error("--config requires a value");
105
+ args.config = rawArgs[index + 1];
106
+ index += 1;
107
+ break;
108
+ case "--package-scope":
109
+ if (rawArgs[index + 1] === undefined)
110
+ throw new Error("--package-scope requires a value");
111
+ args.packageScope = rawArgs[index + 1];
112
+ index += 1;
113
+ break;
114
+ case "--host":
115
+ if (rawArgs[index + 1] === undefined)
116
+ throw new Error("--host requires a value");
117
+ args.host = rawArgs[index + 1];
118
+ index += 1;
119
+ break;
120
+ case "--port":
121
+ args.port = parsePort(rawArgs[index + 1]);
122
+ index += 1;
123
+ break;
124
+ case "--llm": {
125
+ const value = rawArgs[index + 1];
126
+ if (value !== "config" && value !== "test") {
127
+ throw new Error("--llm must be one of config, test");
128
+ }
129
+ args.llm = value;
130
+ index += 1;
131
+ break;
132
+ }
133
+ case "--llm-response":
134
+ if (rawArgs[index + 1] === undefined)
135
+ throw new Error("--llm-response requires a value");
136
+ args.llmResponse = rawArgs[index + 1];
137
+ index += 1;
138
+ break;
139
+ case "--json":
140
+ args.json = true;
141
+ break;
142
+ default:
143
+ throw new Error(`unexpected argument ${arg}`);
144
+ }
145
+ }
146
+ return args;
147
+ };
148
+ const parseHeader = (value) => {
149
+ if (value === undefined)
150
+ throw new Error("--header requires a value");
151
+ const separator = value.indexOf("=");
152
+ if (separator <= 0) {
153
+ throw new Error("--header must use name=value");
154
+ }
155
+ const name = value.slice(0, separator).trim().toLowerCase();
156
+ const headerValue = value.slice(separator + 1);
157
+ if (name.length === 0) {
158
+ throw new Error("--header name must be non-empty");
159
+ }
160
+ return [name, headerValue];
161
+ };
162
+ const parseEvalArgs = (rawArgs) => {
163
+ const args = {
164
+ cwd: process.cwd(),
165
+ config: "agentos.config.jsonc",
166
+ headers: {},
167
+ llm: "config",
168
+ llmResponse: "ok",
169
+ json: false,
170
+ };
171
+ for (let index = 0; index < rawArgs.length; index += 1) {
172
+ const arg = rawArgs[index];
173
+ switch (arg) {
174
+ case "--cwd":
175
+ if (rawArgs[index + 1] === undefined)
176
+ throw new Error("--cwd requires a value");
177
+ args.cwd = rawArgs[index + 1];
178
+ index += 1;
179
+ break;
180
+ case "--config":
181
+ if (rawArgs[index + 1] === undefined)
182
+ throw new Error("--config requires a value");
183
+ args.config = rawArgs[index + 1];
184
+ index += 1;
185
+ break;
186
+ case "--package-scope":
187
+ if (rawArgs[index + 1] === undefined)
188
+ throw new Error("--package-scope requires a value");
189
+ args.packageScope = rawArgs[index + 1];
190
+ index += 1;
191
+ break;
192
+ case "--target": {
193
+ const value = rawArgs[index + 1];
194
+ if (value !== "local" && value !== "remote") {
195
+ throw new Error("--target must be one of local, remote");
196
+ }
197
+ args.target = value;
198
+ index += 1;
199
+ break;
200
+ }
201
+ case "--base-url":
202
+ if (rawArgs[index + 1] === undefined)
203
+ throw new Error("--base-url requires a value");
204
+ args.baseUrl = rawArgs[index + 1];
205
+ index += 1;
206
+ break;
207
+ case "--header": {
208
+ const [name, value] = parseHeader(rawArgs[index + 1]);
209
+ args.headers[name] = value;
210
+ index += 1;
211
+ break;
212
+ }
213
+ case "--llm": {
214
+ const value = rawArgs[index + 1];
215
+ if (value !== "config" && value !== "test") {
216
+ throw new Error("--llm must be one of config, test");
217
+ }
218
+ args.llm = value;
219
+ index += 1;
220
+ break;
221
+ }
222
+ case "--llm-response":
223
+ if (rawArgs[index + 1] === undefined)
224
+ throw new Error("--llm-response requires a value");
225
+ args.llmResponse = rawArgs[index + 1];
226
+ index += 1;
227
+ break;
228
+ case "--json":
229
+ args.json = true;
230
+ break;
231
+ default:
232
+ throw new Error(`unexpected argument ${arg}`);
233
+ }
234
+ }
235
+ return Object.freeze({
236
+ ...args,
237
+ headers: Object.freeze({ ...args.headers }),
238
+ });
239
+ };
240
+ const parsePreflightLlmArgs = (rawArgs) => {
241
+ const args = {
242
+ cwd: process.cwd(),
243
+ config: "agentos.config.jsonc",
244
+ routeBindingRef: "default",
245
+ json: false,
246
+ };
247
+ const [subcommand, ...rest] = rawArgs;
248
+ if (subcommand !== "llm") {
249
+ throw new Error("preflight: choose llm");
250
+ }
251
+ for (let index = 0; index < rest.length; index += 1) {
252
+ const arg = rest[index];
253
+ switch (arg) {
254
+ case "--cwd":
255
+ if (rest[index + 1] === undefined)
256
+ throw new Error("--cwd requires a value");
257
+ args.cwd = rest[index + 1];
258
+ index += 1;
259
+ break;
260
+ case "--config":
261
+ if (rest[index + 1] === undefined)
262
+ throw new Error("--config requires a value");
263
+ args.config = rest[index + 1];
264
+ index += 1;
265
+ break;
266
+ case "--route":
267
+ if (rest[index + 1] === undefined)
268
+ throw new Error("--route requires a value");
269
+ args.routeBindingRef = rest[index + 1];
270
+ index += 1;
271
+ break;
272
+ case "--json":
273
+ args.json = true;
274
+ break;
275
+ default:
276
+ throw new Error(`unexpected argument ${arg}`);
277
+ }
278
+ }
279
+ return args;
280
+ };
38
281
  const parseArgs = (rawArgs) => {
39
282
  const [command, ...rest] = rawArgs;
40
283
  if (command === undefined || command === "--help" || command === "-h")
41
284
  return { command: "help" };
42
- if (command !== "build")
43
- throw new Error("choose one of build");
44
285
  if (rest.includes("--help") || rest.includes("-h"))
45
286
  return { command: "help" };
46
- return { command: "build", args: parseBuildArgs(rest) };
287
+ if (command === "build")
288
+ return { command: "build", args: parseBuildArgs(rest) };
289
+ if (command === "info")
290
+ return { command: "info", args: parseInfoArgs(rest) };
291
+ if (command === "serve" || command === "dev") {
292
+ return { command, args: parseServeArgs(rest) };
293
+ }
294
+ if (command === "eval")
295
+ return { command: "eval", args: parseEvalArgs(rest) };
296
+ if (command === "preflight")
297
+ return { command: "preflight", args: parsePreflightLlmArgs(rest) };
298
+ throw new Error("choose one of build, info, serve, dev, eval, preflight");
47
299
  };
48
300
  const help = `Usage:
49
301
  agentos build [--cwd <path>] [--config <path>] [--package-scope <scope>]
302
+ agentos info [--cwd <path>] [--config <path>] [--json]
303
+ agentos serve [--cwd <path>] [--config <path>] [--package-scope <scope>] [--host <host>] [--port <port>] [--llm config|test] [--llm-response <text>] [--json]
304
+ agentos dev [--cwd <path>] [--config <path>] [--package-scope <scope>] [--host <host>] [--port <port>] [--llm config|test] [--llm-response <text>] [--json]
305
+ agentos eval [--cwd <path>] [--config <path>] [--package-scope <scope>] [--target local|remote] [--base-url <url>] [--header <name=value>] [--llm config|test] [--llm-response <text>] [--json]
306
+ agentos preflight llm [--cwd <path>] [--config <path>] [--route <binding-ref>] [--json]
50
307
 
51
- Compiles agent/ + agentos.config.jsonc into .agentos/generated/.
308
+ Compiles agent/ + workflows/ + agent/schedules/ + agentos.config.jsonc into .agentos/generated/.
309
+ Prints compile-only agent inspection without starting a runtime.
310
+ Serves the generated local node app through the CLI-owned public command/event protocol.
311
+ Runs evals/**/*.eval.ts against the generated app public command/event/channel protocol.
312
+ Checks configured LLM route material before submit without printing material values.
52
313
  `;
53
314
  const stripJsonc = (text) => {
54
315
  let output = "";
@@ -174,12 +435,54 @@ const inferPackageScope = (config, explicitScope) => {
174
435
  return match?.[1];
175
436
  };
176
437
  const loadToolDeclaration = async (file) => {
177
- const mod = await import(`${pathToFileURL(file).href}?agentos-build=${Date.now()}`);
438
+ const mod = await importBundledModule(file, { prefix: "agentos-tool-declaration-" });
439
+ if (!Object.hasOwn(mod, "declaration")) {
440
+ throw new Error(`${file}: missing exported declaration`);
441
+ }
442
+ return mod.declaration;
443
+ };
444
+ const loadScheduleDeclaration = async (file) => {
445
+ const mod = await importBundledModule(file, { prefix: "agentos-schedule-declaration-" });
446
+ if (!Object.hasOwn(mod, "default")) {
447
+ throw new Error(`${file}: missing default schedule export`);
448
+ }
449
+ return mod.default;
450
+ };
451
+ const loadDynamicResolverDeclaration = async (file) => {
452
+ const mod = await importBundledModule(file, { prefix: "agentos-dynamic-resolver-" });
178
453
  if (!Object.hasOwn(mod, "declaration")) {
179
454
  throw new Error(`${file}: missing exported declaration`);
180
455
  }
181
456
  return mod.declaration;
182
457
  };
458
+ const collectFiles = async (dir) => {
459
+ const files = [];
460
+ const entries = (await readdir(dir, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
461
+ for (const entry of entries) {
462
+ const file = path.join(dir, entry.name);
463
+ if (entry.isDirectory()) {
464
+ files.push(...(await collectFiles(file)));
465
+ continue;
466
+ }
467
+ if (entry.isFile()) {
468
+ files.push({ path: file, sourceKind: "regular" });
469
+ continue;
470
+ }
471
+ if (entry.isSymbolicLink()) {
472
+ files.push({ path: file, sourceKind: "symlink" });
473
+ continue;
474
+ }
475
+ files.push({ path: file, sourceKind: "non_regular" });
476
+ }
477
+ return files;
478
+ };
479
+ const isMainSkillRelativePath = (relativePath) => {
480
+ const parts = relativePath.split(path.sep);
481
+ return ((parts.length === 1 && (parts[0] ?? "").endsWith(".md")) ||
482
+ (parts.length === 2 && parts[1] === "SKILL.md"));
483
+ };
484
+ const isDynamicResolverRelativePath = (relativePath) => relativePath.split(path.sep).at(-1)?.endsWith(".dynamic.ts") ?? false;
485
+ const shouldLoadDynamicResolverDeclaration = (relativePath, sourceKind) => sourceKind === "regular" && relativePath.split(path.sep).length === 1;
183
486
  const loadAuthoredTree = async (cwd, agentDir) => {
184
487
  const files = [
185
488
  {
@@ -196,17 +499,140 @@ const loadAuthoredTree = async (cwd, agentDir) => {
196
499
  value: await readJson(agentJsonPath),
197
500
  });
198
501
  }
502
+ const forbiddenDynamicDir = path.join(agentDir, "dynamic");
503
+ if (await pathExists(forbiddenDynamicDir)) {
504
+ for (const file of await collectFiles(forbiddenDynamicDir)) {
505
+ const relativeDynamicPath = path.relative(forbiddenDynamicDir, file.path);
506
+ if (!isDynamicResolverRelativePath(relativeDynamicPath))
507
+ continue;
508
+ files.push({
509
+ path: toAuthoredPath(cwd, file.path),
510
+ kind: "dynamic",
511
+ sourceKind: file.sourceKind,
512
+ });
513
+ }
514
+ }
199
515
  const toolsDir = path.join(agentDir, "tools");
200
516
  if (await pathExists(toolsDir)) {
201
- const toolFiles = (await readdir(toolsDir, { withFileTypes: true }))
202
- .filter((entry) => entry.isFile() && entry.name.endsWith(".ts"))
203
- .map((entry) => path.join(toolsDir, entry.name))
204
- .sort((left, right) => left.localeCompare(right));
205
- for (const file of toolFiles) {
517
+ for (const file of await collectFiles(toolsDir)) {
518
+ const relativeToolPath = path.relative(toolsDir, file.path);
519
+ if (isDynamicResolverRelativePath(relativeToolPath)) {
520
+ files.push({
521
+ path: toAuthoredPath(cwd, file.path),
522
+ kind: "dynamic",
523
+ declaration: shouldLoadDynamicResolverDeclaration(relativeToolPath, file.sourceKind)
524
+ ? await loadDynamicResolverDeclaration(file.path)
525
+ : undefined,
526
+ sourceKind: file.sourceKind,
527
+ });
528
+ continue;
529
+ }
530
+ const parts = relativeToolPath.split(path.sep);
531
+ if (parts.length !== 1 || !parts[0]?.endsWith(".ts"))
532
+ continue;
533
+ if (file.sourceKind !== "regular")
534
+ continue;
206
535
  files.push({
207
- path: toAuthoredPath(cwd, file),
536
+ path: toAuthoredPath(cwd, file.path),
208
537
  kind: "tool",
209
- declaration: await loadToolDeclaration(file),
538
+ declaration: await loadToolDeclaration(file.path),
539
+ });
540
+ }
541
+ }
542
+ const channelsDir = path.join(agentDir, "channels");
543
+ if (await pathExists(channelsDir)) {
544
+ for (const file of await collectFiles(channelsDir)) {
545
+ files.push({
546
+ path: toAuthoredPath(cwd, file.path),
547
+ kind: "channel",
548
+ sourceKind: file.sourceKind,
549
+ });
550
+ }
551
+ }
552
+ const workflowsDir = path.join(cwd, "workflows");
553
+ if (await pathExists(workflowsDir)) {
554
+ for (const file of await collectFiles(workflowsDir)) {
555
+ files.push({
556
+ path: toAuthoredPath(cwd, file.path),
557
+ kind: "workflow",
558
+ sourceKind: file.sourceKind,
559
+ });
560
+ }
561
+ }
562
+ const schedulesDir = path.join(agentDir, "schedules");
563
+ if (await pathExists(schedulesDir)) {
564
+ for (const file of await collectFiles(schedulesDir)) {
565
+ files.push({
566
+ path: toAuthoredPath(cwd, file.path),
567
+ kind: "schedule",
568
+ declaration: file.sourceKind === "regular" ? await loadScheduleDeclaration(file.path) : undefined,
569
+ sourceKind: file.sourceKind,
570
+ });
571
+ }
572
+ }
573
+ const skillsDir = path.join(agentDir, "skills");
574
+ if (await pathExists(skillsDir)) {
575
+ for (const file of await collectFiles(skillsDir)) {
576
+ const authoredPath = toAuthoredPath(cwd, file.path);
577
+ const relativeSkillPath = path.relative(skillsDir, file.path);
578
+ if (isDynamicResolverRelativePath(relativeSkillPath)) {
579
+ files.push({
580
+ path: authoredPath,
581
+ kind: "dynamic",
582
+ declaration: shouldLoadDynamicResolverDeclaration(relativeSkillPath, file.sourceKind)
583
+ ? await loadDynamicResolverDeclaration(file.path)
584
+ : undefined,
585
+ sourceKind: file.sourceKind,
586
+ });
587
+ continue;
588
+ }
589
+ if (isMainSkillRelativePath(relativeSkillPath)) {
590
+ files.push({
591
+ path: authoredPath,
592
+ kind: "markdown",
593
+ text: file.sourceKind === "regular" ? await readFile(file.path, "utf8") : "",
594
+ sourceKind: file.sourceKind,
595
+ });
596
+ continue;
597
+ }
598
+ files.push({
599
+ path: authoredPath,
600
+ kind: "text",
601
+ bytes: file.sourceKind === "regular" ? await readFile(file.path) : new Uint8Array(),
602
+ sourceKind: file.sourceKind,
603
+ });
604
+ }
605
+ }
606
+ const instructionFragmentsDir = path.join(agentDir, "instructions");
607
+ if (await pathExists(instructionFragmentsDir)) {
608
+ for (const file of await collectFiles(instructionFragmentsDir)) {
609
+ const authoredPath = toAuthoredPath(cwd, file.path);
610
+ const relativeInstructionPath = path.relative(instructionFragmentsDir, file.path);
611
+ if (isDynamicResolverRelativePath(relativeInstructionPath)) {
612
+ files.push({
613
+ path: authoredPath,
614
+ kind: "dynamic",
615
+ declaration: shouldLoadDynamicResolverDeclaration(relativeInstructionPath, file.sourceKind)
616
+ ? await loadDynamicResolverDeclaration(file.path)
617
+ : undefined,
618
+ sourceKind: file.sourceKind,
619
+ });
620
+ continue;
621
+ }
622
+ if (file.path.endsWith(".md")) {
623
+ files.push({
624
+ path: authoredPath,
625
+ kind: "markdown",
626
+ text: file.sourceKind === "regular" ? await readFile(file.path, "utf8") : "",
627
+ sourceKind: file.sourceKind,
628
+ });
629
+ continue;
630
+ }
631
+ files.push({
632
+ path: authoredPath,
633
+ kind: "text",
634
+ bytes: file.sourceKind === "regular" ? await readFile(file.path) : new Uint8Array(),
635
+ sourceKind: file.sourceKind,
210
636
  });
211
637
  }
212
638
  }
@@ -220,16 +646,7 @@ const writeGeneratedFiles = async (cwd, files) => {
220
646
  await writeFile(output, file.text);
221
647
  }
222
648
  };
223
- const main = async () => {
224
- const parsed = parseArgs(process.argv.slice(2));
225
- if (parsed.command === "help") {
226
- process.stdout.write(help);
227
- return;
228
- }
229
- const args = parsed.args;
230
- if (process.versions.bun === undefined) {
231
- throw new Error("agentos build must run under Bun because authored tools are TypeScript modules");
232
- }
649
+ const loadCompileFacts = async (args) => {
233
650
  const cwd = path.resolve(args.cwd);
234
651
  const configPath = path.resolve(cwd, args.config);
235
652
  const configValue = await readJsonc(configPath);
@@ -252,13 +669,1184 @@ const main = async () => {
252
669
  if (!linked.ok) {
253
670
  throw new Error(`static target link failed: ${JSON.stringify(linked.issues, null, 2)}`);
254
671
  }
255
- await writeGeneratedFiles(cwd, linked.value.files);
256
- console.log(`generated ${linked.value.files.length} agentOS files`);
672
+ return { cwd, normalized: normalized.value, linked: linked.value };
673
+ };
674
+ const unavailable = (reason) => ({ status: "unavailable", reason });
675
+ const manifestCapabilityBindingRefs = (capabilities) => Object.values(capabilities ?? {})
676
+ .map((capability) => capability.bindingRef)
677
+ .sort((left, right) => left.localeCompare(right));
678
+ const projectInfo = (facts) => ({
679
+ compile: {
680
+ status: "available",
681
+ cwd: facts.cwd,
682
+ profile: facts.normalized.profile,
683
+ target: facts.normalized.target.kind,
684
+ agent: {
685
+ id: facts.normalized.deployment.manifest.agentId,
686
+ scope: facts.normalized.deployment.manifest.scope,
687
+ },
688
+ deployment: {
689
+ id: facts.normalized.deployment.deploymentId,
690
+ backend: facts.normalized.deployment.backend,
691
+ adapter: facts.normalized.deployment.adapter,
692
+ version: facts.normalized.deploymentVersion,
693
+ },
694
+ manifest: {
695
+ host: facts.normalized.target.kind,
696
+ capabilities: manifestCapabilityBindingRefs(facts.normalized.deployment.manifest.capabilities),
697
+ tools: Object.keys(facts.normalized.deployment.manifest.tools ?? {}).sort((left, right) => left.localeCompare(right)),
698
+ workflows: facts.normalized.workflows.map((workflow) => workflow.name),
699
+ schedules: facts.normalized.schedules.map((schedule) => schedule.scheduleId),
700
+ },
701
+ generated: {
702
+ files: facts.linked.files.map((file) => file.path),
703
+ mount: facts.linked.mount,
704
+ canonicalDeployment: facts.linked.canonicalDeployment,
705
+ },
706
+ provenance: facts.normalized.provenance,
707
+ },
708
+ resolve: unavailable("agentos info is compile-only; resolved install graph is unavailable"),
709
+ runtime: unavailable("agentos info does not start a local or Cloudflare runtime"),
710
+ });
711
+ const envNamePattern = /^[A-Za-z_][A-Za-z0-9_]*$/u;
712
+ const unwrapDevVarsValue = (rawValue, line) => {
713
+ if (rawValue.length < 2)
714
+ return { ok: true, value: rawValue };
715
+ const quote = rawValue[0];
716
+ if (quote !== "'" && quote !== '"')
717
+ return { ok: true, value: rawValue };
718
+ if (!rawValue.endsWith(quote)) {
719
+ return {
720
+ ok: false,
721
+ issue: { file: ".dev.vars", line, reason: "unterminated_quote" },
722
+ };
723
+ }
724
+ const inner = rawValue.slice(1, -1);
725
+ if (quote === "'")
726
+ return { ok: true, value: inner };
727
+ return {
728
+ ok: true,
729
+ value: inner.replace(/\\([nrt"\\])/gu, (_match, escaped) => {
730
+ if (escaped === "n")
731
+ return "\n";
732
+ if (escaped === "r")
733
+ return "\r";
734
+ if (escaped === "t")
735
+ return "\t";
736
+ return escaped;
737
+ }),
738
+ };
739
+ };
740
+ const parseDevVars = (text) => {
741
+ const values = {};
742
+ const issues = [];
743
+ const lines = text.split(/\r?\n/u);
744
+ for (let index = 0; index < lines.length; index += 1) {
745
+ const lineNumber = index + 1;
746
+ const trimmed = lines[index].trim();
747
+ if (trimmed.length === 0 || trimmed.startsWith("#"))
748
+ continue;
749
+ const separator = trimmed.indexOf("=");
750
+ if (separator <= 0) {
751
+ issues.push({ file: ".dev.vars", line: lineNumber, reason: "missing_separator" });
752
+ continue;
753
+ }
754
+ const key = trimmed.slice(0, separator).trim();
755
+ if (!envNamePattern.test(key)) {
756
+ issues.push({ file: ".dev.vars", line: lineNumber, reason: "invalid_name", key });
757
+ continue;
758
+ }
759
+ const value = unwrapDevVarsValue(trimmed.slice(separator + 1).trim(), lineNumber);
760
+ if (!value.ok) {
761
+ issues.push({ ...value.issue, key });
762
+ continue;
763
+ }
764
+ values[key] = value.value;
765
+ }
766
+ return { values, issues };
767
+ };
768
+ const isNotFoundError = (error) => typeof error === "object" &&
769
+ error !== null &&
770
+ "code" in error &&
771
+ error.code === "ENOENT";
772
+ const readPreflightEnv = async (cwd) => {
773
+ const values = {};
774
+ const valueSources = {};
775
+ for (const [key, value] of Object.entries(process.env)) {
776
+ if (typeof value !== "string")
777
+ continue;
778
+ values[key] = value;
779
+ valueSources[key] = "process.env";
780
+ }
781
+ const sources = new Set(["process.env"]);
782
+ const devVarsPath = path.join(cwd, ".dev.vars");
783
+ try {
784
+ const parsed = parseDevVars(await readFile(devVarsPath, "utf8"));
785
+ sources.add(".dev.vars");
786
+ for (const [key, value] of Object.entries(parsed.values)) {
787
+ values[key] = value;
788
+ valueSources[key] = ".dev.vars";
789
+ }
790
+ return { sources: [...sources], values, valueSources, issues: parsed.issues };
791
+ }
792
+ catch (error) {
793
+ if (isNotFoundError(error))
794
+ return { sources: [...sources], values, valueSources, issues: [] };
795
+ throw error;
796
+ }
797
+ };
798
+ const providerMaterialDetailFrom = (diagnostics) => {
799
+ const diagnostic = diagnostics.find((item) => item.pass === "provider_material");
800
+ if (diagnostic === undefined)
801
+ return undefined;
802
+ const parsed = JSON.parse(diagnostic.detail);
803
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
804
+ return undefined;
805
+ return parsed;
806
+ };
807
+ const materialStatusFromDetail = (detail, kind, fallback) => {
808
+ const row = detail?.materials?.find((item) => item.kind === kind);
809
+ const status = row?.status;
810
+ return status === "present" ||
811
+ status === "missing" ||
812
+ status === "invalid" ||
813
+ status === "resolver_threw"
814
+ ? status
815
+ : fallback;
816
+ };
817
+ const materialValueFor = (binding, env) => env.values[binding.envName] ?? null;
818
+ const preflightDiagnostic = (pass, reason, detail) => ({ pass, reason, detail: JSON.stringify(detail) });
819
+ const runPreflightLlm = async (args) => {
820
+ const facts = await loadCompileFacts({ cwd: args.cwd, config: args.config });
821
+ const routeBindingRef = args.routeBindingRef;
822
+ const availableRoutes = Object.keys(facts.normalized.llmRoutes).sort();
823
+ const llm = facts.normalized.llmRoutes[routeBindingRef];
824
+ const env = await readPreflightEnv(facts.cwd);
825
+ const envDiagnostics = env.issues.map((issue) => preflightDiagnostic("env_file", ".dev.vars is invalid", issue));
826
+ if (llm === undefined) {
827
+ const output = {
828
+ protocol: "agentos-preflight-llm@1",
829
+ ok: false,
830
+ cwd: facts.cwd,
831
+ config: args.config,
832
+ route: {
833
+ bindingRef: routeBindingRef,
834
+ status: "missing",
835
+ available: availableRoutes,
836
+ },
837
+ materials: [],
838
+ diagnostics: [
839
+ ...envDiagnostics,
840
+ preflightDiagnostic("llm_route", "LLM route binding not found", {
841
+ requested: routeBindingRef,
842
+ available: availableRoutes,
843
+ }),
844
+ ],
845
+ };
846
+ printPreflightLlm(output, args.json);
847
+ process.exitCode = 1;
848
+ return;
849
+ }
850
+ const bindings = llmMaterialEnvBindings(llm);
851
+ const bindingByKind = Object.fromEntries(bindings.map((binding) => [binding.kind, binding]));
852
+ const modelValue = materialValueFor(bindingByKind.model, env);
853
+ const refResolver = {
854
+ material: (ref) => {
855
+ const binding = bindings.find((candidate) => candidate.kind === ref.kind && candidate.ref === ref.ref);
856
+ return binding === undefined ? null : materialValueFor(binding, env);
857
+ },
858
+ };
859
+ const providerDiagnostics = envDiagnostics.length > 0
860
+ ? []
861
+ : preflightOpenAiCompatibleProviderMaterial({
862
+ route: {
863
+ kind: llm.route,
864
+ endpointRef: llm.endpointRef,
865
+ credentialRef: llm.credentialRef,
866
+ modelId: typeof modelValue === "string" ? modelValue : "",
867
+ },
868
+ refResolver,
869
+ routeBindingRef,
870
+ modelMaterial: {
871
+ ref: llm.modelRef,
872
+ value: modelValue,
873
+ },
874
+ });
875
+ const providerDetail = providerMaterialDetailFrom(providerDiagnostics);
876
+ const diagnostics = [
877
+ ...envDiagnostics,
878
+ ...providerDiagnostics,
879
+ ];
880
+ const ok = diagnostics.length === 0;
881
+ const materials = bindings.map((binding) => ({
882
+ kind: binding.kind,
883
+ ref: binding.ref,
884
+ envName: binding.envName,
885
+ source: env.valueSources[binding.envName] ?? "none",
886
+ status: materialStatusFromDetail(providerDetail, binding.kind, ok ? "present" : "invalid"),
887
+ }));
888
+ const output = {
889
+ protocol: "agentos-preflight-llm@1",
890
+ ok,
891
+ cwd: facts.cwd,
892
+ config: args.config,
893
+ route: {
894
+ bindingRef: routeBindingRef,
895
+ status: providerDetail?.routeStatus ?? (ok ? "present" : "invalid"),
896
+ kind: llm.route,
897
+ },
898
+ env: {
899
+ sources: env.sources,
900
+ },
901
+ materials,
902
+ diagnostics,
903
+ };
904
+ printPreflightLlm(output, args.json);
905
+ if (!ok)
906
+ process.exitCode = 1;
907
+ };
908
+ const printPreflightLlm = (output, json) => {
909
+ if (json) {
910
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
911
+ return;
912
+ }
913
+ const lines = [
914
+ `agentOS LLM preflight ${output.ok ? "passed" : "failed"}`,
915
+ `route: ${output.route.bindingRef} (${output.route.kind ?? "unknown"}) ${output.route.status}`,
916
+ ...output.materials.map((row) => `${row.kind}: ${row.status} ${row.envName} (${row.source})`),
917
+ ...output.diagnostics.map((diagnostic) => `diagnostic: ${diagnostic.pass} - ${diagnostic.reason}`),
918
+ ];
919
+ process.stdout.write(`${lines.join("\n")}\n`);
920
+ };
921
+ const PRODUCT_COMMAND = {
922
+ SUBMIT_SESSION_TURN: "submitSessionTurn",
923
+ INSPECT_SESSION: "inspectSession",
924
+ LIST_SESSIONS: "listSessions",
925
+ RUN_WORKFLOW: "runWorkflow",
926
+ INSPECT_WORKFLOW_RUN: "inspectWorkflowRun",
927
+ LIST_WORKFLOW_RUNS: "listWorkflowRuns",
928
+ };
929
+ class HttpFailure extends Error {
930
+ status;
931
+ constructor(status, message) {
932
+ super(message);
933
+ this.status = status;
934
+ }
935
+ }
936
+ const isPlainRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
937
+ const requireStringField = (value, field, message) => {
938
+ const fieldValue = value[field];
939
+ if (typeof fieldValue !== "string" || fieldValue.length === 0) {
940
+ throw new HttpFailure(400, message);
941
+ }
942
+ return fieldValue;
943
+ };
944
+ const assertSubmitRunInput = (value, label) => {
945
+ if (!isPlainRecord(value))
946
+ throw new HttpFailure(400, `invalid ${label} command input`);
947
+ if (typeof value.intent !== "string" || !isPlainRecord(value.context)) {
948
+ throw new HttpFailure(400, `invalid ${label} submit run input`);
949
+ }
950
+ return value;
951
+ };
952
+ const commandRequestFromUnknown = (value) => {
953
+ if (!isPlainRecord(value))
954
+ throw new HttpFailure(400, "command request must be an object");
955
+ const name = requireStringField(value, "name", "command request name must be a non-empty string");
956
+ return { name, input: value.input };
957
+ };
958
+ const jsonResponse = (response, status, body) => {
959
+ response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
960
+ response.end(`${JSON.stringify(body)}\n`);
961
+ };
962
+ const errorResponse = (response, error) => {
963
+ const status = error instanceof HttpFailure ? error.status : 500;
964
+ const message = error instanceof Error ? error.message : String(error);
965
+ jsonResponse(response, status, { ok: false, error: { message } });
966
+ };
967
+ const notFound = (response) => {
968
+ jsonResponse(response, 404, { ok: false, error: { message: "not found" } });
969
+ };
970
+ const readJsonBody = (request) => new Promise((resolve, reject) => {
971
+ let body = "";
972
+ request.setEncoding("utf8");
973
+ request.on("data", (chunk) => {
974
+ body += chunk;
975
+ if (body.length > 1_000_000) {
976
+ reject(new HttpFailure(413, "request body too large"));
977
+ request.destroy();
978
+ }
979
+ });
980
+ request.on("end", () => {
981
+ try {
982
+ resolve(body.trim().length === 0 ? {} : JSON.parse(body));
983
+ }
984
+ catch {
985
+ reject(new HttpFailure(400, "request body must be JSON"));
986
+ }
987
+ });
988
+ request.on("error", reject);
989
+ });
990
+ const readRawBody = async (request) => {
991
+ const chunks = [];
992
+ let size = 0;
993
+ for await (const chunk of request) {
994
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
995
+ size += buffer.byteLength;
996
+ if (size > 1_000_000)
997
+ throw new HttpFailure(413, "request body too large");
998
+ chunks.push(buffer);
999
+ }
1000
+ return Buffer.concat(chunks);
1001
+ };
1002
+ const headersFromIncoming = (request) => {
1003
+ const headers = new Headers();
1004
+ for (const [name, value] of Object.entries(request.headers)) {
1005
+ if (value === undefined)
1006
+ continue;
1007
+ if (Array.isArray(value)) {
1008
+ for (const item of value)
1009
+ headers.append(name, item);
1010
+ }
1011
+ else {
1012
+ headers.set(name, value);
1013
+ }
1014
+ }
1015
+ return headers;
1016
+ };
1017
+ const webRequestFromIncoming = async (request, url) => {
1018
+ const method = request.method ?? "GET";
1019
+ const hasBody = method !== "GET" && method !== "HEAD";
1020
+ const body = hasBody ? await readRawBody(request) : undefined;
1021
+ return new Request(url, {
1022
+ method,
1023
+ headers: headersFromIncoming(request),
1024
+ ...(body === undefined
1025
+ ? {}
1026
+ : {
1027
+ body: body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength),
1028
+ }),
1029
+ });
1030
+ };
1031
+ const writeWebResponse = async (response, webResponse) => {
1032
+ const headers = {};
1033
+ webResponse.headers.forEach((value, name) => {
1034
+ headers[name] = value;
1035
+ });
1036
+ response.writeHead(webResponse.status, headers);
1037
+ response.end(Buffer.from(await webResponse.arrayBuffer()));
1038
+ };
1039
+ const commandInputRecord = (input, label) => {
1040
+ if (!isPlainRecord(input))
1041
+ throw new HttpFailure(400, `invalid ${label} command input`);
1042
+ return input;
1043
+ };
1044
+ const invokeLocalAgentCommand = async (app, request) => {
1045
+ const { name, input } = request;
1046
+ if (name === WORKSPACE_AGENT_COMMAND.SUBMIT) {
1047
+ const record = commandInputRecord(input, "submit");
1048
+ return app.runtime.submit(assertSubmitRunInput(record.input, "submit"));
1049
+ }
1050
+ if (name === PRODUCT_COMMAND.SUBMIT_SESSION_TURN) {
1051
+ if (app.sessions === undefined)
1052
+ throw new HttpFailure(501, "sessions are unavailable");
1053
+ const record = commandInputRecord(input, "submitSessionTurn");
1054
+ assertSubmitRunInput(record, "submitSessionTurn");
1055
+ requireStringField(record, "sessionRef", "invalid session turn identity");
1056
+ requireStringField(record, "turnRef", "invalid session turn identity");
1057
+ return app.sessions.submitTurn(record);
1058
+ }
1059
+ if (name === PRODUCT_COMMAND.INSPECT_SESSION) {
1060
+ if (app.sessions === undefined)
1061
+ throw new HttpFailure(501, "sessions are unavailable");
1062
+ const record = commandInputRecord(input, "inspectSession");
1063
+ return app.sessions.inspect(requireStringField(record, "sessionRef", "invalid sessionRef"));
1064
+ }
1065
+ if (name === PRODUCT_COMMAND.LIST_SESSIONS) {
1066
+ if (app.sessions === undefined)
1067
+ throw new HttpFailure(501, "sessions are unavailable");
1068
+ return app.sessions.list();
1069
+ }
1070
+ if (name === PRODUCT_COMMAND.RUN_WORKFLOW) {
1071
+ if (app.workflows === undefined)
1072
+ throw new HttpFailure(501, "workflows are unavailable");
1073
+ const record = commandInputRecord(input, "runWorkflow");
1074
+ assertSubmitRunInput(record, "runWorkflow");
1075
+ requireStringField(record, "workflowId", "invalid workflow run identity");
1076
+ requireStringField(record, "workflowRunId", "invalid workflow run identity");
1077
+ return app.workflows.run(record);
1078
+ }
1079
+ if (name === PRODUCT_COMMAND.INSPECT_WORKFLOW_RUN) {
1080
+ if (app.workflows === undefined)
1081
+ throw new HttpFailure(501, "workflows are unavailable");
1082
+ const record = commandInputRecord(input, "inspectWorkflowRun");
1083
+ return app.workflows.inspectRun(requireStringField(record, "workflowId", "invalid workflow run identity"), requireStringField(record, "workflowRunId", "invalid workflow run identity"));
1084
+ }
1085
+ if (name === PRODUCT_COMMAND.LIST_WORKFLOW_RUNS) {
1086
+ if (app.workflows === undefined)
1087
+ throw new HttpFailure(501, "workflows are unavailable");
1088
+ const record = commandInputRecord(input, "listWorkflowRuns");
1089
+ return app.workflows.listRuns(requireStringField(record, "workflowId", "invalid workflowId"));
1090
+ }
1091
+ if (name === WORKSPACE_AGENT_COMMAND.CUSTOM) {
1092
+ if (app.customCommand === undefined) {
1093
+ throw new HttpFailure(501, "custom commands are unavailable");
1094
+ }
1095
+ return app.customCommand(input);
1096
+ }
1097
+ throw new HttpFailure(501, `unsupported generated app command ${name}`);
1098
+ };
1099
+ const localAppOptionsFor = (args) => ({
1100
+ cwd: path.resolve(args.cwd),
1101
+ inheritEnv: true,
1102
+ ...(args.llm === "test"
1103
+ ? {
1104
+ llm: {
1105
+ kind: "test",
1106
+ responses: [
1107
+ {
1108
+ items: [{ type: "message", text: args.llmResponse }],
1109
+ usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
1110
+ },
1111
+ ],
1112
+ },
1113
+ }
1114
+ : {}),
1115
+ });
1116
+ const loadGeneratedLocalApp = async (args, facts) => {
1117
+ if (facts.normalized.target.kind !== "node@1") {
1118
+ throw new Error(`agentos serve requires target kind node@1; observed ${facts.normalized.target.kind}`);
1119
+ }
1120
+ await writeGeneratedFiles(facts.cwd, facts.linked.files);
1121
+ const generatedLocal = path.join(facts.cwd, ".agentos", "generated", "local.ts");
1122
+ const mod = (await importBundledModule(generatedLocal, {
1123
+ prefix: "agentos-generated-local-app-",
1124
+ tempRoot: path.join(facts.cwd, ".agentos", ".cache"),
1125
+ }));
1126
+ if (typeof mod.createLocalAgentApp !== "function") {
1127
+ throw new Error(".agentos/generated/local.ts must export createLocalAgentApp");
1128
+ }
1129
+ return mod.createLocalAgentApp(localAppOptionsFor(args));
1130
+ };
1131
+ const parseAfterId = (url) => {
1132
+ const value = url.searchParams.get("afterId");
1133
+ if (value === null || value.length === 0)
1134
+ return undefined;
1135
+ const parsed = Number(value);
1136
+ if (!Number.isInteger(parsed) || parsed < 0) {
1137
+ throw new HttpFailure(400, "afterId must be a non-negative integer");
1138
+ }
1139
+ return parsed;
1140
+ };
1141
+ const writeSseEvents = (response, events) => {
1142
+ response.writeHead(200, {
1143
+ "content-type": "text/event-stream; charset=utf-8",
1144
+ "cache-control": "no-cache",
1145
+ connection: "keep-alive",
1146
+ });
1147
+ for (const event of events) {
1148
+ response.write(`event: ledger\ndata: ${JSON.stringify(event)}\n\n`);
1149
+ }
1150
+ response.end();
1151
+ };
1152
+ const startGeneratedAppServer = async (command, args) => {
1153
+ const facts = await loadCompileFacts(args);
1154
+ const app = await loadGeneratedLocalApp(args, facts);
1155
+ const server = http.createServer((request, response) => {
1156
+ void (async () => {
1157
+ const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
1158
+ if (request.method === "GET" && url.pathname === "/agentos/health") {
1159
+ jsonResponse(response, 200, {
1160
+ ok: true,
1161
+ protocol: "agentos-local-app@1",
1162
+ mode: command,
1163
+ target: facts.normalized.target.kind,
1164
+ llm: args.llm,
1165
+ diagnostics: app.runtime.diagnostics(),
1166
+ });
1167
+ return;
1168
+ }
1169
+ if (request.method === "POST" && url.pathname === "/agentos/command") {
1170
+ const body = await readJsonBody(request);
1171
+ const result = await invokeLocalAgentCommand(app, commandRequestFromUnknown(body));
1172
+ jsonResponse(response, 200, { ok: true, value: result });
1173
+ return;
1174
+ }
1175
+ if (request.method === "GET" && url.pathname === "/agentos/events") {
1176
+ writeSseEvents(response, app.runtime.events({ afterId: parseAfterId(url) }));
1177
+ return;
1178
+ }
1179
+ if (url.pathname.startsWith("/channels/")) {
1180
+ if (app.channels === undefined) {
1181
+ throw new HttpFailure(501, "channels are unavailable");
1182
+ }
1183
+ const channelResponse = await app.channels.handle(await webRequestFromIncoming(request, url));
1184
+ if (channelResponse === null) {
1185
+ notFound(response);
1186
+ return;
1187
+ }
1188
+ await writeWebResponse(response, channelResponse);
1189
+ return;
1190
+ }
1191
+ notFound(response);
1192
+ })().catch((error) => {
1193
+ errorResponse(response, error);
1194
+ });
1195
+ });
1196
+ await new Promise((resolve, reject) => {
1197
+ server.once("error", reject);
1198
+ server.listen(args.port, args.host, () => resolve());
1199
+ });
1200
+ const address = server.address();
1201
+ const port = typeof address === "object" && address !== null ? address.port : args.port;
1202
+ const url = `http://${args.host}:${port}`;
1203
+ const payload = {
1204
+ status: "listening",
1205
+ protocol: "agentos-local-app@1",
1206
+ mode: command,
1207
+ target: facts.normalized.target.kind,
1208
+ llm: args.llm,
1209
+ url,
1210
+ endpoints: {
1211
+ health: `${url}/agentos/health`,
1212
+ command: `${url}/agentos/command`,
1213
+ events: `${url}/agentos/events`,
1214
+ },
1215
+ };
1216
+ return {
1217
+ payload,
1218
+ close: () => new Promise((resolve, reject) => {
1219
+ server.close((error) => (error === undefined ? resolve() : reject(error)));
1220
+ }),
1221
+ };
1222
+ };
1223
+ const serveGeneratedApp = async (command, args) => {
1224
+ const started = await startGeneratedAppServer(command, args);
1225
+ process.stdout.write(args.json
1226
+ ? `${JSON.stringify(started.payload)}\n`
1227
+ : `agentOS ${command} listening on ${started.payload.url} (${started.payload.protocol}, llm=${args.llm})\n`);
1228
+ await new Promise((resolve) => {
1229
+ const close = () => {
1230
+ void started.close().finally(resolve);
1231
+ };
1232
+ process.once("SIGINT", close);
1233
+ process.once("SIGTERM", close);
1234
+ });
1235
+ };
1236
+ const collectEvalFiles = async (directory) => {
1237
+ if (!(await pathExists(directory)))
1238
+ return [];
1239
+ const files = [];
1240
+ const visit = async (current) => {
1241
+ for (const entry of await readdir(current, { withFileTypes: true })) {
1242
+ const target = path.join(current, entry.name);
1243
+ if (entry.isDirectory()) {
1244
+ await visit(target);
1245
+ continue;
1246
+ }
1247
+ if (entry.isFile() && /\.eval\.[cm]?[jt]sx?$/u.test(entry.name)) {
1248
+ files.push(target);
1249
+ }
1250
+ }
1251
+ };
1252
+ await visit(directory);
1253
+ return files.sort((left, right) => left.localeCompare(right));
1254
+ };
1255
+ const evalDefinitionsFromModule = (mod, file) => {
1256
+ const values = Object.values(mod).flatMap((value) => Array.isArray(value) ? value : [value]);
1257
+ const definitions = values.flatMap((value) => {
1258
+ if (!isPlainRecord(value))
1259
+ return [];
1260
+ try {
1261
+ return [parseEvalDefinition(value)];
1262
+ }
1263
+ catch (error) {
1264
+ throw new Error(`${path.relative(process.cwd(), file)} exports invalid eval declaration: ${errorMessage(error)}`);
1265
+ }
1266
+ });
1267
+ if (definitions.length === 0) {
1268
+ throw new Error(`${path.relative(process.cwd(), file)} exports no defineEval declaration`);
1269
+ }
1270
+ return definitions;
1271
+ };
1272
+ const loadEvalDefinitions = async (cwd, files) => {
1273
+ const definitions = [];
1274
+ for (const file of files) {
1275
+ const mod = (await importBundledModule(file, {
1276
+ define: { "import.meta.url": JSON.stringify(pathToFileURL(file).href) },
1277
+ prefix: "agentos-eval-file-",
1278
+ tempRoot: path.join(cwd, ".agentos", ".cache"),
1279
+ }));
1280
+ definitions.push(...evalDefinitionsFromModule(mod, file));
1281
+ }
1282
+ return definitions;
1283
+ };
1284
+ const loadEvalConfig = async (cwd) => {
1285
+ const configPath = path.join(cwd, "evals", "evals.config.ts");
1286
+ if (!(await pathExists(configPath)))
1287
+ return parseEvalConfig(undefined);
1288
+ const mod = (await importBundledModule(configPath, {
1289
+ define: { "import.meta.url": JSON.stringify(pathToFileURL(configPath).href) },
1290
+ prefix: "agentos-eval-config-",
1291
+ tempRoot: path.join(cwd, ".agentos", ".cache"),
1292
+ }));
1293
+ const config = mod.default ?? mod.config;
1294
+ try {
1295
+ return parseEvalConfig(config);
1296
+ }
1297
+ catch (error) {
1298
+ throw new Error(`evals/evals.config.ts must export defineEvalConfig() as default or config: ${errorMessage(error)}`);
1299
+ }
1300
+ };
1301
+ const targetFromArgs = (args, config, localUrl) => {
1302
+ const configuredTarget = config.target;
1303
+ const mode = args.target ?? (args.baseUrl !== undefined ? "remote" : configuredTarget?.kind) ?? "local";
1304
+ if (mode === "remote") {
1305
+ const baseUrl = args.baseUrl ?? (configuredTarget?.kind === "remote" ? configuredTarget.baseUrl : undefined);
1306
+ if (baseUrl === undefined) {
1307
+ throw new Error("agentos eval: remote target requires --base-url or evals.config.ts target");
1308
+ }
1309
+ return {
1310
+ kind: "remote",
1311
+ baseUrl,
1312
+ headers: Object.freeze({
1313
+ ...(configuredTarget?.kind === "remote" ? (configuredTarget.headers ?? {}) : {}),
1314
+ ...args.headers,
1315
+ }),
1316
+ };
1317
+ }
1318
+ if (localUrl === undefined) {
1319
+ throw new Error("agentos eval: local target did not start");
1320
+ }
1321
+ return {
1322
+ kind: "local",
1323
+ baseUrl: localUrl,
1324
+ headers: Object.freeze({ ...args.headers }),
1325
+ };
1326
+ };
1327
+ const parseJsonOrText = async (response) => {
1328
+ const text = await response.text();
1329
+ if (text.length === 0)
1330
+ return null;
1331
+ const contentType = response.headers.get("content-type") ?? "";
1332
+ if (contentType.includes("json"))
1333
+ return JSON.parse(text);
1334
+ return text;
1335
+ };
1336
+ const parseSseJsonEvents = (text) => text.split(/\n\n/u).flatMap((chunk) => chunk
1337
+ .split("\n")
1338
+ .filter((line) => line.startsWith("data: "))
1339
+ .map((line) => JSON.parse(line.slice("data: ".length))));
1340
+ const evalEventRecordFromUnknown = (value) => {
1341
+ if (!isPlainRecord(value) || typeof value.kind !== "string") {
1342
+ throw new Error("/agentos/events emitted a non-event record");
1343
+ }
1344
+ if (typeof value.id !== "number" || !Number.isInteger(value.id) || value.id <= 0) {
1345
+ throw new Error(`/agentos/events emitted id-less event ${value.kind}`);
1346
+ }
1347
+ return Object.freeze({
1348
+ id: value.id,
1349
+ kind: value.kind,
1350
+ ...(Object.hasOwn(value, "payload") ? { payload: value.payload } : {}),
1351
+ ...(typeof value.timestamp === "string" ? { timestamp: value.timestamp } : {}),
1352
+ });
1353
+ };
1354
+ const responseHeaders = (headers) => {
1355
+ const output = {};
1356
+ headers.forEach((value, name) => {
1357
+ output[name] = value;
1358
+ });
1359
+ return Object.freeze(output);
1360
+ };
1361
+ const headersRecord = (headers) => {
1362
+ const output = {};
1363
+ new Headers(headers).forEach((value, name) => {
1364
+ output[name] = value;
1365
+ });
1366
+ return output;
1367
+ };
1368
+ const jsonFetch = async (target, pathName, init = {}) => {
1369
+ const response = await fetch(new URL(pathName, target.baseUrl), {
1370
+ ...init,
1371
+ headers: {
1372
+ ...target.headers,
1373
+ ...(init.body === undefined ? {} : { "content-type": "application/json" }),
1374
+ ...headersRecord(init.headers),
1375
+ },
1376
+ });
1377
+ const body = await parseJsonOrText(response);
1378
+ if (!response.ok) {
1379
+ const detail = isPlainRecord(body) && isPlainRecord(body.error) ? body.error.message : body;
1380
+ throw new Error(`HTTP ${response.status} ${pathName}: ${String(detail)}`);
1381
+ }
1382
+ return body;
1383
+ };
1384
+ const commandValue = async (target, name, input) => {
1385
+ const body = await jsonFetch(target, "/agentos/command", {
1386
+ method: "POST",
1387
+ body: JSON.stringify({ name, input }),
1388
+ });
1389
+ if (!isPlainRecord(body) || body.ok !== true) {
1390
+ throw new Error(`agentos command ${name} returned invalid response`);
1391
+ }
1392
+ return body.value;
1393
+ };
1394
+ const eventQueryParams = (query) => {
1395
+ if (query?.afterId === undefined)
1396
+ return "";
1397
+ return `?afterId=${encodeURIComponent(String(query.afterId))}`;
1398
+ };
1399
+ const createEvalFacades = (target) => {
1400
+ const events = async (query) => {
1401
+ const response = await fetch(new URL(`/agentos/events${eventQueryParams(query)}`, target.baseUrl), {
1402
+ headers: target.headers,
1403
+ });
1404
+ if (!response.ok)
1405
+ throw new Error(`HTTP ${response.status} /agentos/events`);
1406
+ return parseSseJsonEvents(await response.text()).map(evalEventRecordFromUnknown);
1407
+ };
1408
+ const sessions = {
1409
+ submitTurn: (input) => commandValue(target, "submitSessionTurn", input),
1410
+ inspect: (sessionRef) => commandValue(target, "inspectSession", { sessionRef }),
1411
+ list: () => commandValue(target, "listSessions"),
1412
+ command: (name, input) => commandValue(target, name, input),
1413
+ events,
1414
+ projection: (name, input) => commandValue(target, name, input),
1415
+ };
1416
+ const workflows = {
1417
+ run: (input) => commandValue(target, "runWorkflow", input),
1418
+ inspectRun: (workflowId, workflowRunId) => commandValue(target, "inspectWorkflowRun", { workflowId, workflowRunId }),
1419
+ listRuns: (workflowId) => commandValue(target, "listWorkflowRuns", { workflowId }),
1420
+ start: (name, input) => commandValue(target, "runWorkflow", {
1421
+ workflowId: name,
1422
+ ...(isPlainRecord(input) ? input : { input }),
1423
+ }),
1424
+ inspect: (workflowRef) => commandValue(target, "inspectWorkflowRun", { workflowRunId: workflowRef }),
1425
+ };
1426
+ const channels = {
1427
+ request: async (input) => {
1428
+ const response = await fetch(new URL(input.path, target.baseUrl), {
1429
+ method: input.method ?? "POST",
1430
+ headers: {
1431
+ ...target.headers,
1432
+ ...(input.body === undefined ? {} : { "content-type": "application/json" }),
1433
+ ...(input.headers ?? {}),
1434
+ },
1435
+ ...(input.body === undefined ? {} : { body: JSON.stringify(input.body) }),
1436
+ });
1437
+ return Object.freeze({
1438
+ status: response.status,
1439
+ headers: responseHeaders(response.headers),
1440
+ body: await parseJsonOrText(response),
1441
+ });
1442
+ },
1443
+ dispatch: async (channel, payload) => {
1444
+ const response = await channels.request({
1445
+ method: "POST",
1446
+ path: `/channels/${channel}`,
1447
+ body: payload,
1448
+ });
1449
+ if (response.status < 200 || response.status >= 300) {
1450
+ throw new Error(`channel ${channel} returned HTTP ${response.status}`);
1451
+ }
1452
+ return response.body;
1453
+ },
1454
+ };
1455
+ return Object.freeze({
1456
+ sessions,
1457
+ workflows,
1458
+ channels,
1459
+ });
1460
+ };
1461
+ const assertionProjectionNames = (assertions) => [
1462
+ ...new Set(assertions
1463
+ .filter((assertion) => assertion.kind === "projection")
1464
+ .map((assertion) => assertion.name)),
1465
+ ].sort((left, right) => left.localeCompare(right));
1466
+ const projectionObservationsForAssertions = async (target, assertions) => {
1467
+ const projections = {};
1468
+ for (const name of assertionProjectionNames(assertions)) {
1469
+ try {
1470
+ projections[name] = Object.freeze({ ok: true, value: await commandValue(target, name) });
1471
+ }
1472
+ catch (error) {
1473
+ projections[name] = Object.freeze({
1474
+ ok: false,
1475
+ error: error instanceof Error ? error.message : String(error),
1476
+ });
1477
+ }
1478
+ }
1479
+ return Object.freeze(projections);
1480
+ };
1481
+ const completedEventKinds = new Set(["agent.run.completed", "runtime.completed_after_tools"]);
1482
+ const waitingEventKinds = new Set(["agent.run.interrupted", "agent.run.input_request"]);
1483
+ const toolEventKinds = new Set(["tool.executed", "tool.rejected"]);
1484
+ const eventPayload = (event) => isPlainRecord(event.payload) ? event.payload : {};
1485
+ const isEvalJsonValue = (value) => {
1486
+ if (value === null)
1487
+ return true;
1488
+ if (["boolean", "number", "string"].includes(typeof value))
1489
+ return true;
1490
+ if (Array.isArray(value))
1491
+ return value.every(isEvalJsonValue);
1492
+ return isPlainRecord(value) && Object.values(value).every(isEvalJsonValue);
1493
+ };
1494
+ const statusFromEvents = (events) => {
1495
+ let status;
1496
+ for (const event of events) {
1497
+ if (completedEventKinds.has(event.kind)) {
1498
+ status = "completed";
1499
+ continue;
1500
+ }
1501
+ if (waitingEventKinds.has(event.kind)) {
1502
+ status = "waiting";
1503
+ continue;
1504
+ }
1505
+ if (event.kind.startsWith("agent.aborted.") || event.kind.endsWith(".failed")) {
1506
+ status = "failed";
1507
+ }
1508
+ }
1509
+ return status;
1510
+ };
1511
+ const usageFromEvents = (events) => {
1512
+ let tokensUsed = 0;
1513
+ let hasTokensUsed = false;
1514
+ let llmUsage;
1515
+ for (const event of events) {
1516
+ const payload = eventPayload(event);
1517
+ if (typeof payload.tokensUsed === "number") {
1518
+ tokensUsed += payload.tokensUsed;
1519
+ hasTokensUsed = true;
1520
+ }
1521
+ if (isEvalJsonValue(payload.usage)) {
1522
+ llmUsage = payload.usage;
1523
+ }
1524
+ }
1525
+ const usage = {};
1526
+ if (hasTokensUsed)
1527
+ usage.tokensUsed = tokensUsed;
1528
+ if (llmUsage !== undefined)
1529
+ usage.llmUsage = llmUsage;
1530
+ return Object.freeze(usage);
1531
+ };
1532
+ const calledToolNames = (events) => {
1533
+ const names = new Set();
1534
+ for (const event of events) {
1535
+ if (!toolEventKinds.has(event.kind))
1536
+ continue;
1537
+ const payload = eventPayload(event);
1538
+ const name = payload.name ?? payload.toolName;
1539
+ if (typeof name === "string")
1540
+ names.add(name);
1541
+ }
1542
+ return names;
1543
+ };
1544
+ const failureReasonTokens = (events, runError) => {
1545
+ const reasons = new Set();
1546
+ if (runError instanceof Error)
1547
+ reasons.add(runError.message);
1548
+ for (const event of events) {
1549
+ if (!(event.kind.startsWith("agent.aborted.") || event.kind.endsWith(".failed")))
1550
+ continue;
1551
+ reasons.add(event.kind);
1552
+ const payload = eventPayload(event);
1553
+ for (const key of ["reason", "code", "publicMessage"]) {
1554
+ const value = payload[key];
1555
+ if (typeof value === "string")
1556
+ reasons.add(value);
1557
+ }
1558
+ }
1559
+ return reasons;
1560
+ };
1561
+ const checkObservationFromArtifact = (observation) => {
1562
+ const projections = new Map();
1563
+ for (const [name, result] of Object.entries(observation.projections)) {
1564
+ if (result.ok)
1565
+ projections.set(name, result.value);
1566
+ }
1567
+ return Object.freeze({
1568
+ status: observation.status,
1569
+ events: observation.events,
1570
+ projections,
1571
+ usage: observation.usage,
1572
+ });
1573
+ };
1574
+ const assertionResult = (assertion, status, message) => {
1575
+ const name = assertion.kind === "called_tool" || assertion.kind === "not_called_tool"
1576
+ ? assertion.toolName
1577
+ : assertion.kind === "projection" || assertion.kind === "check"
1578
+ ? assertion.name
1579
+ : assertion.kind === "failed"
1580
+ ? assertion.reason
1581
+ : undefined;
1582
+ return Object.freeze({
1583
+ kind: assertion.kind,
1584
+ ...(name === undefined ? {} : { name }),
1585
+ status,
1586
+ ...(message === undefined ? {} : { message }),
1587
+ });
1588
+ };
1589
+ const evaluateEvalAssertion = async (assertion, observation, runError) => {
1590
+ switch (assertion.kind) {
1591
+ case "completed":
1592
+ return observation.status === "completed"
1593
+ ? assertionResult(assertion, "passed")
1594
+ : assertionResult(assertion, "failed", `expected completed, observed ${observation.status ?? "unknown"}`);
1595
+ case "waiting":
1596
+ return observation.status === "waiting"
1597
+ ? assertionResult(assertion, "passed")
1598
+ : assertionResult(assertion, "failed", `expected waiting, observed ${observation.status ?? "unknown"}`);
1599
+ case "failed": {
1600
+ if (observation.status !== "failed") {
1601
+ return assertionResult(assertion, "failed", `expected failed, observed ${observation.status ?? "unknown"}`);
1602
+ }
1603
+ if (assertion.reason !== undefined &&
1604
+ !failureReasonTokens(observation.events, runError).has(assertion.reason)) {
1605
+ return assertionResult(assertion, "failed", `missing failure reason ${assertion.reason}`);
1606
+ }
1607
+ return assertionResult(assertion, "passed");
1608
+ }
1609
+ case "called_tool": {
1610
+ const tools = calledToolNames(observation.events);
1611
+ return tools.has(assertion.toolName)
1612
+ ? assertionResult(assertion, "passed")
1613
+ : assertionResult(assertion, "failed", `missing tool call ${assertion.toolName}`);
1614
+ }
1615
+ case "not_called_tool": {
1616
+ const tools = calledToolNames(observation.events);
1617
+ return tools.has(assertion.toolName)
1618
+ ? assertionResult(assertion, "failed", `unexpected tool call ${assertion.toolName}`)
1619
+ : assertionResult(assertion, "passed");
1620
+ }
1621
+ case "used_no_tools": {
1622
+ const tools = calledToolNames(observation.events);
1623
+ return tools.size === 0
1624
+ ? assertionResult(assertion, "passed")
1625
+ : assertionResult(assertion, "failed", `observed tool calls: ${[...tools].sort().join(", ")}`);
1626
+ }
1627
+ case "projection": {
1628
+ const projection = observation.projections[assertion.name];
1629
+ if (projection?.ok === true)
1630
+ return assertionResult(assertion, "passed");
1631
+ return assertionResult(assertion, "failed", projection?.error ?? `missing projection ${assertion.name}`);
1632
+ }
1633
+ case "check": {
1634
+ try {
1635
+ const passed = await assertion.check(checkObservationFromArtifact(observation));
1636
+ return passed
1637
+ ? assertionResult(assertion, "passed")
1638
+ : assertionResult(assertion, "failed", "custom check returned false");
1639
+ }
1640
+ catch (error) {
1641
+ return assertionResult(assertion, "failed", error instanceof Error ? error.message : String(error));
1642
+ }
1643
+ }
1644
+ }
1645
+ };
1646
+ const collectEvalObservation = async (facades, target, assertions, runError, eventQuery) => {
1647
+ const [rawEvents, projections] = await Promise.all([
1648
+ facades.sessions.events(eventQuery),
1649
+ projectionObservationsForAssertions(target, assertions),
1650
+ ]);
1651
+ const events = rawEvents;
1652
+ const observedStatus = statusFromEvents(events);
1653
+ return Object.freeze({
1654
+ status: observedStatus ?? (runError === undefined ? "completed" : "failed"),
1655
+ events: Object.freeze(events),
1656
+ projections,
1657
+ usage: usageFromEvents(events),
1658
+ });
1659
+ };
1660
+ const maxObservedEventId = (events) => {
1661
+ let max = 0;
1662
+ for (const event of events) {
1663
+ if (event.id > max) {
1664
+ max = event.id;
1665
+ }
1666
+ }
1667
+ return max;
1668
+ };
1669
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
1670
+ const runEvalDefinition = async (definition, target) => {
1671
+ const facades = createEvalFacades(target);
1672
+ const results = [];
1673
+ const assertions = definition.assertions ?? [];
1674
+ for (const testCase of definition.cases) {
1675
+ const afterId = definition.run === undefined
1676
+ ? undefined
1677
+ : maxObservedEventId(await facades.sessions.events());
1678
+ let runError;
1679
+ try {
1680
+ if (definition.run !== undefined) {
1681
+ const evalTarget = target.kind === "remote"
1682
+ ? { kind: "remote", baseUrl: target.baseUrl, headers: target.headers }
1683
+ : { kind: "local" };
1684
+ await definition.run({
1685
+ case: testCase,
1686
+ target: evalTarget,
1687
+ t: facades,
1688
+ sessions: facades.sessions,
1689
+ workflows: facades.workflows,
1690
+ channels: facades.channels,
1691
+ });
1692
+ }
1693
+ }
1694
+ catch (error) {
1695
+ runError = error;
1696
+ }
1697
+ const observation = await collectEvalObservation(facades, target, assertions, runError, afterId === undefined ? undefined : { afterId });
1698
+ const assertionResults = await Promise.all(assertions.map((assertion) => evaluateEvalAssertion(assertion, observation, runError)));
1699
+ const failedAssertions = assertionResults.filter((result) => result.status === "failed");
1700
+ const failedStatusExpected = assertions.some((assertion) => assertion.kind === "failed");
1701
+ const passed = failedAssertions.length === 0 && (runError === undefined || failedStatusExpected);
1702
+ const messages = [
1703
+ ...(runError !== undefined && !failedStatusExpected ? [errorMessage(runError)] : []),
1704
+ ...failedAssertions.flatMap((result) => result.message === undefined
1705
+ ? []
1706
+ : [
1707
+ `${result.kind}${result.name === undefined ? "" : `:${result.name}`}: ${result.message}`,
1708
+ ]),
1709
+ ];
1710
+ results.push({
1711
+ evalId: definition.id,
1712
+ caseId: testCase.id,
1713
+ status: passed ? "passed" : "failed",
1714
+ ...(messages.length === 0 ? {} : { error: messages.join("; ") }),
1715
+ assertions: Object.freeze(assertionResults),
1716
+ observation,
1717
+ });
1718
+ }
1719
+ return results;
1720
+ };
1721
+ const writeEvalReportArtifact = async (cwd, report) => {
1722
+ const directory = path.join(cwd, ".agentos", "eval-results");
1723
+ await mkdir(directory, { recursive: true });
1724
+ const timestamp = new Date().toISOString().replaceAll(/[:.]/gu, "-");
1725
+ const file = path.join(directory, `${timestamp}.json`);
1726
+ const relative = path.relative(cwd, file).split(path.sep).join("/");
1727
+ await writeFile(file, `${JSON.stringify({ ...report, artifact: relative }, null, 2)}\n`);
1728
+ return relative;
1729
+ };
1730
+ const runEval = async (args) => {
1731
+ const cwd = path.resolve(args.cwd);
1732
+ const evalFiles = await collectEvalFiles(path.join(cwd, "evals"));
1733
+ if (evalFiles.length === 0) {
1734
+ throw new Error("agentos eval: no evals/**/*.eval.ts files found");
1735
+ }
1736
+ const [config, definitions] = await Promise.all([
1737
+ loadEvalConfig(cwd),
1738
+ loadEvalDefinitions(cwd, evalFiles),
1739
+ ]);
1740
+ let started;
1741
+ try {
1742
+ const configuredMode = args.target ?? (args.baseUrl !== undefined ? "remote" : config.target?.kind);
1743
+ if (configuredMode !== "remote") {
1744
+ started = await startGeneratedAppServer("serve", {
1745
+ cwd,
1746
+ config: args.config,
1747
+ ...(args.packageScope === undefined ? {} : { packageScope: args.packageScope }),
1748
+ host: "127.0.0.1",
1749
+ port: 0,
1750
+ llm: args.llm,
1751
+ llmResponse: args.llmResponse,
1752
+ json: true,
1753
+ });
1754
+ }
1755
+ const target = targetFromArgs(args, config, started?.payload.url);
1756
+ const results = [];
1757
+ for (const definition of definitions) {
1758
+ results.push(...(await runEvalDefinition(definition, target)));
1759
+ }
1760
+ const failed = results.filter((result) => result.status === "failed");
1761
+ const report = {
1762
+ ok: failed.length === 0,
1763
+ target: {
1764
+ kind: target.kind,
1765
+ ...(target.kind === "remote" ? { baseUrl: target.baseUrl } : {}),
1766
+ },
1767
+ files: evalFiles.map((file) => path.relative(cwd, file)),
1768
+ evals: definitions.map((definition) => definition.id),
1769
+ total: results.length,
1770
+ passed: results.length - failed.length,
1771
+ failed: failed.length,
1772
+ results,
1773
+ };
1774
+ const artifact = await writeEvalReportArtifact(cwd, report);
1775
+ const reportWithArtifact = { ...report, artifact };
1776
+ process.stdout.write(args.json
1777
+ ? `${JSON.stringify(reportWithArtifact, null, 2)}\n`
1778
+ : `agentOS eval ${report.ok ? "passed" : "failed"}: ${report.passed}/${report.total} (${artifact})\n`);
1779
+ if (!report.ok)
1780
+ process.exitCode = 1;
1781
+ }
1782
+ finally {
1783
+ await started?.close();
1784
+ }
1785
+ };
1786
+ const printInfoHuman = (info) => {
1787
+ const lines = [
1788
+ "agentOS info",
1789
+ `profile: ${info.compile.profile}`,
1790
+ `target: ${info.compile.target}`,
1791
+ `agent: ${info.compile.agent.id}`,
1792
+ `deployment: ${info.compile.deployment.id} (${info.compile.deployment.backend}/${info.compile.deployment.adapter})`,
1793
+ `generated files: ${info.compile.generated.files.length}`,
1794
+ `resolve: ${info.resolve.status} - ${info.resolve.reason}`,
1795
+ `runtime: ${info.runtime.status} - ${info.runtime.reason}`,
1796
+ ];
1797
+ process.stdout.write(`${lines.join("\n")}\n`);
1798
+ };
1799
+ const main = async () => {
1800
+ const parsed = parseArgs(process.argv.slice(2));
1801
+ if (parsed.command === "help") {
1802
+ process.stdout.write(help);
1803
+ return;
1804
+ }
1805
+ if (parsed.command === "build") {
1806
+ const facts = await loadCompileFacts(parsed.args);
1807
+ await writeGeneratedFiles(facts.cwd, facts.linked.files);
1808
+ console.log(`generated ${facts.linked.files.length} agentOS files`);
1809
+ return;
1810
+ }
1811
+ if (parsed.command === "serve" || parsed.command === "dev") {
1812
+ await serveGeneratedApp(parsed.command, parsed.args);
1813
+ return;
1814
+ }
1815
+ if (parsed.command === "eval") {
1816
+ await runEval(parsed.args);
1817
+ return;
1818
+ }
1819
+ if (parsed.command === "preflight") {
1820
+ await runPreflightLlm(parsed.args);
1821
+ return;
1822
+ }
1823
+ const facts = await loadCompileFacts({ ...parsed.args, packageScope: undefined });
1824
+ const info = projectInfo(facts);
1825
+ if (parsed.args.json) {
1826
+ process.stdout.write(`${JSON.stringify(info, null, 2)}\n`);
1827
+ }
1828
+ else {
1829
+ printInfoHuman(info);
1830
+ }
257
1831
  };
258
1832
  try {
259
1833
  await main();
260
1834
  }
261
1835
  catch (error) {
262
- process.stderr.write(`agentos build: ${error instanceof Error ? error.message : String(error)}\n`);
1836
+ const command = process.argv[2];
1837
+ const prefix = command === "build"
1838
+ ? "agentos build"
1839
+ : command === "info"
1840
+ ? "agentos info"
1841
+ : command === "serve"
1842
+ ? "agentos serve"
1843
+ : command === "dev"
1844
+ ? "agentos dev"
1845
+ : command === "eval"
1846
+ ? "agentos eval"
1847
+ : command === "preflight"
1848
+ ? "agentos preflight"
1849
+ : "agentos";
1850
+ process.stderr.write(`${prefix}: ${error instanceof Error ? error.message : String(error)}\n`);
263
1851
  process.exitCode = 1;
264
1852
  }