spawnfile 0.1.0 → 0.1.2

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 (107) hide show
  1. package/README.md +80 -397
  2. package/dist/cli/modelCommands.d.ts +3 -0
  3. package/dist/cli/modelCommands.js +68 -0
  4. package/dist/cli/runCli.d.ts +6 -1
  5. package/dist/cli/runCli.js +12 -67
  6. package/dist/cli/runtimeCommands.d.ts +3 -0
  7. package/dist/cli/runtimeCommands.js +20 -0
  8. package/dist/cli/surfaceCommands.d.ts +3 -0
  9. package/dist/cli/surfaceCommands.js +98 -0
  10. package/dist/compiler/agentSurfaces.js +51 -5
  11. package/dist/compiler/buildCompilePlan.js +36 -40
  12. package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
  13. package/dist/compiler/buildCompilePlanRuntime.js +39 -0
  14. package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
  15. package/dist/compiler/buildCompilePlanTeams.js +38 -0
  16. package/dist/compiler/compilePlanHelpers.js +4 -1
  17. package/dist/compiler/compileProject.js +62 -13
  18. package/dist/compiler/compileProjectSupport.d.ts +17 -0
  19. package/dist/compiler/compileProjectSupport.js +136 -0
  20. package/dist/compiler/containerArtifacts.d.ts +6 -1
  21. package/dist/compiler/containerArtifacts.js +26 -4
  22. package/dist/compiler/containerArtifactsPlans.js +16 -1
  23. package/dist/compiler/containerArtifactsRender.d.ts +4 -2
  24. package/dist/compiler/containerArtifactsRender.js +21 -126
  25. package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
  26. package/dist/compiler/containerEntrypointRender.d.ts +12 -0
  27. package/dist/compiler/containerEntrypointRender.js +186 -0
  28. package/dist/compiler/index.d.ts +2 -0
  29. package/dist/compiler/index.js +2 -0
  30. package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
  31. package/dist/compiler/interactiveSurfaceScopes.js +21 -0
  32. package/dist/compiler/moltnetArtifacts.d.ts +27 -0
  33. package/dist/compiler/moltnetArtifacts.js +204 -0
  34. package/dist/compiler/moltnetBinaries.d.ts +4 -0
  35. package/dist/compiler/moltnetBinaries.js +103 -0
  36. package/dist/compiler/moltnetClientConfig.d.ts +11 -0
  37. package/dist/compiler/moltnetClientConfig.js +89 -0
  38. package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
  39. package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
  40. package/dist/compiler/moltnetResolution.d.ts +3 -0
  41. package/dist/compiler/moltnetResolution.js +201 -0
  42. package/dist/compiler/runProject.js +1 -1
  43. package/dist/compiler/surfaceDefinitions.d.ts +55 -0
  44. package/dist/compiler/surfaceDefinitions.js +204 -0
  45. package/dist/compiler/teamContextHelpers.d.ts +18 -0
  46. package/dist/compiler/teamContextHelpers.js +112 -0
  47. package/dist/compiler/teamContextSupport.d.ts +4 -0
  48. package/dist/compiler/teamContextSupport.js +264 -0
  49. package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
  50. package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
  51. package/dist/compiler/teamContextTypes.d.ts +28 -0
  52. package/dist/compiler/teamRoster.d.ts +12 -0
  53. package/dist/compiler/teamRoster.js +48 -0
  54. package/dist/compiler/teamRosterEntries.d.ts +13 -0
  55. package/dist/compiler/teamRosterEntries.js +230 -0
  56. package/dist/compiler/teamRosterTypes.d.ts +45 -0
  57. package/dist/compiler/types.d.ts +72 -6
  58. package/dist/compiler/updateProjectRuntime.d.ts +9 -0
  59. package/dist/compiler/updateProjectRuntime.js +67 -0
  60. package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
  61. package/dist/compiler/updateProjectSurfaces.js +106 -0
  62. package/dist/manifest/loadManifest.js +4 -4
  63. package/dist/manifest/renderSpawnfile.js +74 -8
  64. package/dist/manifest/scaffold.js +1 -3
  65. package/dist/manifest/schemas.d.ts +227 -17
  66. package/dist/manifest/schemas.js +62 -20
  67. package/dist/manifest/surfaceSchemas.d.ts +154 -0
  68. package/dist/manifest/surfaceSchemas.js +77 -5
  69. package/dist/runtime/common.js +3 -0
  70. package/dist/runtime/openclaw/adapter.js +38 -5
  71. package/dist/runtime/openclaw/moltnet.d.ts +12 -0
  72. package/dist/runtime/openclaw/moltnet.js +124 -0
  73. package/dist/runtime/openclaw/surfaces.js +3 -0
  74. package/dist/runtime/picoclaw/adapter.js +27 -8
  75. package/dist/runtime/picoclaw/pico.d.ts +2 -0
  76. package/dist/runtime/picoclaw/pico.js +2 -0
  77. package/dist/runtime/picoclaw/surfaces.js +11 -0
  78. package/dist/runtime/tinyclaw/adapter.js +22 -8
  79. package/dist/runtime/tinyclaw/runAuth.js +28 -1
  80. package/dist/runtime/tinyclaw/surfaces.js +8 -0
  81. package/dist/runtime/types.d.ts +11 -0
  82. package/package.json +10 -3
  83. package/runtimes.yaml +4 -4
  84. package/dist/.env.example +0 -5
  85. package/dist/Dockerfile +0 -21
  86. package/dist/compiler/discordSurface.d.ts +0 -4
  87. package/dist/compiler/discordSurface.js +0 -28
  88. package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +0 -16
  89. package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +0 -1
  90. package/dist/e2e/cli.js +0 -40
  91. package/dist/e2e/dockerAuth.d.ts +0 -18
  92. package/dist/e2e/dockerAuth.js +0 -212
  93. package/dist/e2e/fixtures.d.ts +0 -2
  94. package/dist/e2e/fixtures.js +0 -49
  95. package/dist/e2e/index.d.ts +0 -4
  96. package/dist/e2e/index.js +0 -4
  97. package/dist/e2e/runtimePrompts.d.ts +0 -13
  98. package/dist/e2e/runtimePrompts.js +0 -132
  99. package/dist/e2e/scenarios.d.ts +0 -3
  100. package/dist/e2e/scenarios.js +0 -84
  101. package/dist/e2e/types.d.ts +0 -35
  102. package/dist/entrypoint.sh +0 -71
  103. package/dist/runtimes/picoclaw/agents/assistant/config.json +0 -16
  104. package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +0 -1
  105. package/dist/spawnfile-report.json +0 -71
  106. /package/dist/{e2e/cli.d.ts → compiler/teamContextTypes.js} +0 -0
  107. /package/dist/{e2e/types.js → compiler/teamRosterTypes.js} +0 -0
