vibe-coding-master 0.8.2 → 0.8.4
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 +7 -9
- package/dist/backend/api/task-routes.js +2 -31
- package/dist/backend/cli/install-vcm-harness.js +0 -7
- package/dist/backend/server.js +4 -10
- package/dist/backend/services/artifact-service.js +14 -0
- package/dist/backend/services/claude-hook-service.js +6 -102
- package/dist/backend/services/harness-service.js +0 -7
- package/dist/backend/services/pm-user-question.js +62 -0
- package/dist/backend/services/runtime-coordinator-service.js +0 -10
- package/dist/backend/services/session-service.js +3 -70
- package/dist/backend/services/workflow-control-service.js +73 -33
- package/dist/backend/templates/harness/project-manager-agent.js +9 -3
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +7 -1
- package/dist-frontend/assets/{index-BDUbu5St.js → index-ClaVB8jM.js} +45 -45
- package/dist-frontend/assets/{index-DDmygnV6.css → index-Dc0_KSf4.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +4 -1
- package/scripts/harness-tools/run-long-check +90 -43
- package/dist/backend/services/role-stall-detector-service.js +0 -322
- package/dist/shared/types/role-stall.js +0 -1
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { isVcmRoleName } from "../../shared/constants.js";
|
|
2
|
-
import { VcmError } from "../errors.js";
|
|
3
|
-
export const ROLE_MODEL_STALL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
4
|
-
export const ROLE_TOOL_STALL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
5
|
-
export const ROLE_SUBAGENT_STALL_TIMEOUT_MS = 30 * 60 * 1000;
|
|
6
|
-
const TOOL_TIMEOUT_GRACE_MS = 30_000;
|
|
7
|
-
export function createRoleStallDetectorService(deps) {
|
|
8
|
-
const modelTimeoutMs = deps.modelTimeoutMs ?? ROLE_MODEL_STALL_TIMEOUT_MS;
|
|
9
|
-
const toolTimeoutMs = deps.toolTimeoutMs ?? ROLE_TOOL_STALL_TIMEOUT_MS;
|
|
10
|
-
const subagentTimeoutMs = deps.subagentTimeoutMs ?? ROLE_SUBAGENT_STALL_TIMEOUT_MS;
|
|
11
|
-
const now = deps.now ?? (() => new Date().toISOString());
|
|
12
|
-
const setTimer = deps.setTimeout ?? ((callback, delayMs) => globalThis.setTimeout(callback, delayMs));
|
|
13
|
-
const clearTimer = deps.clearTimeout ?? ((timer) => globalThis.clearTimeout(timer));
|
|
14
|
-
const monitors = new Map();
|
|
15
|
-
return {
|
|
16
|
-
async recordHook(input) {
|
|
17
|
-
if (!isVcmRoleName(input.role)) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const key = monitorKey(input.repoRoot, input.taskSlug);
|
|
21
|
-
if (input.eventName === "Stop" || input.eventName === "StopFailure") {
|
|
22
|
-
clearMonitor(key);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const [session, round] = await Promise.all([
|
|
26
|
-
deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, input.role),
|
|
27
|
-
deps.roundService.getSessionRoundState(roundInput(input))
|
|
28
|
-
]);
|
|
29
|
-
if (!session
|
|
30
|
-
|| session.status !== "running"
|
|
31
|
-
|| session.activityStatus !== "running"
|
|
32
|
-
|| round.status !== "running"
|
|
33
|
-
|| round.activeRole !== input.role
|
|
34
|
-
|| !round.roundId) {
|
|
35
|
-
clearMonitor(key);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
let monitor = monitors.get(key);
|
|
39
|
-
if (!monitor
|
|
40
|
-
|| monitor.sessionId !== session.id
|
|
41
|
-
|| monitor.runtimeSessionToken !== session.runtimeSessionToken
|
|
42
|
-
|| monitor.roundId !== round.roundId
|
|
43
|
-
|| monitor.role !== input.role) {
|
|
44
|
-
clearMonitor(key);
|
|
45
|
-
monitor = {
|
|
46
|
-
context: taskInput(input),
|
|
47
|
-
role: input.role,
|
|
48
|
-
sessionId: session.id,
|
|
49
|
-
runtimeSessionToken: session.runtimeSessionToken,
|
|
50
|
-
roundId: round.roundId,
|
|
51
|
-
phase: "awaiting-model",
|
|
52
|
-
phaseStartedAt: now(),
|
|
53
|
-
generation: 0,
|
|
54
|
-
activeTools: new Map()
|
|
55
|
-
};
|
|
56
|
-
monitors.set(key, monitor);
|
|
57
|
-
}
|
|
58
|
-
advanceMonitor(key, monitor, input.eventName, input.event);
|
|
59
|
-
},
|
|
60
|
-
async reconcileTask(input) {
|
|
61
|
-
const key = monitorKey(input.repoRoot, input.taskSlug);
|
|
62
|
-
for (const [candidateKey, monitor] of monitors) {
|
|
63
|
-
if (monitor.context.repoRoot === input.repoRoot && candidateKey !== key) {
|
|
64
|
-
clearMonitor(candidateKey);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const monitor = monitors.get(key);
|
|
68
|
-
if (!monitor) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const [session, round] = await Promise.all([
|
|
72
|
-
deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, monitor.role),
|
|
73
|
-
deps.roundService.getSessionRoundState(roundInput(input))
|
|
74
|
-
]);
|
|
75
|
-
if (!session
|
|
76
|
-
|| session.id !== monitor.sessionId
|
|
77
|
-
|| session.runtimeSessionToken !== monitor.runtimeSessionToken
|
|
78
|
-
|| session.status !== "running"
|
|
79
|
-
|| session.activityStatus !== "running"
|
|
80
|
-
|| round.status !== "running"
|
|
81
|
-
|| round.activeRole !== monitor.role
|
|
82
|
-
|| round.roundId !== monitor.roundId) {
|
|
83
|
-
clearMonitor(key);
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
getWarning(repoRoot, taskSlug) {
|
|
87
|
-
const warning = monitors.get(monitorKey(repoRoot, taskSlug))?.warning;
|
|
88
|
-
return warning ? { ...warning } : null;
|
|
89
|
-
},
|
|
90
|
-
ignoreWarning(repoRoot, taskSlug, warningId) {
|
|
91
|
-
const monitor = requireWarning(repoRoot, taskSlug, warningId);
|
|
92
|
-
monitor.ignoredGeneration = monitor.generation;
|
|
93
|
-
monitor.warning = undefined;
|
|
94
|
-
cancelTimer(monitor);
|
|
95
|
-
return { ok: true, warning: null };
|
|
96
|
-
},
|
|
97
|
-
async recoverWarning(input) {
|
|
98
|
-
const key = monitorKey(input.repoRoot, input.taskSlug);
|
|
99
|
-
const monitor = requireWarning(input.repoRoot, input.taskSlug, input.warningId);
|
|
100
|
-
const [session, round] = await Promise.all([
|
|
101
|
-
deps.sessionService.getRoleSession(input.repoRoot, input.taskSlug, monitor.role),
|
|
102
|
-
deps.roundService.getSessionRoundState(roundInput(input))
|
|
103
|
-
]);
|
|
104
|
-
if (!session
|
|
105
|
-
|| session.id !== monitor.sessionId
|
|
106
|
-
|| session.runtimeSessionToken !== monitor.runtimeSessionToken
|
|
107
|
-
|| session.status !== "running"
|
|
108
|
-
|| session.activityStatus !== "running"
|
|
109
|
-
|| round.status !== "running"
|
|
110
|
-
|| round.activeRole !== monitor.role
|
|
111
|
-
|| round.roundId !== monitor.roundId) {
|
|
112
|
-
clearMonitor(key);
|
|
113
|
-
return { ok: true, warning: null };
|
|
114
|
-
}
|
|
115
|
-
await deps.sessionService.recoverRoleSession(input.repoRoot, input.taskSlug, {
|
|
116
|
-
role: monitor.role,
|
|
117
|
-
expectedSessionId: monitor.sessionId,
|
|
118
|
-
expectedRuntimeSessionToken: monitor.runtimeSessionToken,
|
|
119
|
-
recoveryPrompt: renderRecoveryPrompt(monitor)
|
|
120
|
-
});
|
|
121
|
-
clearMonitor(key);
|
|
122
|
-
return { ok: true, warning: null };
|
|
123
|
-
},
|
|
124
|
-
clearProject(repoRoot) {
|
|
125
|
-
for (const [key, monitor] of monitors) {
|
|
126
|
-
if (monitor.context.repoRoot === repoRoot) {
|
|
127
|
-
clearMonitor(key);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
stop() {
|
|
132
|
-
for (const key of [...monitors.keys()]) {
|
|
133
|
-
clearMonitor(key);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
function advanceMonitor(key, monitor, eventName, event) {
|
|
138
|
-
monitor.generation += 1;
|
|
139
|
-
monitor.warning = undefined;
|
|
140
|
-
monitor.ignoredGeneration = undefined;
|
|
141
|
-
cancelTimer(monitor);
|
|
142
|
-
switch (eventName) {
|
|
143
|
-
case "UserPromptSubmit":
|
|
144
|
-
case "PostCompact":
|
|
145
|
-
case "PostToolBatch":
|
|
146
|
-
monitor.activeTools.clear();
|
|
147
|
-
monitor.currentTool = undefined;
|
|
148
|
-
setPhase(key, monitor, "awaiting-model", modelTimeoutMs);
|
|
149
|
-
return;
|
|
150
|
-
case "PreToolUse": {
|
|
151
|
-
const tool = readTool(event, toolTimeoutMs, subagentTimeoutMs);
|
|
152
|
-
const toolKey = tool.toolUseId ?? `anonymous-${monitor.generation}`;
|
|
153
|
-
monitor.activeTools.set(toolKey, tool);
|
|
154
|
-
monitor.currentTool = tool;
|
|
155
|
-
setPhase(key, monitor, tool.toolName === "Agent" ? "subagent-running" : "tool-running", tool.timeoutMs);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
case "SubagentStart":
|
|
159
|
-
setPhase(key, monitor, "subagent-running", subagentTimeoutMs);
|
|
160
|
-
return;
|
|
161
|
-
case "PostToolUse":
|
|
162
|
-
case "PostToolUseFailure": {
|
|
163
|
-
const toolUseId = stringValue(event.tool_use_id);
|
|
164
|
-
if (toolUseId) {
|
|
165
|
-
monitor.activeTools.delete(toolUseId);
|
|
166
|
-
}
|
|
167
|
-
monitor.currentTool = firstTool(monitor.activeTools);
|
|
168
|
-
if (monitor.currentTool) {
|
|
169
|
-
setPhase(key, monitor, monitor.currentTool.toolName === "Agent" ? "subagent-running" : "tool-running", monitor.currentTool.timeoutMs);
|
|
170
|
-
}
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
case "SubagentStop":
|
|
174
|
-
monitor.currentTool = firstTool(monitor.activeTools);
|
|
175
|
-
if (monitor.currentTool) {
|
|
176
|
-
setPhase(key, monitor, monitor.currentTool.toolName === "Agent" ? "subagent-running" : "tool-running", monitor.currentTool.timeoutMs);
|
|
177
|
-
}
|
|
178
|
-
return;
|
|
179
|
-
case "PreCompact":
|
|
180
|
-
monitor.activeTools.clear();
|
|
181
|
-
monitor.currentTool = undefined;
|
|
182
|
-
setPhase(key, monitor, "compacting", modelTimeoutMs);
|
|
183
|
-
return;
|
|
184
|
-
case "PermissionRequest":
|
|
185
|
-
setPhase(key, monitor, "awaiting-permission");
|
|
186
|
-
return;
|
|
187
|
-
default:
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function setPhase(key, monitor, phase, timeoutMs) {
|
|
192
|
-
monitor.phase = phase;
|
|
193
|
-
monitor.phaseStartedAt = now();
|
|
194
|
-
if (timeoutMs === undefined) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
const generation = monitor.generation;
|
|
198
|
-
monitor.timer = setTimer(() => {
|
|
199
|
-
void detectStall(key, generation).catch(() => undefined);
|
|
200
|
-
}, timeoutMs);
|
|
201
|
-
}
|
|
202
|
-
async function detectStall(key, generation) {
|
|
203
|
-
const monitor = monitors.get(key);
|
|
204
|
-
if (!monitor || monitor.generation !== generation || monitor.ignoredGeneration === generation) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const [session, round] = await Promise.all([
|
|
208
|
-
deps.sessionService.getRoleSession(monitor.context.repoRoot, monitor.context.taskSlug, monitor.role),
|
|
209
|
-
deps.roundService.getSessionRoundState(roundInput(monitor.context))
|
|
210
|
-
]);
|
|
211
|
-
if (!session
|
|
212
|
-
|| session.id !== monitor.sessionId
|
|
213
|
-
|| session.runtimeSessionToken !== monitor.runtimeSessionToken
|
|
214
|
-
|| session.status !== "running"
|
|
215
|
-
|| session.activityStatus !== "running"
|
|
216
|
-
|| round.status !== "running"
|
|
217
|
-
|| round.activeRole !== monitor.role
|
|
218
|
-
|| round.roundId !== monitor.roundId) {
|
|
219
|
-
clearMonitor(key);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const detectedAt = now();
|
|
223
|
-
monitor.timer = undefined;
|
|
224
|
-
monitor.warning = {
|
|
225
|
-
id: `${monitor.sessionId}:${monitor.roundId}:${generation}:${detectedAt}`,
|
|
226
|
-
taskSlug: monitor.context.taskSlug,
|
|
227
|
-
role: monitor.role,
|
|
228
|
-
sessionId: monitor.sessionId,
|
|
229
|
-
runtimeSessionToken: monitor.runtimeSessionToken,
|
|
230
|
-
roundId: monitor.roundId,
|
|
231
|
-
phase: monitor.phase,
|
|
232
|
-
phaseStartedAt: monitor.phaseStartedAt,
|
|
233
|
-
detectedAt,
|
|
234
|
-
toolUseId: monitor.currentTool?.toolUseId,
|
|
235
|
-
toolName: monitor.currentTool?.toolName
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
function requireWarning(repoRoot, taskSlug, warningId) {
|
|
239
|
-
const monitor = monitors.get(monitorKey(repoRoot, taskSlug));
|
|
240
|
-
if (!monitor?.warning || monitor.warning.id !== warningId) {
|
|
241
|
-
throw new VcmError({
|
|
242
|
-
code: "ROLE_STALL_WARNING_STALE",
|
|
243
|
-
message: "The role stall warning is no longer active.",
|
|
244
|
-
statusCode: 409,
|
|
245
|
-
hint: "Refresh the task workspace before retrying the action."
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
return monitor;
|
|
249
|
-
}
|
|
250
|
-
function cancelTimer(monitor) {
|
|
251
|
-
if (monitor.timer === undefined) {
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
clearTimer(monitor.timer);
|
|
255
|
-
monitor.timer = undefined;
|
|
256
|
-
}
|
|
257
|
-
function clearMonitor(key) {
|
|
258
|
-
const monitor = monitors.get(key);
|
|
259
|
-
if (!monitor) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
cancelTimer(monitor);
|
|
263
|
-
monitors.delete(key);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function monitorKey(repoRoot, taskSlug) {
|
|
267
|
-
return `${repoRoot}:${taskSlug}`;
|
|
268
|
-
}
|
|
269
|
-
function taskInput(input) {
|
|
270
|
-
return {
|
|
271
|
-
repoRoot: input.repoRoot,
|
|
272
|
-
taskRepoRoot: input.taskRepoRoot,
|
|
273
|
-
stateRoot: input.stateRoot,
|
|
274
|
-
taskSlug: input.taskSlug
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
function roundInput(input) {
|
|
278
|
-
return {
|
|
279
|
-
repoRoot: input.repoRoot,
|
|
280
|
-
stateRepoRoot: input.taskRepoRoot,
|
|
281
|
-
stateRoot: input.stateRoot,
|
|
282
|
-
taskSlug: input.taskSlug
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
function readTool(event, defaultTimeoutMs, subagentTimeoutMs) {
|
|
286
|
-
const toolName = stringValue(event.tool_name);
|
|
287
|
-
const toolUseId = stringValue(event.tool_use_id);
|
|
288
|
-
const input = objectValue(event.tool_input);
|
|
289
|
-
const requestedTimeout = numberValue(input?.timeout);
|
|
290
|
-
return {
|
|
291
|
-
toolName,
|
|
292
|
-
toolUseId,
|
|
293
|
-
timeoutMs: toolName === "Agent"
|
|
294
|
-
? subagentTimeoutMs
|
|
295
|
-
: Math.max(defaultTimeoutMs, requestedTimeout ? requestedTimeout + TOOL_TIMEOUT_GRACE_MS : 0)
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function firstTool(tools) {
|
|
299
|
-
return tools.values().next().value;
|
|
300
|
-
}
|
|
301
|
-
function renderRecoveryPrompt(monitor) {
|
|
302
|
-
const phase = monitor.currentTool?.toolName
|
|
303
|
-
? `${monitor.phase} (${monitor.currentTool.toolName})`
|
|
304
|
-
: monitor.phase;
|
|
305
|
-
return [
|
|
306
|
-
"[VCM Session Recovery]",
|
|
307
|
-
`The previous ${monitor.role} turn may have stalled during ${phase}.`,
|
|
308
|
-
"Continue the current assigned work from the existing Claude session context.",
|
|
309
|
-
"Do not repeat completed work unless it is needed to verify the current state."
|
|
310
|
-
].join("\n");
|
|
311
|
-
}
|
|
312
|
-
function objectValue(value) {
|
|
313
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
314
|
-
? value
|
|
315
|
-
: undefined;
|
|
316
|
-
}
|
|
317
|
-
function stringValue(value) {
|
|
318
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
319
|
-
}
|
|
320
|
-
function numberValue(value) {
|
|
321
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
322
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|