agent-relay 2.3.2 → 2.3.5
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/README.md +1 -1
- package/dist/index.cjs +1 -1
- package/dist/src/cli/index.js +124 -7
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +20 -26
- package/packages/acp-bridge/package.json +2 -2
- package/packages/bridge/package.json +7 -7
- package/packages/config/dist/cloud-config.d.ts +1 -1
- package/packages/config/dist/cloud-config.d.ts.map +1 -1
- package/packages/config/dist/cloud-config.js.map +1 -1
- package/packages/config/dist/schemas.d.ts +5 -5
- package/packages/config/dist/schemas.js +1 -1
- package/packages/config/dist/schemas.js.map +1 -1
- package/packages/config/package.json +2 -2
- package/packages/config/src/cloud-config.ts +2 -2
- package/packages/config/src/schemas.test.ts +48 -0
- package/packages/config/src/schemas.ts +1 -1
- package/packages/continuity/package.json +2 -2
- package/packages/daemon/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +5 -5
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/dist/index.d.ts +1 -29
- package/packages/sdk/dist/index.d.ts.map +1 -1
- package/packages/sdk/dist/index.js +1 -38
- package/packages/sdk/dist/index.js.map +1 -1
- package/packages/sdk/package.json +4 -25
- package/packages/sdk/src/index.ts +1 -69
- package/packages/sdk-py/README.md +56 -0
- package/packages/sdk-py/pyproject.toml +23 -0
- package/packages/sdk-py/src/agent_relay/__init__.py +27 -0
- package/packages/sdk-py/src/agent_relay/builder.py +367 -0
- package/packages/sdk-py/src/agent_relay/types.py +92 -0
- package/packages/sdk-py/tests/__init__.py +0 -0
- package/packages/sdk-py/tests/test_builder.py +101 -0
- package/packages/sdk-ts/dist/__tests__/facade.test.d.ts +2 -0
- package/packages/sdk-ts/dist/__tests__/facade.test.d.ts.map +1 -0
- package/packages/sdk-ts/dist/__tests__/facade.test.js +257 -0
- package/packages/sdk-ts/dist/__tests__/facade.test.js.map +1 -0
- package/packages/sdk-ts/dist/__tests__/unit.test.d.ts +2 -0
- package/packages/sdk-ts/dist/__tests__/unit.test.d.ts.map +1 -0
- package/packages/sdk-ts/dist/__tests__/unit.test.js +124 -0
- package/packages/sdk-ts/dist/__tests__/unit.test.js.map +1 -0
- package/packages/sdk-ts/dist/client.d.ts +2 -0
- package/packages/sdk-ts/dist/client.d.ts.map +1 -1
- package/packages/sdk-ts/dist/client.js +2 -0
- package/packages/sdk-ts/dist/client.js.map +1 -1
- package/packages/sdk-ts/dist/index.d.ts +1 -0
- package/packages/sdk-ts/dist/index.d.ts.map +1 -1
- package/packages/sdk-ts/dist/index.js +1 -0
- package/packages/sdk-ts/dist/index.js.map +1 -1
- package/packages/sdk-ts/dist/protocol.d.ts +1 -0
- package/packages/sdk-ts/dist/protocol.d.ts.map +1 -1
- package/packages/sdk-ts/dist/relay.d.ts +44 -0
- package/packages/sdk-ts/dist/relay.d.ts.map +1 -1
- package/packages/sdk-ts/dist/relay.js +89 -11
- package/packages/sdk-ts/dist/relay.js.map +1 -1
- package/packages/sdk-ts/dist/relaycast.js +2 -2
- package/packages/sdk-ts/dist/relaycast.js.map +1 -1
- package/packages/sdk-ts/dist/workflows/barrier.d.ts +72 -0
- package/packages/sdk-ts/dist/workflows/barrier.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/barrier.js +162 -0
- package/packages/sdk-ts/dist/workflows/barrier.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/builder.d.ts +101 -0
- package/packages/sdk-ts/dist/workflows/builder.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/builder.js +179 -0
- package/packages/sdk-ts/dist/workflows/builder.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/cli.d.ts +10 -0
- package/packages/sdk-ts/dist/workflows/cli.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/cli.js +82 -0
- package/packages/sdk-ts/dist/workflows/cli.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/coordinator.d.ts +68 -0
- package/packages/sdk-ts/dist/workflows/coordinator.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/coordinator.js +353 -0
- package/packages/sdk-ts/dist/workflows/coordinator.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/index.d.ts +10 -0
- package/packages/sdk-ts/dist/workflows/index.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/index.js +10 -0
- package/packages/sdk-ts/dist/workflows/index.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/memory-db.d.ts +17 -0
- package/packages/sdk-ts/dist/workflows/memory-db.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/memory-db.js +33 -0
- package/packages/sdk-ts/dist/workflows/memory-db.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/run.d.ts +31 -0
- package/packages/sdk-ts/dist/workflows/run.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/run.js +24 -0
- package/packages/sdk-ts/dist/workflows/run.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/runner.d.ts +119 -0
- package/packages/sdk-ts/dist/workflows/runner.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/runner.js +650 -0
- package/packages/sdk-ts/dist/workflows/runner.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/state.d.ts +77 -0
- package/packages/sdk-ts/dist/workflows/state.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/state.js +140 -0
- package/packages/sdk-ts/dist/workflows/state.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/templates.d.ts +47 -0
- package/packages/sdk-ts/dist/workflows/templates.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/templates.js +395 -0
- package/packages/sdk-ts/dist/workflows/templates.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/types.d.ts +126 -0
- package/packages/sdk-ts/dist/workflows/types.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/types.js +8 -0
- package/packages/sdk-ts/dist/workflows/types.js.map +1 -0
- package/packages/sdk-ts/package.json +9 -3
- package/packages/sdk-ts/src/__tests__/error-scenarios.test.ts +682 -0
- package/packages/sdk-ts/src/__tests__/facade.test.ts +296 -0
- package/packages/sdk-ts/src/__tests__/swarm-coordinator.test.ts +416 -0
- package/packages/sdk-ts/src/__tests__/unit.test.ts +152 -0
- package/packages/sdk-ts/src/__tests__/workflow-runner.test.ts +333 -0
- package/packages/sdk-ts/src/client.ts +4 -0
- package/packages/sdk-ts/src/index.ts +1 -0
- package/packages/sdk-ts/src/protocol.ts +1 -1
- package/packages/sdk-ts/src/relay.ts +112 -11
- package/packages/sdk-ts/src/relaycast.ts +2 -2
- package/packages/sdk-ts/src/workflows/README.md +450 -0
- package/packages/sdk-ts/src/workflows/barrier.ts +254 -0
- package/packages/sdk-ts/src/workflows/builder.ts +241 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/bug-fix.yaml +75 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/code-review.yaml +82 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/documentation.yaml +70 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/feature-dev.yaml +76 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/refactor.yaml +82 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/security-audit.yaml +84 -0
- package/packages/sdk-ts/src/workflows/cli.ts +93 -0
- package/packages/sdk-ts/src/workflows/coordinator.ts +520 -0
- package/packages/sdk-ts/src/workflows/index.ts +9 -0
- package/packages/sdk-ts/src/workflows/memory-db.ts +39 -0
- package/packages/sdk-ts/src/workflows/run.ts +47 -0
- package/packages/sdk-ts/src/workflows/runner.ts +873 -0
- package/packages/sdk-ts/src/workflows/schema.json +321 -0
- package/packages/sdk-ts/src/workflows/state.ts +279 -0
- package/packages/sdk-ts/src/workflows/templates.ts +544 -0
- package/packages/sdk-ts/src/workflows/types.ts +178 -0
- package/packages/sdk-ts/tsconfig.json +6 -1
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +3 -3
- package/packages/wrapper/package.json +5 -6
- package/scripts/postinstall.js +106 -2
- package/packages/api-types/.trajectories/active/traj_xbsvuzogscey.json +0 -15
- package/packages/api-types/.trajectories/index.json +0 -12
- package/packages/api-types/dist/index.d.ts +0 -21
- package/packages/api-types/dist/index.d.ts.map +0 -1
- package/packages/api-types/dist/index.js +0 -22
- package/packages/api-types/dist/index.js.map +0 -1
- package/packages/api-types/dist/schemas/agent.d.ts +0 -259
- package/packages/api-types/dist/schemas/agent.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/agent.js +0 -102
- package/packages/api-types/dist/schemas/agent.js.map +0 -1
- package/packages/api-types/dist/schemas/api.d.ts +0 -290
- package/packages/api-types/dist/schemas/api.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/api.js +0 -162
- package/packages/api-types/dist/schemas/api.js.map +0 -1
- package/packages/api-types/dist/schemas/decision.d.ts +0 -230
- package/packages/api-types/dist/schemas/decision.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/decision.js +0 -104
- package/packages/api-types/dist/schemas/decision.js.map +0 -1
- package/packages/api-types/dist/schemas/fleet.d.ts +0 -615
- package/packages/api-types/dist/schemas/fleet.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/fleet.js +0 -71
- package/packages/api-types/dist/schemas/fleet.js.map +0 -1
- package/packages/api-types/dist/schemas/history.d.ts +0 -180
- package/packages/api-types/dist/schemas/history.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/history.js +0 -72
- package/packages/api-types/dist/schemas/history.js.map +0 -1
- package/packages/api-types/dist/schemas/index.d.ts +0 -14
- package/packages/api-types/dist/schemas/index.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/index.js +0 -22
- package/packages/api-types/dist/schemas/index.js.map +0 -1
- package/packages/api-types/dist/schemas/message.d.ts +0 -456
- package/packages/api-types/dist/schemas/message.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/message.js +0 -88
- package/packages/api-types/dist/schemas/message.js.map +0 -1
- package/packages/api-types/dist/schemas/session.d.ts +0 -60
- package/packages/api-types/dist/schemas/session.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/session.js +0 -36
- package/packages/api-types/dist/schemas/session.js.map +0 -1
- package/packages/api-types/dist/schemas/task.d.ts +0 -111
- package/packages/api-types/dist/schemas/task.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/task.js +0 -64
- package/packages/api-types/dist/schemas/task.js.map +0 -1
- package/packages/api-types/package.json +0 -61
- package/packages/api-types/scripts/generate-openapi.ts +0 -106
- package/packages/api-types/src/index.ts +0 -22
- package/packages/api-types/src/schemas/agent.test.ts +0 -164
- package/packages/api-types/src/schemas/agent.ts +0 -110
- package/packages/api-types/src/schemas/api.test.ts +0 -372
- package/packages/api-types/src/schemas/api.ts +0 -194
- package/packages/api-types/src/schemas/decision.test.ts +0 -324
- package/packages/api-types/src/schemas/decision.ts +0 -136
- package/packages/api-types/src/schemas/fleet.test.ts +0 -212
- package/packages/api-types/src/schemas/fleet.ts +0 -83
- package/packages/api-types/src/schemas/history.test.ts +0 -242
- package/packages/api-types/src/schemas/history.ts +0 -84
- package/packages/api-types/src/schemas/index.ts +0 -148
- package/packages/api-types/src/schemas/message.test.ts +0 -192
- package/packages/api-types/src/schemas/message.ts +0 -98
- package/packages/api-types/src/schemas/session.test.ts +0 -104
- package/packages/api-types/src/schemas/session.ts +0 -40
- package/packages/api-types/src/schemas/task.test.ts +0 -192
- package/packages/api-types/src/schemas/task.ts +0 -78
- package/packages/api-types/tsconfig.json +0 -19
- package/packages/api-types/vitest.config.ts +0 -9
- package/packages/benchmark/README.md +0 -200
- package/packages/benchmark/datasets/coding-tasks.yaml +0 -127
- package/packages/benchmark/datasets/coordination-tasks.yaml +0 -122
- package/packages/benchmark/datasets/quick-test.yaml +0 -20
- package/packages/benchmark/dist/benchmark.d.ts +0 -47
- package/packages/benchmark/dist/benchmark.d.ts.map +0 -1
- package/packages/benchmark/dist/benchmark.js +0 -224
- package/packages/benchmark/dist/benchmark.js.map +0 -1
- package/packages/benchmark/dist/cli.d.ts +0 -8
- package/packages/benchmark/dist/cli.d.ts.map +0 -1
- package/packages/benchmark/dist/cli.js +0 -185
- package/packages/benchmark/dist/cli.js.map +0 -1
- package/packages/benchmark/dist/harbor.d.ts +0 -53
- package/packages/benchmark/dist/harbor.d.ts.map +0 -1
- package/packages/benchmark/dist/harbor.js +0 -127
- package/packages/benchmark/dist/harbor.js.map +0 -1
- package/packages/benchmark/dist/index.d.ts +0 -48
- package/packages/benchmark/dist/index.d.ts.map +0 -1
- package/packages/benchmark/dist/index.js +0 -50
- package/packages/benchmark/dist/index.js.map +0 -1
- package/packages/benchmark/dist/runners/base.d.ts +0 -63
- package/packages/benchmark/dist/runners/base.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/base.js +0 -156
- package/packages/benchmark/dist/runners/base.js.map +0 -1
- package/packages/benchmark/dist/runners/index.d.ts +0 -10
- package/packages/benchmark/dist/runners/index.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/index.js +0 -10
- package/packages/benchmark/dist/runners/index.js.map +0 -1
- package/packages/benchmark/dist/runners/single.d.ts +0 -19
- package/packages/benchmark/dist/runners/single.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/single.js +0 -111
- package/packages/benchmark/dist/runners/single.js.map +0 -1
- package/packages/benchmark/dist/runners/subagent.d.ts +0 -32
- package/packages/benchmark/dist/runners/subagent.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/subagent.js +0 -212
- package/packages/benchmark/dist/runners/subagent.js.map +0 -1
- package/packages/benchmark/dist/runners/swarm.d.ts +0 -36
- package/packages/benchmark/dist/runners/swarm.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/swarm.js +0 -273
- package/packages/benchmark/dist/runners/swarm.js.map +0 -1
- package/packages/benchmark/dist/types.d.ts +0 -178
- package/packages/benchmark/dist/types.d.ts.map +0 -1
- package/packages/benchmark/dist/types.js +0 -16
- package/packages/benchmark/dist/types.js.map +0 -1
- package/packages/benchmark/package.json +0 -80
- package/packages/benchmark/src/benchmark.ts +0 -298
- package/packages/benchmark/src/cli.ts +0 -240
- package/packages/benchmark/src/harbor.ts +0 -170
- package/packages/benchmark/src/index.ts +0 -73
- package/packages/benchmark/src/runners/base.ts +0 -205
- package/packages/benchmark/src/runners/index.ts +0 -10
- package/packages/benchmark/src/runners/single.ts +0 -121
- package/packages/benchmark/src/runners/subagent.ts +0 -240
- package/packages/benchmark/src/runners/swarm.ts +0 -326
- package/packages/benchmark/src/types.ts +0 -205
- package/packages/benchmark/tsconfig.json +0 -20
- package/packages/cli-tester/README.md +0 -277
- package/packages/cli-tester/dist/index.d.ts +0 -21
- package/packages/cli-tester/dist/index.d.ts.map +0 -1
- package/packages/cli-tester/dist/index.js +0 -21
- package/packages/cli-tester/dist/index.js.map +0 -1
- package/packages/cli-tester/dist/utils/credential-check.d.ts +0 -56
- package/packages/cli-tester/dist/utils/credential-check.d.ts.map +0 -1
- package/packages/cli-tester/dist/utils/credential-check.js +0 -230
- package/packages/cli-tester/dist/utils/credential-check.js.map +0 -1
- package/packages/cli-tester/dist/utils/socket-client.d.ts +0 -76
- package/packages/cli-tester/dist/utils/socket-client.d.ts.map +0 -1
- package/packages/cli-tester/dist/utils/socket-client.js +0 -153
- package/packages/cli-tester/dist/utils/socket-client.js.map +0 -1
- package/packages/cli-tester/docker/Dockerfile +0 -61
- package/packages/cli-tester/docker/docker-compose.yml +0 -71
- package/packages/cli-tester/docker/entrypoint.sh +0 -58
- package/packages/cli-tester/package.json +0 -32
- package/packages/cli-tester/scripts/clear-auth.sh +0 -101
- package/packages/cli-tester/scripts/inject-message.sh +0 -42
- package/packages/cli-tester/scripts/start.sh +0 -71
- package/packages/cli-tester/scripts/test-cli.sh +0 -56
- package/packages/cli-tester/scripts/test-full-spawn.sh +0 -238
- package/packages/cli-tester/scripts/test-registration.sh +0 -182
- package/packages/cli-tester/scripts/test-setup-flow.sh +0 -202
- package/packages/cli-tester/scripts/test-spawn.sh +0 -140
- package/packages/cli-tester/scripts/test-with-daemon.sh +0 -247
- package/packages/cli-tester/scripts/verify-auth.sh +0 -112
- package/packages/cli-tester/src/index.ts +0 -40
- package/packages/cli-tester/src/utils/credential-check.ts +0 -284
- package/packages/cli-tester/src/utils/socket-client.ts +0 -211
- package/packages/cli-tester/tests/credential-check.test.ts +0 -56
- package/packages/cli-tester/tsconfig.json +0 -11
- package/packages/sdk/dist/browser-client.d.ts +0 -212
- package/packages/sdk/dist/browser-client.d.ts.map +0 -1
- package/packages/sdk/dist/browser-client.js +0 -750
- package/packages/sdk/dist/browser-client.js.map +0 -1
- package/packages/sdk/dist/browser-framing.d.ts +0 -46
- package/packages/sdk/dist/browser-framing.d.ts.map +0 -1
- package/packages/sdk/dist/browser-framing.js +0 -122
- package/packages/sdk/dist/browser-framing.js.map +0 -1
- package/packages/sdk/dist/standalone.d.ts +0 -89
- package/packages/sdk/dist/standalone.d.ts.map +0 -1
- package/packages/sdk/dist/standalone.js +0 -131
- package/packages/sdk/dist/standalone.js.map +0 -1
- package/packages/sdk/dist/transports/index.d.ts +0 -92
- package/packages/sdk/dist/transports/index.d.ts.map +0 -1
- package/packages/sdk/dist/transports/index.js +0 -129
- package/packages/sdk/dist/transports/index.js.map +0 -1
- package/packages/sdk/dist/transports/socket-transport.d.ts +0 -30
- package/packages/sdk/dist/transports/socket-transport.d.ts.map +0 -1
- package/packages/sdk/dist/transports/socket-transport.js +0 -94
- package/packages/sdk/dist/transports/socket-transport.js.map +0 -1
- package/packages/sdk/dist/transports/types.d.ts +0 -69
- package/packages/sdk/dist/transports/types.d.ts.map +0 -1
- package/packages/sdk/dist/transports/types.js +0 -10
- package/packages/sdk/dist/transports/types.js.map +0 -1
- package/packages/sdk/dist/transports/websocket-transport.d.ts +0 -55
- package/packages/sdk/dist/transports/websocket-transport.d.ts.map +0 -1
- package/packages/sdk/dist/transports/websocket-transport.js +0 -180
- package/packages/sdk/dist/transports/websocket-transport.js.map +0 -1
- package/packages/sdk/src/browser-client.ts +0 -985
- package/packages/sdk/src/browser-framing.test.ts +0 -115
- package/packages/sdk/src/browser-framing.ts +0 -150
- package/packages/sdk/src/standalone.ts +0 -183
- package/packages/sdk/src/transports/index.ts +0 -197
- package/packages/sdk/src/transports/socket-transport.ts +0 -115
- package/packages/sdk/src/transports/types.ts +0 -77
- package/packages/sdk/src/transports/websocket-transport.ts +0 -245
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
import type { RelayYamlConfig } from './types.js';
|
|
6
|
+
|
|
7
|
+
const YAML_EXTENSIONS = ['.yaml', '.yml'] as const;
|
|
8
|
+
|
|
9
|
+
export const BUILT_IN_TEMPLATE_NAMES = [
|
|
10
|
+
'feature-dev',
|
|
11
|
+
'bug-fix',
|
|
12
|
+
'code-review',
|
|
13
|
+
'security-audit',
|
|
14
|
+
'refactor',
|
|
15
|
+
'documentation',
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type BuiltInTemplateName = (typeof BUILT_IN_TEMPLATE_NAMES)[number];
|
|
19
|
+
|
|
20
|
+
export interface TemplateRegistryOptions {
|
|
21
|
+
builtInTemplatesDir?: string;
|
|
22
|
+
customTemplatesDir?: string;
|
|
23
|
+
workspaceDir?: string;
|
|
24
|
+
fetcher?: typeof fetch;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LoadTemplateOptions {
|
|
28
|
+
overrides?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TemplateShorthandConfig {
|
|
32
|
+
swarm: string;
|
|
33
|
+
overrides?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type TemplateReferenceInput =
|
|
37
|
+
| string
|
|
38
|
+
| RelayYamlConfig
|
|
39
|
+
| (Partial<Omit<RelayYamlConfig, 'swarm'>> & TemplateShorthandConfig);
|
|
40
|
+
|
|
41
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
42
|
+
return typeof value === 'object' && value !== null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasYamlExtension(fileName: string): boolean {
|
|
46
|
+
return YAML_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class TemplateRegistry {
|
|
50
|
+
private readonly builtInTemplatesDir: string;
|
|
51
|
+
private readonly customTemplatesDir: string;
|
|
52
|
+
private readonly fetcher: typeof fetch;
|
|
53
|
+
|
|
54
|
+
constructor(options: TemplateRegistryOptions = {}) {
|
|
55
|
+
this.builtInTemplatesDir = this.resolveBuiltInTemplatesDir(options.builtInTemplatesDir);
|
|
56
|
+
this.customTemplatesDir = options.customTemplatesDir
|
|
57
|
+
? path.resolve(options.customTemplatesDir)
|
|
58
|
+
: path.resolve(options.workspaceDir ?? process.cwd(), '.relay/workflows');
|
|
59
|
+
|
|
60
|
+
this.fetcher = options.fetcher ?? fetch;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
listBuiltInTemplates(): string[] {
|
|
64
|
+
return [...BUILT_IN_TEMPLATE_NAMES];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async listCustomTemplates(): Promise<string[]> {
|
|
68
|
+
const files = await this.safeReadDir(this.customTemplatesDir);
|
|
69
|
+
return files
|
|
70
|
+
.filter((fileName) => hasYamlExtension(fileName))
|
|
71
|
+
.map((fileName) => this.normalizeTemplateName(fileName))
|
|
72
|
+
.sort();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async listTemplates(): Promise<string[]> {
|
|
76
|
+
const custom = await this.listCustomTemplates();
|
|
77
|
+
const merged = new Set<string>([...BUILT_IN_TEMPLATE_NAMES, ...custom]);
|
|
78
|
+
return Array.from(merged).sort();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async hasTemplate(name: string): Promise<boolean> {
|
|
82
|
+
try {
|
|
83
|
+
await this.resolveTemplatePath(name);
|
|
84
|
+
return true;
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async loadTemplate(
|
|
91
|
+
name: string,
|
|
92
|
+
options: LoadTemplateOptions = {}
|
|
93
|
+
): Promise<RelayYamlConfig> {
|
|
94
|
+
const templatePath = await this.resolveTemplatePath(name);
|
|
95
|
+
const template = await this.readTemplateFile(templatePath);
|
|
96
|
+
|
|
97
|
+
if (options.overrides && Object.keys(options.overrides).length > 0) {
|
|
98
|
+
return this.applyOverrides(template, options.overrides);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return template;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async resolveTemplateReference(
|
|
105
|
+
input: TemplateReferenceInput,
|
|
106
|
+
options: LoadTemplateOptions = {}
|
|
107
|
+
): Promise<RelayYamlConfig> {
|
|
108
|
+
if (typeof input === 'string') {
|
|
109
|
+
return this.loadTemplate(input, options);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (this.isTemplateShorthand(input)) {
|
|
113
|
+
const { swarm, overrides = {}, ...rest } = input;
|
|
114
|
+
const mergedOverrides = {
|
|
115
|
+
...overrides,
|
|
116
|
+
...(options.overrides ?? {}),
|
|
117
|
+
};
|
|
118
|
+
const baseTemplate = await this.loadTemplate(swarm, {
|
|
119
|
+
overrides: mergedOverrides,
|
|
120
|
+
});
|
|
121
|
+
return this.mergeRelayConfig(baseTemplate, rest);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const config = this.cloneValue(input as RelayYamlConfig);
|
|
125
|
+
|
|
126
|
+
if (options.overrides && Object.keys(options.overrides).length > 0) {
|
|
127
|
+
return this.applyOverrides(config, options.overrides);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return config;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
applyOverrides(
|
|
134
|
+
config: RelayYamlConfig,
|
|
135
|
+
overrides: Record<string, unknown>
|
|
136
|
+
): RelayYamlConfig {
|
|
137
|
+
const nextConfig = this.cloneValue(config);
|
|
138
|
+
|
|
139
|
+
for (const [overridePath, value] of Object.entries(overrides)) {
|
|
140
|
+
this.setOverride(nextConfig, overridePath, value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return nextConfig;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async installExternalTemplate(url: string, name?: string): Promise<string> {
|
|
147
|
+
const response = await this.fetcher(url);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`Failed to fetch template from ${url}: ${response.status} ${response.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const raw = await response.text();
|
|
153
|
+
const parsed = parseYaml(raw);
|
|
154
|
+
|
|
155
|
+
if (!isRecord(parsed)) {
|
|
156
|
+
throw new Error(`Template from ${url} is not a YAML object`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const inferredName = typeof parsed.name === 'string' ? parsed.name : '';
|
|
160
|
+
const templateName = this.normalizeTemplateName(name ?? inferredName);
|
|
161
|
+
|
|
162
|
+
if (!templateName) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
'Template name is required. Provide name explicitly or include a string "name" field.'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (templateName.includes('/') || templateName.includes('\\') || templateName.includes('..') || path.isAbsolute(templateName)) {
|
|
169
|
+
throw new Error(`Invalid template name: "${templateName}" contains path separators or traversal sequences`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.validateRelayConfig(parsed, url);
|
|
173
|
+
|
|
174
|
+
await fs.mkdir(this.customTemplatesDir, { recursive: true });
|
|
175
|
+
const targetPath = path.join(this.customTemplatesDir, `${templateName}.yaml`);
|
|
176
|
+
await fs.writeFile(targetPath, stringifyYaml(parsed), 'utf-8');
|
|
177
|
+
return targetPath;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private isTemplateShorthand(
|
|
181
|
+
input: TemplateReferenceInput
|
|
182
|
+
): input is Partial<Omit<RelayYamlConfig, 'swarm'>> & TemplateShorthandConfig {
|
|
183
|
+
return isRecord(input) && typeof input.swarm === 'string';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private mergeRelayConfig(
|
|
187
|
+
base: RelayYamlConfig,
|
|
188
|
+
patch: Partial<Omit<RelayYamlConfig, 'swarm'>>
|
|
189
|
+
): RelayYamlConfig {
|
|
190
|
+
const merged = this.cloneValue(base);
|
|
191
|
+
|
|
192
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
193
|
+
if (value === undefined) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
(merged as unknown as Record<string, unknown>)[key] = this.cloneValue(value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return merged;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private normalizeTemplateName(name: string): string {
|
|
203
|
+
return name.replace(/\.ya?ml$/i, '').trim();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private resolveBuiltInTemplatesDir(explicitDir?: string): string {
|
|
207
|
+
if (explicitDir) {
|
|
208
|
+
return path.resolve(explicitDir);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
212
|
+
const candidates = [
|
|
213
|
+
path.resolve(currentDir, 'builtin-templates'),
|
|
214
|
+
path.resolve(currentDir, '../workflows/builtin-templates'),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
for (const candidate of candidates) {
|
|
218
|
+
if (existsSync(candidate)) {
|
|
219
|
+
return candidate;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return candidates[0];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async resolveTemplatePath(name: string): Promise<string> {
|
|
227
|
+
const normalizedName = this.normalizeTemplateName(name);
|
|
228
|
+
|
|
229
|
+
const customPath = await this.findTemplatePath(this.customTemplatesDir, normalizedName);
|
|
230
|
+
if (customPath) {
|
|
231
|
+
return customPath;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const builtInPath = await this.findTemplatePath(this.builtInTemplatesDir, normalizedName);
|
|
235
|
+
if (builtInPath) {
|
|
236
|
+
return builtInPath;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
throw new Error(`Template not found: ${name}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async findTemplatePath(
|
|
243
|
+
directory: string,
|
|
244
|
+
templateName: string
|
|
245
|
+
): Promise<string | undefined> {
|
|
246
|
+
for (const ext of YAML_EXTENSIONS) {
|
|
247
|
+
const candidate = path.join(directory, `${templateName}${ext}`);
|
|
248
|
+
try {
|
|
249
|
+
const stat = await fs.stat(candidate);
|
|
250
|
+
if (stat.isFile()) {
|
|
251
|
+
return candidate;
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
// Continue checking other extensions.
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async readTemplateFile(templatePath: string): Promise<RelayYamlConfig> {
|
|
262
|
+
const raw = await fs.readFile(templatePath, 'utf-8');
|
|
263
|
+
const parsed = parseYaml(raw);
|
|
264
|
+
|
|
265
|
+
if (!isRecord(parsed)) {
|
|
266
|
+
throw new Error(`Template at ${templatePath} is not a YAML object`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const normalized = this.normalizeLegacyTemplate(parsed);
|
|
270
|
+
this.validateRelayConfig(normalized, templatePath);
|
|
271
|
+
return normalized;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private normalizeLegacyTemplate(rawTemplate: Record<string, unknown>): Record<string, unknown> {
|
|
275
|
+
const normalized = this.cloneValue(rawTemplate);
|
|
276
|
+
|
|
277
|
+
if (!isRecord(normalized.swarm) && typeof normalized.pattern === 'string') {
|
|
278
|
+
normalized.swarm = { pattern: normalized.pattern };
|
|
279
|
+
delete normalized.pattern;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (Array.isArray(normalized.agents)) {
|
|
283
|
+
normalized.agents = normalized.agents.map((agent) => {
|
|
284
|
+
if (!isRecord(agent)) {
|
|
285
|
+
return agent;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (typeof agent.name !== 'string' && typeof agent.id === 'string') {
|
|
289
|
+
return { ...agent, name: agent.id };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return agent;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!Array.isArray(normalized.workflows) && isRecord(normalized.workflow)) {
|
|
297
|
+
const workflowName = typeof normalized.name === 'string'
|
|
298
|
+
? `${normalized.name}-workflow`
|
|
299
|
+
: 'default-workflow';
|
|
300
|
+
|
|
301
|
+
const workflow = normalized.workflow;
|
|
302
|
+
const steps = Array.isArray(workflow.steps)
|
|
303
|
+
? workflow.steps.map((step) => this.normalizeLegacyStep(step)).filter((step) => step !== null)
|
|
304
|
+
: [];
|
|
305
|
+
|
|
306
|
+
normalized.workflows = [
|
|
307
|
+
{
|
|
308
|
+
name: workflowName,
|
|
309
|
+
description: typeof workflow.description === 'string' ? workflow.description : undefined,
|
|
310
|
+
onError: typeof workflow.onError === 'string' ? workflow.onError : undefined,
|
|
311
|
+
steps,
|
|
312
|
+
},
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
delete normalized.workflow;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return normalized;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private normalizeLegacyStep(step: unknown): Record<string, unknown> | null {
|
|
322
|
+
if (!isRecord(step)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const name = typeof step.name === 'string'
|
|
327
|
+
? step.name
|
|
328
|
+
: typeof step.id === 'string'
|
|
329
|
+
? step.id
|
|
330
|
+
: undefined;
|
|
331
|
+
|
|
332
|
+
const task = typeof step.task === 'string'
|
|
333
|
+
? step.task
|
|
334
|
+
: typeof step.prompt === 'string'
|
|
335
|
+
? step.prompt
|
|
336
|
+
: undefined;
|
|
337
|
+
|
|
338
|
+
if (!name || typeof step.agent !== 'string' || !task) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const normalized: Record<string, unknown> = {
|
|
343
|
+
name,
|
|
344
|
+
agent: step.agent,
|
|
345
|
+
task,
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
if (Array.isArray(step.dependsOn)) {
|
|
349
|
+
normalized.dependsOn = step.dependsOn;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (typeof step.timeoutMs === 'number') {
|
|
353
|
+
normalized.timeoutMs = step.timeoutMs;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (typeof step.retries === 'number') {
|
|
357
|
+
normalized.retries = step.retries;
|
|
358
|
+
} else if (typeof step.maxRetries === 'number') {
|
|
359
|
+
normalized.retries = step.maxRetries;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (isRecord(step.verification)) {
|
|
363
|
+
normalized.verification = step.verification;
|
|
364
|
+
} else if (typeof step.expects === 'string') {
|
|
365
|
+
normalized.verification = {
|
|
366
|
+
type: 'output_contains',
|
|
367
|
+
value: step.expects,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return normalized;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private validateRelayConfig(rawConfig: unknown, source: string): asserts rawConfig is RelayYamlConfig {
|
|
375
|
+
if (!isRecord(rawConfig)) {
|
|
376
|
+
throw new Error(`Template at ${source} is not an object`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (typeof rawConfig.version !== 'string') {
|
|
380
|
+
throw new Error(`Template at ${source} is missing required string field: version`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (typeof rawConfig.name !== 'string') {
|
|
384
|
+
throw new Error(`Template at ${source} is missing required string field: name`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (!isRecord(rawConfig.swarm) || typeof rawConfig.swarm.pattern !== 'string') {
|
|
388
|
+
throw new Error(`Template at ${source} is missing required field: swarm.pattern`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (!Array.isArray(rawConfig.agents) || rawConfig.agents.length === 0) {
|
|
392
|
+
throw new Error(`Template at ${source} must include a non-empty agents array`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
for (const agent of rawConfig.agents) {
|
|
396
|
+
if (!isRecord(agent) || typeof agent.name !== 'string' || typeof agent.cli !== 'string') {
|
|
397
|
+
throw new Error(`Template at ${source} contains an invalid agent definition`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (rawConfig.workflows !== undefined) {
|
|
402
|
+
if (!Array.isArray(rawConfig.workflows)) {
|
|
403
|
+
throw new Error(`Template at ${source} has invalid workflows; expected an array`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const workflow of rawConfig.workflows) {
|
|
407
|
+
if (!isRecord(workflow) || typeof workflow.name !== 'string' || !Array.isArray(workflow.steps)) {
|
|
408
|
+
throw new Error(`Template at ${source} contains an invalid workflow definition`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for (const step of workflow.steps) {
|
|
412
|
+
if (
|
|
413
|
+
!isRecord(step) ||
|
|
414
|
+
typeof step.name !== 'string' ||
|
|
415
|
+
typeof step.agent !== 'string' ||
|
|
416
|
+
typeof step.task !== 'string'
|
|
417
|
+
) {
|
|
418
|
+
throw new Error(`Template at ${source} contains an invalid workflow step`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private setOverride(
|
|
426
|
+
config: RelayYamlConfig,
|
|
427
|
+
overridePath: string,
|
|
428
|
+
value: unknown
|
|
429
|
+
): void {
|
|
430
|
+
const pathParts = overridePath
|
|
431
|
+
.replace(/\[(\d+)\]/g, '.$1')
|
|
432
|
+
.split('.')
|
|
433
|
+
.map((part) => part.trim())
|
|
434
|
+
.filter(Boolean);
|
|
435
|
+
|
|
436
|
+
if (pathParts.length === 0) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (pathParts[0] === 'steps') {
|
|
441
|
+
const workflow = config.workflows?.[0];
|
|
442
|
+
if (!workflow) {
|
|
443
|
+
throw new Error(`Cannot apply override "${overridePath}": workflows[0] is missing`);
|
|
444
|
+
}
|
|
445
|
+
this.setOnValue(workflow.steps as unknown, pathParts.slice(1), value, overridePath);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (pathParts[0] === 'workflow' && pathParts[1] === 'steps') {
|
|
450
|
+
const workflow = config.workflows?.[0];
|
|
451
|
+
if (!workflow) {
|
|
452
|
+
throw new Error(`Cannot apply override "${overridePath}": workflows[0] is missing`);
|
|
453
|
+
}
|
|
454
|
+
this.setOnValue(workflow.steps as unknown, pathParts.slice(2), value, overridePath);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this.setOnValue(config as unknown, pathParts, value, overridePath);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private setOnValue(
|
|
462
|
+
target: unknown,
|
|
463
|
+
pathParts: string[],
|
|
464
|
+
value: unknown,
|
|
465
|
+
fullPath: string
|
|
466
|
+
): void {
|
|
467
|
+
if (pathParts.length === 0) {
|
|
468
|
+
throw new Error(`Invalid override path: ${fullPath}`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let current: unknown = target;
|
|
472
|
+
|
|
473
|
+
for (let i = 0; i < pathParts.length - 1; i += 1) {
|
|
474
|
+
const part = pathParts[i];
|
|
475
|
+
const nextPart = pathParts[i + 1];
|
|
476
|
+
|
|
477
|
+
if (Array.isArray(current)) {
|
|
478
|
+
const index = this.resolveArrayItemIndex(current, part);
|
|
479
|
+
if (index < 0) {
|
|
480
|
+
throw new Error(`Cannot apply override "${fullPath}": array item "${part}" was not found`);
|
|
481
|
+
}
|
|
482
|
+
current = current[index];
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!isRecord(current)) {
|
|
487
|
+
throw new Error(`Cannot apply override "${fullPath}": segment "${part}" is not an object`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (!(part in current) || current[part] === undefined || current[part] === null) {
|
|
491
|
+
current[part] = /^\d+$/.test(nextPart) ? [] : {};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
current = current[part];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const finalPart = pathParts[pathParts.length - 1];
|
|
498
|
+
|
|
499
|
+
if (Array.isArray(current)) {
|
|
500
|
+
const index = this.resolveArrayItemIndex(current, finalPart);
|
|
501
|
+
if (index < 0) {
|
|
502
|
+
throw new Error(`Cannot apply override "${fullPath}": array item "${finalPart}" was not found`);
|
|
503
|
+
}
|
|
504
|
+
current[index] = value;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (!isRecord(current)) {
|
|
509
|
+
throw new Error(`Cannot apply override "${fullPath}": parent object is invalid`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
current[finalPart] = value;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private resolveArrayItemIndex(items: unknown[], segment: string): number {
|
|
516
|
+
if (/^\d+$/.test(segment)) {
|
|
517
|
+
const index = Number.parseInt(segment, 10);
|
|
518
|
+
return index >= 0 && index < items.length ? index : -1;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return items.findIndex(
|
|
522
|
+
(item) =>
|
|
523
|
+
isRecord(item) &&
|
|
524
|
+
((typeof item.name === 'string' && item.name === segment) ||
|
|
525
|
+
(typeof item.id === 'string' && item.id === segment))
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private async safeReadDir(directory: string): Promise<string[]> {
|
|
530
|
+
try {
|
|
531
|
+
return await fs.readdir(directory);
|
|
532
|
+
} catch {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private cloneValue<T>(value: T): T {
|
|
538
|
+
if (typeof structuredClone === 'function') {
|
|
539
|
+
return structuredClone(value);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Types for Relay Cloud Swarm Patterns
|
|
3
|
+
*
|
|
4
|
+
* Shared TypeScript types for relay.yaml configuration, workflow execution,
|
|
5
|
+
* and database row representations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ── relay.yaml top-level config ─────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
/** Top-level relay.yaml configuration file structure. */
|
|
11
|
+
export interface RelayYamlConfig {
|
|
12
|
+
version: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
swarm: SwarmConfig;
|
|
16
|
+
agents: AgentDefinition[];
|
|
17
|
+
workflows?: WorkflowDefinition[];
|
|
18
|
+
coordination?: CoordinationConfig;
|
|
19
|
+
state?: StateConfig;
|
|
20
|
+
errorHandling?: ErrorHandlingConfig;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Swarm configuration ─────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/** Swarm-level settings controlling the overall pattern. */
|
|
26
|
+
export interface SwarmConfig {
|
|
27
|
+
pattern: SwarmPattern;
|
|
28
|
+
maxConcurrency?: number;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
channel?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type SwarmPattern =
|
|
34
|
+
| "fan-out"
|
|
35
|
+
| "pipeline"
|
|
36
|
+
| "hub-spoke"
|
|
37
|
+
| "consensus"
|
|
38
|
+
| "mesh"
|
|
39
|
+
| "handoff"
|
|
40
|
+
| "cascade"
|
|
41
|
+
| "dag"
|
|
42
|
+
| "debate"
|
|
43
|
+
| "hierarchical";
|
|
44
|
+
|
|
45
|
+
// ── Agent definitions ───────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/** Definition of an agent participating in a workflow. */
|
|
48
|
+
export interface AgentDefinition {
|
|
49
|
+
name: string;
|
|
50
|
+
cli: AgentCli;
|
|
51
|
+
role?: string;
|
|
52
|
+
task?: string;
|
|
53
|
+
channels?: string[];
|
|
54
|
+
constraints?: AgentConstraints;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type AgentCli = "claude" | "codex" | "gemini" | "aider" | "goose";
|
|
58
|
+
|
|
59
|
+
/** Resource and behavioral constraints for an agent. */
|
|
60
|
+
export interface AgentConstraints {
|
|
61
|
+
maxTokens?: number;
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
retries?: number;
|
|
64
|
+
model?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Workflow definitions ────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** A named workflow composed of sequential or parallel steps. */
|
|
70
|
+
export interface WorkflowDefinition {
|
|
71
|
+
name: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
steps: WorkflowStep[];
|
|
74
|
+
onError?: "fail" | "skip" | "retry";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A single step within a workflow. */
|
|
78
|
+
export interface WorkflowStep {
|
|
79
|
+
name: string;
|
|
80
|
+
agent: string;
|
|
81
|
+
task: string;
|
|
82
|
+
dependsOn?: string[];
|
|
83
|
+
verification?: VerificationCheck;
|
|
84
|
+
timeoutMs?: number;
|
|
85
|
+
retries?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Verification check to validate a step's output. */
|
|
89
|
+
export interface VerificationCheck {
|
|
90
|
+
type: "output_contains" | "exit_code" | "file_exists" | "custom";
|
|
91
|
+
value: string;
|
|
92
|
+
description?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Coordination ────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/** Coordination settings for multi-agent synchronization. */
|
|
98
|
+
export interface CoordinationConfig {
|
|
99
|
+
barriers?: Barrier[];
|
|
100
|
+
votingThreshold?: number;
|
|
101
|
+
consensusStrategy?: "majority" | "unanimous" | "quorum";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A synchronization barrier that gates downstream work. */
|
|
105
|
+
export interface Barrier {
|
|
106
|
+
name: string;
|
|
107
|
+
waitFor: string[];
|
|
108
|
+
timeoutMs?: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── State management ────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/** Shared state configuration for workflows. */
|
|
114
|
+
export interface StateConfig {
|
|
115
|
+
backend: "memory" | "redis" | "database";
|
|
116
|
+
ttlMs?: number;
|
|
117
|
+
namespace?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ── Error handling ──────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
/** Global error handling configuration. */
|
|
123
|
+
export interface ErrorHandlingConfig {
|
|
124
|
+
strategy: "fail-fast" | "continue" | "retry";
|
|
125
|
+
maxRetries?: number;
|
|
126
|
+
retryDelayMs?: number;
|
|
127
|
+
notifyChannel?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Database row types ──────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
export type WorkflowRunStatus =
|
|
133
|
+
| "pending"
|
|
134
|
+
| "running"
|
|
135
|
+
| "completed"
|
|
136
|
+
| "failed"
|
|
137
|
+
| "cancelled";
|
|
138
|
+
|
|
139
|
+
/** Database row representing a workflow run. */
|
|
140
|
+
export interface WorkflowRunRow {
|
|
141
|
+
id: string;
|
|
142
|
+
workspaceId: string;
|
|
143
|
+
workflowName: string;
|
|
144
|
+
pattern: SwarmPattern;
|
|
145
|
+
status: WorkflowRunStatus;
|
|
146
|
+
config: RelayYamlConfig;
|
|
147
|
+
stateSnapshot?: Record<string, unknown>;
|
|
148
|
+
startedAt: string;
|
|
149
|
+
completedAt?: string;
|
|
150
|
+
error?: string;
|
|
151
|
+
createdAt: string;
|
|
152
|
+
updatedAt: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type WorkflowStepStatus =
|
|
156
|
+
| "pending"
|
|
157
|
+
| "running"
|
|
158
|
+
| "completed"
|
|
159
|
+
| "failed"
|
|
160
|
+
| "skipped";
|
|
161
|
+
|
|
162
|
+
/** Database row representing a single workflow step execution. */
|
|
163
|
+
export interface WorkflowStepRow {
|
|
164
|
+
id: string;
|
|
165
|
+
runId: string;
|
|
166
|
+
stepName: string;
|
|
167
|
+
agentName: string;
|
|
168
|
+
status: WorkflowStepStatus;
|
|
169
|
+
task: string;
|
|
170
|
+
dependsOn: string[];
|
|
171
|
+
output?: string;
|
|
172
|
+
error?: string;
|
|
173
|
+
startedAt?: string;
|
|
174
|
+
completedAt?: string;
|
|
175
|
+
retryCount: number;
|
|
176
|
+
createdAt: string;
|
|
177
|
+
updatedAt: string;
|
|
178
|
+
}
|