@@ -1,212 +0,0 @@
1
- import os from "node:os";
2
- import path from "node:path";
3
- import { mkdtemp } from "node:fs/promises";
4
- import { requireAuthProfile } from "../auth/index.js";
5
- import { buildProject, createDockerRunInvocation, runDockerContainer, syncProjectAuth } from "../compiler/index.js";
6
- import { removeDirectory } from "../filesystem/index.js";
7
- import { SpawnfileError } from "../shared/index.js";
8
- import { materializeDockerAuthFixture } from "./fixtures.js";
9
- import { waitForRuntimeReady, promptRuntime } from "./runtimePrompts.js";
10
- import { filterDockerAuthE2EScenarios } from "./scenarios.js";
11
- const DEFAULT_ENV_FILE = path.resolve(process.cwd(), "../headhunter/.env");
12
- const createLogger = (logger) => logger ?? {
13
- error: (message) => console.error(message),
14
- info: (message) => console.log(message)
15
- };
16
- const createScenarioImageTag = (scenario) => `spawnfile-e2e-${scenario.id}-${Date.now()}`;
17
- const createScenarioPrompt = (scenarioId, runtime) => `Reply with exactly SF-E2E-${scenarioId.toUpperCase()}-${runtime.toUpperCase()} and nothing else.`;
18
- const extractSentinel = (prompt) => prompt.replace("Reply with exactly ", "").replace(" and nothing else.", "");
19
- const findPromptInstance = (scenario, instances, runtime) => {
20
- const runtimeInstances = instances.filter((instance) => instance.runtime === runtime);
21
- if (runtimeInstances.length === 0) {
22
- return null;
23
- }
24
- if (scenario.kind === "single-agent") {
25
- return runtimeInstances[0] ?? null;
26
- }
27
- return runtimeInstances[0] ?? null;
28
- };
29
- const resolveEnvFilePath = (inputPath) => inputPath ?? DEFAULT_ENV_FILE;
30
- const withSpawnfileHome = async (spawnfileHome, fn) => {
31
- const previousValue = process.env.SPAWNFILE_HOME;
32
- process.env.SPAWNFILE_HOME = spawnfileHome;
33
- try {
34
- return await fn();
35
- }
36
- finally {
37
- if (typeof previousValue === "string") {
38
- process.env.SPAWNFILE_HOME = previousValue;
39
- }
40
- else {
41
- delete process.env.SPAWNFILE_HOME;
42
- }
43
- }
44
- };
45
- const runDockerCommand = async (dockerCommand, args) => {
46
- const { spawn } = await import("node:child_process");
47
- return new Promise((resolve, reject) => {
48
- const child = spawn(dockerCommand, args, {
49
- stdio: ["ignore", "pipe", "pipe"]
50
- });
51
- const stdout = [];
52
- const stderr = [];
53
- child.stdout.on("data", (chunk) => stdout.push(String(chunk)));
54
- child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
55
- child.once("error", (error) => {
56
- reject(new SpawnfileError("runtime_error", `Unable to start docker command ${dockerCommand}: ${error.message}`));
57
- });
58
- child.once("exit", (code, signal) => {
59
- if (code === 0) {
60
- resolve(stdout.join("").trim());
61
- return;
62
- }
63
- reject(new SpawnfileError("runtime_error", signal
64
- ? `Docker command exited from signal ${signal}: ${dockerCommand} ${args.join(" ")}`
65
- : `Docker command failed with exit code ${code ?? "unknown"}: ${dockerCommand} ${args.join(" ")}\n${stderr.join("")}`.trim()));
66
- });
67
- });
68
- };
69
- const cleanupDockerArtifacts = async (dockerCommand, containerName, imageTag, options) => {
70
- try {
71
- await runDockerCommand(dockerCommand, ["rm", "-f", containerName]);
72
- }
73
- catch {
74
- // Ignore best-effort cleanup failures.
75
- }
76
- if (options.keepImages) {
77
- return;
78
- }
79
- try {
80
- await runDockerCommand(dockerCommand, ["image", "rm", "-f", imageTag]);
81
- }
82
- catch {
83
- // Ignore best-effort cleanup failures.
84
- }
85
- };
86
- const readDockerLogs = async (dockerCommand, containerName) => {
87
- try {
88
- return await runDockerCommand(dockerCommand, ["logs", containerName]);
89
- }
90
- catch {
91
- return "";
92
- }
93
- };
94
- const runScenario = async (scenario, options) => {
95
- const startedAt = Date.now();
96
- const logger = createLogger(options.logger);
97
- const scenarioRoot = await mkdtemp(path.join(os.tmpdir(), `spawnfile-e2e-${scenario.id}-`));
98
- const projectDirectory = path.join(scenarioRoot, "project");
99
- const outputDirectory = path.join(scenarioRoot, "dist");
100
- const spawnfileHome = path.join(scenarioRoot, "spawnfile-home");
101
- const profileName = "e2e";
102
- const imageTag = createScenarioImageTag(scenario);
103
- const containerName = `spawnfile-e2e-${scenario.id}`;
104
- logger.info(`scenario ${scenario.id}: materializing fixture`);
105
- try {
106
- await materializeDockerAuthFixture(scenario, projectDirectory);
107
- await withSpawnfileHome(spawnfileHome, async () => {
108
- logger.info(`scenario ${scenario.id}: syncing auth`);
109
- await syncProjectAuth(projectDirectory, {
110
- claudeCodeDirectory: options.claudeCodeDirectory,
111
- codexDirectory: options.codexDirectory,
112
- envFilePath: resolveEnvFilePath(options.envFilePath),
113
- profileName
114
- });
115
- logger.info(`scenario ${scenario.id}: building image ${imageTag}`);
116
- const buildResult = await buildProject(projectDirectory, {
117
- dockerCommand: options.dockerCommand,
118
- imageTag,
119
- outputDirectory
120
- });
121
- const runtimeInstances = buildResult.report.container?.runtime_instances ?? [];
122
- const authProfile = await requireAuthProfile(profileName);
123
- const invocation = await createDockerRunInvocation(buildResult, imageTag, {
124
- authProfile,
125
- containerName,
126
- detach: true,
127
- dockerCommand: options.dockerCommand
128
- });
129
- try {
130
- logger.info(`scenario ${scenario.id}: starting container ${containerName}`);
131
- await runDockerContainer(invocation);
132
- for (const check of scenario.promptChecks) {
133
- const prompt = createScenarioPrompt(scenario.id, check.runtime);
134
- const sentinel = extractSentinel(prompt);
135
- const promptInstance = findPromptInstance(scenario, runtimeInstances, check.runtime);
136
- logger.info(`scenario ${scenario.id}: waiting for ${check.runtime}`);
137
- await waitForRuntimeReady(check.runtime);
138
- logger.info(`scenario ${scenario.id}: prompting ${check.runtime}`);
139
- const output = await promptRuntime(check.runtime, {
140
- agentName: check.agentName,
141
- command: options.dockerCommand,
142
- configPath: promptInstance?.config_path,
143
- containerName,
144
- homePath: promptInstance?.home_path ?? undefined,
145
- prompt
146
- });
147
- if (!output.includes(sentinel)) {
148
- throw new SpawnfileError("runtime_error", `Scenario ${scenario.id} did not return sentinel ${sentinel} for ${check.runtime}`);
149
- }
150
- }
151
- }
152
- catch (error) {
153
- const logs = await readDockerLogs(options.dockerCommand, containerName);
154
- throw new SpawnfileError("runtime_error", `${error instanceof Error ? error.message : String(error)}${logs ? `\n\nDocker logs:\n${logs}` : ""}`);
155
- }
156
- finally {
157
- await cleanupDockerArtifacts(options.dockerCommand, containerName, imageTag, {
158
- keepImages: options.keepImages
159
- });
160
- await removeDirectory(invocation.supportDirectory);
161
- }
162
- });
163
- if (!options.keepArtifacts) {
164
- await removeDirectory(scenarioRoot);
165
- }
166
- return {
167
- durationMs: Date.now() - startedAt,
168
- id: scenario.id,
169
- success: true
170
- };
171
- }
172
- catch (error) {
173
- if (!options.keepArtifacts) {
174
- await removeDirectory(scenarioRoot);
175
- }
176
- return {
177
- durationMs: Date.now() - startedAt,
178
- errorMessage: error instanceof Error ? error.message : String(error),
179
- id: scenario.id,
180
- success: false
181
- };
182
- }
183
- };
184
- const formatFailures = (results) => results
185
- .filter((result) => !result.success)
186
- .map((result) => `- ${result.id}: ${result.errorMessage ?? "unknown error"}`)
187
- .join("\n");
188
- export const runDockerAuthE2E = async (options = {}) => {
189
- const logger = createLogger(options.logger);
190
- const scenarios = filterDockerAuthE2EScenarios(options);
191
- if (scenarios.length === 0) {
192
- throw new SpawnfileError("validation_error", "No Docker auth E2E scenarios matched the filter");
193
- }
194
- const results = [];
195
- for (const scenario of scenarios) {
196
- const result = await runScenario(scenario, {
197
- claudeCodeDirectory: options.claudeCodeDirectory,
198
- codexDirectory: options.codexDirectory,
199
- dockerCommand: options.dockerCommand ?? "docker",
200
- envFilePath: options.envFilePath,
201
- keepArtifacts: options.keepArtifacts ?? false,
202
- keepImages: options.keepImages ?? false,
203
- logger
204
- });
205
- results.push(result);
206
- logger.info(`${result.success ? "PASS" : "FAIL"} ${result.id} (${Math.round(result.durationMs / 1000)}s)`);
207
- }
208
- if (results.some((result) => !result.success)) {
209
- throw new SpawnfileError("runtime_error", `Docker auth E2E failed:\n${formatFailures(results)}`);
210
- }
211
- return { results };
212
- };
@@ -1,2 +0,0 @@
1
- import type { DockerAuthE2EScenario } from "./types.js";
2
- export declare const materializeDockerAuthFixture: (scenario: DockerAuthE2EScenario, destinationDirectory: string) => Promise<void>;
@@ -1,49 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import YAML from "yaml";
4
- import { copyDirectory, readUtf8File, writeUtf8File } from "../filesystem/index.js";
5
- const FIXTURES_ROOT = fileURLToPath(new URL("../../fixtures/e2e", import.meta.url));
6
- const readYamlFile = async (filePath) => YAML.parse(await readUtf8File(filePath));
7
- const writeYamlFile = async (filePath, value) => {
8
- await writeUtf8File(filePath, YAML.stringify(value));
9
- };
10
- const applyAgentSpec = (manifest, spec) => ({
11
- ...manifest,
12
- execution: {
13
- ...(manifest.execution ?? {}),
14
- model: {
15
- ...((manifest.execution?.model ?? {})),
16
- auth: {
17
- method: spec.authMethod
18
- },
19
- primary: {
20
- name: spec.modelName,
21
- provider: spec.provider
22
- }
23
- }
24
- },
25
- kind: "agent",
26
- name: spec.name,
27
- runtime: spec.runtime
28
- });
29
- const patchSingleAgentFixture = async (destinationDirectory, scenario) => {
30
- const manifestPath = path.join(destinationDirectory, "Spawnfile");
31
- const manifest = await readYamlFile(manifestPath);
32
- await writeYamlFile(manifestPath, applyAgentSpec(manifest, scenario.agents[0]));
33
- };
34
- const patchTeamFixture = async (destinationDirectory, scenario) => {
35
- for (const agent of scenario.agents) {
36
- const manifestPath = path.join(destinationDirectory, "agents", agent.directoryName, "Spawnfile");
37
- const manifest = await readYamlFile(manifestPath);
38
- await writeYamlFile(manifestPath, applyAgentSpec(manifest, agent));
39
- }
40
- };
41
- export const materializeDockerAuthFixture = async (scenario, destinationDirectory) => {
42
- const sourceDirectory = path.join(FIXTURES_ROOT, scenario.fixture);
43
- await copyDirectory(sourceDirectory, destinationDirectory);
44
- if (scenario.fixture === "agent") {
45
- await patchSingleAgentFixture(destinationDirectory, scenario);
46
- return;
47
- }
48
- await patchTeamFixture(destinationDirectory, scenario);
49
- };
@@ -1,4 +0,0 @@
1
- export * from "./dockerAuth.js";
2
- export * from "./fixtures.js";
3
- export * from "./scenarios.js";
4
- export * from "./types.js";
package/dist/e2e/index.js DELETED
@@ -1,4 +0,0 @@
1
- export * from "./dockerAuth.js";
2
- export * from "./fixtures.js";
3
- export * from "./scenarios.js";
4
- export * from "./types.js";
@@ -1,13 +0,0 @@
1
- import type { E2ERuntime } from "./types.js";
2
- interface RuntimePromptOptions {
3
- agentName?: string;
4
- command?: string;
5
- configPath?: string;
6
- containerName: string;
7
- homePath?: string;
8
- prompt: string;
9
- timeoutMs?: number;
10
- }
11
- export declare const waitForRuntimeReady: (runtime: E2ERuntime, timeoutMs?: number) => Promise<void>;
12
- export declare const promptRuntime: (runtime: E2ERuntime, options: RuntimePromptOptions) => Promise<string>;
13
- export {};
@@ -1,132 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { SpawnfileError } from "../shared/index.js";
3
- const wait = async (delayMs) => new Promise((resolve) => {
4
- setTimeout(resolve, delayMs);
5
- });
6
- const runCommand = async (command, args, timeoutMs = 180_000) => new Promise((resolve, reject) => {
7
- const child = spawn(command, args, {
8
- stdio: ["ignore", "pipe", "pipe"]
9
- });
10
- const stdout = [];
11
- const stderr = [];
12
- const timer = setTimeout(() => {
13
- child.kill("SIGTERM");
14
- reject(new SpawnfileError("runtime_error", `Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`));
15
- }, timeoutMs);
16
- child.stdout.on("data", (chunk) => {
17
- stdout.push(String(chunk));
18
- });
19
- child.stderr.on("data", (chunk) => {
20
- stderr.push(String(chunk));
21
- });
22
- child.once("error", (error) => {
23
- clearTimeout(timer);
24
- reject(new SpawnfileError("runtime_error", `Unable to start command ${command}: ${error.message}`));
25
- });
26
- child.once("exit", (code, signal) => {
27
- clearTimeout(timer);
28
- if (code === 0) {
29
- resolve({
30
- stderr: stderr.join(""),
31
- stdout: stdout.join("")
32
- });
33
- return;
34
- }
35
- reject(new SpawnfileError("runtime_error", signal
36
- ? `Command exited from signal ${signal}: ${command} ${args.join(" ")}`
37
- : `Command failed with exit code ${code ?? "unknown"}: ${command} ${args.join(" ")}\n${stderr.join("")}`.trim()));
38
- });
39
- });
40
- const fetchText = async (url) => {
41
- const response = await fetch(url);
42
- return {
43
- body: await response.text(),
44
- status: response.status
45
- };
46
- };
47
- const getHealthUrl = (runtime) => runtime === "openclaw"
48
- ? "http://127.0.0.1:18789/healthz"
49
- : runtime === "picoclaw"
50
- ? "http://127.0.0.1:18790/health"
51
- : "http://127.0.0.1:3777/api/agents";
52
- export const waitForRuntimeReady = async (runtime, timeoutMs = 120_000) => {
53
- const startedAt = Date.now();
54
- const url = getHealthUrl(runtime);
55
- while (Date.now() - startedAt <= timeoutMs) {
56
- try {
57
- const response = await fetch(url);
58
- if (response.ok) {
59
- return;
60
- }
61
- }
62
- catch {
63
- // Ignore readiness races and keep polling.
64
- }
65
- await wait(2_000);
66
- }
67
- throw new SpawnfileError("runtime_error", `Runtime ${runtime} did not become ready within ${timeoutMs}ms (${url})`);
68
- };
69
- const promptOpenClaw = async (options) => {
70
- const result = await runCommand(options.command ?? "docker", [
71
- "exec",
72
- "-u",
73
- "0",
74
- ...(options.homePath ? ["-e", `OPENCLAW_HOME=${options.homePath}`] : []),
75
- ...(options.configPath ? ["-e", `OPENCLAW_CONFIG_PATH=${options.configPath}`] : []),
76
- options.containerName,
77
- "openclaw",
78
- "agent",
79
- "--local",
80
- "--agent",
81
- "main",
82
- "--message",
83
- options.prompt,
84
- "--json"
85
- ], options.timeoutMs);
86
- return `${result.stdout}\n${result.stderr}`;
87
- };
88
- const promptPicoClaw = async (options) => {
89
- const result = await runCommand(options.command ?? "docker", [
90
- "exec",
91
- ...(options.homePath ? ["-e", `HOME=${options.homePath}`, "-e", `PICOCLAW_HOME=${options.homePath}`] : []),
92
- ...(options.configPath ? ["-e", `PICOCLAW_CONFIG=${options.configPath}`] : []),
93
- options.containerName,
94
- "picoclaw",
95
- "agent",
96
- "-m",
97
- options.prompt
98
- ], options.timeoutMs);
99
- return `${result.stdout}\n${result.stderr}`;
100
- };
101
- const promptTinyClaw = async (options) => {
102
- const enqueueResponse = await fetch("http://127.0.0.1:3777/api/message", {
103
- body: JSON.stringify({
104
- ...(options.agentName ? { agent: options.agentName } : {}),
105
- channel: "spawnfile-e2e",
106
- message: options.prompt,
107
- sender: "spawnfile-e2e"
108
- }),
109
- headers: {
110
- "Content-Type": "application/json"
111
- },
112
- method: "POST"
113
- });
114
- if (!enqueueResponse.ok) {
115
- throw new SpawnfileError("runtime_error", `TinyClaw enqueue failed with status ${enqueueResponse.status}`);
116
- }
117
- const startedAt = Date.now();
118
- const timeoutMs = options.timeoutMs ?? 180_000;
119
- while (Date.now() - startedAt <= timeoutMs) {
120
- const { body, status } = await fetchText("http://127.0.0.1:3777/api/responses?limit=20");
121
- if (status === 200 && body.includes(options.prompt.replace("Reply with exactly ", "").replace(" and nothing else.", ""))) {
122
- return body;
123
- }
124
- await wait(2_000);
125
- }
126
- throw new SpawnfileError("runtime_error", `TinyClaw did not return a response within ${timeoutMs}ms`);
127
- };
128
- export const promptRuntime = async (runtime, options) => runtime === "openclaw"
129
- ? promptOpenClaw(options)
130
- : runtime === "picoclaw"
131
- ? promptPicoClaw(options)
132
- : promptTinyClaw(options);
@@ -1,3 +0,0 @@
1
- import type { DockerAuthE2EFilters, DockerAuthE2EScenario } from "./types.js";
2
- export declare const listDockerAuthE2EScenarios: () => DockerAuthE2EScenario[];
3
- export declare const filterDockerAuthE2EScenarios: (filters?: DockerAuthE2EFilters) => DockerAuthE2EScenario[];
@@ -1,84 +0,0 @@
1
- const createSingleAgentScenario = (runtime, provider, modelName, authMethod) => {
2
- const id = `${runtime}-${authMethod}`;
3
- const agent = {
4
- authMethod,
5
- directoryName: runtime,
6
- modelName,
7
- name: `${runtime}-assistant`,
8
- provider,
9
- runtime
10
- };
11
- return {
12
- agents: [agent],
13
- description: `${runtime} single-agent Docker auth smoke using ${authMethod}`,
14
- fixture: "agent",
15
- id,
16
- kind: "single-agent",
17
- promptChecks: [{ runtime }]
18
- };
19
- };
20
- const SINGLE_AGENT_SCENARIOS = [
21
- createSingleAgentScenario("openclaw", "openai", "gpt-5", "api_key"),
22
- createSingleAgentScenario("openclaw", "openai", "gpt-5", "codex"),
23
- createSingleAgentScenario("openclaw", "anthropic", "claude-sonnet-4-5", "claude-code"),
24
- createSingleAgentScenario("picoclaw", "openai", "gpt-5", "api_key"),
25
- createSingleAgentScenario("picoclaw", "openai", "gpt-5", "codex"),
26
- createSingleAgentScenario("picoclaw", "anthropic", "claude-sonnet-4-5", "claude-code"),
27
- createSingleAgentScenario("tinyclaw", "openai", "gpt-5", "codex"),
28
- createSingleAgentScenario("tinyclaw", "anthropic", "claude-sonnet-4-5", "claude-code")
29
- ];
30
- const TEAM_SCENARIOS = [
31
- {
32
- agents: [
33
- {
34
- authMethod: "codex",
35
- directoryName: "openclaw",
36
- modelName: "gpt-5",
37
- name: "openclaw",
38
- provider: "openai",
39
- runtime: "openclaw"
40
- },
41
- {
42
- authMethod: "api_key",
43
- directoryName: "picoclaw",
44
- modelName: "gpt-5",
45
- name: "picoclaw",
46
- provider: "openai",
47
- runtime: "picoclaw"
48
- },
49
- {
50
- authMethod: "codex",
51
- directoryName: "tinyclaw",
52
- modelName: "gpt-5",
53
- name: "tinyclaw",
54
- provider: "openai",
55
- runtime: "tinyclaw"
56
- }
57
- ],
58
- description: "multi-runtime Docker auth smoke team",
59
- fixture: "team",
60
- id: "team-multi-runtime",
61
- kind: "team",
62
- promptChecks: [
63
- { runtime: "openclaw" },
64
- { runtime: "picoclaw" },
65
- { agentName: "tinyclaw", runtime: "tinyclaw" }
66
- ]
67
- }
68
- ];
69
- export const listDockerAuthE2EScenarios = () => [
70
- ...SINGLE_AGENT_SCENARIOS,
71
- ...TEAM_SCENARIOS
72
- ];
73
- const includesScenarioId = (filters, scenario) => !filters.scenarioIds ||
74
- filters.scenarioIds.length === 0 ||
75
- filters.scenarioIds.includes(scenario.id);
76
- const includesAuthMethod = (filters, scenario) => !filters.authMethods ||
77
- filters.authMethods.length === 0 ||
78
- scenario.agents.some((agent) => filters.authMethods.includes(agent.authMethod));
79
- const includesRuntime = (filters, scenario) => !filters.runtimes ||
80
- filters.runtimes.length === 0 ||
81
- scenario.agents.some((agent) => filters.runtimes.includes(agent.runtime));
82
- export const filterDockerAuthE2EScenarios = (filters = {}) => listDockerAuthE2EScenarios().filter((scenario) => includesScenarioId(filters, scenario) &&
83
- includesAuthMethod(filters, scenario) &&
84
- includesRuntime(filters, scenario));
@@ -1,35 +0,0 @@
1
- import type { ModelAuthMethod } from "../shared/index.js";
2
- export type E2ERuntime = "openclaw" | "picoclaw" | "tinyclaw";
3
- export type E2EFixtureKind = "agent" | "team";
4
- export type E2EScenarioKind = "single-agent" | "team";
5
- export interface E2EAgentSpec {
6
- authMethod: ModelAuthMethod;
7
- directoryName: string;
8
- modelName: string;
9
- name: string;
10
- provider: string;
11
- runtime: E2ERuntime;
12
- }
13
- export interface E2EPromptCheck {
14
- agentName?: string;
15
- runtime: E2ERuntime;
16
- }
17
- export interface DockerAuthE2EScenario {
18
- agents: E2EAgentSpec[];
19
- description: string;
20
- fixture: E2EFixtureKind;
21
- id: string;
22
- kind: E2EScenarioKind;
23
- promptChecks: E2EPromptCheck[];
24
- }
25
- export interface DockerAuthE2EFilters {
26
- authMethods?: ModelAuthMethod[];
27
- runtimes?: E2ERuntime[];
28
- scenarioIds?: string[];
29
- }
30
- export interface DockerAuthE2EScenarioResult {
31
- durationMs: number;
32
- errorMessage?: string;
33
- id: string;
34
- success: boolean;
35
- }
@@ -1,71 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- require_env() {
5
- local name="$1"
6
- if [ -z "${!name:-}" ]; then
7
- echo "Missing required env: $name" >&2
8
- exit 1
9
- fi
10
- }
11
-
12
- require_file() {
13
- local target="$1"
14
- if [ ! -f "$target" ]; then
15
- echo "Missing required file: $target" >&2
16
- exit 1
17
- fi
18
- }
19
-
20
- write_env_file() {
21
- local name="$1"
22
- local target="$2"
23
- if [ -z "${!name:-}" ]; then
24
- return
25
- fi
26
- mkdir -p "$(dirname "$target")"
27
- printf %s "${!name:-}" > "$target"
28
- }
29
-
30
- apply_json_env_value() {
31
- local target="$1"
32
- local name="$2"
33
- local json_path="$3"
34
- if [ -z "${!name:-}" ]; then
35
- return
36
- fi
37
- python3 - "$target" "$name" "$json_path" <<'PY'
38
- import json
39
- import os
40
- import sys
41
-
42
- target_path = sys.argv[1]
43
- env_name = sys.argv[2]
44
- json_path = sys.argv[3].split('.')
45
- value = os.environ.get(env_name)
46
- if value is None:
47
- raise SystemExit(0)
48
-
49
- with open(target_path, encoding='utf-8') as handle:
50
- data = json.load(handle)
51
-
52
- cursor = data
53
- for part in json_path[:-1]:
54
- child = cursor.get(part)
55
- if not isinstance(child, dict):
56
- child = {}
57
- cursor[part] = child
58
- cursor = child
59
-
60
- cursor[json_path[-1]] = value
61
-
62
- with open(target_path, 'w', encoding='utf-8') as handle:
63
- json.dump(data, handle, indent=2)
64
- handle.write('\n')
65
- PY
66
- }
67
-
68
- mkdir -p '/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace'
69
- require_file '/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json'
70
- write_env_file 'ANTHROPIC_API_KEY' '/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/secrets/ANTHROPIC_API_KEY'
71
- HOME='/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw' PICOCLAW_HOME='/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw' PICOCLAW_CONFIG='/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json' PICOCLAW_GATEWAY_PORT='18790' PICOCLAW_GATEWAY_HOST='0.0.0.0' exec 'picoclaw' 'gateway' '--allow-empty'
@@ -1,16 +0,0 @@
1
- {
2
- "agents": {
3
- "defaults": {
4
- "workspace": "<workspace-path>",
5
- "restrict_to_workspace": true,
6
- "model_name": "claude-sonnet-4.6"
7
- }
8
- },
9
- "model_list": [
10
- {
11
- "api_key": "file://secrets/ANTHROPIC_API_KEY",
12
- "model_name": "claude-sonnet-4.6",
13
- "model": "anthropic/claude-sonnet-4.6"
14
- }
15
- ]
16
- }
@@ -1 +0,0 @@
1
- You are a concise assistant. Reply with exactly the requested token and nothing else.