vibe-coding-master 0.3.29 → 0.3.30
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 +56 -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-K34QFpWK.js → index-D0ezB4na.js} +36 -36
- 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
|
@@ -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);
|
|
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 = {}) {
|
|
@@ -395,7 +394,7 @@ export function createSessionService(deps) {
|
|
|
395
394
|
updatedAt: timestamp
|
|
396
395
|
};
|
|
397
396
|
deps.registry.upsert(updated);
|
|
398
|
-
await
|
|
397
|
+
await persistTranslatorSession(deps.fs, repoRoot, updated);
|
|
399
398
|
return updated;
|
|
400
399
|
},
|
|
401
400
|
startRoleSession(repoRoot, taskSlug, role, input = {}) {
|
|
@@ -403,7 +402,7 @@ export function createSessionService(deps) {
|
|
|
403
402
|
return launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug)
|
|
404
403
|
.then((session) => scopeProjectRoleSession(session, taskSlug));
|
|
405
404
|
}
|
|
406
|
-
if (role ===
|
|
405
|
+
if (role === TRANSLATOR_ROLE) {
|
|
407
406
|
void taskSlug;
|
|
408
407
|
return this.startProjectTranslatorSession(repoRoot, input);
|
|
409
408
|
}
|
|
@@ -414,7 +413,7 @@ export function createSessionService(deps) {
|
|
|
414
413
|
return launchProjectGateReviewerSession(repoRoot, input, "resume", taskSlug)
|
|
415
414
|
.then((session) => scopeProjectRoleSession(session, taskSlug));
|
|
416
415
|
}
|
|
417
|
-
if (role ===
|
|
416
|
+
if (role === TRANSLATOR_ROLE) {
|
|
418
417
|
void taskSlug;
|
|
419
418
|
return this.resumeProjectTranslatorSession(repoRoot, input);
|
|
420
419
|
}
|
|
@@ -424,7 +423,7 @@ export function createSessionService(deps) {
|
|
|
424
423
|
if (role === GATE_REVIEWER_ROLE) {
|
|
425
424
|
return scopeProjectRoleSession(await this.stopProjectGateReviewerSession(repoRoot), taskSlug);
|
|
426
425
|
}
|
|
427
|
-
if (role ===
|
|
426
|
+
if (role === TRANSLATOR_ROLE) {
|
|
428
427
|
void taskSlug;
|
|
429
428
|
return this.stopProjectTranslatorSession(repoRoot);
|
|
430
429
|
}
|
|
@@ -462,7 +461,7 @@ export function createSessionService(deps) {
|
|
|
462
461
|
}
|
|
463
462
|
return scopeProjectRoleSession(await launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug), taskSlug);
|
|
464
463
|
}
|
|
465
|
-
if (role ===
|
|
464
|
+
if (role === TRANSLATOR_ROLE) {
|
|
466
465
|
void taskSlug;
|
|
467
466
|
return this.restartProjectTranslatorSession(repoRoot, input);
|
|
468
467
|
}
|
|
@@ -484,7 +483,7 @@ export function createSessionService(deps) {
|
|
|
484
483
|
}
|
|
485
484
|
return scopeProjectRoleSession(await bindProjectGateReviewerSession(repoRoot, session, taskSlug), taskSlug);
|
|
486
485
|
}
|
|
487
|
-
if (role ===
|
|
486
|
+
if (role === TRANSLATOR_ROLE) {
|
|
488
487
|
void taskSlug;
|
|
489
488
|
return this.getProjectTranslatorSession(repoRoot);
|
|
490
489
|
}
|
|
@@ -544,7 +543,7 @@ export function createSessionService(deps) {
|
|
|
544
543
|
await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
|
|
545
544
|
return scopeProjectRoleSession(updated, input.taskSlug);
|
|
546
545
|
}
|
|
547
|
-
if (input.role ===
|
|
546
|
+
if (input.role === TRANSLATOR_ROLE) {
|
|
548
547
|
void input.taskSlug;
|
|
549
548
|
return this.recordProjectTranslatorHookEvent(repoRoot, {
|
|
550
549
|
eventName: input.eventName,
|
|
@@ -667,80 +666,6 @@ function toRoleSessionRecordView(record, runtime) {
|
|
|
667
666
|
exitCode: runtimeSession.exitCode
|
|
668
667
|
};
|
|
669
668
|
}
|
|
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
669
|
function matchesRoleHookSession(record, input) {
|
|
745
670
|
if (input.sessionId && record.claudeSessionId === input.sessionId) {
|
|
746
671
|
return true;
|
|
@@ -778,7 +703,7 @@ function getHandoffArtifactPath(paths, role) {
|
|
|
778
703
|
return undefined;
|
|
779
704
|
}
|
|
780
705
|
function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
|
|
781
|
-
if (role !==
|
|
706
|
+
if (role !== TRANSLATOR_ROLE && role !== GATE_REVIEWER_ROLE) {
|
|
782
707
|
return registry.getByRole(taskSlug, role);
|
|
783
708
|
}
|
|
784
709
|
const candidates = registry.list().filter((session) => session.role === role);
|
|
@@ -794,7 +719,7 @@ function getRegisteredProjectGateReviewerSession(registry, runtime) {
|
|
|
794
719
|
return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
|
|
795
720
|
}
|
|
796
721
|
function getRegisteredProjectTranslatorSession(registry, runtime) {
|
|
797
|
-
const candidates = registry.list().filter((session) => session.role ===
|
|
722
|
+
const candidates = registry.list().filter((session) => session.role === TRANSLATOR_ROLE);
|
|
798
723
|
const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
|
|
799
724
|
return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
|
|
800
725
|
}
|
|
@@ -802,7 +727,7 @@ function compareSessionUpdatedAtDesc(left, right) {
|
|
|
802
727
|
return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
|
|
803
728
|
}
|
|
804
729
|
function scopeProjectRoleSession(record, taskSlug) {
|
|
805
|
-
if (!record || (record.role !==
|
|
730
|
+
if (!record || (record.role !== TRANSLATOR_ROLE && record.role !== GATE_REVIEWER_ROLE)) {
|
|
806
731
|
return record;
|
|
807
732
|
}
|
|
808
733
|
return {
|
|
@@ -815,9 +740,9 @@ async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, st
|
|
|
815
740
|
void taskSlug;
|
|
816
741
|
return loadPersistedProjectGateReviewerSession(fs, baseRepoRoot);
|
|
817
742
|
}
|
|
818
|
-
if (role ===
|
|
743
|
+
if (role === TRANSLATOR_ROLE) {
|
|
819
744
|
void taskSlug;
|
|
820
|
-
return
|
|
745
|
+
return loadPersistedTranslatorSession(fs, baseRepoRoot);
|
|
821
746
|
}
|
|
822
747
|
return loadPersistedRoleRecord(fs, taskRepoRoot, stateRoot, taskSlug, role);
|
|
823
748
|
}
|
|
@@ -836,8 +761,8 @@ async function loadPersistedProjectGateReviewerSession(fs, repoRoot) {
|
|
|
836
761
|
taskSlug: PROJECT_GATE_REVIEWER_SCOPE
|
|
837
762
|
};
|
|
838
763
|
}
|
|
839
|
-
async function
|
|
840
|
-
const sessionPath = resolveRepoPath(repoRoot,
|
|
764
|
+
async function loadPersistedTranslatorSession(fs, repoRoot) {
|
|
765
|
+
const sessionPath = resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH);
|
|
841
766
|
if (!(await fs.pathExists(sessionPath))) {
|
|
842
767
|
return undefined;
|
|
843
768
|
}
|
|
@@ -874,12 +799,8 @@ function normalizePersistedRoleRecord(record) {
|
|
|
874
799
|
? legacy.lastStopAt
|
|
875
800
|
: undefined),
|
|
876
801
|
permissionMode: normalizeClaudePermissionMode(record.permissionMode),
|
|
877
|
-
model:
|
|
878
|
-
|
|
879
|
-
: normalizeClaudeModel(record.model),
|
|
880
|
-
effort: isCodexRoleName(record.role)
|
|
881
|
-
? normalizeCodexEffort(record.effort)
|
|
882
|
-
: normalizeClaudeEffort(record.effort)
|
|
802
|
+
model: normalizeClaudeModel(record.model),
|
|
803
|
+
effort: normalizeClaudeEffort(record.effort)
|
|
883
804
|
}
|
|
884
805
|
: undefined;
|
|
885
806
|
}
|
|
@@ -913,8 +834,8 @@ async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoo
|
|
|
913
834
|
await persistProjectGateReviewerSession(fs, baseRepoRoot, session);
|
|
914
835
|
return;
|
|
915
836
|
}
|
|
916
|
-
if (session.role ===
|
|
917
|
-
await
|
|
837
|
+
if (session.role === TRANSLATOR_ROLE) {
|
|
838
|
+
await persistTranslatorSession(fs, baseRepoRoot, session);
|
|
918
839
|
return;
|
|
919
840
|
}
|
|
920
841
|
await persistTaskSession(fs, taskRepoRoot, stateRoot, session);
|
|
@@ -930,8 +851,8 @@ async function persistProjectGateReviewerSession(fs, repoRoot, session) {
|
|
|
930
851
|
}
|
|
931
852
|
});
|
|
932
853
|
}
|
|
933
|
-
async function
|
|
934
|
-
await fs.writeJsonAtomic(resolveRepoPath(repoRoot,
|
|
854
|
+
async function persistTranslatorSession(fs, repoRoot, session) {
|
|
855
|
+
await fs.writeJsonAtomic(resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH), {
|
|
935
856
|
version: 1,
|
|
936
857
|
role: session.role,
|
|
937
858
|
updatedAt: session.updatedAt,
|
|
@@ -977,12 +898,6 @@ function normalizeClaudeModel(value) {
|
|
|
977
898
|
}
|
|
978
899
|
return "default";
|
|
979
900
|
}
|
|
980
|
-
function normalizeCodexModel(value) {
|
|
981
|
-
if (value === "default" || value === "gpt-5.5") {
|
|
982
|
-
return value;
|
|
983
|
-
}
|
|
984
|
-
return "gpt-5.5";
|
|
985
|
-
}
|
|
986
901
|
function normalizeClaudeEffort(value) {
|
|
987
902
|
if (value === "low"
|
|
988
903
|
|| value === "medium"
|
|
@@ -994,25 +909,3 @@ function normalizeClaudeEffort(value) {
|
|
|
994
909
|
}
|
|
995
910
|
return "default";
|
|
996
911
|
}
|
|
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
|
}
|