vibe-coding-master 0.3.29 → 0.3.31
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 +9 -13
- package/dist/backend/api/project-routes.js +1 -1
- package/dist/backend/api/{codex-translation-routes.js → translation-worker-routes.js} +21 -21
- package/dist/backend/cli/install-vcm-harness.js +30 -25
- package/dist/backend/server.js +12 -22
- package/dist/backend/services/claude-hook-service.js +51 -1
- package/dist/backend/services/harness-service.js +49 -29
- package/dist/backend/services/session-service.js +57 -163
- package/dist/backend/services/translation-service.js +17 -17
- package/dist/backend/services/{codex-translation-service.js → translation-worker-service.js} +11 -11
- package/dist/backend/templates/harness/gate-review.js +2 -58
- package/dist/shared/constants.js +13 -10
- package/dist/shared/types/app-settings.js +1 -1
- package/dist/shared/types/session.js +2 -14
- package/dist-frontend/assets/{index-Cfum1Prr.css → index-BvUHLq4X.css} +1 -1
- package/dist-frontend/assets/index-Cu2xwZfF.js +94 -0
- package/dist-frontend/index.html +2 -2
- package/docs/{codex-translation-plan.md → claude-code-translation-plan.md} +151 -153
- package/docs/full-harness-baseline.md +3 -7
- package/docs/gateway-design.md +1 -1
- package/docs/product-design.md +2 -2
- package/docs/v0.4-custom-workflow-plan.md +0 -1
- package/docs/vcm-cc-best-practices.md +2 -5
- package/package.json +1 -1
- package/dist/backend/api/codex-hook-routes.js +0 -9
- package/dist/backend/codex-sandbox-mode.js +0 -28
- package/dist/backend/services/codex-hook-service.js +0 -71
- package/dist/shared/types/codex-hook.js +0 -1
- package/dist-frontend/assets/index-K34QFpWK.js +0 -94
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { CORE_VCM_ROLE_NAMES,
|
|
3
|
+
import { CORE_VCM_ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
5
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
6
6
|
import { claudeTranscriptPath } from "./claude-transcript-service.js";
|
|
7
7
|
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
8
8
|
const GATE_REVIEWER_ROLE = "gate-reviewer";
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const CODEX_TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
|
|
13
|
-
const CODEX_TRANSLATOR_CONFIG_PATH = ".ai/codex-translator/config.toml";
|
|
9
|
+
const TRANSLATOR_ROLE = "translator";
|
|
10
|
+
const TRANSLATION_DIR = ".ai/vcm/translations";
|
|
11
|
+
const TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
|
|
14
12
|
const GATE_REVIEWER_SESSION_PATH = ".ai/vcm/gate-reviewer/session.json";
|
|
15
13
|
const PROJECT_TRANSLATOR_SCOPE = "__project__";
|
|
16
14
|
const PROJECT_GATE_REVIEWER_SCOPE = "__project_gate_reviewer__";
|
|
@@ -26,16 +24,9 @@ export function createSessionService(deps) {
|
|
|
26
24
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
27
25
|
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
28
26
|
const persisted = await loadPersistedRoleRecordForRole(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, taskSlug, role);
|
|
29
|
-
const isCodexRole = isCodexRoleName(role);
|
|
30
|
-
const isTranslator = role === CODEX_TRANSLATOR_ROLE;
|
|
31
|
-
const sessionRepoRoot = isTranslator ? repoRoot : taskRepoRoot;
|
|
32
27
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
33
|
-
const model =
|
|
34
|
-
|
|
35
|
-
: normalizeClaudeModel(input.model ?? persisted?.model);
|
|
36
|
-
const effort = isCodexRole
|
|
37
|
-
? normalizeCodexEffort(input.effort ?? persisted?.effort)
|
|
38
|
-
: normalizeClaudeEffort(input.effort ?? persisted?.effort);
|
|
28
|
+
const model = normalizeClaudeModel(input.model ?? persisted?.model);
|
|
29
|
+
const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort);
|
|
39
30
|
const claudeSessionId = launchMode === "resume"
|
|
40
31
|
? persisted?.claudeSessionId
|
|
41
32
|
: randomUUID();
|
|
@@ -49,13 +40,11 @@ export function createSessionService(deps) {
|
|
|
49
40
|
}
|
|
50
41
|
const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
|
|
51
42
|
? persisted.transcriptPath
|
|
52
|
-
:
|
|
53
|
-
const startCommand =
|
|
54
|
-
|
|
55
|
-
:
|
|
56
|
-
|
|
57
|
-
cwd: taskRepoRoot
|
|
58
|
-
};
|
|
43
|
+
: claudeTranscriptPath(taskRepoRoot, claudeSessionId);
|
|
44
|
+
const startCommand = {
|
|
45
|
+
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
|
|
46
|
+
cwd: taskRepoRoot
|
|
47
|
+
};
|
|
59
48
|
const runtimeSession = await deps.runtime.createSession({
|
|
60
49
|
taskSlug,
|
|
61
50
|
role,
|
|
@@ -64,7 +53,7 @@ export function createSessionService(deps) {
|
|
|
64
53
|
cwd: startCommand.cwd,
|
|
65
54
|
env: {
|
|
66
55
|
VCM_API_URL: deps.apiUrl,
|
|
67
|
-
VCM_TASK_REPO_ROOT:
|
|
56
|
+
VCM_TASK_REPO_ROOT: taskRepoRoot,
|
|
68
57
|
VCM_TASK_SLUG: taskSlug,
|
|
69
58
|
VCM_ROLE: role,
|
|
70
59
|
VCM_SESSION_ID: claudeSessionId
|
|
@@ -198,24 +187,33 @@ export function createSessionService(deps) {
|
|
|
198
187
|
if (live && live.status === "running") {
|
|
199
188
|
return live;
|
|
200
189
|
}
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
190
|
+
const config = await deps.projectService.loadConfig(repoRoot);
|
|
191
|
+
const persisted = await loadPersistedTranslatorSession(deps.fs, repoRoot);
|
|
192
|
+
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
193
|
+
const model = normalizeClaudeModel(input.model ?? persisted?.model);
|
|
194
|
+
const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
|
|
204
195
|
const claudeSessionId = launchMode === "resume"
|
|
205
196
|
? persisted?.claudeSessionId
|
|
206
197
|
: randomUUID();
|
|
207
198
|
if (!claudeSessionId) {
|
|
208
199
|
throw new VcmError({
|
|
209
|
-
code: "
|
|
210
|
-
message: "
|
|
200
|
+
code: "TRANSLATOR_SESSION_MISSING",
|
|
201
|
+
message: "Translator does not have a session id to resume.",
|
|
211
202
|
statusCode: 409,
|
|
212
203
|
hint: "Start the translator once before using Resume."
|
|
213
204
|
});
|
|
214
205
|
}
|
|
215
|
-
|
|
206
|
+
await deps.fs.ensureDir(resolveRepoPath(repoRoot, TRANSLATION_DIR));
|
|
207
|
+
const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
|
|
208
|
+
? persisted.transcriptPath
|
|
209
|
+
: claudeTranscriptPath(repoRoot, claudeSessionId);
|
|
210
|
+
const startCommand = {
|
|
211
|
+
...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
|
|
212
|
+
cwd: repoRoot
|
|
213
|
+
};
|
|
216
214
|
const runtimeSession = await deps.runtime.createSession({
|
|
217
215
|
taskSlug: PROJECT_TRANSLATOR_SCOPE,
|
|
218
|
-
role:
|
|
216
|
+
role: TRANSLATOR_ROLE,
|
|
219
217
|
command: startCommand.command,
|
|
220
218
|
args: startCommand.args,
|
|
221
219
|
cwd: startCommand.cwd,
|
|
@@ -223,7 +221,7 @@ export function createSessionService(deps) {
|
|
|
223
221
|
VCM_API_URL: deps.apiUrl,
|
|
224
222
|
VCM_TASK_REPO_ROOT: repoRoot,
|
|
225
223
|
VCM_TASK_SLUG: PROJECT_TRANSLATOR_SCOPE,
|
|
226
|
-
VCM_ROLE:
|
|
224
|
+
VCM_ROLE: TRANSLATOR_ROLE,
|
|
227
225
|
VCM_SESSION_ID: claudeSessionId
|
|
228
226
|
},
|
|
229
227
|
cols: input.cols,
|
|
@@ -233,12 +231,13 @@ export function createSessionService(deps) {
|
|
|
233
231
|
const record = {
|
|
234
232
|
id: runtimeSession.id,
|
|
235
233
|
claudeSessionId,
|
|
234
|
+
transcriptPath,
|
|
236
235
|
taskSlug: PROJECT_TRANSLATOR_SCOPE,
|
|
237
|
-
role:
|
|
236
|
+
role: TRANSLATOR_ROLE,
|
|
238
237
|
status: runtimeSession.status,
|
|
239
238
|
activityStatus: "idle",
|
|
240
239
|
command: startCommand.display,
|
|
241
|
-
permissionMode
|
|
240
|
+
permissionMode,
|
|
242
241
|
model,
|
|
243
242
|
effort,
|
|
244
243
|
cwd: startCommand.cwd,
|
|
@@ -250,7 +249,7 @@ export function createSessionService(deps) {
|
|
|
250
249
|
exitCode: runtimeSession.exitCode
|
|
251
250
|
};
|
|
252
251
|
deps.registry.upsert(record);
|
|
253
|
-
await
|
|
252
|
+
await persistTranslatorSession(deps.fs, repoRoot, record);
|
|
254
253
|
return record;
|
|
255
254
|
}
|
|
256
255
|
return {
|
|
@@ -326,7 +325,7 @@ export function createSessionService(deps) {
|
|
|
326
325
|
if (!existing) {
|
|
327
326
|
throw new VcmError({
|
|
328
327
|
code: "SESSION_MISSING",
|
|
329
|
-
message: "
|
|
328
|
+
message: "Translator session has not been started.",
|
|
330
329
|
statusCode: 404
|
|
331
330
|
});
|
|
332
331
|
}
|
|
@@ -340,7 +339,7 @@ export function createSessionService(deps) {
|
|
|
340
339
|
updatedAt: now()
|
|
341
340
|
};
|
|
342
341
|
deps.registry.upsert(updated);
|
|
343
|
-
await
|
|
342
|
+
await persistTranslatorSession(deps.fs, repoRoot, updated);
|
|
344
343
|
return updated;
|
|
345
344
|
},
|
|
346
345
|
async restartProjectTranslatorSession(repoRoot, input = {}) {
|
|
@@ -356,7 +355,7 @@ export function createSessionService(deps) {
|
|
|
356
355
|
},
|
|
357
356
|
async getProjectTranslatorSession(repoRoot) {
|
|
358
357
|
const record = getRegisteredProjectTranslatorSession(deps.registry, deps.runtime)
|
|
359
|
-
?? await
|
|
358
|
+
?? await loadPersistedTranslatorSession(deps.fs, repoRoot);
|
|
360
359
|
return toRoleSessionRecordView(record, deps.runtime);
|
|
361
360
|
},
|
|
362
361
|
async ensureProjectTranslatorSession(repoRoot, input = {}) {
|
|
@@ -366,6 +365,7 @@ export function createSessionService(deps) {
|
|
|
366
365
|
}
|
|
367
366
|
if (existing?.claudeSessionId) {
|
|
368
367
|
return this.resumeProjectTranslatorSession(repoRoot, {
|
|
368
|
+
permissionMode: input.permissionMode ?? existing.permissionMode,
|
|
369
369
|
model: input.model ?? existing.model,
|
|
370
370
|
effort: input.effort ?? existing.effort,
|
|
371
371
|
cols: input.cols,
|
|
@@ -395,7 +395,7 @@ export function createSessionService(deps) {
|
|
|
395
395
|
updatedAt: timestamp
|
|
396
396
|
};
|
|
397
397
|
deps.registry.upsert(updated);
|
|
398
|
-
await
|
|
398
|
+
await persistTranslatorSession(deps.fs, repoRoot, updated);
|
|
399
399
|
return updated;
|
|
400
400
|
},
|
|
401
401
|
startRoleSession(repoRoot, taskSlug, role, input = {}) {
|
|
@@ -403,7 +403,7 @@ export function createSessionService(deps) {
|
|
|
403
403
|
return launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug)
|
|
404
404
|
.then((session) => scopeProjectRoleSession(session, taskSlug));
|
|
405
405
|
}
|
|
406
|
-
if (role ===
|
|
406
|
+
if (role === TRANSLATOR_ROLE) {
|
|
407
407
|
void taskSlug;
|
|
408
408
|
return this.startProjectTranslatorSession(repoRoot, input);
|
|
409
409
|
}
|
|
@@ -414,7 +414,7 @@ export function createSessionService(deps) {
|
|
|
414
414
|
return launchProjectGateReviewerSession(repoRoot, input, "resume", taskSlug)
|
|
415
415
|
.then((session) => scopeProjectRoleSession(session, taskSlug));
|
|
416
416
|
}
|
|
417
|
-
if (role ===
|
|
417
|
+
if (role === TRANSLATOR_ROLE) {
|
|
418
418
|
void taskSlug;
|
|
419
419
|
return this.resumeProjectTranslatorSession(repoRoot, input);
|
|
420
420
|
}
|
|
@@ -424,7 +424,7 @@ export function createSessionService(deps) {
|
|
|
424
424
|
if (role === GATE_REVIEWER_ROLE) {
|
|
425
425
|
return scopeProjectRoleSession(await this.stopProjectGateReviewerSession(repoRoot), taskSlug);
|
|
426
426
|
}
|
|
427
|
-
if (role ===
|
|
427
|
+
if (role === TRANSLATOR_ROLE) {
|
|
428
428
|
void taskSlug;
|
|
429
429
|
return this.stopProjectTranslatorSession(repoRoot);
|
|
430
430
|
}
|
|
@@ -462,7 +462,7 @@ export function createSessionService(deps) {
|
|
|
462
462
|
}
|
|
463
463
|
return scopeProjectRoleSession(await launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug), taskSlug);
|
|
464
464
|
}
|
|
465
|
-
if (role ===
|
|
465
|
+
if (role === TRANSLATOR_ROLE) {
|
|
466
466
|
void taskSlug;
|
|
467
467
|
return this.restartProjectTranslatorSession(repoRoot, input);
|
|
468
468
|
}
|
|
@@ -484,7 +484,7 @@ export function createSessionService(deps) {
|
|
|
484
484
|
}
|
|
485
485
|
return scopeProjectRoleSession(await bindProjectGateReviewerSession(repoRoot, session, taskSlug), taskSlug);
|
|
486
486
|
}
|
|
487
|
-
if (role ===
|
|
487
|
+
if (role === TRANSLATOR_ROLE) {
|
|
488
488
|
void taskSlug;
|
|
489
489
|
return this.getProjectTranslatorSession(repoRoot);
|
|
490
490
|
}
|
|
@@ -544,7 +544,7 @@ export function createSessionService(deps) {
|
|
|
544
544
|
await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
|
|
545
545
|
return scopeProjectRoleSession(updated, input.taskSlug);
|
|
546
546
|
}
|
|
547
|
-
if (input.role ===
|
|
547
|
+
if (input.role === TRANSLATOR_ROLE) {
|
|
548
548
|
void input.taskSlug;
|
|
549
549
|
return this.recordProjectTranslatorHookEvent(repoRoot, {
|
|
550
550
|
eventName: input.eventName,
|
|
@@ -667,80 +667,6 @@ function toRoleSessionRecordView(record, runtime) {
|
|
|
667
667
|
exitCode: runtimeSession.exitCode
|
|
668
668
|
};
|
|
669
669
|
}
|
|
670
|
-
async function buildCodexStartCommand(fs, baseRepoRoot, _taskRepoRoot, role, launchMode, selectedModel, selectedEffort, sandboxMode, resumeSessionId) {
|
|
671
|
-
const isTranslator = role === CODEX_TRANSLATOR_ROLE;
|
|
672
|
-
if (!isTranslator) {
|
|
673
|
-
throw new VcmError({
|
|
674
|
-
code: "CODEX_ROLE_UNSUPPORTED",
|
|
675
|
-
message: `${role} is not a Codex role.`,
|
|
676
|
-
statusCode: 400
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
const codexDir = resolveRepoPath(baseRepoRoot, CODEX_TRANSLATOR_DIR);
|
|
680
|
-
const outputDir = resolveRepoPath(baseRepoRoot, CODEX_TRANSLATION_DIR);
|
|
681
|
-
if (!(await fs.pathExists(codexDir))) {
|
|
682
|
-
throw new VcmError({
|
|
683
|
-
code: "CODEX_TRANSLATOR_CONFIG_MISSING",
|
|
684
|
-
message: `${CODEX_TRANSLATOR_DIR} does not exist.`,
|
|
685
|
-
statusCode: 409,
|
|
686
|
-
hint: "Apply the VCM harness before starting Codex Translator."
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
await fs.ensureDir(outputDir);
|
|
690
|
-
const config = await loadCodexSessionConfig(fs, baseRepoRoot, CODEX_TRANSLATOR_CONFIG_PATH);
|
|
691
|
-
const args = launchMode === "resume"
|
|
692
|
-
? resumeSessionId ? ["resume", resumeSessionId] : ["resume", "--last"]
|
|
693
|
-
: [];
|
|
694
|
-
args.push("--cd", codexDir);
|
|
695
|
-
if (isDevContainerSandbox(sandboxMode)) {
|
|
696
|
-
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
697
|
-
}
|
|
698
|
-
else {
|
|
699
|
-
args.push("--add-dir", baseRepoRoot);
|
|
700
|
-
args.push("--sandbox", "workspace-write", "--ask-for-approval", "never");
|
|
701
|
-
}
|
|
702
|
-
args.push("--dangerously-bypass-hook-trust", "--search");
|
|
703
|
-
if (selectedModel !== "default") {
|
|
704
|
-
args.push("--model", selectedModel);
|
|
705
|
-
}
|
|
706
|
-
if (selectedEffort !== "default") {
|
|
707
|
-
args.push("--config", `model_reasoning_effort="${selectedEffort}"`);
|
|
708
|
-
}
|
|
709
|
-
return {
|
|
710
|
-
command: config.command,
|
|
711
|
-
args,
|
|
712
|
-
cwd: baseRepoRoot,
|
|
713
|
-
display: [config.command, ...args].map(formatDisplayArg).join(" ")
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
function hasCapturedCodexSession(record) {
|
|
717
|
-
return Boolean(record?.lastHookEventAt || record?.transcriptPath);
|
|
718
|
-
}
|
|
719
|
-
function isDevContainerSandbox(value) {
|
|
720
|
-
const normalized = value?.toLowerCase().replace(/[\s_-]+/g, "");
|
|
721
|
-
return normalized === "devcontainer" ||
|
|
722
|
-
normalized === "container" ||
|
|
723
|
-
normalized === "docker" ||
|
|
724
|
-
normalized === "podman" ||
|
|
725
|
-
normalized === "codespaces" ||
|
|
726
|
-
normalized === "bypass" ||
|
|
727
|
-
normalized === "nosandbox" ||
|
|
728
|
-
normalized === "disabled" ||
|
|
729
|
-
normalized === "off" ||
|
|
730
|
-
normalized === "none";
|
|
731
|
-
}
|
|
732
|
-
async function loadCodexSessionConfig(fs, repoRoot, configRelativePath) {
|
|
733
|
-
const configPath = resolveRepoPath(repoRoot, configRelativePath);
|
|
734
|
-
if (!(await fs.pathExists(configPath))) {
|
|
735
|
-
return {
|
|
736
|
-
command: "codex"
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
const content = await fs.readText(configPath);
|
|
740
|
-
return {
|
|
741
|
-
command: parseTomlString(content, "command") ?? "codex"
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
670
|
function matchesRoleHookSession(record, input) {
|
|
745
671
|
if (input.sessionId && record.claudeSessionId === input.sessionId) {
|
|
746
672
|
return true;
|
|
@@ -778,7 +704,7 @@ function getHandoffArtifactPath(paths, role) {
|
|
|
778
704
|
return undefined;
|
|
779
705
|
}
|
|
780
706
|
function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
|
|
781
|
-
if (role !==
|
|
707
|
+
if (role !== TRANSLATOR_ROLE && role !== GATE_REVIEWER_ROLE) {
|
|
782
708
|
return registry.getByRole(taskSlug, role);
|
|
783
709
|
}
|
|
784
710
|
const candidates = registry.list().filter((session) => session.role === role);
|
|
@@ -794,7 +720,7 @@ function getRegisteredProjectGateReviewerSession(registry, runtime) {
|
|
|
794
720
|
return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
|
|
795
721
|
}
|
|
796
722
|
function getRegisteredProjectTranslatorSession(registry, runtime) {
|
|
797
|
-
const candidates = registry.list().filter((session) => session.role ===
|
|
723
|
+
const candidates = registry.list().filter((session) => session.role === TRANSLATOR_ROLE);
|
|
798
724
|
const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
|
|
799
725
|
return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
|
|
800
726
|
}
|
|
@@ -802,7 +728,7 @@ function compareSessionUpdatedAtDesc(left, right) {
|
|
|
802
728
|
return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
803
729
|
}
|
|
804
730
|
function scopeProjectRoleSession(record, taskSlug) {
|
|
805
|
-
if (!record || (record.role !==
|
|
731
|
+
if (!record || (record.role !== TRANSLATOR_ROLE && record.role !== GATE_REVIEWER_ROLE)) {
|
|
806
732
|
return record;
|
|
807
733
|
}
|
|
808
734
|
return {
|
|
@@ -815,9 +741,9 @@ async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, st
|
|
|
815
741
|
void taskSlug;
|
|
816
742
|
return loadPersistedProjectGateReviewerSession(fs, baseRepoRoot);
|
|
817
743
|
}
|
|
818
|
-
if (role ===
|
|
744
|
+
if (role === TRANSLATOR_ROLE) {
|
|
819
745
|
void taskSlug;
|
|
820
|
-
return
|
|
746
|
+
return loadPersistedTranslatorSession(fs, baseRepoRoot);
|
|
821
747
|
}
|
|
822
748
|
return loadPersistedRoleRecord(fs, taskRepoRoot, stateRoot, taskSlug, role);
|
|
823
749
|
}
|
|
@@ -836,8 +762,8 @@ async function loadPersistedProjectGateReviewerSession(fs, repoRoot) {
|
|
|
836
762
|
taskSlug: PROJECT_GATE_REVIEWER_SCOPE
|
|
837
763
|
};
|
|
838
764
|
}
|
|
839
|
-
async function
|
|
840
|
-
const sessionPath = resolveRepoPath(repoRoot,
|
|
765
|
+
async function loadPersistedTranslatorSession(fs, repoRoot) {
|
|
766
|
+
const sessionPath = resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH);
|
|
841
767
|
if (!(await fs.pathExists(sessionPath))) {
|
|
842
768
|
return undefined;
|
|
843
769
|
}
|
|
@@ -874,12 +800,8 @@ function normalizePersistedRoleRecord(record) {
|
|
|
874
800
|
? legacy.lastStopAt
|
|
875
801
|
: undefined),
|
|
876
802
|
permissionMode: normalizeClaudePermissionMode(record.permissionMode),
|
|
877
|
-
model:
|
|
878
|
-
|
|
879
|
-
: normalizeClaudeModel(record.model),
|
|
880
|
-
effort: isCodexRoleName(record.role)
|
|
881
|
-
? normalizeCodexEffort(record.effort)
|
|
882
|
-
: normalizeClaudeEffort(record.effort)
|
|
803
|
+
model: normalizeClaudeModel(record.model),
|
|
804
|
+
effort: normalizeClaudeEffort(record.effort)
|
|
883
805
|
}
|
|
884
806
|
: undefined;
|
|
885
807
|
}
|
|
@@ -913,8 +835,8 @@ async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoo
|
|
|
913
835
|
await persistProjectGateReviewerSession(fs, baseRepoRoot, session);
|
|
914
836
|
return;
|
|
915
837
|
}
|
|
916
|
-
if (session.role ===
|
|
917
|
-
await
|
|
838
|
+
if (session.role === TRANSLATOR_ROLE) {
|
|
839
|
+
await persistTranslatorSession(fs, baseRepoRoot, session);
|
|
918
840
|
return;
|
|
919
841
|
}
|
|
920
842
|
await persistTaskSession(fs, taskRepoRoot, stateRoot, session);
|
|
@@ -930,8 +852,8 @@ async function persistProjectGateReviewerSession(fs, repoRoot, session) {
|
|
|
930
852
|
}
|
|
931
853
|
});
|
|
932
854
|
}
|
|
933
|
-
async function
|
|
934
|
-
await fs.writeJsonAtomic(resolveRepoPath(repoRoot,
|
|
855
|
+
async function persistTranslatorSession(fs, repoRoot, session) {
|
|
856
|
+
await fs.writeJsonAtomic(resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH), {
|
|
935
857
|
version: 1,
|
|
936
858
|
role: session.role,
|
|
937
859
|
updatedAt: session.updatedAt,
|
|
@@ -977,12 +899,6 @@ function normalizeClaudeModel(value) {
|
|
|
977
899
|
}
|
|
978
900
|
return "default";
|
|
979
901
|
}
|
|
980
|
-
function normalizeCodexModel(value) {
|
|
981
|
-
if (value === "default" || value === "gpt-5.5") {
|
|
982
|
-
return value;
|
|
983
|
-
}
|
|
984
|
-
return "gpt-5.5";
|
|
985
|
-
}
|
|
986
902
|
function normalizeClaudeEffort(value) {
|
|
987
903
|
if (value === "low"
|
|
988
904
|
|| value === "medium"
|
|
@@ -994,25 +910,3 @@ function normalizeClaudeEffort(value) {
|
|
|
994
910
|
}
|
|
995
911
|
return "default";
|
|
996
912
|
}
|
|
997
|
-
function normalizeCodexEffort(value) {
|
|
998
|
-
if (value === "low"
|
|
999
|
-
|| value === "medium"
|
|
1000
|
-
|| value === "high"
|
|
1001
|
-
|| value === "xhigh") {
|
|
1002
|
-
return value;
|
|
1003
|
-
}
|
|
1004
|
-
return "default";
|
|
1005
|
-
}
|
|
1006
|
-
function parseTomlString(content, key) {
|
|
1007
|
-
const pattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, "m");
|
|
1008
|
-
return pattern.exec(content)?.[1];
|
|
1009
|
-
}
|
|
1010
|
-
function escapeRegExp(value) {
|
|
1011
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1012
|
-
}
|
|
1013
|
-
function formatDisplayArg(value) {
|
|
1014
|
-
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) {
|
|
1015
|
-
return value;
|
|
1016
|
-
}
|
|
1017
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
1018
|
-
}
|
|
@@ -7,8 +7,8 @@ const TRANSLATION_SOURCE_LANGUAGE = "auto";
|
|
|
7
7
|
const TRANSLATION_INPUT_MODE = "review-before-send";
|
|
8
8
|
const TRANSLATION_CONTEXT_ENABLED = false;
|
|
9
9
|
const TRANSLATION_TIMEOUT_MS = 120000;
|
|
10
|
-
const TRANSLATION_PROVIDER = "
|
|
11
|
-
const TRANSLATION_MODEL = "
|
|
10
|
+
const TRANSLATION_PROVIDER = "claude-code";
|
|
11
|
+
const TRANSLATION_MODEL = "translator";
|
|
12
12
|
const OUTPUT_TRANSLATION_BATCH_DELAY_MS = 10000;
|
|
13
13
|
const TRANSCRIPT_REPLAY_GRACE_MS = 5000;
|
|
14
14
|
export function createTranslationService(deps) {
|
|
@@ -313,7 +313,7 @@ export function createTranslationService(deps) {
|
|
|
313
313
|
const jobs = [];
|
|
314
314
|
for (let index = 0; index < items.length; index += 1) {
|
|
315
315
|
const item = items[index];
|
|
316
|
-
const job = await
|
|
316
|
+
const job = await createConversationJob({
|
|
317
317
|
repoRoot: item.repoRoot,
|
|
318
318
|
taskSlug: item.entry.taskSlug,
|
|
319
319
|
role: item.entry.role,
|
|
@@ -329,7 +329,7 @@ export function createTranslationService(deps) {
|
|
|
329
329
|
}
|
|
330
330
|
for (const { item, job } of jobs) {
|
|
331
331
|
try {
|
|
332
|
-
const result = await
|
|
332
|
+
const result = await waitForConversationResult(item.repoRoot, job, item.config.requestTimeoutMs);
|
|
333
333
|
const completed = {
|
|
334
334
|
...item.entry,
|
|
335
335
|
status: "translated",
|
|
@@ -882,28 +882,28 @@ export function createTranslationService(deps) {
|
|
|
882
882
|
await submitTerminalInput(deps.runtime, record.id, text);
|
|
883
883
|
}
|
|
884
884
|
async function translateText(input) {
|
|
885
|
-
const job = await
|
|
886
|
-
const result = await
|
|
885
|
+
const job = await createConversationJob(input);
|
|
886
|
+
const result = await waitForConversationResult(input.repoRoot, job, input.config.requestTimeoutMs);
|
|
887
887
|
return {
|
|
888
888
|
text: result.translatedText
|
|
889
889
|
};
|
|
890
890
|
}
|
|
891
|
-
async function
|
|
892
|
-
if (!deps.
|
|
891
|
+
async function createConversationJob(input) {
|
|
892
|
+
if (!deps.translationWorkerService) {
|
|
893
893
|
throw new VcmError({
|
|
894
|
-
code: "
|
|
895
|
-
message: "
|
|
894
|
+
code: "TRANSLATION_WORKER_UNAVAILABLE",
|
|
895
|
+
message: "translation service is unavailable.",
|
|
896
896
|
statusCode: 500
|
|
897
897
|
});
|
|
898
898
|
}
|
|
899
899
|
if (!input.repoRoot) {
|
|
900
900
|
throw new VcmError({
|
|
901
901
|
code: "TRANSLATION_REPO_ROOT_MISSING",
|
|
902
|
-
message: "
|
|
902
|
+
message: "translation requires a base repository root.",
|
|
903
903
|
statusCode: 500
|
|
904
904
|
});
|
|
905
905
|
}
|
|
906
|
-
return deps.
|
|
906
|
+
return deps.translationWorkerService.createConversationJob(input.repoRoot, {
|
|
907
907
|
direction: input.direction,
|
|
908
908
|
sourceText: input.text,
|
|
909
909
|
sourceLanguage: input.sourceLanguage,
|
|
@@ -912,18 +912,18 @@ export function createTranslationService(deps) {
|
|
|
912
912
|
deferDispatch: input.deferDispatch
|
|
913
913
|
});
|
|
914
914
|
}
|
|
915
|
-
async function
|
|
915
|
+
async function waitForConversationResult(repoRoot, job, timeoutMs) {
|
|
916
916
|
const deadline = Date.now() + timeoutMs;
|
|
917
917
|
let lastError;
|
|
918
918
|
while (Date.now() <= deadline) {
|
|
919
|
-
const state = await deps.
|
|
919
|
+
const state = await deps.translationWorkerService.getState(repoRoot);
|
|
920
920
|
const item = job.queueItemId
|
|
921
921
|
? state.queue.items.find((candidate) => candidate.id === job.queueItemId)
|
|
922
922
|
: undefined;
|
|
923
923
|
if (item && ["failed", "cancelled", "interrupted", "skipped"].includes(item.status)) {
|
|
924
924
|
throw new VcmError({
|
|
925
925
|
code: "TRANSLATION_FAILED",
|
|
926
|
-
message: item.error ?? "
|
|
926
|
+
message: item.error ?? "translation failed.",
|
|
927
927
|
statusCode: 502
|
|
928
928
|
});
|
|
929
929
|
}
|
|
@@ -932,7 +932,7 @@ export function createTranslationService(deps) {
|
|
|
932
932
|
continue;
|
|
933
933
|
}
|
|
934
934
|
try {
|
|
935
|
-
return await deps.
|
|
935
|
+
return await deps.translationWorkerService.validateConversationResult(repoRoot, {
|
|
936
936
|
resultPath: job.resultPath,
|
|
937
937
|
sourceHash: job.sourceHash,
|
|
938
938
|
targetLanguage: job.targetLanguage
|
|
@@ -948,7 +948,7 @@ export function createTranslationService(deps) {
|
|
|
948
948
|
}
|
|
949
949
|
throw new VcmError({
|
|
950
950
|
code: "TRANSLATION_TIMEOUT",
|
|
951
|
-
message: lastError instanceof Error ? `
|
|
951
|
+
message: lastError instanceof Error ? `translation timed out: ${lastError.message}` : "translation timed out.",
|
|
952
952
|
statusCode: 504
|
|
953
953
|
});
|
|
954
954
|
}
|
package/dist/backend/services/{codex-translation-service.js → translation-worker-service.js}
RENAMED
|
@@ -19,7 +19,7 @@ const DEFAULT_CHUNK_SOURCE_TOKEN_TARGET = 80000;
|
|
|
19
19
|
const BOOTSTRAP_DEFAULT_LIMIT = 12;
|
|
20
20
|
const MEMORY_TOTAL_LIMIT_BYTES = 80 * 1024;
|
|
21
21
|
const MEMORY_FILE_NAMES = ["glossary.md", "style-guide.md", "project-context.md", "decisions.md"];
|
|
22
|
-
const
|
|
22
|
+
const TRANSLATOR_ROLE = "translator";
|
|
23
23
|
const FILE_BROWSER_DEFAULT_LIMIT = 200;
|
|
24
24
|
const FILE_BROWSER_MAX_LIMIT = 500;
|
|
25
25
|
const FILE_BROWSER_SEARCH_MAX_DEPTH = 6;
|
|
@@ -94,7 +94,7 @@ const BINARY_LIKE_EXTENSIONS = new Set([
|
|
|
94
94
|
".webp",
|
|
95
95
|
".zip"
|
|
96
96
|
]);
|
|
97
|
-
export function
|
|
97
|
+
export function createTranslationWorkerService(deps) {
|
|
98
98
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
99
99
|
const createId = deps.id ?? (() => randomUUID());
|
|
100
100
|
async function ensureLayout(repoRoot) {
|
|
@@ -225,7 +225,7 @@ export function createCodexTranslationService(deps) {
|
|
|
225
225
|
const failedAt = now();
|
|
226
226
|
for (const item of failedItems) {
|
|
227
227
|
item.status = "failed";
|
|
228
|
-
item.error = error instanceof Error ? error.message : "Failed to dispatch
|
|
228
|
+
item.error = error instanceof Error ? error.message : "Failed to dispatch Translator task.";
|
|
229
229
|
item.updatedAt = failedAt;
|
|
230
230
|
}
|
|
231
231
|
queue.activeItemId = undefined;
|
|
@@ -259,13 +259,13 @@ export function createCodexTranslationService(deps) {
|
|
|
259
259
|
void targetLanguage;
|
|
260
260
|
if (!deps.sessionService) {
|
|
261
261
|
throw new VcmError({
|
|
262
|
-
code: "
|
|
263
|
-
message: "
|
|
262
|
+
code: "TRANSLATOR_SESSION_UNAVAILABLE",
|
|
263
|
+
message: "Translator session service is unavailable.",
|
|
264
264
|
statusCode: 500
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
267
|
return deps.sessionService.ensureProjectTranslatorSession(repoRoot, {
|
|
268
|
-
model: "
|
|
268
|
+
model: "default",
|
|
269
269
|
effort: "medium"
|
|
270
270
|
});
|
|
271
271
|
}
|
|
@@ -284,7 +284,7 @@ export function createCodexTranslationService(deps) {
|
|
|
284
284
|
? resolveRepoPath(repoRoot, item.reportPath)
|
|
285
285
|
: undefined;
|
|
286
286
|
return [
|
|
287
|
-
"[VCM
|
|
287
|
+
"[VCM TRANSLATION TASK]",
|
|
288
288
|
`Queue Item: ${item.id}`,
|
|
289
289
|
`Type: ${item.type}`,
|
|
290
290
|
`Target Language: ${item.targetLanguage}`,
|
|
@@ -376,7 +376,7 @@ export function createCodexTranslationService(deps) {
|
|
|
376
376
|
const requestPath = resolveRepoPath(repoRoot, item.requestPath);
|
|
377
377
|
const memoryDir = resolveRepoPath(repoRoot, MEMORY_DIR);
|
|
378
378
|
return [
|
|
379
|
-
"[VCM
|
|
379
|
+
"[VCM TRANSLATION TASK]",
|
|
380
380
|
`Queue Item: ${item.id}`,
|
|
381
381
|
"Type: memory-update",
|
|
382
382
|
`Target Language: ${item.targetLanguage}`,
|
|
@@ -386,7 +386,7 @@ export function createCodexTranslationService(deps) {
|
|
|
386
386
|
requestPath,
|
|
387
387
|
"",
|
|
388
388
|
"Task: update and compact VCM translation memory.",
|
|
389
|
-
"Use the current
|
|
389
|
+
"Use the current Translator session context, recent stable user corrections, completed translation behavior, and existing memory files.",
|
|
390
390
|
"Only keep stable, reusable translation knowledge. Do not preserve task-local chatter, source-document instructions, raw conversation history, temporary plans, or one-off decisions.",
|
|
391
391
|
"",
|
|
392
392
|
`Memory directory: ${memoryDir}`,
|
|
@@ -1265,7 +1265,7 @@ export function createCodexTranslationService(deps) {
|
|
|
1265
1265
|
await deps.fs.writeText(absoluteTargetPath, content);
|
|
1266
1266
|
return job;
|
|
1267
1267
|
},
|
|
1268
|
-
async
|
|
1268
|
+
async handleTranslatorHook(repoRoot, eventName, taskSlug) {
|
|
1269
1269
|
void taskSlug;
|
|
1270
1270
|
if (eventName === "UserPromptSubmit") {
|
|
1271
1271
|
const queue = await loadQueue(repoRoot);
|
|
@@ -1281,7 +1281,7 @@ export function createCodexTranslationService(deps) {
|
|
|
1281
1281
|
}
|
|
1282
1282
|
return;
|
|
1283
1283
|
}
|
|
1284
|
-
if (eventName === "Stop") {
|
|
1284
|
+
if (eventName === "Stop" || eventName === "StopFailure") {
|
|
1285
1285
|
await validateActiveQueueItem(repoRoot);
|
|
1286
1286
|
await dispatchNext(repoRoot);
|
|
1287
1287
|
}
|