vibe-coding-master 0.8.1 → 0.8.3
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 +20 -16
- package/dist/backend/api/task-routes.js +2 -31
- package/dist/backend/cli/install-vcm-harness.js +0 -7
- package/dist/backend/gateway/gateway-command-parser.js +17 -0
- package/dist/backend/gateway/gateway-service.js +168 -86
- package/dist/backend/gateway/gateway-settings-service.js +21 -8
- package/dist/backend/server.js +0 -9
- package/dist/backend/services/claude-hook-service.js +2 -91
- package/dist/backend/services/harness-service.js +0 -7
- package/dist/backend/services/runtime-coordinator-service.js +0 -10
- package/dist/backend/services/session-service.js +5 -71
- package/dist/backend/services/translation-service.js +1 -1
- package/dist/shared/types/session.js +9 -2
- package/dist-frontend/assets/{index-Dh7uVCmk.js → index-C21rIHsU.js} +45 -45
- package/dist-frontend/assets/{index-DDmygnV6.css → index-Dc0_KSf4.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist/backend/services/role-stall-detector-service.js +0 -322
- package/dist/shared/types/role-stall.js +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { isVcmRoleName } from "../../shared/constants.js";
|
|
2
3
|
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
3
4
|
const DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
4
5
|
const DEFAULT_GATEWAY_CHANNEL = "weixin-ilink";
|
|
@@ -41,6 +42,7 @@ export function createGatewaySettingsService(deps) {
|
|
|
41
42
|
translationEnabled: input.translationEnabled ?? current.translationEnabled,
|
|
42
43
|
currentProjectId: input.currentProjectId !== undefined ? normalizeNullableString(input.currentProjectId) : current.currentProjectId,
|
|
43
44
|
currentTaskSlug: input.currentTaskSlug !== undefined ? normalizeNullableString(input.currentTaskSlug) : current.currentTaskSlug,
|
|
45
|
+
targetRole: input.targetRole ?? current.targetRole,
|
|
44
46
|
binding: {
|
|
45
47
|
...current.binding,
|
|
46
48
|
baseUrl: input.baseUrl !== undefined
|
|
@@ -71,7 +73,7 @@ export function createGatewaySettingsService(deps) {
|
|
|
71
73
|
recentInboundMessageIds: []
|
|
72
74
|
},
|
|
73
75
|
pushCursors: {},
|
|
74
|
-
|
|
76
|
+
latestRoleReplies: current.latestRoleReplies,
|
|
75
77
|
lastPollStatus: {
|
|
76
78
|
state: "idle"
|
|
77
79
|
},
|
|
@@ -89,6 +91,8 @@ export function createGatewaySettingsService(deps) {
|
|
|
89
91
|
translationEnabled: settings.translationEnabled,
|
|
90
92
|
currentProjectId: settings.currentProjectId,
|
|
91
93
|
currentTaskSlug: settings.currentTaskSlug,
|
|
94
|
+
targetRole: settings.targetRole,
|
|
95
|
+
targetRoleSessionStatus: null,
|
|
92
96
|
binding: {
|
|
93
97
|
accountId: settings.binding.accountId,
|
|
94
98
|
baseUrl: settings.binding.baseUrl,
|
|
@@ -128,9 +132,12 @@ export function normalizeSettings(input, timestamp, options = {
|
|
|
128
132
|
? input.pendingConfirmations
|
|
129
133
|
: {};
|
|
130
134
|
const pushCursors = isObject(input.pushCursors) ? input.pushCursors : {};
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
const legacyInput = input;
|
|
136
|
+
const latestRoleReplies = isObject(input.latestRoleReplies)
|
|
137
|
+
? input.latestRoleReplies
|
|
138
|
+
: isObject(legacyInput.latestPmReplies)
|
|
139
|
+
? legacyInput.latestPmReplies
|
|
140
|
+
: {};
|
|
134
141
|
return {
|
|
135
142
|
version: 1,
|
|
136
143
|
enabled: input.enabled === true,
|
|
@@ -138,6 +145,7 @@ export function normalizeSettings(input, timestamp, options = {
|
|
|
138
145
|
translationEnabled: input.translationEnabled !== false,
|
|
139
146
|
currentProjectId: normalizeNullableString(input.currentProjectId),
|
|
140
147
|
currentTaskSlug: normalizeNullableString(input.currentTaskSlug),
|
|
148
|
+
targetRole: normalizeTargetRole(input.targetRole),
|
|
141
149
|
binding: {
|
|
142
150
|
...createDefaultBinding(options.defaultBaseUrl),
|
|
143
151
|
accountId: normalizeNullableString(bindingInput.accountId),
|
|
@@ -169,7 +177,7 @@ export function normalizeSettings(input, timestamp, options = {
|
|
|
169
177
|
closeTask: normalizeCloseTaskConfirmation(pendingInput.closeTask)
|
|
170
178
|
},
|
|
171
179
|
pushCursors: normalizePushCursors(pushCursors),
|
|
172
|
-
|
|
180
|
+
latestRoleReplies: normalizeLatestRoleReplies(latestRoleReplies),
|
|
173
181
|
lastPollStatus: normalizePollStatus(input.lastPollStatus),
|
|
174
182
|
lastMessageStatus: normalizeMessageStatus(input.lastMessageStatus),
|
|
175
183
|
updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : timestamp
|
|
@@ -200,6 +208,9 @@ function normalizeGatewayChannel(input, fallback) {
|
|
|
200
208
|
function normalizeLarkDomain(input) {
|
|
201
209
|
return input === "lark" || input === "feishu" ? input : null;
|
|
202
210
|
}
|
|
211
|
+
function normalizeTargetRole(input) {
|
|
212
|
+
return typeof input === "string" && isVcmRoleName(input) ? input : "project-manager";
|
|
213
|
+
}
|
|
203
214
|
function normalizeCloseTaskConfirmation(input) {
|
|
204
215
|
if (!isObject(input)) {
|
|
205
216
|
return null;
|
|
@@ -225,14 +236,15 @@ function normalizePushCursors(input) {
|
|
|
225
236
|
}
|
|
226
237
|
return out;
|
|
227
238
|
}
|
|
228
|
-
function
|
|
239
|
+
function normalizeLatestRoleReplies(input) {
|
|
229
240
|
const out = {};
|
|
230
|
-
for (const
|
|
241
|
+
for (const value of Object.values(input)) {
|
|
231
242
|
if (!isObject(value)) {
|
|
232
243
|
continue;
|
|
233
244
|
}
|
|
234
245
|
const repoRoot = normalizeNullableString(value.repoRoot);
|
|
235
246
|
const taskSlug = normalizeNullableString(value.taskSlug);
|
|
247
|
+
const role = normalizeTargetRole("role" in value ? value.role : undefined);
|
|
236
248
|
const sessionId = normalizeNullableString(value.sessionId);
|
|
237
249
|
const claudeSessionId = normalizeNullableString(value.claudeSessionId);
|
|
238
250
|
const capturedAt = normalizeNullableString(value.capturedAt);
|
|
@@ -240,9 +252,10 @@ function normalizeLatestPmReplies(input) {
|
|
|
240
252
|
if (!repoRoot || !taskSlug || !sessionId || !claudeSessionId || !capturedAt || !text) {
|
|
241
253
|
continue;
|
|
242
254
|
}
|
|
243
|
-
out[
|
|
255
|
+
out[JSON.stringify([repoRoot, taskSlug, role])] = {
|
|
244
256
|
repoRoot,
|
|
245
257
|
taskSlug,
|
|
258
|
+
role,
|
|
246
259
|
sessionId,
|
|
247
260
|
claudeSessionId,
|
|
248
261
|
transcriptEventId: normalizeNullableString(value.transcriptEventId),
|
package/dist/backend/server.js
CHANGED
|
@@ -14,7 +14,6 @@ import { createCodexBridgeIntegrationService } from "./services/codex-bridge-int
|
|
|
14
14
|
import { createAutoMemoryService } from "./services/auto-memory-service.js";
|
|
15
15
|
import { createArchitectRestartService } from "./services/architect-restart-service.js";
|
|
16
16
|
import { createRoleContextRestartService } from "./services/role-context-restart-service.js";
|
|
17
|
-
import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
|
|
18
17
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
19
18
|
import { createGateReviewService } from "./services/gate-review-service.js";
|
|
20
19
|
import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
|
|
@@ -137,7 +136,6 @@ export async function createServer(deps, options = {}) {
|
|
|
137
136
|
roundService: deps.roundService,
|
|
138
137
|
taskWorkflowService: deps.taskWorkflowService,
|
|
139
138
|
architectRestartService: deps.architectRestartService,
|
|
140
|
-
roleStallDetector: deps.roleStallDetector,
|
|
141
139
|
translationService: deps.translationService
|
|
142
140
|
});
|
|
143
141
|
registerSessionRoutes(app, {
|
|
@@ -351,10 +349,6 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
351
349
|
sessionService
|
|
352
350
|
});
|
|
353
351
|
const transcripts = createClaudeTranscriptService();
|
|
354
|
-
const roleStallDetector = createRoleStallDetectorService({
|
|
355
|
-
sessionService,
|
|
356
|
-
roundService
|
|
357
|
-
});
|
|
358
352
|
const translationService = createTranslationService({
|
|
359
353
|
runtime,
|
|
360
354
|
sessionRegistry: registry,
|
|
@@ -432,7 +426,6 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
432
426
|
translationWorkerService,
|
|
433
427
|
architectRestartService,
|
|
434
428
|
roleContextRestartService,
|
|
435
|
-
roleStallDetector,
|
|
436
429
|
workflowControlService
|
|
437
430
|
});
|
|
438
431
|
const runtimeCoordinator = createRuntimeCoordinatorService({
|
|
@@ -445,7 +438,6 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
445
438
|
harnessFeedbackService,
|
|
446
439
|
autoMemoryService,
|
|
447
440
|
roundService,
|
|
448
|
-
roleStallDetector,
|
|
449
441
|
gatewayService,
|
|
450
442
|
async getStateRoot(repoRoot) {
|
|
451
443
|
return (await projectService.loadConfig(repoRoot)).stateRoot;
|
|
@@ -487,7 +479,6 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
487
479
|
autoMemoryService,
|
|
488
480
|
commandDispatcher,
|
|
489
481
|
claudeHookService,
|
|
490
|
-
roleStallDetector,
|
|
491
482
|
messageService,
|
|
492
483
|
taskLaunchService,
|
|
493
484
|
gateReviewService,
|
|
@@ -257,12 +257,6 @@ export function createClaudeHookService(deps) {
|
|
|
257
257
|
role: input.role,
|
|
258
258
|
eventName
|
|
259
259
|
});
|
|
260
|
-
await deps.roleStallDetector?.recordHook({
|
|
261
|
-
...createStallContext(context),
|
|
262
|
-
role: input.role,
|
|
263
|
-
eventName,
|
|
264
|
-
event: input.event
|
|
265
|
-
});
|
|
266
260
|
}
|
|
267
261
|
if (session) {
|
|
268
262
|
await deps.translationService.recordConversationBoundary({
|
|
@@ -360,12 +354,6 @@ export function createClaudeHookService(deps) {
|
|
|
360
354
|
}
|
|
361
355
|
}
|
|
362
356
|
}
|
|
363
|
-
await deps.roleStallDetector?.recordHook({
|
|
364
|
-
...createStallContext(context),
|
|
365
|
-
role: input.role,
|
|
366
|
-
eventName,
|
|
367
|
-
event: input.event
|
|
368
|
-
});
|
|
369
357
|
if (pmAwaitingUser) {
|
|
370
358
|
return recordTurnEnd(input, context, eventName, {
|
|
371
359
|
dispatchRouteFiles: false,
|
|
@@ -417,12 +405,6 @@ export function createClaudeHookService(deps) {
|
|
|
417
405
|
if (memoryResult) {
|
|
418
406
|
return memoryResult;
|
|
419
407
|
}
|
|
420
|
-
await deps.roleStallDetector?.recordHook({
|
|
421
|
-
...createStallContext(context),
|
|
422
|
-
role: input.role,
|
|
423
|
-
eventName,
|
|
424
|
-
event: input.event
|
|
425
|
-
});
|
|
426
408
|
const routeDispatchInput = createRouteDispatchInput(input, context);
|
|
427
409
|
const pending = await deps.messageService.listPendingRouteFiles(routeDispatchInput);
|
|
428
410
|
const hasCompletionEvidence = pending.some((routeFile) => routeFile.fromRole === input.role);
|
|
@@ -496,14 +478,6 @@ export function createClaudeHookService(deps) {
|
|
|
496
478
|
runtimeSessionId: stringOrUndefined(input.event.vcm_runtime_session_id),
|
|
497
479
|
runtimeSessionToken: input.runtimeSessionToken
|
|
498
480
|
});
|
|
499
|
-
if (session) {
|
|
500
|
-
await deps.roleStallDetector?.recordHook({
|
|
501
|
-
...createStallContext(context),
|
|
502
|
-
role: input.role,
|
|
503
|
-
eventName,
|
|
504
|
-
event: input.event
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
481
|
return {
|
|
508
482
|
ok: true,
|
|
509
483
|
eventName,
|
|
@@ -568,8 +542,8 @@ export function createClaudeHookService(deps) {
|
|
|
568
542
|
if (eventName === "Stop" && input.role === "architect" && session) {
|
|
569
543
|
await deps.architectRestartService?.recordArchitectStop(context.project.repoRoot, context.taskSlug, session.id);
|
|
570
544
|
}
|
|
571
|
-
if (options.notifyGateway && session && input.role
|
|
572
|
-
void deps.gatewayService?.
|
|
545
|
+
if (options.notifyGateway && session && isVcmRoleName(input.role)) {
|
|
546
|
+
void deps.gatewayService?.handleRoleStop({
|
|
573
547
|
repoRoot: context.project.repoRoot,
|
|
574
548
|
taskSlug: context.taskSlug,
|
|
575
549
|
session
|
|
@@ -637,14 +611,6 @@ export function createClaudeHookService(deps) {
|
|
|
637
611
|
eventName
|
|
638
612
|
});
|
|
639
613
|
}
|
|
640
|
-
if (boundToTask) {
|
|
641
|
-
await deps.roleStallDetector?.recordHook({
|
|
642
|
-
...createStallContext(context),
|
|
643
|
-
role: input.role,
|
|
644
|
-
eventName,
|
|
645
|
-
event: input.event
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
614
|
await deps.autoMemoryService.handleRoleHook({
|
|
649
615
|
baseRepoRoot: context.project.repoRoot,
|
|
650
616
|
taskRepoRoot: context.taskRepoRoot,
|
|
@@ -869,31 +835,6 @@ export function createClaudeHookService(deps) {
|
|
|
869
835
|
taskSlug: context.taskSlug
|
|
870
836
|
};
|
|
871
837
|
}
|
|
872
|
-
function createStallContext(context) {
|
|
873
|
-
return {
|
|
874
|
-
repoRoot: context.project.repoRoot,
|
|
875
|
-
taskRepoRoot: context.taskRepoRoot,
|
|
876
|
-
stateRoot: context.config.stateRoot,
|
|
877
|
-
taskSlug: context.taskSlug
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
async function processProgressHook(input, eventName) {
|
|
881
|
-
const role = input.role;
|
|
882
|
-
if (!isVcmRoleName(role)) {
|
|
883
|
-
return completedHookResult(input, eventName);
|
|
884
|
-
}
|
|
885
|
-
const context = await getHookContext(input);
|
|
886
|
-
if (!(await isCurrentRoleHook(context, input, eventName))) {
|
|
887
|
-
return completedHookResult(input, eventName);
|
|
888
|
-
}
|
|
889
|
-
await deps.roleStallDetector?.recordHook({
|
|
890
|
-
...createStallContext(context),
|
|
891
|
-
role,
|
|
892
|
-
eventName,
|
|
893
|
-
event: input.event
|
|
894
|
-
});
|
|
895
|
-
return completedHookResult(input, eventName);
|
|
896
|
-
}
|
|
897
838
|
function renderStopFailureRecoveryPrompt() {
|
|
898
839
|
return [
|
|
899
840
|
"[VCM Recovery]",
|
|
@@ -936,17 +877,6 @@ export function createClaudeHookService(deps) {
|
|
|
936
877
|
});
|
|
937
878
|
}
|
|
938
879
|
const preferences = await deps.appSettings.getPreferences();
|
|
939
|
-
if (deps.roleStallDetector) {
|
|
940
|
-
const context = await getHookContext(input);
|
|
941
|
-
if (await isCurrentRoleHook(context, input, "PermissionRequest")) {
|
|
942
|
-
await deps.roleStallDetector.recordHook({
|
|
943
|
-
...createStallContext(context),
|
|
944
|
-
role: input.role,
|
|
945
|
-
eventName: "PermissionRequest",
|
|
946
|
-
event: input.event
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
880
|
if (preferences.permissionRequestMode !== "allowAll") {
|
|
951
881
|
return undefined;
|
|
952
882
|
}
|
|
@@ -963,9 +893,6 @@ export function createClaudeHookService(deps) {
|
|
|
963
893
|
async handleHook(input) {
|
|
964
894
|
return withRoleHookLock(input, async () => {
|
|
965
895
|
const eventName = parseHookEvent(input.event.hook_event_name);
|
|
966
|
-
if (isProgressHook(eventName)) {
|
|
967
|
-
return processProgressHook(input, eventName);
|
|
968
|
-
}
|
|
969
896
|
if (isTranslatorToolRoleName(input.role)) {
|
|
970
897
|
return processTranslatorHook(input);
|
|
971
898
|
}
|
|
@@ -1004,13 +931,6 @@ function parseHookEvent(value) {
|
|
|
1004
931
|
if (value === "UserPromptSubmit"
|
|
1005
932
|
|| value === "Stop"
|
|
1006
933
|
|| value === "StopFailure"
|
|
1007
|
-
|| value === "PreToolUse"
|
|
1008
|
-
|| value === "PostToolUse"
|
|
1009
|
-
|| value === "PostToolUseFailure"
|
|
1010
|
-
|| value === "PostToolBatch"
|
|
1011
|
-
|| value === "SubagentStart"
|
|
1012
|
-
|| value === "SubagentStop"
|
|
1013
|
-
|| value === "PreCompact"
|
|
1014
934
|
|| value === "PostCompact") {
|
|
1015
935
|
return value;
|
|
1016
936
|
}
|
|
@@ -1021,15 +941,6 @@ function parseHookEvent(value) {
|
|
|
1021
941
|
hint: "Use a Claude Code event installed by the VCM Harness."
|
|
1022
942
|
});
|
|
1023
943
|
}
|
|
1024
|
-
function isProgressHook(eventName) {
|
|
1025
|
-
return eventName === "PreToolUse"
|
|
1026
|
-
|| eventName === "PostToolUse"
|
|
1027
|
-
|| eventName === "PostToolUseFailure"
|
|
1028
|
-
|| eventName === "PostToolBatch"
|
|
1029
|
-
|| eventName === "SubagentStart"
|
|
1030
|
-
|| eventName === "SubagentStop"
|
|
1031
|
-
|| eventName === "PreCompact";
|
|
1032
|
-
}
|
|
1033
944
|
function parseBusinessHookEvent(value) {
|
|
1034
945
|
const eventName = parseHookEvent(value);
|
|
1035
946
|
if (eventName === "UserPromptSubmit"
|
|
@@ -63,15 +63,8 @@ const VCM_HOOK_DEFINITIONS = [
|
|
|
63
63
|
{ eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
64
64
|
{ eventName: "PreToolUse", matcher: "Agent", command: VCM_SUBAGENT_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
65
65
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
66
|
-
{ eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
67
|
-
{ eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
68
|
-
{ eventName: "PostToolUseFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
69
|
-
{ eventName: "PostToolBatch", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
70
|
-
{ eventName: "SubagentStart", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
71
|
-
{ eventName: "SubagentStop", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
72
66
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
73
67
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
74
|
-
{ eventName: "PreCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
75
68
|
{ eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
76
69
|
{ eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
|
|
77
70
|
];
|
|
@@ -42,17 +42,9 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
42
42
|
]);
|
|
43
43
|
const preferences = await deps.appSettings.getPreferences();
|
|
44
44
|
if (!activeTask) {
|
|
45
|
-
deps.roleStallDetector.clearProject(repoRoot);
|
|
46
45
|
return { activeTask: null, gatewayStatus };
|
|
47
46
|
}
|
|
48
47
|
const taskRepoRoot = getTaskRuntimeRepoRoot(activeTask);
|
|
49
|
-
const stateRoot = await deps.getStateRoot(repoRoot);
|
|
50
|
-
await deps.roleStallDetector.reconcileTask({
|
|
51
|
-
repoRoot,
|
|
52
|
-
taskRepoRoot,
|
|
53
|
-
stateRoot,
|
|
54
|
-
taskSlug: activeTask.taskSlug
|
|
55
|
-
});
|
|
56
48
|
const harnessInitialized = await deps.harnessService.getHarnessStatus(taskRepoRoot)
|
|
57
49
|
.then((status) => status.initialized)
|
|
58
50
|
.catch(() => false);
|
|
@@ -86,7 +78,6 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
86
78
|
void reconcileCurrentProject().catch(() => undefined);
|
|
87
79
|
},
|
|
88
80
|
stop() {
|
|
89
|
-
deps.roleStallDetector.stop();
|
|
90
81
|
if (reconcileTimer === undefined) {
|
|
91
82
|
return;
|
|
92
83
|
}
|
|
@@ -98,7 +89,6 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
98
89
|
async function reconcileCurrentProject() {
|
|
99
90
|
const project = await deps.projectService.getCurrentProject();
|
|
100
91
|
if (!project) {
|
|
101
|
-
deps.roleStallDetector.stop();
|
|
102
92
|
return;
|
|
103
93
|
}
|
|
104
94
|
await reconcileProject(project.repoRoot);
|
|
@@ -721,8 +721,7 @@ export function createSessionService(deps) {
|
|
|
721
721
|
return undefined;
|
|
722
722
|
}
|
|
723
723
|
const timestamp = now();
|
|
724
|
-
const turnWasRunning = current.activityStatus === "running"
|
|
725
|
-
&& current.expectedRuntimeExitReason !== "role-stall-recovery";
|
|
724
|
+
const turnWasRunning = current.activityStatus === "running";
|
|
726
725
|
const updated = {
|
|
727
726
|
...current,
|
|
728
727
|
status: input.status,
|
|
@@ -738,71 +737,6 @@ export function createSessionService(deps) {
|
|
|
738
737
|
await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, updated);
|
|
739
738
|
return { record: updated, turnWasRunning };
|
|
740
739
|
}
|
|
741
|
-
async function persistTaskRoleRecord(repoRoot, taskSlug, record) {
|
|
742
|
-
deps.registry.upsert(record);
|
|
743
|
-
const config = await deps.projectService.loadConfig(repoRoot);
|
|
744
|
-
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
745
|
-
await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, record);
|
|
746
|
-
return record;
|
|
747
|
-
}
|
|
748
|
-
async function recoverRoleSession(repoRoot, taskSlug, input) {
|
|
749
|
-
const current = await getTaskRoleSessionView(repoRoot, taskSlug, input.role);
|
|
750
|
-
if (!current
|
|
751
|
-
|| current.id !== input.expectedSessionId
|
|
752
|
-
|| current.runtimeSessionToken !== input.expectedRuntimeSessionToken
|
|
753
|
-
|| current.status !== "running"
|
|
754
|
-
|| current.activityStatus !== "running") {
|
|
755
|
-
throw new VcmError({
|
|
756
|
-
code: "ROLE_STALL_SESSION_CHANGED",
|
|
757
|
-
message: `${input.role} session changed before recovery could begin.`,
|
|
758
|
-
statusCode: 409
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
if (!current.claudeSessionId) {
|
|
762
|
-
throw new VcmError({
|
|
763
|
-
code: "CLAUDE_SESSION_MISSING",
|
|
764
|
-
message: `${input.role} does not have a confirmed Claude session id to resume.`,
|
|
765
|
-
statusCode: 409
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
const prepared = await persistTaskRoleRecord(repoRoot, taskSlug, {
|
|
769
|
-
...current,
|
|
770
|
-
expectedRuntimeExitReason: "role-stall-recovery",
|
|
771
|
-
updatedAt: now()
|
|
772
|
-
});
|
|
773
|
-
if (deps.runtime.getSession(prepared.id)) {
|
|
774
|
-
await deps.runtime.stop(prepared.id);
|
|
775
|
-
}
|
|
776
|
-
await persistTaskRoleRecord(repoRoot, taskSlug, {
|
|
777
|
-
...prepared,
|
|
778
|
-
status: "exited",
|
|
779
|
-
activityStatus: "idle",
|
|
780
|
-
pid: undefined,
|
|
781
|
-
expectedRuntimeExitReason: undefined,
|
|
782
|
-
updatedAt: now()
|
|
783
|
-
});
|
|
784
|
-
const resumed = await launchRoleSession(repoRoot, taskSlug, input.role, {
|
|
785
|
-
permissionMode: prepared.permissionMode,
|
|
786
|
-
model: prepared.model,
|
|
787
|
-
effort: prepared.effort
|
|
788
|
-
}, "resume");
|
|
789
|
-
if ((await waitForSessionInputReady(resumed.id)) === "exited") {
|
|
790
|
-
throw new VcmError({
|
|
791
|
-
code: "ROLE_STALL_RESUME_FAILED",
|
|
792
|
-
message: `${input.role} session exited before recovery input could be submitted.`,
|
|
793
|
-
statusCode: 409
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
await submitTerminalInput(deps.runtime, resumed.id, input.recoveryPrompt);
|
|
797
|
-
return persistTaskRoleRecord(repoRoot, taskSlug, {
|
|
798
|
-
...resumed,
|
|
799
|
-
activityStatus: "running",
|
|
800
|
-
lastTurnStartedAt: now(),
|
|
801
|
-
lastHookEventAt: now(),
|
|
802
|
-
expectedRuntimeExitReason: undefined,
|
|
803
|
-
updatedAt: now()
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
740
|
return {
|
|
807
741
|
async assertModelLaunchReady(model = "default") {
|
|
808
742
|
await getModelLaunchEnvironment(normalizeClaudeModel(model));
|
|
@@ -1307,8 +1241,7 @@ export function createSessionService(deps) {
|
|
|
1307
1241
|
: undefined;
|
|
1308
1242
|
}
|
|
1309
1243
|
return markTaskRoleActivityIdle(repoRoot, taskSlug, role);
|
|
1310
|
-
}
|
|
1311
|
-
recoverRoleSession
|
|
1244
|
+
}
|
|
1312
1245
|
};
|
|
1313
1246
|
}
|
|
1314
1247
|
function toRoleSessionRecordView(record, runtime) {
|
|
@@ -1398,7 +1331,7 @@ function isSessionMissingError(error) {
|
|
|
1398
1331
|
error.code === "SESSION_MISSING";
|
|
1399
1332
|
}
|
|
1400
1333
|
function withoutRuntimeOnlySessionFields(session) {
|
|
1401
|
-
const { pid: _pid, runtimeSessionToken: _runtimeSessionToken,
|
|
1334
|
+
const { pid: _pid, runtimeSessionToken: _runtimeSessionToken, ...persisted } = session;
|
|
1402
1335
|
return persisted;
|
|
1403
1336
|
}
|
|
1404
1337
|
function defaultIsProcessAlive(pid) {
|
|
@@ -1675,7 +1608,8 @@ function normalizeClaudePermissionMode(value) {
|
|
|
1675
1608
|
return "default";
|
|
1676
1609
|
}
|
|
1677
1610
|
function normalizeClaudeModel(value) {
|
|
1678
|
-
if (value === "
|
|
1611
|
+
if (value === "claude-fable-5-1"
|
|
1612
|
+
|| value === "opus"
|
|
1679
1613
|
|| value === "claude-opus-4-8"
|
|
1680
1614
|
|| value === "sonnet"
|
|
1681
1615
|
|| value === "fable"
|
|
@@ -1096,7 +1096,7 @@ export function createTranslationService(deps) {
|
|
|
1096
1096
|
code: "GATEWAY_TRANSLATION_RESULT_MISSING",
|
|
1097
1097
|
message: "Gateway output translation is not available in the current translation panel.",
|
|
1098
1098
|
statusCode: 409,
|
|
1099
|
-
hint: "Wait for the translation panel to finish translating the
|
|
1099
|
+
hint: "Wait for the translation panel to finish translating the selected role's Round Final Reply, then retry from Gateway."
|
|
1100
1100
|
});
|
|
1101
1101
|
},
|
|
1102
1102
|
async shouldDelayFlowPauseNotification(input) {
|
|
@@ -23,10 +23,17 @@ export const CLAUDE_MODEL_OPTIONS = [
|
|
|
23
23
|
source: "claude",
|
|
24
24
|
available: true
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
value: "claude-fable-5-1",
|
|
28
|
+
label: "Fable 5.1",
|
|
29
|
+
description: "Pinned Claude Fable 5.1",
|
|
30
|
+
source: "claude",
|
|
31
|
+
available: true
|
|
32
|
+
},
|
|
26
33
|
{
|
|
27
34
|
value: "fable",
|
|
28
|
-
label: "Fable",
|
|
29
|
-
description: "Claude Fable",
|
|
35
|
+
label: "Fable Alias",
|
|
36
|
+
description: "Claude Code provider-selected Fable",
|
|
30
37
|
source: "claude",
|
|
31
38
|
available: true
|
|
32
39
|
},
|