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.
@@ -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`;
@@ -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
  }