@usezombie/zombiectl 0.3.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.
- package/README.md +76 -0
- package/bin/zombiectl.js +11 -0
- package/bun.lock +29 -0
- package/package.json +28 -0
- package/scripts/run-tests.mjs +38 -0
- package/src/cli.js +275 -0
- package/src/commands/admin.js +39 -0
- package/src/commands/agent.js +98 -0
- package/src/commands/agent_harness.js +43 -0
- package/src/commands/agent_improvement_report.js +42 -0
- package/src/commands/agent_profile.js +39 -0
- package/src/commands/agent_proposals.js +158 -0
- package/src/commands/agent_scores.js +44 -0
- package/src/commands/core-ops.js +108 -0
- package/src/commands/core.js +537 -0
- package/src/commands/harness.js +35 -0
- package/src/commands/harness_activate.js +53 -0
- package/src/commands/harness_active.js +32 -0
- package/src/commands/harness_compile.js +40 -0
- package/src/commands/harness_source.js +72 -0
- package/src/commands/run_preview.js +212 -0
- package/src/commands/run_preview_walk.js +1 -0
- package/src/commands/runs.js +35 -0
- package/src/commands/spec_init.js +287 -0
- package/src/commands/workspace_billing.js +26 -0
- package/src/constants/error-codes.js +1 -0
- package/src/lib/agent-loop.js +106 -0
- package/src/lib/analytics.js +114 -0
- package/src/lib/api-paths.js +2 -0
- package/src/lib/browser.js +96 -0
- package/src/lib/http.js +149 -0
- package/src/lib/sse-parser.js +50 -0
- package/src/lib/state.js +67 -0
- package/src/lib/tool-executors.js +110 -0
- package/src/lib/walk-dir.js +41 -0
- package/src/program/args.js +95 -0
- package/src/program/auth-guard.js +12 -0
- package/src/program/auth-token.js +44 -0
- package/src/program/banner.js +46 -0
- package/src/program/command-registry.js +17 -0
- package/src/program/http-client.js +38 -0
- package/src/program/io.js +83 -0
- package/src/program/routes.js +20 -0
- package/src/program/suggest.js +76 -0
- package/src/program/validate.js +24 -0
- package/src/ui-progress.js +59 -0
- package/src/ui-theme.js +62 -0
- package/test/admin_config.unit.test.js +25 -0
- package/test/agent-loop.unit.test.js +497 -0
- package/test/agent_harness.unit.test.js +52 -0
- package/test/agent_improvement_report.unit.test.js +74 -0
- package/test/agent_profile.unit.test.js +156 -0
- package/test/agent_proposals.unit.test.js +167 -0
- package/test/agent_scores.unit.test.js +220 -0
- package/test/analytics.unit.test.js +41 -0
- package/test/args.unit.test.js +69 -0
- package/test/auth-guard.test.js +33 -0
- package/test/auth-token.unit.test.js +112 -0
- package/test/banner.unit.test.js +442 -0
- package/test/browser.unit.test.js +16 -0
- package/test/cli-analytics.unit.test.js +296 -0
- package/test/did-you-mean.integration.test.js +76 -0
- package/test/doctor-json.test.js +81 -0
- package/test/error-codes.unit.test.js +7 -0
- package/test/harness-command.unit.test.js +180 -0
- package/test/harness-compile.test.js +81 -0
- package/test/harness-lifecycle.integration.test.js +339 -0
- package/test/harness-source-put.test.js +72 -0
- package/test/harness_activate.unit.test.js +48 -0
- package/test/harness_active.unit.test.js +53 -0
- package/test/harness_compile.unit.test.js +54 -0
- package/test/harness_source.unit.test.js +59 -0
- package/test/help.test.js +276 -0
- package/test/helpers-fs.js +32 -0
- package/test/helpers.js +31 -0
- package/test/io.unit.test.js +57 -0
- package/test/login.unit.test.js +115 -0
- package/test/logout.unit.test.js +65 -0
- package/test/parse.test.js +16 -0
- package/test/run-preview.edge.test.js +422 -0
- package/test/run-preview.integration.test.js +135 -0
- package/test/run-preview.security.test.js +246 -0
- package/test/run-preview.unit.test.js +131 -0
- package/test/run.unit.test.js +149 -0
- package/test/runs-cancel.unit.test.js +288 -0
- package/test/runs-list.unit.test.js +105 -0
- package/test/skill-secret.unit.test.js +94 -0
- package/test/spec-init.edge.test.js +232 -0
- package/test/spec-init.integration.test.js +128 -0
- package/test/spec-init.security.test.js +285 -0
- package/test/spec-init.unit.test.js +160 -0
- package/test/specs-sync.unit.test.js +164 -0
- package/test/sse-parser.unit.test.js +54 -0
- package/test/state.unit.test.js +34 -0
- package/test/streamfetch.unit.test.js +211 -0
- package/test/suggest.test.js +75 -0
- package/test/tool-executors.unit.test.js +165 -0
- package/test/validate.test.js +81 -0
- package/test/workspace-add.test.js +106 -0
- package/test/workspace.unit.test.js +230 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { test } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
import { runCli } from "../src/cli.js";
|
|
5
|
+
|
|
6
|
+
function bufferStream() {
|
|
7
|
+
let data = "";
|
|
8
|
+
return {
|
|
9
|
+
stream: new Writable({
|
|
10
|
+
write(chunk, _enc, cb) {
|
|
11
|
+
data += String(chunk);
|
|
12
|
+
cb();
|
|
13
|
+
},
|
|
14
|
+
}),
|
|
15
|
+
read: () => data,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test("harness compile sends profile_id selector", async () => {
|
|
20
|
+
const out = bufferStream();
|
|
21
|
+
const err = bufferStream();
|
|
22
|
+
|
|
23
|
+
const fetchImpl = async (url, options) => {
|
|
24
|
+
assert.equal(url, "http://localhost:3000/v1/workspaces/ws_123/harness/compile");
|
|
25
|
+
assert.equal(options.method, "POST");
|
|
26
|
+
const payload = JSON.parse(String(options.body));
|
|
27
|
+
assert.equal(payload.agent_id, "ws_123-harness");
|
|
28
|
+
assert.equal(payload.config_version_id, null);
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
status: 200,
|
|
32
|
+
text: async () => JSON.stringify({ compile_job_id: "cjob_123", is_valid: true }),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const code = await runCli(
|
|
37
|
+
["harness", "compile", "--workspace-id", "ws_123", "--agent-id", "ws_123-harness"],
|
|
38
|
+
{
|
|
39
|
+
env: { ...process.env, ZOMBIE_TOKEN: "header.payload.sig" },
|
|
40
|
+
stdout: out.stream,
|
|
41
|
+
stderr: err.stream,
|
|
42
|
+
fetchImpl,
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
assert.equal(code, 0);
|
|
47
|
+
assert.equal(err.read(), "");
|
|
48
|
+
assert.match(out.read(), /Harness compile/);
|
|
49
|
+
assert.match(out.read(), /compile_job_id[\s·]+cjob_123/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("harness compile sends profile_version_id selector", async () => {
|
|
53
|
+
const out = bufferStream();
|
|
54
|
+
const err = bufferStream();
|
|
55
|
+
|
|
56
|
+
const fetchImpl = async (_url, options) => {
|
|
57
|
+
const payload = JSON.parse(String(options.body));
|
|
58
|
+
assert.equal(payload.agent_id, null);
|
|
59
|
+
assert.equal(payload.config_version_id, "pver_456");
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
status: 200,
|
|
63
|
+
text: async () => JSON.stringify({ compile_job_id: "cjob_456", is_valid: true }),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const code = await runCli(
|
|
68
|
+
["harness", "compile", "--workspace-id", "ws_123", "--config-version-id", "pver_456"],
|
|
69
|
+
{
|
|
70
|
+
env: { ...process.env, ZOMBIE_TOKEN: "header.payload.sig" },
|
|
71
|
+
stdout: out.stream,
|
|
72
|
+
stderr: err.stream,
|
|
73
|
+
fetchImpl,
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
assert.equal(code, 0);
|
|
78
|
+
assert.equal(err.read(), "");
|
|
79
|
+
assert.match(out.read(), /Harness compile/);
|
|
80
|
+
assert.match(out.read(), /compile_job_id[\s·]+cjob_456/);
|
|
81
|
+
});
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { test } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { Writable } from "node:stream";
|
|
7
|
+
import { runCli } from "../src/cli.js";
|
|
8
|
+
|
|
9
|
+
function bufferStream() {
|
|
10
|
+
let data = "";
|
|
11
|
+
return {
|
|
12
|
+
stream: new Writable({
|
|
13
|
+
write(chunk, _enc, cb) {
|
|
14
|
+
data += String(chunk);
|
|
15
|
+
cb();
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
read: () => data,
|
|
19
|
+
reset: () => {
|
|
20
|
+
data = "";
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseJsonOutput(text) {
|
|
26
|
+
return JSON.parse(String(text).trim());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
test("harness lifecycle: activate deterministically changes subsequent run snapshot linkage", async () => {
|
|
30
|
+
const out = bufferStream();
|
|
31
|
+
const err = bufferStream();
|
|
32
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "zombiectl-harness-lifecycle-"));
|
|
33
|
+
const filePath = path.join(tmpDir, "profile.md");
|
|
34
|
+
await fs.writeFile(filePath, "# Harness\n\n```json\n{\"profile_id\":\"ws_123-harness\",\"stages\":[]}\n```", "utf8");
|
|
35
|
+
const prevStateDir = process.env.ZOMBIE_STATE_DIR;
|
|
36
|
+
process.env.ZOMBIE_STATE_DIR = tmpDir;
|
|
37
|
+
|
|
38
|
+
const state = {
|
|
39
|
+
activeProfileVersion: null,
|
|
40
|
+
profileByVersion: new Map(),
|
|
41
|
+
sequence: 0,
|
|
42
|
+
runCounter: 0,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const fetchImpl = async (url, options = {}) => {
|
|
46
|
+
const u = new URL(url);
|
|
47
|
+
const route = `${options.method || "GET"} ${u.pathname}`;
|
|
48
|
+
|
|
49
|
+
if (route === "PUT /v1/workspaces/ws_123/harness/source") {
|
|
50
|
+
state.sequence += 1;
|
|
51
|
+
const version = `pver_${state.sequence}`;
|
|
52
|
+
state.profileByVersion.set(version, "ws_123-harness");
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
status: 200,
|
|
56
|
+
text: async () => JSON.stringify({ agent_id: "ws_123-harness", config_version_id: version }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (route === "POST /v1/workspaces/ws_123/harness/compile") {
|
|
61
|
+
const body = JSON.parse(String(options.body || "{}"));
|
|
62
|
+
const version = body.config_version_id;
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
status: 200,
|
|
66
|
+
text: async () => JSON.stringify({
|
|
67
|
+
compile_job_id: `cjob_${version}`,
|
|
68
|
+
agent_id: state.profileByVersion.get(version),
|
|
69
|
+
config_version_id: version,
|
|
70
|
+
is_valid: true,
|
|
71
|
+
validation_report_json: "{}",
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (route === "POST /v1/workspaces/ws_123/harness/activate") {
|
|
77
|
+
const body = JSON.parse(String(options.body || "{}"));
|
|
78
|
+
state.activeProfileVersion = body.config_version_id;
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
status: 200,
|
|
82
|
+
text: async () => JSON.stringify({
|
|
83
|
+
workspace_id: "ws_123",
|
|
84
|
+
agent_id: state.profileByVersion.get(state.activeProfileVersion),
|
|
85
|
+
config_version_id: state.activeProfileVersion,
|
|
86
|
+
run_snapshot_version: state.activeProfileVersion,
|
|
87
|
+
activated_by: body.activated_by || "zombiectl",
|
|
88
|
+
activated_at: 1730000000,
|
|
89
|
+
}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (route === "GET /v1/workspaces/ws_123/harness/active") {
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
status: 200,
|
|
97
|
+
text: async () => JSON.stringify({
|
|
98
|
+
workspace_id: "ws_123",
|
|
99
|
+
source: state.activeProfileVersion ? "active" : "default-v1",
|
|
100
|
+
agent_id: state.activeProfileVersion ? state.profileByVersion.get(state.activeProfileVersion) : null,
|
|
101
|
+
config_version_id: state.activeProfileVersion,
|
|
102
|
+
run_snapshot_version: state.activeProfileVersion,
|
|
103
|
+
active_at: state.activeProfileVersion ? 1730000000 : null,
|
|
104
|
+
profile: { profile_id: state.activeProfileVersion || "default-v1", stages: [] },
|
|
105
|
+
}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (route === "POST /v1/runs") {
|
|
110
|
+
state.runCounter += 1;
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
status: 202,
|
|
114
|
+
text: async () => JSON.stringify({
|
|
115
|
+
run_id: `r_${state.runCounter}`,
|
|
116
|
+
state: "SPEC_QUEUED",
|
|
117
|
+
attempt: 1,
|
|
118
|
+
run_snapshot_version: state.activeProfileVersion,
|
|
119
|
+
request_id: `req_${state.runCounter}`,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error(`unexpected route: ${route}`);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const env = { ...process.env, ZOMBIE_TOKEN: "header.payload.sig" };
|
|
129
|
+
|
|
130
|
+
assert.equal(
|
|
131
|
+
await runCli(["harness", "source", "put", "--workspace-id", "ws_123", "--file", filePath], {
|
|
132
|
+
env,
|
|
133
|
+
stdout: out.stream,
|
|
134
|
+
stderr: err.stream,
|
|
135
|
+
fetchImpl,
|
|
136
|
+
}),
|
|
137
|
+
0,
|
|
138
|
+
);
|
|
139
|
+
assert.equal(
|
|
140
|
+
await runCli(["harness", "compile", "--workspace-id", "ws_123", "--config-version-id", "pver_1"], {
|
|
141
|
+
env,
|
|
142
|
+
stdout: out.stream,
|
|
143
|
+
stderr: err.stream,
|
|
144
|
+
fetchImpl,
|
|
145
|
+
}),
|
|
146
|
+
0,
|
|
147
|
+
);
|
|
148
|
+
assert.equal(
|
|
149
|
+
await runCli(["harness", "activate", "--workspace-id", "ws_123", "--config-version-id", "pver_1"], {
|
|
150
|
+
env,
|
|
151
|
+
stdout: out.stream,
|
|
152
|
+
stderr: err.stream,
|
|
153
|
+
fetchImpl,
|
|
154
|
+
}),
|
|
155
|
+
0,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
out.reset();
|
|
159
|
+
|
|
160
|
+
assert.equal(
|
|
161
|
+
await runCli([
|
|
162
|
+
"--json",
|
|
163
|
+
"run",
|
|
164
|
+
"--workspace-id",
|
|
165
|
+
"ws_123",
|
|
166
|
+
"--spec-id",
|
|
167
|
+
"spec_123",
|
|
168
|
+
"--idempotency-key",
|
|
169
|
+
"idem_1",
|
|
170
|
+
], {
|
|
171
|
+
env,
|
|
172
|
+
stdout: out.stream,
|
|
173
|
+
stderr: err.stream,
|
|
174
|
+
fetchImpl,
|
|
175
|
+
}),
|
|
176
|
+
0,
|
|
177
|
+
);
|
|
178
|
+
const firstRun = parseJsonOutput(out.read());
|
|
179
|
+
assert.equal(firstRun.run_snapshot_version, "pver_1");
|
|
180
|
+
|
|
181
|
+
out.reset();
|
|
182
|
+
assert.equal(
|
|
183
|
+
await runCli(["harness", "source", "put", "--workspace-id", "ws_123", "--file", filePath], {
|
|
184
|
+
env,
|
|
185
|
+
stdout: out.stream,
|
|
186
|
+
stderr: err.stream,
|
|
187
|
+
fetchImpl,
|
|
188
|
+
}),
|
|
189
|
+
0,
|
|
190
|
+
);
|
|
191
|
+
assert.equal(
|
|
192
|
+
await runCli(["harness", "compile", "--workspace-id", "ws_123", "--config-version-id", "pver_2"], {
|
|
193
|
+
env,
|
|
194
|
+
stdout: out.stream,
|
|
195
|
+
stderr: err.stream,
|
|
196
|
+
fetchImpl,
|
|
197
|
+
}),
|
|
198
|
+
0,
|
|
199
|
+
);
|
|
200
|
+
assert.equal(
|
|
201
|
+
await runCli(["harness", "activate", "--workspace-id", "ws_123", "--config-version-id", "pver_2"], {
|
|
202
|
+
env,
|
|
203
|
+
stdout: out.stream,
|
|
204
|
+
stderr: err.stream,
|
|
205
|
+
fetchImpl,
|
|
206
|
+
}),
|
|
207
|
+
0,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
out.reset();
|
|
211
|
+
assert.equal(
|
|
212
|
+
await runCli([
|
|
213
|
+
"--json",
|
|
214
|
+
"run",
|
|
215
|
+
"--workspace-id",
|
|
216
|
+
"ws_123",
|
|
217
|
+
"--spec-id",
|
|
218
|
+
"spec_123",
|
|
219
|
+
"--idempotency-key",
|
|
220
|
+
"idem_2",
|
|
221
|
+
], {
|
|
222
|
+
env,
|
|
223
|
+
stdout: out.stream,
|
|
224
|
+
stderr: err.stream,
|
|
225
|
+
fetchImpl,
|
|
226
|
+
}),
|
|
227
|
+
0,
|
|
228
|
+
);
|
|
229
|
+
const secondRun = parseJsonOutput(out.read());
|
|
230
|
+
|
|
231
|
+
assert.equal(secondRun.run_snapshot_version, "pver_2");
|
|
232
|
+
assert.notEqual(firstRun.run_snapshot_version, secondRun.run_snapshot_version);
|
|
233
|
+
assert.equal(err.read(), "");
|
|
234
|
+
} finally {
|
|
235
|
+
if (prevStateDir === undefined) delete process.env.ZOMBIE_STATE_DIR;
|
|
236
|
+
else process.env.ZOMBIE_STATE_DIR = prevStateDir;
|
|
237
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
238
|
+
}
|
|
239
|
+
}, 15000);
|
|
240
|
+
|
|
241
|
+
test("harness lifecycle contract: API and CLI JSON expose profile identity parity fields", async () => {
|
|
242
|
+
const out = bufferStream();
|
|
243
|
+
const err = bufferStream();
|
|
244
|
+
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "zombiectl-harness-parity-"));
|
|
245
|
+
const prevStateDir = process.env.ZOMBIE_STATE_DIR;
|
|
246
|
+
process.env.ZOMBIE_STATE_DIR = stateDir;
|
|
247
|
+
|
|
248
|
+
const fetchImpl = async (url, options = {}) => {
|
|
249
|
+
const u = new URL(url);
|
|
250
|
+
const route = `${options.method || "GET"} ${u.pathname}`;
|
|
251
|
+
|
|
252
|
+
if (route === "POST /v1/workspaces/ws_123/harness/activate") {
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
status: 200,
|
|
256
|
+
text: async () => JSON.stringify({
|
|
257
|
+
workspace_id: "ws_123",
|
|
258
|
+
agent_id: "ws_123-harness",
|
|
259
|
+
config_version_id: "pver_2",
|
|
260
|
+
run_snapshot_version: "pver_2",
|
|
261
|
+
activated_by: "operator",
|
|
262
|
+
activated_at: 1730000100,
|
|
263
|
+
}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (route === "GET /v1/workspaces/ws_123/harness/active") {
|
|
268
|
+
return {
|
|
269
|
+
ok: true,
|
|
270
|
+
status: 200,
|
|
271
|
+
text: async () => JSON.stringify({
|
|
272
|
+
workspace_id: "ws_123",
|
|
273
|
+
source: "active",
|
|
274
|
+
agent_id: "ws_123-harness",
|
|
275
|
+
config_version_id: "pver_2",
|
|
276
|
+
run_snapshot_version: "pver_2",
|
|
277
|
+
active_at: 1730000100,
|
|
278
|
+
profile: { profile_id: "ws_123-harness", stages: [] },
|
|
279
|
+
}),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
throw new Error(`unexpected route: ${route}`);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const env = { ...process.env, ZOMBIE_TOKEN: "header.payload.sig" };
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
assert.equal(
|
|
290
|
+
await runCli(
|
|
291
|
+
[
|
|
292
|
+
"--json",
|
|
293
|
+
"harness",
|
|
294
|
+
"activate",
|
|
295
|
+
"--workspace-id",
|
|
296
|
+
"ws_123",
|
|
297
|
+
"--config-version-id",
|
|
298
|
+
"pver_2",
|
|
299
|
+
"--activated-by",
|
|
300
|
+
"operator",
|
|
301
|
+
],
|
|
302
|
+
{
|
|
303
|
+
env,
|
|
304
|
+
stdout: out.stream,
|
|
305
|
+
stderr: err.stream,
|
|
306
|
+
fetchImpl,
|
|
307
|
+
},
|
|
308
|
+
),
|
|
309
|
+
0,
|
|
310
|
+
);
|
|
311
|
+
const activatePayload = parseJsonOutput(out.read());
|
|
312
|
+
assert.equal(activatePayload.agent_id, "ws_123-harness");
|
|
313
|
+
assert.equal(activatePayload.config_version_id, "pver_2");
|
|
314
|
+
assert.equal(activatePayload.run_snapshot_version, "pver_2");
|
|
315
|
+
assert.equal(activatePayload.activated_at, 1730000100);
|
|
316
|
+
|
|
317
|
+
out.reset();
|
|
318
|
+
|
|
319
|
+
assert.equal(
|
|
320
|
+
await runCli(["--json", "harness", "active", "--workspace-id", "ws_123"], {
|
|
321
|
+
env,
|
|
322
|
+
stdout: out.stream,
|
|
323
|
+
stderr: err.stream,
|
|
324
|
+
fetchImpl,
|
|
325
|
+
}),
|
|
326
|
+
0,
|
|
327
|
+
);
|
|
328
|
+
const activePayload = parseJsonOutput(out.read());
|
|
329
|
+
assert.equal(activePayload.agent_id, "ws_123-harness");
|
|
330
|
+
assert.equal(activePayload.config_version_id, "pver_2");
|
|
331
|
+
assert.equal(activePayload.run_snapshot_version, "pver_2");
|
|
332
|
+
assert.equal(activePayload.active_at, 1730000100);
|
|
333
|
+
assert.equal(err.read(), "");
|
|
334
|
+
} finally {
|
|
335
|
+
if (prevStateDir === undefined) delete process.env.ZOMBIE_STATE_DIR;
|
|
336
|
+
else process.env.ZOMBIE_STATE_DIR = prevStateDir;
|
|
337
|
+
await fs.rm(stateDir, { recursive: true, force: true });
|
|
338
|
+
}
|
|
339
|
+
}, 15000);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { test } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { Writable } from "node:stream";
|
|
7
|
+
import { runCli } from "../src/cli.js";
|
|
8
|
+
|
|
9
|
+
function bufferStream() {
|
|
10
|
+
let data = "";
|
|
11
|
+
return {
|
|
12
|
+
stream: new Writable({
|
|
13
|
+
write(chunk, _enc, cb) {
|
|
14
|
+
data += String(chunk);
|
|
15
|
+
cb();
|
|
16
|
+
},
|
|
17
|
+
}),
|
|
18
|
+
read: () => data,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("harness source put uploads markdown file content as source_markdown", async () => {
|
|
23
|
+
const out = bufferStream();
|
|
24
|
+
const err = bufferStream();
|
|
25
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "zombiectl-harness-"));
|
|
26
|
+
const filePath = path.join(tmpDir, "agent-profile.md");
|
|
27
|
+
const markdown = "# Harness\n\n```json\n{\"profile_id\":\"ws_123-harness\",\"stages\":[]}\n```";
|
|
28
|
+
await fs.writeFile(filePath, markdown, "utf8");
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const fetchImpl = async (url, options) => {
|
|
32
|
+
assert.equal(url, "http://localhost:3000/v1/workspaces/ws_123/harness/source");
|
|
33
|
+
assert.equal(options.method, "PUT");
|
|
34
|
+
const payload = JSON.parse(String(options.body));
|
|
35
|
+
assert.equal(payload.agent_id, "ws_123-harness");
|
|
36
|
+
assert.equal(payload.name, "agent-profile");
|
|
37
|
+
assert.equal(payload.source_markdown, markdown);
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
status: 200,
|
|
41
|
+
text: async () => JSON.stringify({ config_version_id: "pver_123" }),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const code = await runCli(
|
|
46
|
+
[
|
|
47
|
+
"harness",
|
|
48
|
+
"source",
|
|
49
|
+
"put",
|
|
50
|
+
"--workspace-id",
|
|
51
|
+
"ws_123",
|
|
52
|
+
"--file",
|
|
53
|
+
filePath,
|
|
54
|
+
"--agent-id",
|
|
55
|
+
"ws_123-harness",
|
|
56
|
+
],
|
|
57
|
+
{
|
|
58
|
+
env: { ...process.env, ZOMBIE_TOKEN: "header.payload.sig" },
|
|
59
|
+
stdout: out.stream,
|
|
60
|
+
stderr: err.stream,
|
|
61
|
+
fetchImpl,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
assert.equal(code, 0);
|
|
66
|
+
assert.equal(err.read(), "");
|
|
67
|
+
assert.match(out.read(), /Harness source stored/);
|
|
68
|
+
assert.match(out.read(), /config_version_id[\s·]+pver_123/);
|
|
69
|
+
} finally {
|
|
70
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { test } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { commandHarnessActivate } from "../src/commands/harness_activate.js";
|
|
4
|
+
import { makeNoop, makeBufferStream, ui, PVER_ID } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
test("commandHarnessActivate returns 2 when profile-version-id is missing", async () => {
|
|
7
|
+
const err = makeBufferStream();
|
|
8
|
+
const deps = { ui, writeLine: (stream, line = "") => stream.write(`${line}\n`) };
|
|
9
|
+
const parsed = { options: {}, positionals: [] };
|
|
10
|
+
const code = await commandHarnessActivate({ stdout: makeNoop(), stderr: err.stream, jsonMode: false }, parsed, "ws_123", deps);
|
|
11
|
+
assert.equal(code, 2);
|
|
12
|
+
assert.match(err.read(), /--config-version-id/);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("commandHarnessActivate sends profile_version_id and activated_by", async () => {
|
|
16
|
+
let captured = null;
|
|
17
|
+
const deps = {
|
|
18
|
+
request: async (_ctx, reqPath, options) => {
|
|
19
|
+
captured = { reqPath, options };
|
|
20
|
+
return { agent_id: "agent_1", config_version_id: PVER_ID, run_snapshot_version: PVER_ID };
|
|
21
|
+
},
|
|
22
|
+
apiHeaders: () => ({}),
|
|
23
|
+
ui,
|
|
24
|
+
printJson: () => {},
|
|
25
|
+
writeLine: () => {},
|
|
26
|
+
};
|
|
27
|
+
const parsed = { options: { "config-version-id": PVER_ID, "activated-by": "operator" }, positionals: [] };
|
|
28
|
+
const code = await commandHarnessActivate({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: false }, parsed, "ws_123", deps);
|
|
29
|
+
assert.equal(code, 0);
|
|
30
|
+
assert.equal(captured.reqPath, "/v1/workspaces/ws_123/harness/activate");
|
|
31
|
+
const body = JSON.parse(captured.options.body);
|
|
32
|
+
assert.equal(body.config_version_id, PVER_ID);
|
|
33
|
+
assert.equal(body.activated_by, "operator");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("commandHarnessActivate defaults activated_by to zombiectl", async () => {
|
|
37
|
+
let body = null;
|
|
38
|
+
const deps = {
|
|
39
|
+
request: async (_ctx, _p, options) => { body = JSON.parse(options.body); return {}; },
|
|
40
|
+
apiHeaders: () => ({}),
|
|
41
|
+
ui,
|
|
42
|
+
printJson: () => {},
|
|
43
|
+
writeLine: () => {},
|
|
44
|
+
};
|
|
45
|
+
const parsed = { options: { "config-version-id": PVER_ID }, positionals: [] };
|
|
46
|
+
await commandHarnessActivate({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: false }, parsed, "ws_123", deps);
|
|
47
|
+
assert.equal(body.activated_by, "zombiectl");
|
|
48
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { test } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { commandHarnessActive } from "../src/commands/harness_active.js";
|
|
4
|
+
import { makeNoop, makeBufferStream, ui, PVER_ID } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
test("commandHarnessActive calls GET harness/active", async () => {
|
|
7
|
+
let captured = null;
|
|
8
|
+
const deps = {
|
|
9
|
+
request: async (_ctx, reqPath, options) => {
|
|
10
|
+
captured = { reqPath, options };
|
|
11
|
+
return { agent_id: "agent_1", config_version_id: PVER_ID, run_snapshot_version: PVER_ID };
|
|
12
|
+
},
|
|
13
|
+
apiHeaders: () => ({}),
|
|
14
|
+
ui,
|
|
15
|
+
printJson: () => {},
|
|
16
|
+
writeLine: () => {},
|
|
17
|
+
};
|
|
18
|
+
const parsed = { options: {}, positionals: [] };
|
|
19
|
+
const code = await commandHarnessActive({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: false }, parsed, "ws_123", deps);
|
|
20
|
+
assert.equal(code, 0);
|
|
21
|
+
assert.equal(captured.reqPath, "/v1/workspaces/ws_123/harness/active");
|
|
22
|
+
assert.equal(captured.options.method, "GET");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("commandHarnessActive json mode outputs raw response", async () => {
|
|
26
|
+
let printed = null;
|
|
27
|
+
const deps = {
|
|
28
|
+
request: async () => ({ agent_id: "agent_1", config_version_id: PVER_ID, run_snapshot_version: PVER_ID }),
|
|
29
|
+
apiHeaders: () => ({}),
|
|
30
|
+
ui,
|
|
31
|
+
printJson: (_stream, v) => { printed = v; },
|
|
32
|
+
writeLine: () => {},
|
|
33
|
+
};
|
|
34
|
+
const parsed = { options: {}, positionals: [] };
|
|
35
|
+
const code = await commandHarnessActive({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: true }, parsed, "ws_123", deps);
|
|
36
|
+
assert.equal(code, 0);
|
|
37
|
+
assert.equal(printed.config_version_id, PVER_ID);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("commandHarnessActive falls back to default-v1 for null fields", async () => {
|
|
41
|
+
let output = "";
|
|
42
|
+
const deps = {
|
|
43
|
+
request: async () => ({ agent_id: null, config_version_id: null, run_snapshot_version: null }),
|
|
44
|
+
apiHeaders: () => ({}),
|
|
45
|
+
ui,
|
|
46
|
+
printJson: () => {},
|
|
47
|
+
printSection: (_stream, title) => { output += title; },
|
|
48
|
+
printKeyValue: (_stream, rows) => { output += Object.values(rows).join(" "); },
|
|
49
|
+
};
|
|
50
|
+
const parsed = { options: {}, positionals: [] };
|
|
51
|
+
await commandHarnessActive({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: false }, parsed, "ws_123", deps);
|
|
52
|
+
assert.match(output, /default-v1/);
|
|
53
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { test } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { commandHarnessCompile } from "../src/commands/harness_compile.js";
|
|
4
|
+
import { makeNoop, ui, AGENT_ID, PVER_ID } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
test("commandHarnessCompile sends profile_version_id", async () => {
|
|
7
|
+
let captured = null;
|
|
8
|
+
const deps = {
|
|
9
|
+
request: async (_ctx, reqPath, options) => { captured = { reqPath, options }; return { compile_job_id: "cjob_1", is_valid: true }; },
|
|
10
|
+
apiHeaders: () => ({}),
|
|
11
|
+
ui,
|
|
12
|
+
printJson: () => {},
|
|
13
|
+
writeLine: () => {},
|
|
14
|
+
};
|
|
15
|
+
const parsed = { options: { "config-version-id": PVER_ID }, positionals: [] };
|
|
16
|
+
const code = await commandHarnessCompile({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: false }, parsed, "ws_123", deps);
|
|
17
|
+
assert.equal(code, 0);
|
|
18
|
+
assert.equal(captured.reqPath, "/v1/workspaces/ws_123/harness/compile");
|
|
19
|
+
const body = JSON.parse(captured.options.body);
|
|
20
|
+
assert.equal(body.config_version_id, PVER_ID);
|
|
21
|
+
assert.equal(body.agent_id, null);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("commandHarnessCompile sends profile_id selector", async () => {
|
|
25
|
+
let captured = null;
|
|
26
|
+
const deps = {
|
|
27
|
+
request: async (_ctx, reqPath, options) => { captured = { reqPath, options }; return { compile_job_id: "cjob_2", is_valid: true }; },
|
|
28
|
+
apiHeaders: () => ({}),
|
|
29
|
+
ui,
|
|
30
|
+
printJson: () => {},
|
|
31
|
+
writeLine: () => {},
|
|
32
|
+
};
|
|
33
|
+
const parsed = { options: { "agent-id": AGENT_ID }, positionals: [] };
|
|
34
|
+
const code = await commandHarnessCompile({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: false }, parsed, "ws_123", deps);
|
|
35
|
+
assert.equal(code, 0);
|
|
36
|
+
const body = JSON.parse(captured.options.body);
|
|
37
|
+
assert.equal(body.agent_id, AGENT_ID);
|
|
38
|
+
assert.equal(body.config_version_id, null);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("commandHarnessCompile json mode outputs raw response", async () => {
|
|
42
|
+
let printed = null;
|
|
43
|
+
const deps = {
|
|
44
|
+
request: async () => ({ compile_job_id: "cjob_3", is_valid: false }),
|
|
45
|
+
apiHeaders: () => ({}),
|
|
46
|
+
ui,
|
|
47
|
+
printJson: (_stream, v) => { printed = v; },
|
|
48
|
+
writeLine: () => {},
|
|
49
|
+
};
|
|
50
|
+
const parsed = { options: {}, positionals: [] };
|
|
51
|
+
const code = await commandHarnessCompile({ stdout: makeNoop(), stderr: makeNoop(), jsonMode: true }, parsed, "ws_123", deps);
|
|
52
|
+
assert.equal(code, 0);
|
|
53
|
+
assert.equal(printed.compile_job_id, "cjob_3");
|
|
54
|
+
});
|