vibe-coding-master 0.6.22 → 0.7.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 +37 -3
- package/dist/backend/adapters/filesystem.js +8 -0
- package/dist/backend/api/harness-routes.js +54 -0
- package/dist/backend/api/runtime-state-routes.js +6 -2
- package/dist/backend/api/task-routes.js +2 -32
- package/dist/backend/cli/install-vcm-harness.js +1 -1
- package/dist/backend/gateway/gateway-service.js +4 -37
- package/dist/backend/server.js +50 -15
- package/dist/backend/services/app-settings-service.js +1 -0
- package/dist/backend/services/auto-memory-service.js +760 -0
- package/dist/backend/services/claude-hook-service.js +108 -2
- package/dist/backend/services/claude-transcript-reply.js +81 -1
- package/dist/backend/services/harness-service.js +1 -1
- package/dist/backend/services/runtime-coordinator-service.js +69 -1
- package/dist/backend/services/runtime-recovery-service.js +6 -0
- package/dist/backend/services/session-service.js +3 -0
- package/dist/backend/services/task-close-service.js +88 -0
- package/dist/backend/services/task-service.js +152 -35
- package/dist/backend/services/turn-reconciler-service.js +122 -0
- package/dist/backend/templates/harness/architect-agent.js +3 -0
- package/dist/backend/templates/harness/claude-root.js +3 -1
- package/dist/backend/templates/harness/coder-agent.js +3 -0
- package/dist/backend/templates/harness/gate-review.js +3 -0
- package/dist/backend/templates/harness/harness-engineer-agent.js +32 -8
- package/dist/backend/templates/harness/project-manager-agent.js +3 -0
- package/dist/backend/templates/harness/role-memory.js +9 -0
- package/dist/backend/templates/harness/tester-agent.js +3 -0
- package/dist/shared/types/memory.js +8 -0
- package/dist-frontend/assets/{index-DmSHDyiQ.css → index-C2QzumXk.css} +1 -1
- package/dist-frontend/assets/index-e8Tqa8Qh.js +97 -0
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist-frontend/assets/index-DYBg_qYS.js +0 -96
package/README.md
CHANGED
|
@@ -333,7 +333,7 @@ Gateway can:
|
|
|
333
333
|
- create a task
|
|
334
334
|
- close a task with confirmation
|
|
335
335
|
- send plain text to Project Manager
|
|
336
|
-
- push PM
|
|
336
|
+
- push the PM Round-final reply and Round status back to the active chat
|
|
337
337
|
- translate mobile messages when Gateway translation is enabled
|
|
338
338
|
|
|
339
339
|
Gateway does not expose the embedded terminal and does not send directly to
|
|
@@ -361,6 +361,12 @@ Common commands:
|
|
|
361
361
|
Gateway credentials and audit logs are stored in app-local state, not connected
|
|
362
362
|
repositories.
|
|
363
363
|
|
|
364
|
+
When Gateway starts, VCM enables conversation translation, auto-send, and the
|
|
365
|
+
`Round final reply` scope. On a normal Round end, Gateway sends the PM original
|
|
366
|
+
reply first, then reuses the matching translation already produced for the
|
|
367
|
+
translation panel. `/retry` explicitly creates a new translation only when the
|
|
368
|
+
previous Gateway translation failed or was unavailable.
|
|
369
|
+
|
|
364
370
|
## Harness Studio
|
|
365
371
|
|
|
366
372
|
Harness Studio is the UI for VCM harness maintenance.
|
|
@@ -371,8 +377,10 @@ Use it to:
|
|
|
371
377
|
- run bootstrap
|
|
372
378
|
- open Harness Engineer
|
|
373
379
|
- review harness files
|
|
380
|
+
- view and edit shared or role-specific VCM memory
|
|
381
|
+
- review and revert memory changes recorded in the active task worktree
|
|
374
382
|
- copy file paths for discussion
|
|
375
|
-
- review task harness after
|
|
383
|
+
- review task harness after post-task memory processing completes
|
|
376
384
|
- inspect commit diffs for harness changes
|
|
377
385
|
- merge task harness commits back to the connected repository branch when
|
|
378
386
|
appropriate
|
|
@@ -380,6 +388,32 @@ Use it to:
|
|
|
380
388
|
Harness Engineer is project-scoped and resumable. When it performs task work,
|
|
381
389
|
VCM runs it from the active task worktree.
|
|
382
390
|
|
|
391
|
+
### Auto Memory
|
|
392
|
+
|
|
393
|
+
Enable `Auto memory` in the `VCM Harness` sidebar group to collect durable role
|
|
394
|
+
knowledge after a complete task passes Final Acceptance. Project Manager,
|
|
395
|
+
Architect, Coder, Tester, and an enabled Gate Reviewer submit memory drafts in
|
|
396
|
+
sequence. Harness Engineer verifies and consolidates them before VCM applies
|
|
397
|
+
the result.
|
|
398
|
+
|
|
399
|
+
Canonical memory is stored under the base repository's `.ai/vcm/memory/`.
|
|
400
|
+
Harness Studio shows current memory and task-local applied history. Memory is
|
|
401
|
+
applied before user review; while the task worktree remains available, the user
|
|
402
|
+
can edit current memory or revert a recorded change.
|
|
403
|
+
|
|
404
|
+
Post-task processing is ordered by the backend:
|
|
405
|
+
|
|
406
|
+
```text
|
|
407
|
+
Final Acceptance
|
|
408
|
+
-> Auto Memory, when enabled
|
|
409
|
+
-> Task Harness Retrospective
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Auto Memory adds no retrospective prerequisite when it is disabled. When it is
|
|
413
|
+
enabled, both automatic and manual retrospective requests wait until memory has
|
|
414
|
+
been applied for the current Final Acceptance. A failed memory review must be
|
|
415
|
+
retried from Harness Studio before retrospective can run.
|
|
416
|
+
|
|
383
417
|
## Closing a Task
|
|
384
418
|
|
|
385
419
|
`Close Task` is destructive.
|
|
@@ -480,5 +514,5 @@ npm start
|
|
|
480
514
|
- `docs/ARCHITECTURE.md`: repository architecture
|
|
481
515
|
- `docs/TESTING.md`: validation strategy
|
|
482
516
|
- `docs/vcm-cc-best-practices.md`: current VCM Claude Code harness practice
|
|
483
|
-
- `docs/v0.5-custom-workflow-plan.md`:
|
|
517
|
+
- `docs/v0.5-custom-workflow-plan.md`: deferred custom workflow proposal
|
|
484
518
|
- `docs/cc-best-practices.md`: archived generic Claude Code harness notes
|
|
@@ -47,6 +47,14 @@ export function createNodeFileSystemAdapter() {
|
|
|
47
47
|
await fs.writeFile(targetPath, content, "utf8");
|
|
48
48
|
});
|
|
49
49
|
},
|
|
50
|
+
async writeTextAtomic(targetPath, content) {
|
|
51
|
+
await runFileOperation(async () => {
|
|
52
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
53
|
+
const tempPath = `${targetPath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
54
|
+
await fs.writeFile(tempPath, content, "utf8");
|
|
55
|
+
await fs.rename(tempPath, targetPath);
|
|
56
|
+
});
|
|
57
|
+
},
|
|
50
58
|
async appendText(targetPath, content) {
|
|
51
59
|
await runFileOperation(async () => {
|
|
52
60
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
@@ -66,11 +66,13 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
66
66
|
});
|
|
67
67
|
app.post("/api/projects/harness/bootstrap/start", async (request) => {
|
|
68
68
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
69
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
69
70
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
70
71
|
return deps.harnessService.startHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
|
|
71
72
|
});
|
|
72
73
|
app.post("/api/projects/harness/bootstrap/restart", async (request) => {
|
|
73
74
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
75
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
74
76
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
75
77
|
return deps.harnessService.restartHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
|
|
76
78
|
});
|
|
@@ -80,6 +82,7 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
80
82
|
});
|
|
81
83
|
app.post("/api/projects/harness/bootstrap/run", async (request) => {
|
|
82
84
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
85
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
83
86
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
84
87
|
return deps.harnessService.runHarnessBootstrap(project.repoRoot, task.worktreePath);
|
|
85
88
|
});
|
|
@@ -93,14 +96,26 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
93
96
|
});
|
|
94
97
|
app.post("/api/projects/harness/engineer/session/start", async (request) => {
|
|
95
98
|
const project = await requireCurrentProject(deps.projectService);
|
|
99
|
+
if (request.body?.taskSlug) {
|
|
100
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
|
|
101
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
102
|
+
}
|
|
96
103
|
return deps.sessionService.startProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
97
104
|
});
|
|
98
105
|
app.post("/api/projects/harness/engineer/session/resume", async (request) => {
|
|
99
106
|
const project = await requireCurrentProject(deps.projectService);
|
|
107
|
+
if (request.body?.taskSlug) {
|
|
108
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
|
|
109
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
110
|
+
}
|
|
100
111
|
return deps.sessionService.resumeProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
101
112
|
});
|
|
102
113
|
app.post("/api/projects/harness/engineer/session/restart", async (request) => {
|
|
103
114
|
const project = await requireCurrentProject(deps.projectService);
|
|
115
|
+
if (request.body?.taskSlug) {
|
|
116
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.body.taskSlug);
|
|
117
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
118
|
+
}
|
|
104
119
|
return deps.sessionService.restartProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
105
120
|
});
|
|
106
121
|
app.post("/api/projects/harness/engineer/session/stop", async () => {
|
|
@@ -127,6 +142,10 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
127
142
|
});
|
|
128
143
|
}
|
|
129
144
|
const taskSlug = await normalizeOptionalTaskSlug(deps, project.repoRoot, request.body?.taskSlug);
|
|
145
|
+
if (taskSlug && (action === "approve" || action === "comment")) {
|
|
146
|
+
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
147
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
148
|
+
}
|
|
130
149
|
return deps.harnessFeedbackService.decide(project.repoRoot, {
|
|
131
150
|
action,
|
|
132
151
|
taskSlug,
|
|
@@ -135,6 +154,14 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
135
154
|
});
|
|
136
155
|
app.post("/api/projects/harness/task-retrospective", async (request) => {
|
|
137
156
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
157
|
+
await deps.autoMemoryService.assertTaskRetrospectiveReady({
|
|
158
|
+
baseRepoRoot: project.repoRoot,
|
|
159
|
+
taskRepoRoot: task.worktreePath,
|
|
160
|
+
taskSlug: task.taskSlug,
|
|
161
|
+
handoffDir: task.handoffDir,
|
|
162
|
+
roundReady: true
|
|
163
|
+
});
|
|
164
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
138
165
|
const trigger = request.body?.trigger === "auto" ? "auto" : "manual";
|
|
139
166
|
return deps.harnessFeedbackService.startTaskRetrospective(project.repoRoot, {
|
|
140
167
|
taskSlug: task.taskSlug,
|
|
@@ -143,6 +170,33 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
143
170
|
trigger
|
|
144
171
|
});
|
|
145
172
|
});
|
|
173
|
+
app.get("/api/projects/harness/memory", async (request) => {
|
|
174
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
|
|
175
|
+
return deps.autoMemoryService.getState(project.repoRoot, task.worktreePath);
|
|
176
|
+
});
|
|
177
|
+
app.get("/api/projects/harness/memory/file", async (request) => {
|
|
178
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
|
|
179
|
+
return deps.autoMemoryService.getFile(project.repoRoot, task.worktreePath, request.query.path ?? "");
|
|
180
|
+
});
|
|
181
|
+
app.put("/api/projects/harness/memory/file", async (request) => {
|
|
182
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug ?? request.body?.taskSlug);
|
|
183
|
+
if (typeof request.body?.content !== "string") {
|
|
184
|
+
throw new VcmError({
|
|
185
|
+
code: "MEMORY_FILE_CONTENT_INVALID",
|
|
186
|
+
message: "Memory file content must be a string.",
|
|
187
|
+
statusCode: 400
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return deps.autoMemoryService.updateFile(project.repoRoot, task.worktreePath, task.taskSlug, request.query.path ?? "", request.body.content);
|
|
191
|
+
});
|
|
192
|
+
app.post("/api/projects/harness/memory/revert", async (request) => {
|
|
193
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
194
|
+
return deps.autoMemoryService.revertRun(project.repoRoot, task.worktreePath, request.body?.runId ?? "");
|
|
195
|
+
});
|
|
196
|
+
app.post("/api/projects/harness/memory/retry", async (request) => {
|
|
197
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
198
|
+
return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
|
|
199
|
+
});
|
|
146
200
|
}
|
|
147
201
|
function degradedHarnessStatus(error) {
|
|
148
202
|
return {
|
|
@@ -18,14 +18,16 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
18
18
|
harnessStatus: null,
|
|
19
19
|
harnessBootstrapStatus: null,
|
|
20
20
|
harnessFeedbackState,
|
|
21
|
+
autoMemoryState: null,
|
|
21
22
|
gatewayStatus: coordinated.gatewayStatus
|
|
22
23
|
};
|
|
23
24
|
}
|
|
24
25
|
try {
|
|
25
26
|
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
26
|
-
const [harnessStatus, harnessBootstrapStatus] = await Promise.all([
|
|
27
|
+
const [harnessStatus, harnessBootstrapStatus, autoMemoryState] = await Promise.all([
|
|
27
28
|
withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
|
|
28
|
-
withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error))
|
|
29
|
+
withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error)),
|
|
30
|
+
deps.autoMemoryService.getState(project.repoRoot, task.worktreePath)
|
|
29
31
|
]);
|
|
30
32
|
return {
|
|
31
33
|
translatorSession,
|
|
@@ -34,6 +36,7 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
34
36
|
harnessStatus,
|
|
35
37
|
harnessBootstrapStatus,
|
|
36
38
|
harnessFeedbackState,
|
|
39
|
+
autoMemoryState,
|
|
37
40
|
gatewayStatus: coordinated.gatewayStatus
|
|
38
41
|
};
|
|
39
42
|
}
|
|
@@ -46,6 +49,7 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
46
49
|
harnessStatus: degradedHarnessStatus(error),
|
|
47
50
|
harnessBootstrapStatus: degradedBootstrapStatus(error),
|
|
48
51
|
harnessFeedbackState,
|
|
52
|
+
autoMemoryState: null,
|
|
49
53
|
gatewayStatus: coordinated.gatewayStatus
|
|
50
54
|
};
|
|
51
55
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISPATCHABLE_ROLES
|
|
1
|
+
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
2
2
|
import { isOpenFileLimitError, VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerTaskRoutes(app, deps) {
|
|
@@ -92,39 +92,9 @@ export function registerTaskRoutes(app, deps) {
|
|
|
92
92
|
});
|
|
93
93
|
app.post("/api/tasks/:taskSlug/cleanup", async (request) => {
|
|
94
94
|
const project = await requireCurrentProject(deps.projectService);
|
|
95
|
-
|
|
96
|
-
await stopRunningRoleSessions(deps, project.repoRoot, request.params.taskSlug);
|
|
97
|
-
await moveProjectToolSessionsToSafeCwd(deps, project.repoRoot);
|
|
98
|
-
await deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), request.params.taskSlug, { clearCache: true });
|
|
99
|
-
deps.roundService.stopTask(request.params.taskSlug);
|
|
100
|
-
return deps.taskService.cleanupTask(project.repoRoot, request.params.taskSlug, request.body ?? {});
|
|
95
|
+
return deps.taskCloseService.closeTask(project.repoRoot, request.params.taskSlug);
|
|
101
96
|
});
|
|
102
97
|
}
|
|
103
|
-
async function stopRunningRoleSessions(deps, repoRoot, taskSlug) {
|
|
104
|
-
const sessions = await deps.sessionService.listRoleSessions(repoRoot, taskSlug);
|
|
105
|
-
for (const session of sessions) {
|
|
106
|
-
if (session.status === "running" && VCM_ROLE_NAMES.some((role) => role === session.role)) {
|
|
107
|
-
await deps.sessionService.stopRoleSession(repoRoot, taskSlug, session.role);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async function moveProjectToolSessionsToSafeCwd(deps, repoRoot) {
|
|
112
|
-
await Promise.all([
|
|
113
|
-
ignoreMissingSession(deps.sessionService.moveProjectTranslatorSessionToSafeCwd(repoRoot)),
|
|
114
|
-
ignoreMissingSession(deps.sessionService.moveProjectHarnessEngineerSessionToSafeCwd(repoRoot))
|
|
115
|
-
]);
|
|
116
|
-
}
|
|
117
|
-
async function ignoreMissingSession(operation) {
|
|
118
|
-
try {
|
|
119
|
-
await operation;
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
if (error instanceof VcmError && error.code === "SESSION_MISSING") {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
98
|
async function requireCurrentProject(projectService) {
|
|
129
99
|
const project = await projectService.getCurrentProject();
|
|
130
100
|
if (!project) {
|
|
@@ -36,7 +36,7 @@ const LEGACY_CODEX_HARNESS_PATHS = [
|
|
|
36
36
|
".ai/tools/request-codex-review"
|
|
37
37
|
];
|
|
38
38
|
const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
|
|
39
|
-
const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time
|
|
39
|
+
const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --retry 2 --retry-delay 1 --retry-all-errors --connect-timeout 1 --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
|
|
40
40
|
const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
|
|
41
41
|
const VCM_BASH_GUARD_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ]; then exit 0; fi; guard=""; repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"; if [ -n "$repo" ] && [ -f "$repo/.ai/tools/vcm-bash-guard" ]; then guard="$repo/.ai/tools/vcm-bash-guard"; else cwd="$(pwd -P 2>/dev/null || pwd)"; dir="$cwd"; while [ -n "$dir" ] && [ "$dir" != "/" ]; do if [ -f "$dir/.ai/tools/vcm-bash-guard" ]; then guard="$dir/.ai/tools/vcm-bash-guard"; break; fi; dir="$(dirname "$dir")"; done; if [ -z "$guard" ] && [ -n "\${CLAUDE_PROJECT_DIR:-}" ] && [ -f "\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard" ]; then guard="\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard"; fi; fi; [ -n "$guard" ] || exit 0; python3 "$guard" || exit 0'`;
|
|
42
42
|
const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
2
1
|
import { VcmError } from "../errors.js";
|
|
3
2
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
4
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
@@ -605,15 +604,7 @@ export function createGatewayService(deps) {
|
|
|
605
604
|
hint: settings.currentTaskSlug ? `Use /close-task confirm ${settings.currentTaskSlug}` : undefined
|
|
606
605
|
});
|
|
607
606
|
}
|
|
608
|
-
const
|
|
609
|
-
await stopRunningRoleSessions(project.repoRoot, taskSlug);
|
|
610
|
-
await moveProjectToolSessionsToSafeCwd(project.repoRoot);
|
|
611
|
-
await deps.translationService.stopTask(getTaskRuntimeRepoRoot(task), taskSlug, { clearCache: true });
|
|
612
|
-
deps.roundService.stopTask(taskSlug);
|
|
613
|
-
const result = await deps.taskService.cleanupTask(project.repoRoot, taskSlug, {
|
|
614
|
-
force: true,
|
|
615
|
-
forceDeleteBranch: true
|
|
616
|
-
});
|
|
607
|
+
const result = await deps.taskCloseService.closeTask(project.repoRoot, taskSlug);
|
|
617
608
|
clearFailedTranslation(project.repoRoot, taskSlug);
|
|
618
609
|
const latestPmReplies = { ...settings.latestPmReplies };
|
|
619
610
|
delete latestPmReplies[latestPmReplyKey(project.repoRoot, taskSlug)];
|
|
@@ -629,8 +620,9 @@ export function createGatewayService(deps) {
|
|
|
629
620
|
});
|
|
630
621
|
const lines = [
|
|
631
622
|
`Closed task: ${result.taskSlug}`,
|
|
632
|
-
|
|
633
|
-
|
|
623
|
+
`worktree removed: ${result.worktreeRemoved ? "yes" : "no"}`,
|
|
624
|
+
`branch deleted: ${result.branchDeleted ? "yes" : "no"}`,
|
|
625
|
+
`task state removed: ${result.stateRemoved ? "yes" : "no"}`,
|
|
634
626
|
`removed state paths: ${result.removedStatePaths.length}`
|
|
635
627
|
];
|
|
636
628
|
if (result.warnings?.length) {
|
|
@@ -638,31 +630,6 @@ export function createGatewayService(deps) {
|
|
|
638
630
|
}
|
|
639
631
|
return lines.join("\n");
|
|
640
632
|
}
|
|
641
|
-
async function stopRunningRoleSessions(repoRoot, taskSlug) {
|
|
642
|
-
const sessions = await deps.sessionService.listRoleSessions(repoRoot, taskSlug);
|
|
643
|
-
for (const session of sessions) {
|
|
644
|
-
if (session.status === "running" && VCM_ROLE_NAMES.some((role) => role === session.role)) {
|
|
645
|
-
await deps.sessionService.stopRoleSession(repoRoot, taskSlug, session.role);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
async function moveProjectToolSessionsToSafeCwd(repoRoot) {
|
|
650
|
-
await Promise.all([
|
|
651
|
-
ignoreMissingSession(deps.sessionService.moveProjectTranslatorSessionToSafeCwd(repoRoot)),
|
|
652
|
-
ignoreMissingSession(deps.sessionService.moveProjectHarnessEngineerSessionToSafeCwd(repoRoot))
|
|
653
|
-
]);
|
|
654
|
-
}
|
|
655
|
-
async function ignoreMissingSession(operation) {
|
|
656
|
-
try {
|
|
657
|
-
await operation;
|
|
658
|
-
}
|
|
659
|
-
catch (error) {
|
|
660
|
-
if (error instanceof VcmError && error.code === "SESSION_MISSING") {
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
throw error;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
633
|
async function setGatewayTranslation(enabled) {
|
|
667
634
|
const settings = await deps.settings.updateSettings({ translationEnabled: enabled });
|
|
668
635
|
return `Gateway translation ${settings.translationEnabled ? "on" : "off"}.`;
|
package/dist/backend/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createCommandDispatcher } from "./services/command-dispatcher.js";
|
|
|
9
9
|
import { createClaudeHookService } from "./services/claude-hook-service.js";
|
|
10
10
|
import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
11
11
|
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
12
|
+
import { createAutoMemoryService } from "./services/auto-memory-service.js";
|
|
12
13
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
13
14
|
import { createGateReviewService } from "./services/gate-review-service.js";
|
|
14
15
|
import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
|
|
@@ -34,9 +35,11 @@ import { createRuntimeCoordinatorService } from "./services/runtime-coordinator-
|
|
|
34
35
|
import { createRuntimeRecoveryService } from "./services/runtime-recovery-service.js";
|
|
35
36
|
import { createStatusService } from "./services/status-service.js";
|
|
36
37
|
import { createTaskService } from "./services/task-service.js";
|
|
38
|
+
import { createTaskCloseService } from "./services/task-close-service.js";
|
|
37
39
|
import { createTaskLaunchService } from "./services/task-launch-service.js";
|
|
38
40
|
import { createTerminalInterruptService } from "./services/terminal-interrupt-service.js";
|
|
39
41
|
import { createTranslationService } from "./services/translation-service.js";
|
|
42
|
+
import { createTurnReconcilerService } from "./services/turn-reconciler-service.js";
|
|
40
43
|
import { createDiagnosticsService } from "./services/diagnostics-service.js";
|
|
41
44
|
import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
42
45
|
import { registerArtifactRoutes } from "./api/artifact-routes.js";
|
|
@@ -94,6 +97,7 @@ export async function createServer(deps, options = {}) {
|
|
|
94
97
|
projectService: deps.projectService,
|
|
95
98
|
harnessService: deps.harnessService,
|
|
96
99
|
harnessFeedbackService: deps.harnessFeedbackService,
|
|
100
|
+
autoMemoryService: deps.autoMemoryService,
|
|
97
101
|
sessionService: deps.sessionService,
|
|
98
102
|
taskService: deps.taskService
|
|
99
103
|
});
|
|
@@ -104,16 +108,16 @@ export async function createServer(deps, options = {}) {
|
|
|
104
108
|
translationWorkerService: deps.translationWorkerService,
|
|
105
109
|
harnessService: deps.harnessService,
|
|
106
110
|
harnessFeedbackService: deps.harnessFeedbackService,
|
|
111
|
+
autoMemoryService: deps.autoMemoryService,
|
|
107
112
|
runtimeCoordinator: deps.runtimeCoordinator
|
|
108
113
|
});
|
|
109
114
|
registerTaskRoutes(app, {
|
|
110
115
|
projectService: deps.projectService,
|
|
111
116
|
taskService: deps.taskService,
|
|
112
|
-
|
|
117
|
+
taskCloseService: deps.taskCloseService,
|
|
113
118
|
statusService: deps.statusService,
|
|
114
119
|
messageService: deps.messageService,
|
|
115
120
|
taskLaunchService: deps.taskLaunchService,
|
|
116
|
-
translationService: deps.translationService,
|
|
117
121
|
roundService: deps.roundService
|
|
118
122
|
});
|
|
119
123
|
registerSessionRoutes(app, {
|
|
@@ -151,9 +155,11 @@ export async function createServer(deps, options = {}) {
|
|
|
151
155
|
});
|
|
152
156
|
app.addHook("onReady", async () => {
|
|
153
157
|
await cleanupRecentTranslationRuntime(deps);
|
|
158
|
+
deps.runtimeCoordinator.start();
|
|
154
159
|
await deps.gatewayService.start();
|
|
155
160
|
});
|
|
156
161
|
app.addHook("onClose", async () => {
|
|
162
|
+
deps.runtimeCoordinator.stop();
|
|
157
163
|
await deps.gatewayService.stop();
|
|
158
164
|
});
|
|
159
165
|
if (options.staticDir) {
|
|
@@ -222,6 +228,16 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
222
228
|
runtime,
|
|
223
229
|
sessionService
|
|
224
230
|
});
|
|
231
|
+
const autoMemoryService = createAutoMemoryService({
|
|
232
|
+
fs,
|
|
233
|
+
runtime,
|
|
234
|
+
sessionService,
|
|
235
|
+
appSettings,
|
|
236
|
+
async isHarnessEngineerAvailable(repoRoot) {
|
|
237
|
+
const state = await harnessFeedbackService.getState(repoRoot);
|
|
238
|
+
return state.status === "idle";
|
|
239
|
+
}
|
|
240
|
+
});
|
|
225
241
|
const commandDispatcher = createCommandDispatcher({
|
|
226
242
|
runtime,
|
|
227
243
|
sessionService,
|
|
@@ -293,6 +309,12 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
293
309
|
fs,
|
|
294
310
|
auditPath: gatewaySettings.getAuditPath()
|
|
295
311
|
});
|
|
312
|
+
const taskCloseService = createTaskCloseService({
|
|
313
|
+
taskService,
|
|
314
|
+
sessionService,
|
|
315
|
+
translationService,
|
|
316
|
+
roundService
|
|
317
|
+
});
|
|
296
318
|
const gatewayService = createGatewayService({
|
|
297
319
|
fs,
|
|
298
320
|
settings: gatewaySettings,
|
|
@@ -300,6 +322,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
300
322
|
channels: gatewayChannels,
|
|
301
323
|
projectService,
|
|
302
324
|
taskService,
|
|
325
|
+
taskCloseService,
|
|
303
326
|
sessionService,
|
|
304
327
|
taskLaunchService,
|
|
305
328
|
translationService,
|
|
@@ -307,19 +330,6 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
307
330
|
runtime,
|
|
308
331
|
appSettings
|
|
309
332
|
});
|
|
310
|
-
const runtimeCoordinator = createRuntimeCoordinatorService({
|
|
311
|
-
appSettings,
|
|
312
|
-
taskService,
|
|
313
|
-
sessionService,
|
|
314
|
-
translationService,
|
|
315
|
-
harnessService,
|
|
316
|
-
harnessFeedbackService,
|
|
317
|
-
roundService,
|
|
318
|
-
gatewayService,
|
|
319
|
-
async getStateRoot(repoRoot) {
|
|
320
|
-
return (await projectService.loadConfig(repoRoot)).stateRoot;
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
333
|
const runtimeRecoveryService = createRuntimeRecoveryService({
|
|
324
334
|
fs,
|
|
325
335
|
runtime,
|
|
@@ -338,10 +348,33 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
338
348
|
runtime,
|
|
339
349
|
harnessService,
|
|
340
350
|
harnessFeedbackService,
|
|
351
|
+
autoMemoryService,
|
|
341
352
|
gatewayService,
|
|
342
353
|
jobGuard: createJobGuardService(),
|
|
343
354
|
translationWorkerService
|
|
344
355
|
});
|
|
356
|
+
const turnReconciler = createTurnReconcilerService({
|
|
357
|
+
sessionService,
|
|
358
|
+
roundService,
|
|
359
|
+
claudeHookService,
|
|
360
|
+
runtime
|
|
361
|
+
});
|
|
362
|
+
const runtimeCoordinator = createRuntimeCoordinatorService({
|
|
363
|
+
appSettings,
|
|
364
|
+
projectService,
|
|
365
|
+
taskService,
|
|
366
|
+
sessionService,
|
|
367
|
+
translationService,
|
|
368
|
+
harnessService,
|
|
369
|
+
harnessFeedbackService,
|
|
370
|
+
autoMemoryService,
|
|
371
|
+
roundService,
|
|
372
|
+
gatewayService,
|
|
373
|
+
turnReconciler,
|
|
374
|
+
async getStateRoot(repoRoot) {
|
|
375
|
+
return (await projectService.loadConfig(repoRoot)).stateRoot;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
345
378
|
const terminalInterruptService = createTerminalInterruptService({
|
|
346
379
|
runtime,
|
|
347
380
|
projectService,
|
|
@@ -359,10 +392,12 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
359
392
|
appSettings,
|
|
360
393
|
projectService,
|
|
361
394
|
taskService,
|
|
395
|
+
taskCloseService,
|
|
362
396
|
sessionService,
|
|
363
397
|
artifactService,
|
|
364
398
|
harnessService,
|
|
365
399
|
harnessFeedbackService,
|
|
400
|
+
autoMemoryService,
|
|
366
401
|
commandDispatcher,
|
|
367
402
|
claudeHookService,
|
|
368
403
|
messageService,
|
|
@@ -242,6 +242,7 @@ function normalizePreferences(input) {
|
|
|
242
242
|
roleRetryEnabled: candidate.roleRetryEnabled !== false,
|
|
243
243
|
permissionRequestMode: normalizePermissionRequestMode(candidate.permissionRequestMode),
|
|
244
244
|
autoTaskHarnessReviewEnabled: candidate.autoTaskHarnessReviewEnabled === true,
|
|
245
|
+
autoMemoryEnabled: candidate.autoMemoryEnabled === true,
|
|
245
246
|
translationEnabled: candidate.translationEnabled === true,
|
|
246
247
|
translationAutoSendEnabled: candidate.translationAutoSendEnabled === true,
|
|
247
248
|
translationTargetLanguage: normalizeTranslationTargetLanguage(candidate.translationTargetLanguage),
|