vibe-coding-master 0.4.38 → 0.4.39
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 +7 -3
- package/dist/backend/gateway/gateway-service.js +39 -9
- package/dist/backend/server.js +17 -1
- package/dist/backend/services/runtime-coordinator-service.js +162 -0
- package/dist/backend/services/session-service.js +7 -1
- package/dist-frontend/assets/index-CsxS5H0d.js +96 -0
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
- package/dist-frontend/assets/index-Dgqz0YEX.js +0 -96
|
@@ -3,6 +3,7 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
3
3
|
app.get("/api/projects/runtime-state", async (request) => {
|
|
4
4
|
const project = await requireCurrentProject(deps.projectService);
|
|
5
5
|
const taskSlug = request.query.taskSlug?.trim();
|
|
6
|
+
const coordinated = await deps.runtimeCoordinator.reconcileProject(project.repoRoot, { taskSlug });
|
|
6
7
|
const [translatorSession, translationState, harnessEngineerSession, harnessFeedbackState] = await Promise.all([
|
|
7
8
|
deps.sessionService.getProjectTranslatorSession(project.repoRoot).then((session) => session ?? null),
|
|
8
9
|
deps.translationWorkerService.getState(project.repoRoot, { visibility: "public" }),
|
|
@@ -16,7 +17,8 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
16
17
|
harnessEngineerSession,
|
|
17
18
|
harnessStatus: null,
|
|
18
19
|
harnessBootstrapStatus: null,
|
|
19
|
-
harnessFeedbackState
|
|
20
|
+
harnessFeedbackState,
|
|
21
|
+
gatewayStatus: coordinated.gatewayStatus
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
try {
|
|
@@ -31,7 +33,8 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
31
33
|
harnessEngineerSession,
|
|
32
34
|
harnessStatus,
|
|
33
35
|
harnessBootstrapStatus,
|
|
34
|
-
harnessFeedbackState
|
|
36
|
+
harnessFeedbackState,
|
|
37
|
+
gatewayStatus: coordinated.gatewayStatus
|
|
35
38
|
};
|
|
36
39
|
}
|
|
37
40
|
catch (error) {
|
|
@@ -42,7 +45,8 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
42
45
|
harnessEngineerSession,
|
|
43
46
|
harnessStatus: degradedHarnessStatus(error),
|
|
44
47
|
harnessBootstrapStatus: degradedBootstrapStatus(error),
|
|
45
|
-
harnessFeedbackState
|
|
48
|
+
harnessFeedbackState,
|
|
49
|
+
gatewayStatus: coordinated.gatewayStatus
|
|
46
50
|
};
|
|
47
51
|
}
|
|
48
52
|
throw error;
|
|
@@ -613,6 +613,16 @@ export function createGatewayService(deps) {
|
|
|
613
613
|
const settings = await deps.settings.updateSettings({ translationEnabled: enabled });
|
|
614
614
|
return `Gateway translation ${settings.translationEnabled ? "on" : "off"}.`;
|
|
615
615
|
}
|
|
616
|
+
async function enableGatewayTranslationRuntime() {
|
|
617
|
+
const preferences = await deps.appSettings.getPreferences();
|
|
618
|
+
if (preferences.translationEnabled && preferences.translationAutoSendEnabled) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
await deps.appSettings.updatePreferences({
|
|
622
|
+
translationEnabled: true,
|
|
623
|
+
translationAutoSendEnabled: true
|
|
624
|
+
});
|
|
625
|
+
}
|
|
616
626
|
async function startGateway() {
|
|
617
627
|
const settings = await deps.settings.loadSettings();
|
|
618
628
|
if (!toAccount(settings)) {
|
|
@@ -621,9 +631,14 @@ export function createGatewayService(deps) {
|
|
|
621
631
|
: "Gateway is not bound. Start QR login from desktop VCM first.";
|
|
622
632
|
}
|
|
623
633
|
if (settings.enabled) {
|
|
634
|
+
await enableGatewayTranslationRuntime();
|
|
624
635
|
return "Gateway is already on.";
|
|
625
636
|
}
|
|
626
|
-
|
|
637
|
+
await enableGatewayTranslationRuntime();
|
|
638
|
+
const enabled = await syncDesktopContext(await deps.settings.updateSettings({
|
|
639
|
+
enabled: true,
|
|
640
|
+
translationEnabled: true
|
|
641
|
+
}));
|
|
627
642
|
await ensurePolling();
|
|
628
643
|
const lines = [
|
|
629
644
|
"Gateway started.",
|
|
@@ -732,16 +747,25 @@ export function createGatewayService(deps) {
|
|
|
732
747
|
},
|
|
733
748
|
async getStatus() {
|
|
734
749
|
const settings = await syncDesktopContext(await deps.settings.loadSettings());
|
|
750
|
+
if (settings.enabled) {
|
|
751
|
+
await enableGatewayTranslationRuntime();
|
|
752
|
+
}
|
|
735
753
|
await ensurePolling();
|
|
736
754
|
return deps.settings.expose(settings, isRunning());
|
|
737
755
|
},
|
|
738
756
|
async updateSettings(input) {
|
|
739
|
-
const
|
|
757
|
+
const gatewayStartInput = input.enabled === true
|
|
758
|
+
? { ...input, translationEnabled: true }
|
|
759
|
+
: input;
|
|
760
|
+
const updateInput = gatewayStartInput.channel && gatewayStartInput.baseUrl === undefined
|
|
740
761
|
? {
|
|
741
|
-
...
|
|
742
|
-
baseUrl: deps.channels.get(
|
|
762
|
+
...gatewayStartInput,
|
|
763
|
+
baseUrl: deps.channels.get(gatewayStartInput.channel).defaultBaseUrl
|
|
743
764
|
}
|
|
744
|
-
:
|
|
765
|
+
: gatewayStartInput;
|
|
766
|
+
if (input.enabled === true) {
|
|
767
|
+
await enableGatewayTranslationRuntime();
|
|
768
|
+
}
|
|
745
769
|
let settings = await deps.settings.updateSettings(updateInput);
|
|
746
770
|
if (settings.enabled) {
|
|
747
771
|
settings = await syncDesktopContext(settings);
|
|
@@ -1025,7 +1049,8 @@ export function createGatewayService(deps) {
|
|
|
1025
1049
|
}
|
|
1026
1050
|
const cursorKey = `${input.taskSlug}:project-manager:${input.session.claudeSessionId}`;
|
|
1027
1051
|
const cursor = settings.pushCursors[cursorKey];
|
|
1028
|
-
const nextEvents = selectEventsAfterCursor(events, cursor?.lastTranscriptEventId)
|
|
1052
|
+
const nextEvents = selectEventsAfterCursor(events, cursor?.lastTranscriptEventId)
|
|
1053
|
+
.filter(isFinalTurnTextEvent);
|
|
1029
1054
|
if (nextEvents.length === 0) {
|
|
1030
1055
|
return;
|
|
1031
1056
|
}
|
|
@@ -1203,13 +1228,17 @@ async function readTranscriptTextEvents(transcriptPath) {
|
|
|
1203
1228
|
events.push({
|
|
1204
1229
|
id: event.id,
|
|
1205
1230
|
timestamp: event.timestamp,
|
|
1206
|
-
text: event.text
|
|
1231
|
+
text: event.text,
|
|
1232
|
+
stopReason: event.stopReason
|
|
1207
1233
|
});
|
|
1208
1234
|
}
|
|
1209
1235
|
}
|
|
1210
1236
|
}
|
|
1211
1237
|
return events;
|
|
1212
1238
|
}
|
|
1239
|
+
function isFinalTurnTextEvent(event) {
|
|
1240
|
+
return event.stopReason === "end_turn";
|
|
1241
|
+
}
|
|
1213
1242
|
function selectEventsAfterCursor(events, cursorId) {
|
|
1214
1243
|
if (events.length === 0) {
|
|
1215
1244
|
return [];
|
|
@@ -1229,9 +1258,10 @@ function selectLatestTurnReply(events, session) {
|
|
|
1229
1258
|
}
|
|
1230
1259
|
const startMs = timestampMs(session.lastTurnStartedAt);
|
|
1231
1260
|
const endMs = timestampMs(session.lastTurnEndedAt);
|
|
1261
|
+
const finalEvents = events.filter(isFinalTurnTextEvent);
|
|
1232
1262
|
const selected = startMs === undefined
|
|
1233
|
-
?
|
|
1234
|
-
:
|
|
1263
|
+
? finalEvents.slice(-1)
|
|
1264
|
+
: finalEvents.filter((event) => {
|
|
1235
1265
|
const eventMs = timestampMs(event.timestamp);
|
|
1236
1266
|
return eventMs !== undefined
|
|
1237
1267
|
&& eventMs >= startMs - 1_000
|
package/dist/backend/server.js
CHANGED
|
@@ -30,6 +30,7 @@ import { createSessionRegistry } from "./runtime/session-registry.js";
|
|
|
30
30
|
import { createSessionService } from "./services/session-service.js";
|
|
31
31
|
import { createMessageService } from "./services/message-service.js";
|
|
32
32
|
import { createRoundService } from "./services/round-service.js";
|
|
33
|
+
import { createRuntimeCoordinatorService } from "./services/runtime-coordinator-service.js";
|
|
33
34
|
import { createStatusService } from "./services/status-service.js";
|
|
34
35
|
import { createTaskService } from "./services/task-service.js";
|
|
35
36
|
import { createTranslationService } from "./services/translation-service.js";
|
|
@@ -99,7 +100,8 @@ export async function createServer(deps, options = {}) {
|
|
|
99
100
|
sessionService: deps.sessionService,
|
|
100
101
|
translationWorkerService: deps.translationWorkerService,
|
|
101
102
|
harnessService: deps.harnessService,
|
|
102
|
-
harnessFeedbackService: deps.harnessFeedbackService
|
|
103
|
+
harnessFeedbackService: deps.harnessFeedbackService,
|
|
104
|
+
runtimeCoordinator: deps.runtimeCoordinator
|
|
103
105
|
});
|
|
104
106
|
registerTaskRoutes(app, {
|
|
105
107
|
projectService: deps.projectService,
|
|
@@ -290,6 +292,19 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
290
292
|
runtime,
|
|
291
293
|
appSettings
|
|
292
294
|
});
|
|
295
|
+
const runtimeCoordinator = createRuntimeCoordinatorService({
|
|
296
|
+
appSettings,
|
|
297
|
+
taskService,
|
|
298
|
+
sessionService,
|
|
299
|
+
translationService,
|
|
300
|
+
harnessService,
|
|
301
|
+
harnessFeedbackService,
|
|
302
|
+
roundService,
|
|
303
|
+
gatewayService,
|
|
304
|
+
async getStateRoot(repoRoot) {
|
|
305
|
+
return (await projectService.loadConfig(repoRoot)).stateRoot;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
293
308
|
const claudeHookService = createClaudeHookService({
|
|
294
309
|
projectService,
|
|
295
310
|
taskService,
|
|
@@ -328,6 +343,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
328
343
|
statusService,
|
|
329
344
|
translationService,
|
|
330
345
|
gatewayService,
|
|
346
|
+
runtimeCoordinator,
|
|
331
347
|
runtime,
|
|
332
348
|
diagnosticsService
|
|
333
349
|
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { isVcmRoleName } from "../../shared/constants.js";
|
|
2
|
+
import { VcmError } from "../errors.js";
|
|
3
|
+
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
4
|
+
const EXPECTED_AUTO_RETROSPECTIVE_SKIP_CODES = new Set([
|
|
5
|
+
"HARNESS_FEEDBACK_ACTIVE",
|
|
6
|
+
"TASK_HARNESS_RETROSPECTIVE_EXISTS",
|
|
7
|
+
"TASK_FINAL_ACCEPTANCE_NOT_READY",
|
|
8
|
+
"HARNESS_ENGINEER_BUSY",
|
|
9
|
+
"HARNESS_ENGINEER_SESSION_MISSING",
|
|
10
|
+
"PROJECT_TOOL_TASK_REQUIRED"
|
|
11
|
+
]);
|
|
12
|
+
export function createRuntimeCoordinatorService(deps) {
|
|
13
|
+
const locks = new Map();
|
|
14
|
+
async function withRepoLock(repoRoot, run) {
|
|
15
|
+
const previous = locks.get(repoRoot) ?? Promise.resolve({
|
|
16
|
+
activeTask: null,
|
|
17
|
+
gatewayStatus: null
|
|
18
|
+
});
|
|
19
|
+
const next = previous.catch(() => ({
|
|
20
|
+
activeTask: null,
|
|
21
|
+
gatewayStatus: null
|
|
22
|
+
})).then(run);
|
|
23
|
+
locks.set(repoRoot, next);
|
|
24
|
+
try {
|
|
25
|
+
return await next;
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
if (locks.get(repoRoot) === next) {
|
|
29
|
+
locks.delete(repoRoot);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
reconcileProject(repoRoot, input = {}) {
|
|
35
|
+
return withRepoLock(repoRoot, async () => {
|
|
36
|
+
const [activeTask, gatewayStatus] = await Promise.all([
|
|
37
|
+
resolveActiveTask(repoRoot, input.taskSlug),
|
|
38
|
+
deps.gatewayService.getStatus().catch(() => null)
|
|
39
|
+
]);
|
|
40
|
+
const preferences = await deps.appSettings.getPreferences();
|
|
41
|
+
if (!activeTask) {
|
|
42
|
+
return { activeTask: null, gatewayStatus };
|
|
43
|
+
}
|
|
44
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(activeTask);
|
|
45
|
+
const harnessInitialized = await deps.harnessService.getHarnessStatus(taskRepoRoot)
|
|
46
|
+
.then((status) => status.initialized)
|
|
47
|
+
.catch(() => false);
|
|
48
|
+
await Promise.all([
|
|
49
|
+
reconcileHarnessEngineer(repoRoot, activeTask),
|
|
50
|
+
reconcileTranslator(repoRoot, activeTask, preferences.translationEnabled && harnessInitialized)
|
|
51
|
+
]);
|
|
52
|
+
if (preferences.translationEnabled && harnessInitialized) {
|
|
53
|
+
await startConversationTranslationListeners(repoRoot, activeTask);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
await deps.translationService.stopTask(taskRepoRoot, activeTask.taskSlug).catch(() => undefined);
|
|
57
|
+
}
|
|
58
|
+
if (preferences.autoTaskHarnessReviewEnabled) {
|
|
59
|
+
await maybeStartTaskHarnessRetrospective(repoRoot, activeTask);
|
|
60
|
+
}
|
|
61
|
+
return { activeTask, gatewayStatus };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
async function resolveActiveTask(repoRoot, requestedTaskSlug) {
|
|
66
|
+
const tasks = await deps.taskService.listTasks(repoRoot);
|
|
67
|
+
const activeTasks = tasks.filter((task) => task.cleanupStatus !== "cleaned");
|
|
68
|
+
if (requestedTaskSlug) {
|
|
69
|
+
const requested = activeTasks.find((task) => task.taskSlug === requestedTaskSlug);
|
|
70
|
+
if (requested) {
|
|
71
|
+
return requested;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return activeTasks[0] ?? null;
|
|
75
|
+
}
|
|
76
|
+
async function reconcileHarnessEngineer(repoRoot, task) {
|
|
77
|
+
const existing = await deps.sessionService.getProjectHarnessEngineerSession(repoRoot);
|
|
78
|
+
if (!shouldAutoEnsureProjectToolSession(existing)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
await deps.sessionService.ensureProjectHarnessEngineerSession(repoRoot, {
|
|
82
|
+
taskSlug: task.taskSlug,
|
|
83
|
+
permissionMode: existing?.permissionMode,
|
|
84
|
+
model: existing?.model,
|
|
85
|
+
effort: existing?.effort
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function reconcileTranslator(repoRoot, task, enabled) {
|
|
89
|
+
if (!enabled) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const existing = await deps.sessionService.getProjectTranslatorSession(repoRoot);
|
|
93
|
+
if (!shouldAutoEnsureProjectToolSession(existing)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await deps.sessionService.ensureProjectTranslatorSession(repoRoot, {
|
|
97
|
+
taskSlug: task.taskSlug,
|
|
98
|
+
permissionMode: existing?.permissionMode,
|
|
99
|
+
model: existing?.model,
|
|
100
|
+
effort: existing?.effort
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function shouldAutoEnsureProjectToolSession(session) {
|
|
104
|
+
return Boolean(session && (session.status === "running" || session.claudeSessionId));
|
|
105
|
+
}
|
|
106
|
+
async function startConversationTranslationListeners(repoRoot, task) {
|
|
107
|
+
const translator = await deps.sessionService.getProjectTranslatorSession(repoRoot);
|
|
108
|
+
if (translator?.status !== "running") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
112
|
+
const sessions = await deps.sessionService.listRoleSessions(repoRoot, task.taskSlug);
|
|
113
|
+
await Promise.all(sessions
|
|
114
|
+
.filter((session) => session.status === "running" && isVcmRoleName(session.role))
|
|
115
|
+
.map((session) => startConversationTranslationListener(repoRoot, taskRepoRoot, task.taskSlug, session.role)));
|
|
116
|
+
}
|
|
117
|
+
async function startConversationTranslationListener(repoRoot, taskRepoRoot, taskSlug, role) {
|
|
118
|
+
try {
|
|
119
|
+
await deps.translationService.startSession({
|
|
120
|
+
repoRoot,
|
|
121
|
+
taskRepoRoot,
|
|
122
|
+
taskSlug,
|
|
123
|
+
role
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error instanceof VcmError && error.code === "SESSION_NOT_RUNNING") {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function maybeStartTaskHarnessRetrospective(repoRoot, task) {
|
|
134
|
+
const stateRoot = await deps.getStateRoot(repoRoot);
|
|
135
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
136
|
+
const roundState = await deps.roundService.getSessionRoundState({
|
|
137
|
+
repoRoot,
|
|
138
|
+
stateRepoRoot: taskRepoRoot,
|
|
139
|
+
stateRoot,
|
|
140
|
+
taskSlug: task.taskSlug
|
|
141
|
+
});
|
|
142
|
+
if (roundState.status !== "stopped"
|
|
143
|
+
|| !roundState.roundId
|
|
144
|
+
|| roundState.roleRecovery?.status === "failed") {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await deps.harnessFeedbackService.startTaskRetrospective(repoRoot, {
|
|
149
|
+
taskSlug: task.taskSlug,
|
|
150
|
+
taskRepoRoot,
|
|
151
|
+
handoffDir: task.handoffDir,
|
|
152
|
+
trigger: "auto"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error instanceof VcmError && (EXPECTED_AUTO_RETROSPECTIVE_SKIP_CODES.has(error.code) || error.statusCode === 409)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -14,6 +14,7 @@ const HARNESS_ENGINEER_DIR = ".ai/vcm/harness-engineer";
|
|
|
14
14
|
const HARNESS_ENGINEER_SESSION_PATH = ".ai/vcm/harness-engineer/session.json";
|
|
15
15
|
const PROJECT_TRANSLATOR_SCOPE = "__project__";
|
|
16
16
|
const PROJECT_HARNESS_ENGINEER_SCOPE = "__project_harness_engineer__";
|
|
17
|
+
const PROJECT_TOOL_CD_ENTER_DELAY_MS = 500;
|
|
17
18
|
export function createSessionService(deps) {
|
|
18
19
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
19
20
|
async function readCurrentHarnessRevision(repoRoot) {
|
|
@@ -301,7 +302,9 @@ export function createSessionService(deps) {
|
|
|
301
302
|
}
|
|
302
303
|
assertSafeCwdTarget(targetCwd);
|
|
303
304
|
const timestamp = now();
|
|
304
|
-
await submitTerminalInput(deps.runtime, session.id,
|
|
305
|
+
await submitTerminalInput(deps.runtime, session.id, formatClaudeCdCommand(targetCwd), {
|
|
306
|
+
enterDelayMs: PROJECT_TOOL_CD_ENTER_DELAY_MS
|
|
307
|
+
});
|
|
305
308
|
const updated = {
|
|
306
309
|
...session,
|
|
307
310
|
cwd: targetCwd,
|
|
@@ -1260,3 +1263,6 @@ function normalizeClaudeEffort(value) {
|
|
|
1260
1263
|
}
|
|
1261
1264
|
return "default";
|
|
1262
1265
|
}
|
|
1266
|
+
function formatClaudeCdCommand(targetCwd) {
|
|
1267
|
+
return `/cd ${JSON.stringify(targetCwd)}`;
|
|
1268
|
+
}
|