agent-relay-server 0.8.1 → 0.10.0

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 (44) hide show
  1. package/README.md +12 -14
  2. package/package.json +18 -1
  3. package/public/index.html +979 -2575
  4. package/public/manifest.webmanifest +6 -6
  5. package/public/sw.js +16 -10
  6. package/recipes/code-review.yaml +26 -0
  7. package/recipes/debug.yaml +20 -0
  8. package/recipes/feature.yaml +26 -0
  9. package/recipes/refactor.yaml +20 -0
  10. package/recipes/test.yaml +20 -0
  11. package/runner/src/adapter.ts +69 -0
  12. package/runner/src/config.ts +144 -0
  13. package/scripts/orchestrator-spawn-smoke.ts +2 -9
  14. package/src/agent-spawn.ts +2 -94
  15. package/src/automations.ts +774 -0
  16. package/src/bus-outbox.ts +75 -0
  17. package/src/bus.ts +439 -0
  18. package/src/cli.ts +251 -5
  19. package/src/commands-db.ts +160 -0
  20. package/src/config.ts +2 -1
  21. package/src/connectors.ts +29 -9
  22. package/src/daemon.ts +1 -0
  23. package/src/db.ts +363 -36
  24. package/src/events.ts +33 -0
  25. package/src/index.ts +100 -5
  26. package/src/recipe-db.ts +163 -0
  27. package/src/recipe-loader.ts +100 -0
  28. package/src/recipe-runner.ts +206 -0
  29. package/src/recipe-validator.ts +85 -0
  30. package/src/routes.ts +661 -158
  31. package/src/security.ts +128 -2
  32. package/src/sse.ts +45 -28
  33. package/src/token-db.ts +96 -0
  34. package/src/types.ts +1 -488
  35. package/src/upgrade.ts +14 -28
  36. package/public/dashboard/actions.js +0 -819
  37. package/public/dashboard/api.js +0 -336
  38. package/public/dashboard/app.js +0 -34
  39. package/public/dashboard/charts.js +0 -128
  40. package/public/dashboard/computed.js +0 -693
  41. package/public/dashboard/constants.js +0 -28
  42. package/public/dashboard/display.js +0 -345
  43. package/public/dashboard/state.js +0 -129
  44. package/public/dashboard/utils.js +0 -207
@@ -0,0 +1,85 @@
1
+ import type { Recipe, RecipeAgent } from "./types";
2
+
3
+ class RecipeValidationError extends Error {}
4
+
5
+ export function validateRecipe(value: unknown): Recipe {
6
+ if (!isRecord(value)) throw new RecipeValidationError("recipe must be an object");
7
+ const name = requiredString(value.name, "name");
8
+ const description = requiredString(value.description, "description");
9
+ if (!Array.isArray(value.agents) || value.agents.length === 0) {
10
+ throw new RecipeValidationError("agents must be a non-empty array");
11
+ }
12
+ const agents = value.agents.map(validateAgent);
13
+ const roles = new Set<string>();
14
+ for (const agent of agents) {
15
+ if (roles.has(agent.role)) throw new RecipeValidationError(`duplicate recipe agent role: ${agent.role}`);
16
+ roles.add(agent.role);
17
+ }
18
+ return {
19
+ name,
20
+ description,
21
+ version: optionalString(value.version, "version"),
22
+ author: optionalString(value.author, "author"),
23
+ agents,
24
+ workflow: isRecord(value.workflow) ? {
25
+ trigger: optionalString(value.workflow.trigger, "workflow.trigger"),
26
+ fanOut: value.workflow.fanOut === "first" ? "first" : value.workflow.fanOut === "all" ? "all" : undefined,
27
+ collect: optionalString(value.workflow.collect, "workflow.collect"),
28
+ routing: undefined,
29
+ } : undefined,
30
+ lifecycle: isRecord(value.lifecycle) ? {
31
+ mode: value.lifecycle.mode === "persistent" ? "persistent" : "ephemeral",
32
+ idleTimeoutMs: optionalPositiveInteger(value.lifecycle.idleTimeoutMs, "lifecycle.idleTimeoutMs"),
33
+ } : undefined,
34
+ };
35
+ }
36
+
37
+ function validateAgent(value: unknown): RecipeAgent {
38
+ if (!isRecord(value)) throw new RecipeValidationError("agent must be an object");
39
+ const provider = value.provider;
40
+ if (provider !== "claude" && provider !== "codex") throw new RecipeValidationError("agent.provider must be claude or codex");
41
+ const capabilities = stringArray(value.capabilities, "agent.capabilities");
42
+ if (capabilities.length === 0) throw new RecipeValidationError("agent.capabilities must not be empty");
43
+ const approvalMode = value.approvalMode;
44
+ if (approvalMode !== undefined && approvalMode !== "open" && approvalMode !== "guarded" && approvalMode !== "read-only") {
45
+ throw new RecipeValidationError("agent.approvalMode must be open, guarded, or read-only");
46
+ }
47
+ return {
48
+ role: requiredString(value.role, "agent.role"),
49
+ provider,
50
+ count: optionalPositiveInteger(value.count, "agent.count"),
51
+ capabilities,
52
+ label: optionalString(value.label, "agent.label"),
53
+ tags: value.tags === undefined ? undefined : stringArray(value.tags, "agent.tags"),
54
+ approvalMode,
55
+ prompt: optionalString(value.prompt, "agent.prompt"),
56
+ model: optionalString(value.model, "agent.model"),
57
+ env: isRecord(value.env) ? Object.fromEntries(Object.entries(value.env).filter((entry): entry is [string, string] => typeof entry[1] === "string")) : undefined,
58
+ };
59
+ }
60
+
61
+ function requiredString(value: unknown, field: string): string {
62
+ if (typeof value !== "string" || !value.trim()) throw new RecipeValidationError(`${field} required`);
63
+ return value.trim();
64
+ }
65
+
66
+ function optionalString(value: unknown, field: string): string | undefined {
67
+ if (value === undefined || value === null) return undefined;
68
+ if (typeof value !== "string") throw new RecipeValidationError(`${field} must be a string`);
69
+ return value.trim() || undefined;
70
+ }
71
+
72
+ function stringArray(value: unknown, field: string): string[] {
73
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new RecipeValidationError(`${field} must be an array of strings`);
74
+ return value.map((item) => item.trim()).filter(Boolean);
75
+ }
76
+
77
+ function optionalPositiveInteger(value: unknown, field: string): number | undefined {
78
+ if (value === undefined || value === null) return undefined;
79
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new RecipeValidationError(`${field} must be a positive integer`);
80
+ return value;
81
+ }
82
+
83
+ function isRecord(value: unknown): value is Record<string, unknown> {
84
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
85
+ }