clawspec 1.0.19 → 1.0.21
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 +6 -0
- package/README.zh-CN.md +6 -0
- package/package.json +1 -2
- package/src/bootstrap/state.ts +128 -0
- package/src/dependencies/acpx.ts +6 -0
- package/src/dependencies/openspec.ts +5 -0
- package/src/index.ts +125 -43
- package/src/watchers/manager.ts +69 -1
- package/test/acp-client.test.ts +0 -309
- package/test/acpx-dependency.test.ts +0 -133
- package/test/assistant-journal.test.ts +0 -203
- package/test/command-surface.test.ts +0 -24
- package/test/config.test.ts +0 -77
- package/test/detach-attach.test.ts +0 -98
- package/test/doctor.test.ts +0 -142
- package/test/file-lock.test.ts +0 -88
- package/test/fs-utils.test.ts +0 -22
- package/test/helpers/harness.ts +0 -305
- package/test/helpers.test.ts +0 -108
- package/test/keywords.test.ts +0 -92
- package/test/notifier.test.ts +0 -29
- package/test/openspec-dependency.test.ts +0 -68
- package/test/paths-utils.test.ts +0 -30
- package/test/pause-cancel.test.ts +0 -55
- package/test/planning-journal.test.ts +0 -155
- package/test/plugin-registration.test.ts +0 -35
- package/test/project-memory.test.ts +0 -42
- package/test/proposal.test.ts +0 -24
- package/test/queue-planning.test.ts +0 -322
- package/test/queue-work.test.ts +0 -220
- package/test/recovery.test.ts +0 -603
- package/test/service-archive.test.ts +0 -87
- package/test/shell-command.test.ts +0 -48
- package/test/state-store.test.ts +0 -74
- package/test/tasks-and-checkpoint.test.ts +0 -60
- package/test/use-project.test.ts +0 -67
- package/test/watcher-planning.test.ts +0 -533
- package/test/watcher-work.test.ts +0 -1771
- package/test/worker-command.test.ts +0 -66
- package/test/worker-io-helper.test.ts +0 -97
- package/test/worker-skills.test.ts +0 -12
package/test/queue-work.test.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { buildWorkerSessionKey, createWorkerSessionKey } from "../src/execution/session.ts";
|
|
5
|
-
import { writeUtf8 } from "../src/utils/fs.ts";
|
|
6
|
-
import { createServiceHarness, seedPlanningProject } from "./helpers/harness.ts";
|
|
7
|
-
|
|
8
|
-
test("work queues background implementation", async () => {
|
|
9
|
-
const harness = await createServiceHarness("clawspec-work-queue-");
|
|
10
|
-
const { service, stateStore, watcherManager, repoPath, workspacePath, changeDir } = harness;
|
|
11
|
-
const channelKey = "discord:work-queue:default:main";
|
|
12
|
-
const tasksPath = path.join(changeDir, "tasks.md");
|
|
13
|
-
await writeUtf8(tasksPath, "- [ ] 1.1 Build the demo endpoint\n");
|
|
14
|
-
|
|
15
|
-
harness.openSpec.instructionsApply = async (cwd: string, changeName: string) => ({
|
|
16
|
-
command: `openspec instructions apply --change ${changeName} --json`,
|
|
17
|
-
cwd,
|
|
18
|
-
stdout: "{}",
|
|
19
|
-
stderr: "",
|
|
20
|
-
durationMs: 1,
|
|
21
|
-
parsed: {
|
|
22
|
-
changeName,
|
|
23
|
-
changeDir,
|
|
24
|
-
schemaName: "spec-driven",
|
|
25
|
-
contextFiles: { tasks: tasksPath },
|
|
26
|
-
progress: { total: 1, complete: 0, remaining: 1 },
|
|
27
|
-
tasks: [{ id: "1.1", description: "Build the demo endpoint", done: false }],
|
|
28
|
-
state: "ready",
|
|
29
|
-
instruction: "Implement the remaining task.",
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
await seedPlanningProject(stateStore, channelKey, {
|
|
34
|
-
workspacePath,
|
|
35
|
-
repoPath,
|
|
36
|
-
projectName: "demo-app",
|
|
37
|
-
changeName: "queue-work",
|
|
38
|
-
changeDir,
|
|
39
|
-
phase: "tasks",
|
|
40
|
-
status: "ready",
|
|
41
|
-
planningDirty: false,
|
|
42
|
-
});
|
|
43
|
-
await stateStore.updateProject(channelKey, (current) => ({
|
|
44
|
-
...current,
|
|
45
|
-
boundSessionKey: "agent:main:discord:channel:work-queue",
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
|
-
const result = await service.queueWorkProject(channelKey, "apply");
|
|
49
|
-
const project = await stateStore.getActiveProject(channelKey);
|
|
50
|
-
|
|
51
|
-
assert.match(result.text ?? "", /Execution Queued/);
|
|
52
|
-
assert.equal(project?.status, "armed");
|
|
53
|
-
assert.equal(project?.execution?.action, "work");
|
|
54
|
-
assert.equal(project?.currentTask, "1.1 Build the demo endpoint");
|
|
55
|
-
assert.equal(
|
|
56
|
-
project?.execution?.sessionKey,
|
|
57
|
-
createWorkerSessionKey(project!, {
|
|
58
|
-
workerSlot: "primary",
|
|
59
|
-
workerAgentId: "codex",
|
|
60
|
-
attemptKey: project?.execution?.armedAt,
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
assert.match(project?.execution?.sessionKey ?? "", new RegExp(`^${buildWorkerSessionKey(project!, "primary", "codex")}:`));
|
|
64
|
-
assert.notEqual(project?.execution?.sessionKey, project?.boundSessionKey);
|
|
65
|
-
assert.deepEqual(watcherManager.wakeCalls, [channelKey]);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("before_dispatch intercepts cs-work and queues background implementation directly", async () => {
|
|
69
|
-
const harness = await createServiceHarness("clawspec-work-dispatch-");
|
|
70
|
-
const { service, stateStore, watcherManager, repoPath, workspacePath, changeDir } = harness;
|
|
71
|
-
const channelKey = "discord:work-dispatch:default:main";
|
|
72
|
-
const tasksPath = path.join(changeDir, "tasks.md");
|
|
73
|
-
await writeUtf8(tasksPath, "- [ ] 1.1 Build the demo endpoint\n");
|
|
74
|
-
|
|
75
|
-
harness.openSpec.instructionsApply = async (cwd: string, changeName: string) => ({
|
|
76
|
-
command: `openspec instructions apply --change ${changeName} --json`,
|
|
77
|
-
cwd,
|
|
78
|
-
stdout: "{}",
|
|
79
|
-
stderr: "",
|
|
80
|
-
durationMs: 1,
|
|
81
|
-
parsed: {
|
|
82
|
-
changeName,
|
|
83
|
-
changeDir,
|
|
84
|
-
schemaName: "spec-driven",
|
|
85
|
-
contextFiles: { tasks: tasksPath },
|
|
86
|
-
progress: { total: 1, complete: 0, remaining: 1 },
|
|
87
|
-
tasks: [{ id: "1.1", description: "Build the demo endpoint", done: false }],
|
|
88
|
-
state: "ready",
|
|
89
|
-
instruction: "Implement the remaining task.",
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
await seedPlanningProject(stateStore, channelKey, {
|
|
94
|
-
workspacePath,
|
|
95
|
-
repoPath,
|
|
96
|
-
projectName: "demo-app",
|
|
97
|
-
changeName: "queue-work",
|
|
98
|
-
changeDir,
|
|
99
|
-
phase: "tasks",
|
|
100
|
-
status: "ready",
|
|
101
|
-
planningDirty: false,
|
|
102
|
-
});
|
|
103
|
-
await stateStore.updateProject(channelKey, (current) => ({
|
|
104
|
-
...current,
|
|
105
|
-
boundSessionKey: "agent:main:discord:channel:work-dispatch",
|
|
106
|
-
}));
|
|
107
|
-
|
|
108
|
-
const result = await service.handleBeforeDispatch(
|
|
109
|
-
{ content: "cs-work", channel: "discord" },
|
|
110
|
-
{
|
|
111
|
-
channelId: "work-dispatch",
|
|
112
|
-
accountId: "default",
|
|
113
|
-
conversationId: "main",
|
|
114
|
-
sessionKey: "agent:main:discord:channel:work-dispatch",
|
|
115
|
-
},
|
|
116
|
-
);
|
|
117
|
-
const project = await stateStore.getActiveProject(channelKey);
|
|
118
|
-
|
|
119
|
-
assert.equal(result?.handled, true);
|
|
120
|
-
assert.match(result?.text ?? "", /Execution Queued/);
|
|
121
|
-
assert.equal(project?.status, "armed");
|
|
122
|
-
assert.equal(project?.execution?.action, "work");
|
|
123
|
-
assert.deepEqual(watcherManager.wakeCalls, [channelKey]);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("main chat agent end does not clear a background worker run", async () => {
|
|
127
|
-
const harness = await createServiceHarness("clawspec-work-session-");
|
|
128
|
-
const { service, stateStore, repoPath, workspacePath, changeDir } = harness;
|
|
129
|
-
const channelKey = "discord:work-session:default:main";
|
|
130
|
-
const workerSessionKey = "clawspec:worker-session";
|
|
131
|
-
const boundSessionKey = "agent:main:discord:channel:work-session";
|
|
132
|
-
|
|
133
|
-
await seedPlanningProject(stateStore, channelKey, {
|
|
134
|
-
workspacePath,
|
|
135
|
-
repoPath,
|
|
136
|
-
projectName: "demo-app",
|
|
137
|
-
changeName: "queue-work",
|
|
138
|
-
changeDir,
|
|
139
|
-
phase: "implementing",
|
|
140
|
-
status: "running",
|
|
141
|
-
planningDirty: false,
|
|
142
|
-
execution: { action: "work", state: "running", mode: "apply" },
|
|
143
|
-
});
|
|
144
|
-
await stateStore.updateProject(channelKey, (current) => ({
|
|
145
|
-
...current,
|
|
146
|
-
boundSessionKey,
|
|
147
|
-
latestSummary: "Worker is running in the background.",
|
|
148
|
-
execution: current.execution
|
|
149
|
-
? {
|
|
150
|
-
...current.execution,
|
|
151
|
-
sessionKey: workerSessionKey,
|
|
152
|
-
workerAgentId: "codex",
|
|
153
|
-
workerSlot: "primary",
|
|
154
|
-
}
|
|
155
|
-
: current.execution,
|
|
156
|
-
}));
|
|
157
|
-
|
|
158
|
-
await service.handleAgentEnd(
|
|
159
|
-
{ messages: [], success: true },
|
|
160
|
-
{ sessionKey: boundSessionKey, trigger: "user" },
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const project = await stateStore.getActiveProject(channelKey);
|
|
164
|
-
assert.equal(project?.status, "running");
|
|
165
|
-
assert.equal(project?.execution?.state, "running");
|
|
166
|
-
assert.equal(project?.execution?.sessionKey, workerSessionKey);
|
|
167
|
-
assert.equal(project?.latestSummary, "Worker is running in the background.");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("work requires OpenClaw ACP default agent when no project override is set", async () => {
|
|
171
|
-
const harness = await createServiceHarness("clawspec-work-missing-acp-");
|
|
172
|
-
const { service, stateStore, watcherManager, repoPath, workspacePath, changeDir } = harness;
|
|
173
|
-
const channelKey = "discord:work-missing-acp:default:main";
|
|
174
|
-
const tasksPath = path.join(changeDir, "tasks.md");
|
|
175
|
-
await writeUtf8(tasksPath, "- [ ] 1.1 Build the demo endpoint\n");
|
|
176
|
-
|
|
177
|
-
(service as any).config = {
|
|
178
|
-
acp: {
|
|
179
|
-
backend: "acpx",
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
harness.openSpec.instructionsApply = async (cwd: string, changeName: string) => ({
|
|
184
|
-
command: `openspec instructions apply --change ${changeName} --json`,
|
|
185
|
-
cwd,
|
|
186
|
-
stdout: "{}",
|
|
187
|
-
stderr: "",
|
|
188
|
-
durationMs: 1,
|
|
189
|
-
parsed: {
|
|
190
|
-
changeName,
|
|
191
|
-
changeDir,
|
|
192
|
-
schemaName: "spec-driven",
|
|
193
|
-
contextFiles: { tasks: tasksPath },
|
|
194
|
-
progress: { total: 1, complete: 0, remaining: 1 },
|
|
195
|
-
tasks: [{ id: "1.1", description: "Build the demo endpoint", done: false }],
|
|
196
|
-
state: "ready",
|
|
197
|
-
instruction: "Implement the remaining task.",
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await seedPlanningProject(stateStore, channelKey, {
|
|
202
|
-
workspacePath,
|
|
203
|
-
repoPath,
|
|
204
|
-
projectName: "demo-app",
|
|
205
|
-
changeName: "queue-work",
|
|
206
|
-
changeDir,
|
|
207
|
-
phase: "tasks",
|
|
208
|
-
status: "ready",
|
|
209
|
-
planningDirty: false,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const result = await service.queueWorkProject(channelKey, "apply");
|
|
213
|
-
const project = await stateStore.getActiveProject(channelKey);
|
|
214
|
-
|
|
215
|
-
assert.match(result.text ?? "", /Worker Setup Required/);
|
|
216
|
-
assert.match(result.text ?? "", /openclaw config set acp\.backend acpx/);
|
|
217
|
-
assert.match(result.text ?? "", /openclaw config set acp\.defaultAgent codex/);
|
|
218
|
-
assert.equal(project?.status, "ready");
|
|
219
|
-
assert.deepEqual(watcherManager.wakeCalls, []);
|
|
220
|
-
});
|