vibe-coding-master 0.4.27 → 0.4.29
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/dist/backend/api/runtime-state-routes.js +104 -0
- package/dist/backend/api/task-routes.js +81 -0
- package/dist/backend/server.js +10 -0
- package/dist/backend/services/session-service.js +43 -0
- package/dist-frontend/assets/index-C-_A3WV8.js +96 -0
- package/dist-frontend/assets/{index-BLY2QIJm.css → index-CXIP6Fyb.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist-frontend/assets/index-D01WIxIS.js +0 -96
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isOpenFileLimitError, VcmError } from "../errors.js";
|
|
2
|
+
export function registerRuntimeStateRoutes(app, deps) {
|
|
3
|
+
app.get("/api/projects/runtime-state", async (request) => {
|
|
4
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
5
|
+
const taskSlug = request.query.taskSlug?.trim();
|
|
6
|
+
const [translatorSession, translationState, harnessEngineerSession, harnessFeedbackState] = await Promise.all([
|
|
7
|
+
deps.sessionService.getProjectTranslatorSession(project.repoRoot).then((session) => session ?? null),
|
|
8
|
+
deps.translationWorkerService.getState(project.repoRoot, { visibility: "public" }),
|
|
9
|
+
deps.sessionService.getProjectHarnessEngineerSession(project.repoRoot).then((session) => session ?? null),
|
|
10
|
+
deps.harnessFeedbackService.getState(project.repoRoot, taskSlug || undefined)
|
|
11
|
+
]);
|
|
12
|
+
if (!taskSlug) {
|
|
13
|
+
return {
|
|
14
|
+
translatorSession,
|
|
15
|
+
translationState,
|
|
16
|
+
harnessEngineerSession,
|
|
17
|
+
harnessStatus: null,
|
|
18
|
+
harnessBootstrapStatus: null,
|
|
19
|
+
harnessFeedbackState
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
24
|
+
const [harnessStatus, harnessBootstrapStatus] = await Promise.all([
|
|
25
|
+
withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
|
|
26
|
+
withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error))
|
|
27
|
+
]);
|
|
28
|
+
return {
|
|
29
|
+
translatorSession,
|
|
30
|
+
translationState,
|
|
31
|
+
harnessEngineerSession,
|
|
32
|
+
harnessStatus,
|
|
33
|
+
harnessBootstrapStatus,
|
|
34
|
+
harnessFeedbackState
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (isOpenFileLimitError(error)) {
|
|
39
|
+
return {
|
|
40
|
+
translatorSession,
|
|
41
|
+
translationState,
|
|
42
|
+
harnessEngineerSession,
|
|
43
|
+
harnessStatus: degradedHarnessStatus(error),
|
|
44
|
+
harnessBootstrapStatus: degradedBootstrapStatus(error),
|
|
45
|
+
harnessFeedbackState
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function requireCurrentProject(projectService) {
|
|
53
|
+
const project = await projectService.getCurrentProject();
|
|
54
|
+
if (!project) {
|
|
55
|
+
throw new VcmError({
|
|
56
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
57
|
+
message: "Connect a repository first.",
|
|
58
|
+
statusCode: 409
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return project;
|
|
62
|
+
}
|
|
63
|
+
function degradedHarnessStatus(error) {
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
harnessRevision: 0,
|
|
67
|
+
initialized: false,
|
|
68
|
+
files: [],
|
|
69
|
+
needsApply: false,
|
|
70
|
+
plannedChanges: [],
|
|
71
|
+
warnings: [
|
|
72
|
+
`Harness status is temporarily unavailable because the backend hit the open-files limit: ${errorMessage(error)}`
|
|
73
|
+
]
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function degradedBootstrapStatus(error) {
|
|
77
|
+
return {
|
|
78
|
+
status: "not_ready",
|
|
79
|
+
canStart: false,
|
|
80
|
+
checks: [{
|
|
81
|
+
key: "fixed-harness",
|
|
82
|
+
label: "Fixed harness",
|
|
83
|
+
status: "unknown",
|
|
84
|
+
detail: `Backend open-files limit reached: ${errorMessage(error)}`
|
|
85
|
+
}],
|
|
86
|
+
warnings: [
|
|
87
|
+
`Harness bootstrap status is temporarily unavailable because the backend hit the open-files limit: ${errorMessage(error)}`
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function withOpenFileLimitFallback(run, fallback) {
|
|
92
|
+
try {
|
|
93
|
+
return await run();
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (isOpenFileLimitError(error)) {
|
|
97
|
+
return fallback(error);
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function errorMessage(error) {
|
|
103
|
+
return error instanceof Error ? error.message : String(error);
|
|
104
|
+
}
|
|
@@ -28,6 +28,61 @@ export function registerTaskRoutes(app, deps) {
|
|
|
28
28
|
throw error;
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
|
+
app.get("/api/tasks/:taskSlug/workspace-state", async (request) => {
|
|
32
|
+
const taskSlug = request.params.taskSlug;
|
|
33
|
+
let repoRoot = "unknown";
|
|
34
|
+
try {
|
|
35
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
36
|
+
repoRoot = project.repoRoot;
|
|
37
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
38
|
+
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
39
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
40
|
+
const context = {
|
|
41
|
+
repoRoot: project.repoRoot,
|
|
42
|
+
taskRepoRoot,
|
|
43
|
+
stateRepoRoot: taskRepoRoot,
|
|
44
|
+
stateRoot: config.stateRoot,
|
|
45
|
+
handoffDir: task.handoffDir,
|
|
46
|
+
taskSlug
|
|
47
|
+
};
|
|
48
|
+
const [taskStatus, messages, orchestration, roundState] = await Promise.all([
|
|
49
|
+
withOpenFileLimitFallback(() => deps.statusService.getTaskStatus(project.repoRoot, taskSlug), (error) => degradedTaskStatus(project.repoRoot, taskSlug, error)),
|
|
50
|
+
withOpenFileLimitFallback(() => deps.messageService.listMessages(context), () => []),
|
|
51
|
+
withOpenFileLimitFallback(() => deps.messageService.getOrchestrationState(context), () => ({
|
|
52
|
+
taskSlug,
|
|
53
|
+
mode: "auto",
|
|
54
|
+
updatedAt: new Date().toISOString()
|
|
55
|
+
})),
|
|
56
|
+
withOpenFileLimitFallback(() => deps.roundService.getSessionRoundState({
|
|
57
|
+
repoRoot: project.repoRoot,
|
|
58
|
+
stateRepoRoot: taskRepoRoot,
|
|
59
|
+
stateRoot: config.stateRoot,
|
|
60
|
+
taskSlug
|
|
61
|
+
}), () => degradedRoundState(taskSlug))
|
|
62
|
+
]);
|
|
63
|
+
return {
|
|
64
|
+
taskStatus,
|
|
65
|
+
messages,
|
|
66
|
+
orchestration,
|
|
67
|
+
roundState
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isOpenFileLimitError(error)) {
|
|
72
|
+
return {
|
|
73
|
+
taskStatus: degradedTaskStatus(repoRoot, taskSlug, error),
|
|
74
|
+
messages: [],
|
|
75
|
+
orchestration: {
|
|
76
|
+
taskSlug,
|
|
77
|
+
mode: "auto",
|
|
78
|
+
updatedAt: new Date().toISOString()
|
|
79
|
+
},
|
|
80
|
+
roundState: degradedRoundState(taskSlug)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
31
86
|
app.post("/api/tasks/:taskSlug/cleanup", async (request) => {
|
|
32
87
|
const project = await requireCurrentProject(deps.projectService);
|
|
33
88
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
@@ -96,6 +151,32 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
|
|
|
96
151
|
]
|
|
97
152
|
};
|
|
98
153
|
}
|
|
154
|
+
function degradedRoundState(taskSlug) {
|
|
155
|
+
return {
|
|
156
|
+
taskSlug,
|
|
157
|
+
status: "stopped",
|
|
158
|
+
turnCount: 0,
|
|
159
|
+
completedTurnCount: 0,
|
|
160
|
+
totalRoundCount: 0,
|
|
161
|
+
totalTurnCount: 0,
|
|
162
|
+
totalCompletedTurnCount: 0,
|
|
163
|
+
totalCcActiveMs: 0,
|
|
164
|
+
currentRoundCcActiveMs: 0,
|
|
165
|
+
roles: [],
|
|
166
|
+
updatedAt: new Date().toISOString()
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async function withOpenFileLimitFallback(run, fallback) {
|
|
170
|
+
try {
|
|
171
|
+
return await run();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (isOpenFileLimitError(error)) {
|
|
175
|
+
return fallback(error);
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
99
180
|
function degradedArtifactSummary(handoffDir) {
|
|
100
181
|
const roleCommandsDir = `${handoffDir}/role-commands`;
|
|
101
182
|
const messagesDir = `${handoffDir}/messages`;
|
package/dist/backend/server.js
CHANGED
|
@@ -41,6 +41,7 @@ import { registerHarnessRoutes } from "./api/harness-routes.js";
|
|
|
41
41
|
import { registerMessageRoutes } from "./api/message-routes.js";
|
|
42
42
|
import { registerProjectRoutes } from "./api/project-routes.js";
|
|
43
43
|
import { registerRoundRoutes } from "./api/round-routes.js";
|
|
44
|
+
import { registerRuntimeStateRoutes } from "./api/runtime-state-routes.js";
|
|
44
45
|
import { registerSessionRoutes } from "./api/session-routes.js";
|
|
45
46
|
import { registerTaskRoutes } from "./api/task-routes.js";
|
|
46
47
|
import { registerTranslationRoutes } from "./api/translation-routes.js";
|
|
@@ -90,11 +91,20 @@ export async function createServer(deps, options = {}) {
|
|
|
90
91
|
sessionService: deps.sessionService,
|
|
91
92
|
taskService: deps.taskService
|
|
92
93
|
});
|
|
94
|
+
registerRuntimeStateRoutes(app, {
|
|
95
|
+
projectService: deps.projectService,
|
|
96
|
+
taskService: deps.taskService,
|
|
97
|
+
sessionService: deps.sessionService,
|
|
98
|
+
translationWorkerService: deps.translationWorkerService,
|
|
99
|
+
harnessService: deps.harnessService,
|
|
100
|
+
harnessFeedbackService: deps.harnessFeedbackService
|
|
101
|
+
});
|
|
93
102
|
registerTaskRoutes(app, {
|
|
94
103
|
projectService: deps.projectService,
|
|
95
104
|
taskService: deps.taskService,
|
|
96
105
|
sessionService: deps.sessionService,
|
|
97
106
|
statusService: deps.statusService,
|
|
107
|
+
messageService: deps.messageService,
|
|
98
108
|
translationService: deps.translationService,
|
|
99
109
|
roundService: deps.roundService
|
|
100
110
|
});
|
|
@@ -476,6 +476,7 @@ export function createSessionService(deps) {
|
|
|
476
476
|
await deps.runtime.stop(existing.id);
|
|
477
477
|
}
|
|
478
478
|
deps.registry.remove(existing.id);
|
|
479
|
+
await clearPersistedTranslatorSession(deps.fs, repoRoot);
|
|
479
480
|
return launchProjectTranslatorSession(repoRoot, input, "fresh");
|
|
480
481
|
},
|
|
481
482
|
async getProjectTranslatorSession(repoRoot) {
|
|
@@ -601,6 +602,7 @@ export function createSessionService(deps) {
|
|
|
601
602
|
await deps.runtime.stop(existing.id);
|
|
602
603
|
}
|
|
603
604
|
deps.registry.remove(existing.id);
|
|
605
|
+
await clearPersistedHarnessEngineerSession(deps.fs, repoRoot);
|
|
604
606
|
return launchProjectHarnessEngineerSession(repoRoot, input, "fresh");
|
|
605
607
|
},
|
|
606
608
|
async getProjectHarnessEngineerSession(repoRoot) {
|
|
@@ -737,6 +739,9 @@ export function createSessionService(deps) {
|
|
|
737
739
|
await deps.runtime.stop(existing.id);
|
|
738
740
|
}
|
|
739
741
|
deps.registry.remove(existing.id);
|
|
742
|
+
const config = await deps.projectService.loadConfig(repoRoot);
|
|
743
|
+
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
744
|
+
await clearPersistedRoleSessionRecord(deps.fs, getTaskRuntimeRepoRoot(task), config.stateRoot, taskSlug, role, now());
|
|
740
745
|
return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
|
|
741
746
|
},
|
|
742
747
|
async getRoleSession(repoRoot, taskSlug, role) {
|
|
@@ -1125,6 +1130,23 @@ async function persistTaskSession(fs, repoRoot, stateRoot, session) {
|
|
|
1125
1130
|
}
|
|
1126
1131
|
});
|
|
1127
1132
|
}
|
|
1133
|
+
async function clearPersistedRoleSessionRecord(fs, repoRoot, stateRoot, taskSlug, role, updatedAt) {
|
|
1134
|
+
const sessionPath = getTaskSessionPath(repoRoot, stateRoot, taskSlug);
|
|
1135
|
+
const current = await fs.pathExists(sessionPath)
|
|
1136
|
+
? await fs.readJson(sessionPath)
|
|
1137
|
+
: createEmptyTaskSessionRecord(taskSlug, updatedAt);
|
|
1138
|
+
await fs.writeJsonAtomic(sessionPath, {
|
|
1139
|
+
...current,
|
|
1140
|
+
updatedAt,
|
|
1141
|
+
roles: {
|
|
1142
|
+
...current.roles,
|
|
1143
|
+
[role]: {
|
|
1144
|
+
id: null,
|
|
1145
|
+
status: "not_started"
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1128
1150
|
async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoot, session) {
|
|
1129
1151
|
if (session.role === TRANSLATOR_ROLE) {
|
|
1130
1152
|
await persistTranslatorSession(fs, baseRepoRoot, session);
|
|
@@ -1150,6 +1172,9 @@ async function persistTranslatorSession(fs, repoRoot, session) {
|
|
|
1150
1172
|
}
|
|
1151
1173
|
});
|
|
1152
1174
|
}
|
|
1175
|
+
async function clearPersistedTranslatorSession(fs, repoRoot) {
|
|
1176
|
+
await removePersistedProjectSessionFile(fs, repoRoot, TRANSLATOR_SESSION_PATH);
|
|
1177
|
+
}
|
|
1153
1178
|
async function persistHarnessEngineerSession(fs, repoRoot, session) {
|
|
1154
1179
|
if (!hasConfirmedClaudeSessionId(session)) {
|
|
1155
1180
|
return;
|
|
@@ -1164,6 +1189,24 @@ async function persistHarnessEngineerSession(fs, repoRoot, session) {
|
|
|
1164
1189
|
}
|
|
1165
1190
|
});
|
|
1166
1191
|
}
|
|
1192
|
+
async function clearPersistedHarnessEngineerSession(fs, repoRoot) {
|
|
1193
|
+
await removePersistedProjectSessionFile(fs, repoRoot, HARNESS_ENGINEER_SESSION_PATH);
|
|
1194
|
+
}
|
|
1195
|
+
async function removePersistedProjectSessionFile(fs, repoRoot, relativePath) {
|
|
1196
|
+
const sessionPath = resolveRepoPath(repoRoot, relativePath);
|
|
1197
|
+
if (!(await fs.pathExists(sessionPath))) {
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (!fs.removePath) {
|
|
1201
|
+
throw new VcmError({
|
|
1202
|
+
code: "SESSION_CLEAR_UNAVAILABLE",
|
|
1203
|
+
message: "VCM cannot clear the persisted Claude session file in this runtime.",
|
|
1204
|
+
statusCode: 500,
|
|
1205
|
+
hint: `Remove ${relativePath} manually before restarting the role.`
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
await fs.removePath(sessionPath, { force: true });
|
|
1209
|
+
}
|
|
1167
1210
|
function isProjectRoleSessionFile(value) {
|
|
1168
1211
|
return "record" in value && typeof value.record === "object" && value.record !== null;
|
|
1169
1212
|
}
|