vibe-coding-master 0.4.42 → 0.5.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/dist/backend/api/task-routes.js +7 -0
- package/dist/backend/errors.js +2 -0
- package/dist/backend/gateway/gateway-service.js +31 -61
- package/dist/backend/server.js +11 -1
- package/dist/backend/services/round-service.js +34 -0
- package/dist/backend/services/task-launch-service.js +91 -0
- package/dist/backend/services/translation-worker-service.js +228 -72
- package/dist-frontend/assets/{index-BrH67ca7.js → index-BaDS9Ohj.js} +45 -45
- package/dist-frontend/index.html +1 -1
- package/docs/ARCHITECTURE.md +123 -0
- package/docs/TESTING.md +121 -73
- package/docs/known-issues.md +155 -0
- package/package.json +1 -1
- package/docs/claude-code-translation-plan.md +0 -1268
- package/docs/full-harness-baseline.md +0 -160
- package/docs/gate-review-gates.md +0 -132
- package/docs/gateway-design.md +0 -813
- package/docs/v0.2-implementation-plan.md +0 -408
- package/docs/v0.4-harness-optimization-plan.md +0 -664
|
@@ -83,6 +83,13 @@ export function registerTaskRoutes(app, deps) {
|
|
|
83
83
|
throw error;
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
|
+
app.post("/api/tasks/:taskSlug/one-click-start", async (request) => {
|
|
87
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
88
|
+
return deps.taskLaunchService.startTaskRoleSessions(project.repoRoot, {
|
|
89
|
+
taskSlug: request.params.taskSlug,
|
|
90
|
+
requireFreshStart: true
|
|
91
|
+
});
|
|
92
|
+
});
|
|
86
93
|
app.post("/api/tasks/:taskSlug/cleanup", async (request) => {
|
|
87
94
|
const project = await requireCurrentProject(deps.projectService);
|
|
88
95
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
package/dist/backend/errors.js
CHANGED
|
@@ -2,12 +2,14 @@ export class VcmError extends Error {
|
|
|
2
2
|
code;
|
|
3
3
|
statusCode;
|
|
4
4
|
hint;
|
|
5
|
+
details;
|
|
5
6
|
constructor(input) {
|
|
6
7
|
super(input.message);
|
|
7
8
|
this.name = "VcmError";
|
|
8
9
|
this.code = input.code;
|
|
9
10
|
this.statusCode = input.statusCode ?? 400;
|
|
10
11
|
this.hint = input.hint;
|
|
12
|
+
this.details = input.details;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
export function toVcmError(error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
3
3
|
import { VcmError } from "../errors.js";
|
|
4
4
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
5
5
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
@@ -430,78 +430,48 @@ export function createGatewayService(deps) {
|
|
|
430
430
|
taskSlug,
|
|
431
431
|
title
|
|
432
432
|
});
|
|
433
|
-
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
434
|
-
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
435
433
|
const preferences = await deps.appSettings.getPreferences();
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const existing = await deps.sessionService.getRoleSession(project.repoRoot, task.taskSlug, definition.name);
|
|
461
|
-
if (existing?.status === "running") {
|
|
462
|
-
startedRoles.push(definition.name);
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (existing?.claudeSessionId) {
|
|
466
|
-
await deps.sessionService.resumeRoleSession(project.repoRoot, task.taskSlug, definition.name, sessionInput);
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
await deps.sessionService.startRoleSession(project.repoRoot, task.taskSlug, definition.name, sessionInput);
|
|
470
|
-
}
|
|
471
|
-
startedRoles.push(definition.name);
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
const settings = await deps.settings.loadSettings();
|
|
475
|
-
await deps.settings.saveSettings({
|
|
476
|
-
...settings,
|
|
477
|
-
currentProjectId: project.repoRoot,
|
|
478
|
-
currentTaskSlug: task.taskSlug,
|
|
479
|
-
translationEnabled: preferences.translationEnabled,
|
|
480
|
-
updatedAt: now()
|
|
481
|
-
});
|
|
434
|
+
// Persist the gateway's current project/task selection. Done after a partial
|
|
435
|
+
// start (so the phone stays pointed at the new task) and on success.
|
|
436
|
+
const persistTaskSelection = async () => {
|
|
437
|
+
const settings = await deps.settings.loadSettings();
|
|
438
|
+
await deps.settings.saveSettings({
|
|
439
|
+
...settings,
|
|
440
|
+
currentProjectId: project.repoRoot,
|
|
441
|
+
currentTaskSlug: task.taskSlug,
|
|
442
|
+
translationEnabled: preferences.translationEnabled,
|
|
443
|
+
updatedAt: now()
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
// Reuse the shared backend launch orchestration (roster + mode + skip/resume/
|
|
447
|
+
// start). A freshly created task has no sessions, so requireFreshStart is false.
|
|
448
|
+
let launch;
|
|
449
|
+
try {
|
|
450
|
+
launch = await deps.taskLaunchService.startTaskRoleSessions(project.repoRoot, {
|
|
451
|
+
taskSlug: task.taskSlug,
|
|
452
|
+
requireFreshStart: false
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
if (error instanceof VcmError && error.code === "TASK_ONE_CLICK_PARTIAL_START") {
|
|
457
|
+
await persistTaskSelection();
|
|
482
458
|
throw new VcmError({
|
|
483
459
|
code: "GATEWAY_TASK_PARTIAL_START",
|
|
484
|
-
message: `Task was created, but ${
|
|
460
|
+
message: `Task was created, but ${error.message}`,
|
|
485
461
|
statusCode: 409,
|
|
486
|
-
hint:
|
|
462
|
+
hint: error.hint
|
|
487
463
|
});
|
|
488
464
|
}
|
|
465
|
+
throw error;
|
|
489
466
|
}
|
|
490
|
-
|
|
491
|
-
await deps.settings.saveSettings({
|
|
492
|
-
...settings,
|
|
493
|
-
currentProjectId: project.repoRoot,
|
|
494
|
-
currentTaskSlug: task.taskSlug,
|
|
495
|
-
translationEnabled: preferences.translationEnabled,
|
|
496
|
-
updatedAt: now()
|
|
497
|
-
});
|
|
467
|
+
await persistTaskSelection();
|
|
498
468
|
return [
|
|
499
469
|
`Task created and initialized: ${task.taskSlug}`,
|
|
500
470
|
`branch: ${task.branch}`,
|
|
501
471
|
`worktree: ${task.worktreePath}`,
|
|
502
|
-
`orchestration: ${
|
|
472
|
+
`orchestration: ${launch.orchestration.mode}`,
|
|
503
473
|
`translation: ${preferences.translationEnabled ? "on" : "off"}`,
|
|
504
|
-
`sessions: ${startedRoles.join(", ")}`
|
|
474
|
+
`sessions: ${launch.startedRoles.join(", ")}`
|
|
505
475
|
].join("\n");
|
|
506
476
|
}
|
|
507
477
|
async function closeTaskPrompt() {
|
package/dist/backend/server.js
CHANGED
|
@@ -33,6 +33,7 @@ import { createRoundService } from "./services/round-service.js";
|
|
|
33
33
|
import { createRuntimeCoordinatorService } from "./services/runtime-coordinator-service.js";
|
|
34
34
|
import { createStatusService } from "./services/status-service.js";
|
|
35
35
|
import { createTaskService } from "./services/task-service.js";
|
|
36
|
+
import { createTaskLaunchService } from "./services/task-launch-service.js";
|
|
36
37
|
import { createTranslationService } from "./services/translation-service.js";
|
|
37
38
|
import { createDiagnosticsService } from "./services/diagnostics-service.js";
|
|
38
39
|
import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
@@ -109,6 +110,7 @@ export async function createServer(deps, options = {}) {
|
|
|
109
110
|
sessionService: deps.sessionService,
|
|
110
111
|
statusService: deps.statusService,
|
|
111
112
|
messageService: deps.messageService,
|
|
113
|
+
taskLaunchService: deps.taskLaunchService,
|
|
112
114
|
translationService: deps.translationService,
|
|
113
115
|
roundService: deps.roundService
|
|
114
116
|
});
|
|
@@ -232,6 +234,13 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
232
234
|
sessionService,
|
|
233
235
|
taskService
|
|
234
236
|
});
|
|
237
|
+
const taskLaunchService = createTaskLaunchService({
|
|
238
|
+
projectService,
|
|
239
|
+
taskService,
|
|
240
|
+
appSettings,
|
|
241
|
+
sessionService,
|
|
242
|
+
messageService
|
|
243
|
+
});
|
|
235
244
|
const roundService = createRoundService({
|
|
236
245
|
fs,
|
|
237
246
|
sessionService,
|
|
@@ -286,7 +295,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
286
295
|
projectService,
|
|
287
296
|
taskService,
|
|
288
297
|
sessionService,
|
|
289
|
-
|
|
298
|
+
taskLaunchService,
|
|
290
299
|
translationService,
|
|
291
300
|
roundService,
|
|
292
301
|
runtime,
|
|
@@ -337,6 +346,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
337
346
|
commandDispatcher,
|
|
338
347
|
claudeHookService,
|
|
339
348
|
messageService,
|
|
349
|
+
taskLaunchService,
|
|
340
350
|
gateReviewService,
|
|
341
351
|
translationWorkerService,
|
|
342
352
|
roundService,
|
|
@@ -383,6 +383,7 @@ function toSessionRoundState(state, updatedAt) {
|
|
|
383
383
|
totalCcActiveMs: state.totalCcActiveMs,
|
|
384
384
|
currentRoundCcActiveMs: 0,
|
|
385
385
|
roleRecovery: state.roleRecovery,
|
|
386
|
+
flowPause: computeFlowPause(undefined, state.roleRecovery),
|
|
386
387
|
roles: [],
|
|
387
388
|
updatedAt
|
|
388
389
|
};
|
|
@@ -411,10 +412,43 @@ function toSessionRoundState(state, updatedAt) {
|
|
|
411
412
|
totalCcActiveMs: state.totalCcActiveMs + activeDurationMs,
|
|
412
413
|
currentRoundCcActiveMs,
|
|
413
414
|
roleRecovery: state.roleRecovery,
|
|
415
|
+
flowPause: computeFlowPause(current, state.roleRecovery),
|
|
414
416
|
roles: current.roles,
|
|
415
417
|
updatedAt
|
|
416
418
|
};
|
|
417
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Authoritative flow-pause predicate (single source of truth). Returns a paused
|
|
422
|
+
* VcmFlowPauseState exactly when a real round has ended and the auto flow has not
|
|
423
|
+
* advanced and we are not mid active-recovery — mirroring the decision the GUI
|
|
424
|
+
* previously derived. Reason is `role-recovery-failed` when recovery has failed,
|
|
425
|
+
* otherwise `stopped-no-next-turn`. Returns a non-paused state (or undefined) when
|
|
426
|
+
* the flow is not paused. The frontend consumes this instead of re-deriving it.
|
|
427
|
+
*
|
|
428
|
+
* Intended logic (to implement):
|
|
429
|
+
* const recovering = roleRecovery?.status === "waiting" || roleRecovery?.status === "retrying";
|
|
430
|
+
* const paused = !!current && current.status === "stopped" && !!current.id && !recovering;
|
|
431
|
+
* if (!paused) return undefined; // or { paused: false }
|
|
432
|
+
* return {
|
|
433
|
+
* paused: true,
|
|
434
|
+
* reason: roleRecovery?.status === "failed" ? "role-recovery-failed" : "stopped-no-next-turn",
|
|
435
|
+
* role: current.activeRole,
|
|
436
|
+
* since: current.stoppedAt ?? current.lastTurnEndedAt
|
|
437
|
+
* };
|
|
438
|
+
*/
|
|
439
|
+
function computeFlowPause(current, roleRecovery) {
|
|
440
|
+
const recovering = roleRecovery?.status === "waiting" || roleRecovery?.status === "retrying";
|
|
441
|
+
const paused = Boolean(current) && current.status === "stopped" && Boolean(current.id) && !recovering;
|
|
442
|
+
if (!paused) {
|
|
443
|
+
return undefined;
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
paused: true,
|
|
447
|
+
reason: roleRecovery?.status === "failed" ? "role-recovery-failed" : "stopped-no-next-turn",
|
|
448
|
+
role: current.activeRole,
|
|
449
|
+
since: current.stoppedAt ?? current.lastTurnEndedAt
|
|
450
|
+
};
|
|
451
|
+
}
|
|
418
452
|
function normalizeRoundFile(input, taskSlug, updatedAt) {
|
|
419
453
|
const legacy = input;
|
|
420
454
|
return {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CORE_VCM_ROLE_DEFINITIONS, GATE_REVIEWER_ROLE_DEFINITION, VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
2
|
+
import { VcmError } from "../errors.js";
|
|
3
|
+
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
4
|
+
const ONE_CLICK_SESSION_COLS = 100;
|
|
5
|
+
const ONE_CLICK_SESSION_ROWS = 28;
|
|
6
|
+
export function createTaskLaunchService(deps) {
|
|
7
|
+
async function assertNoExistingRoleSessions(repoRoot, taskSlug) {
|
|
8
|
+
const sessions = await deps.sessionService.listRoleSessions(repoRoot, taskSlug);
|
|
9
|
+
if (sessions.some((session) => VCM_ROLE_NAMES.some((role) => role === session.role))) {
|
|
10
|
+
throw new VcmError({
|
|
11
|
+
code: "TASK_ONE_CLICK_REQUIRES_FRESH_START",
|
|
12
|
+
message: "One-click start is only available before any role session has started.",
|
|
13
|
+
statusCode: 409
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function applyOrchestrationMode(repoRoot, taskSlug, mode) {
|
|
18
|
+
const config = await deps.projectService.loadConfig(repoRoot);
|
|
19
|
+
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
20
|
+
return deps.messageService.updateOrchestrationState({
|
|
21
|
+
repoRoot,
|
|
22
|
+
stateRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
23
|
+
stateRoot: config.stateRoot,
|
|
24
|
+
taskSlug,
|
|
25
|
+
mode
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function composeRoleDefinitions(gateReviewerEnabled) {
|
|
29
|
+
return [
|
|
30
|
+
...CORE_VCM_ROLE_DEFINITIONS,
|
|
31
|
+
...(gateReviewerEnabled ? [GATE_REVIEWER_ROLE_DEFINITION] : [])
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
// Skip a running role, resume one that has a prior Claude session, otherwise
|
|
35
|
+
// start it fresh — using the launch-template entry's permission/model/effort.
|
|
36
|
+
async function launchRole(repoRoot, taskSlug, role, roleTemplate) {
|
|
37
|
+
const sessionInput = {
|
|
38
|
+
cols: ONE_CLICK_SESSION_COLS,
|
|
39
|
+
rows: ONE_CLICK_SESSION_ROWS,
|
|
40
|
+
permissionMode: roleTemplate.permissionMode,
|
|
41
|
+
model: roleTemplate.model,
|
|
42
|
+
effort: roleTemplate.effort
|
|
43
|
+
};
|
|
44
|
+
const existing = await deps.sessionService.getRoleSession(repoRoot, taskSlug, role);
|
|
45
|
+
if (existing?.status === "running") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (existing?.claudeSessionId) {
|
|
49
|
+
await deps.sessionService.resumeRoleSession(repoRoot, taskSlug, role, sessionInput);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await deps.sessionService.startRoleSession(repoRoot, taskSlug, role, sessionInput);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
async startTaskRoleSessions(repoRoot, input) {
|
|
56
|
+
const { taskSlug, requireFreshStart } = input;
|
|
57
|
+
if (requireFreshStart) {
|
|
58
|
+
await assertNoExistingRoleSessions(repoRoot, taskSlug);
|
|
59
|
+
}
|
|
60
|
+
const preferences = await deps.appSettings.getPreferences();
|
|
61
|
+
const template = preferences.launchTemplate;
|
|
62
|
+
const orchestration = await applyOrchestrationMode(repoRoot, taskSlug, template.autoOrchestration ? "auto" : "manual");
|
|
63
|
+
const gateReview = await deps.appSettings.getGateReviewSettings(repoRoot, taskSlug);
|
|
64
|
+
const roleDefinitions = composeRoleDefinitions(gateReview.enabled);
|
|
65
|
+
const startedRoles = [];
|
|
66
|
+
for (const definition of roleDefinitions) {
|
|
67
|
+
try {
|
|
68
|
+
await launchRole(repoRoot, taskSlug, definition.name, template.roles[definition.name]);
|
|
69
|
+
startedRoles.push(definition.name);
|
|
70
|
+
}
|
|
71
|
+
catch (cause) {
|
|
72
|
+
throw partialStartError(definition.name, startedRoles, cause);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const sessions = await deps.sessionService.listRoleSessions(repoRoot, taskSlug);
|
|
76
|
+
return { taskSlug, orchestration, startedRoles, sessions };
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function partialStartError(failedRole, startedRoles, cause) {
|
|
81
|
+
return new VcmError({
|
|
82
|
+
code: "TASK_ONE_CLICK_PARTIAL_START",
|
|
83
|
+
message: `${failedRole} failed to start.`,
|
|
84
|
+
statusCode: 409,
|
|
85
|
+
hint: errorMessage(cause),
|
|
86
|
+
details: { startedRoles: [...startedRoles], failedRole }
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function errorMessage(error) {
|
|
90
|
+
return error instanceof Error ? error.message : String(error);
|
|
91
|
+
}
|