vibe-coding-master 0.3.22 → 0.3.24
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 +2 -2
- package/dist/backend/adapters/git-adapter.js +53 -0
- package/dist/backend/api/artifact-routes.js +1 -28
- package/dist/backend/api/codex-translation-routes.js +24 -0
- package/dist/backend/api/harness-routes.js +10 -0
- package/dist/backend/api/task-routes.js +2 -6
- package/dist/backend/gateway/gateway-service.js +3 -16
- package/dist/backend/server.js +5 -2
- package/dist/backend/services/artifact-service.js +1 -30
- package/dist/backend/services/codex-hook-service.js +30 -9
- package/dist/backend/services/codex-translation-service.js +17 -40
- package/dist/backend/services/harness-service.js +106 -0
- package/dist/backend/services/session-service.js +189 -15
- package/dist/backend/services/status-service.js +1 -6
- package/dist/backend/services/task-service.js +42 -110
- package/dist/backend/services/translation-service.js +15 -4
- package/dist/backend/templates/handoff.js +3 -3
- package/dist/backend/templates/harness/architect-agent.js +3 -2
- package/dist/backend/templates/harness/coder-agent.js +6 -1
- package/dist/backend/templates/harness/project-manager-agent.js +10 -1
- package/dist/backend/templates/harness/vcm-route-message-skill.js +8 -2
- package/dist/shared/types/app-settings.js +3 -2
- package/dist-frontend/assets/index-CKWy15WL.js +94 -0
- package/dist-frontend/assets/index-Cfum1Prr.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +1 -1
- package/docs/codex-translation-plan.md +41 -37
- package/docs/full-harness-baseline.md +0 -1
- package/docs/gateway-design.md +9 -13
- package/docs/product-design.md +23 -44
- package/docs/v0.4-custom-workflow-plan.md +785 -0
- package/docs/vcm-cc-best-practices.md +15 -13
- package/package.json +1 -1
- package/dist-frontend/assets/index-CEB6Bssn.js +0 -95
- package/dist-frontend/assets/index-Dmefx9m7.css +0 -32
package/README.md
CHANGED
|
@@ -237,7 +237,7 @@ the base repo has uncommitted changes, when the current branch has no upstream,
|
|
|
237
237
|
or when the active task is an inline task using the base repo directly. It does
|
|
238
238
|
not stash, merge, or mutate task worktrees.
|
|
239
239
|
|
|
240
|
-
When VCM is connected to an active task, the bottom of the sidebar shows a task status dock. It stays outside the collapsible groups and shows the active VCM Session title, task status, start time, total elapsed time, total Round count, and role active runtime.
|
|
240
|
+
When VCM is connected to an active task, the bottom of the sidebar shows a task status dock. It stays outside the collapsible groups and shows the active VCM Session title, task status, start time, total elapsed time, total Round count, and role active runtime. The dock also shows the Current Round while it is running, or the Last Round after a flow pause, including start time, total elapsed time, role active runtime, Turn count, and Round status.
|
|
241
241
|
|
|
242
242
|
## Mobile Gateway
|
|
243
243
|
|
|
@@ -586,7 +586,7 @@ The normal path is timer-driven: `Stop` starts a backend timer, and `UserPromptS
|
|
|
586
586
|
|
|
587
587
|
When `Flow pause alert` is enabled, the frontend polls the task Round state and deduplicates each stopped Round so the same stopped state does not alert on every poll. Flow duration is measured from the first `UserPromptSubmit` to `stoppedAt`, falling back to the last `Stop` when needed. If the flow lasted less than 2 minutes, it plays the local chime 3 times at 1.4 second intervals. If the flow lasted 2 minutes or longer, it shows a modal alert and repeats the local chime until the user clicks `Confirm`. A stopped Round can mean normal completion, user decision needed, dispatch failure, or another workflow interruption; the point is to get the user to look with the right amount of urgency.
|
|
588
588
|
|
|
589
|
-
The
|
|
589
|
+
The Round dock separates wall-clock Round duration from role active runtime. For a running Round, `Total` is `now - Round.startedAt`; for a stopped Round, it is `stoppedAt - Round.startedAt`. `Role runtime` is only the accumulated time between each Turn's `UserPromptSubmit` and `Stop`; `Turn count` is the number of accepted prompts inside the Round.
|
|
590
590
|
|
|
591
591
|
## Resume Behavior
|
|
592
592
|
|
|
@@ -81,6 +81,18 @@ export function createGitAdapter(runner) {
|
|
|
81
81
|
}
|
|
82
82
|
return result.stdout;
|
|
83
83
|
},
|
|
84
|
+
async getStagedStatus(repoRoot) {
|
|
85
|
+
const result = await runGit(runner, repoRoot, ["diff", "--cached", "--name-status"]);
|
|
86
|
+
if (result.exitCode !== 0) {
|
|
87
|
+
throw new VcmError({
|
|
88
|
+
code: "GIT_ERROR",
|
|
89
|
+
message: "Unable to read staged Git changes.",
|
|
90
|
+
statusCode: 400,
|
|
91
|
+
hint: result.stderr
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return result.stdout;
|
|
95
|
+
},
|
|
84
96
|
async isIgnored(repoRoot, repoRelativePath) {
|
|
85
97
|
const result = await runGit(runner, repoRoot, ["check-ignore", "-q", "--", repoRelativePath]);
|
|
86
98
|
if (result.exitCode === 0) {
|
|
@@ -111,6 +123,47 @@ export function createGitAdapter(runner) {
|
|
|
111
123
|
hint: result.stderr
|
|
112
124
|
});
|
|
113
125
|
},
|
|
126
|
+
async addPaths(repoRoot, paths) {
|
|
127
|
+
if (paths.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const result = await runGit(runner, repoRoot, ["add", "--", ...paths]);
|
|
131
|
+
if (result.exitCode !== 0) {
|
|
132
|
+
throw new VcmError({
|
|
133
|
+
code: "GIT_ADD_FAILED",
|
|
134
|
+
message: "Unable to stage harness changes.",
|
|
135
|
+
statusCode: 409,
|
|
136
|
+
hint: result.stderr || result.stdout
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
async commit(repoRoot, message) {
|
|
141
|
+
const result = await runGit(runner, repoRoot, ["commit", "-m", message]);
|
|
142
|
+
if (result.exitCode !== 0) {
|
|
143
|
+
throw new VcmError({
|
|
144
|
+
code: "GIT_COMMIT_FAILED",
|
|
145
|
+
message: "Unable to commit harness changes.",
|
|
146
|
+
statusCode: 409,
|
|
147
|
+
hint: result.stderr || result.stdout
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return this.getHeadCommit(repoRoot);
|
|
151
|
+
},
|
|
152
|
+
async rebase(repoRoot, upstream) {
|
|
153
|
+
const result = await runGit(runner, repoRoot, ["rebase", upstream]);
|
|
154
|
+
if (result.exitCode !== 0) {
|
|
155
|
+
throw new VcmError({
|
|
156
|
+
code: "GIT_REBASE_FAILED",
|
|
157
|
+
message: "Unable to rebase task branch onto the harness commit.",
|
|
158
|
+
statusCode: 409,
|
|
159
|
+
hint: result.stderr || result.stdout
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
stdout: result.stdout,
|
|
164
|
+
stderr: result.stderr
|
|
165
|
+
};
|
|
166
|
+
},
|
|
114
167
|
async createWorktree(input) {
|
|
115
168
|
const result = await runGit(runner, input.repoRoot, [
|
|
116
169
|
"worktree",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDispatchableRole
|
|
1
|
+
import { isDispatchableRole } from "../../shared/constants.js";
|
|
2
2
|
import { VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerArtifactRoutes(app, deps) {
|
|
@@ -52,33 +52,6 @@ export function registerArtifactRoutes(app, deps) {
|
|
|
52
52
|
});
|
|
53
53
|
return { ok: true };
|
|
54
54
|
});
|
|
55
|
-
app.get("/api/tasks/:taskSlug/logs/:role", async (request) => {
|
|
56
|
-
const project = await requireCurrentProject(deps.projectService);
|
|
57
|
-
if (!isRoleName(request.params.role)) {
|
|
58
|
-
throw new VcmError({
|
|
59
|
-
code: "UNKNOWN_ROLE",
|
|
60
|
-
message: `Unknown role: ${request.params.role}`,
|
|
61
|
-
statusCode: 400
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
65
|
-
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
66
|
-
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
67
|
-
const artifactPath = paths.roleLogPaths[request.params.role];
|
|
68
|
-
if (!artifactPath) {
|
|
69
|
-
return {
|
|
70
|
-
role: request.params.role,
|
|
71
|
-
content: ""
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
role: request.params.role,
|
|
76
|
-
content: await deps.artifactService.readArtifact({
|
|
77
|
-
repoRoot: taskRepoRoot,
|
|
78
|
-
artifactPath
|
|
79
|
-
})
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
55
|
}
|
|
83
56
|
function parseDispatchableRole(role) {
|
|
84
57
|
if (!isDispatchableRole(role)) {
|
|
@@ -4,6 +4,30 @@ export function registerCodexTranslationRoutes(app, deps) {
|
|
|
4
4
|
const project = await requireCurrentProject(deps.projectService);
|
|
5
5
|
return deps.codexTranslationService.getState(project.repoRoot);
|
|
6
6
|
});
|
|
7
|
+
app.get("/api/translation/codex/session", async () => {
|
|
8
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
9
|
+
return (await deps.sessionService.getProjectTranslatorSession(project.repoRoot)) ?? null;
|
|
10
|
+
});
|
|
11
|
+
app.post("/api/translation/codex/session/ensure", async (request) => {
|
|
12
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
13
|
+
return deps.sessionService.ensureProjectTranslatorSession(project.repoRoot, request.body);
|
|
14
|
+
});
|
|
15
|
+
app.post("/api/translation/codex/session/start", async (request) => {
|
|
16
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
17
|
+
return deps.sessionService.startProjectTranslatorSession(project.repoRoot, request.body);
|
|
18
|
+
});
|
|
19
|
+
app.post("/api/translation/codex/session/resume", async (request) => {
|
|
20
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
21
|
+
return deps.sessionService.resumeProjectTranslatorSession(project.repoRoot, request.body);
|
|
22
|
+
});
|
|
23
|
+
app.post("/api/translation/codex/session/restart", async (request) => {
|
|
24
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
25
|
+
return deps.sessionService.restartProjectTranslatorSession(project.repoRoot, request.body);
|
|
26
|
+
});
|
|
27
|
+
app.post("/api/translation/codex/session/stop", async () => {
|
|
28
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
29
|
+
return deps.sessionService.stopProjectTranslatorSession(project.repoRoot);
|
|
30
|
+
});
|
|
7
31
|
app.get("/api/translation/codex/source-files", async (request) => {
|
|
8
32
|
const project = await requireCurrentProject(deps.projectService);
|
|
9
33
|
return deps.codexTranslationService.browseSourceFiles(project.repoRoot, {
|
|
@@ -16,6 +16,16 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
16
16
|
const project = await requireCurrentProject(deps.projectService);
|
|
17
17
|
return deps.harnessService.applyHarness(project.repoRoot);
|
|
18
18
|
});
|
|
19
|
+
app.post("/api/projects/harness/tasks/:taskSlug/commit-and-rebase", async (request) => {
|
|
20
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
21
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
22
|
+
return deps.harnessService.commitAndRebaseTask(project.repoRoot, {
|
|
23
|
+
taskSlug: task.taskSlug,
|
|
24
|
+
branch: task.branch,
|
|
25
|
+
worktreePath: task.worktreePath,
|
|
26
|
+
changedFiles: request.body?.changedFiles ?? []
|
|
27
|
+
});
|
|
28
|
+
});
|
|
19
29
|
app.get("/api/projects/harness/bootstrap", async () => {
|
|
20
30
|
const project = await requireCurrentProject(deps.projectService);
|
|
21
31
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DISPATCHABLE_ROLES
|
|
1
|
+
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
2
2
|
import { isOpenFileLimitError, VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerTaskRoutes(app, deps) {
|
|
@@ -66,6 +66,7 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
|
|
|
66
66
|
createdAt: timestamp,
|
|
67
67
|
updatedAt: timestamp,
|
|
68
68
|
repoRoot,
|
|
69
|
+
worktreePath: `${repoRoot}/.claude/worktrees/${taskSlug}`,
|
|
69
70
|
branch: "unknown",
|
|
70
71
|
handoffDir,
|
|
71
72
|
status: "stopped"
|
|
@@ -79,18 +80,13 @@ function degradedTaskStatus(repoRoot, taskSlug, error) {
|
|
|
79
80
|
}
|
|
80
81
|
function degradedArtifactSummary(handoffDir) {
|
|
81
82
|
const roleCommandsDir = `${handoffDir}/role-commands`;
|
|
82
|
-
const logsDir = `${handoffDir}/logs`;
|
|
83
83
|
const messagesDir = `${handoffDir}/messages`;
|
|
84
84
|
return {
|
|
85
85
|
paths: {
|
|
86
86
|
handoffDir,
|
|
87
87
|
roleCommandsDir,
|
|
88
|
-
logsDir,
|
|
89
88
|
messagesDir,
|
|
90
89
|
roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
|
|
91
|
-
roleLogPaths: Object.fromEntries(ROLE_NAMES
|
|
92
|
-
.filter((role) => role !== "codex-translator")
|
|
93
|
-
.map((role) => [role, `${logsDir}/${role}.log`])),
|
|
94
90
|
messageRoutePaths: {},
|
|
95
91
|
architecturePlanPath: `${handoffDir}/architecture-plan.md`,
|
|
96
92
|
knownIssuesPath: `${handoffDir}/known-issues.md`,
|
|
@@ -352,17 +352,6 @@ export function createGatewayService(deps) {
|
|
|
352
352
|
}
|
|
353
353
|
async function pullCurrent() {
|
|
354
354
|
const project = await ensureProject();
|
|
355
|
-
const settings = await syncDesktopContext(await deps.settings.loadSettings());
|
|
356
|
-
if (settings.currentTaskSlug) {
|
|
357
|
-
const task = await deps.taskService.loadTask(project.repoRoot, settings.currentTaskSlug);
|
|
358
|
-
if (!task.worktreePath && task.cleanupStatus !== "cleaned") {
|
|
359
|
-
throw new VcmError({
|
|
360
|
-
code: "GATEWAY_PULL_BLOCKED_BY_INLINE_TASK",
|
|
361
|
-
message: `Inline task "${task.taskSlug}" uses the base repository.`,
|
|
362
|
-
statusCode: 409
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
355
|
const pulled = await deps.projectService.pullCurrentProject();
|
|
367
356
|
return [
|
|
368
357
|
"Connected repository updated.",
|
|
@@ -377,7 +366,7 @@ export function createGatewayService(deps) {
|
|
|
377
366
|
if (tasks.length === 0) {
|
|
378
367
|
return "No tasks. Use /create-task <task-slug> [title] to create one.";
|
|
379
368
|
}
|
|
380
|
-
return tasks.map((task, index) => `${index + 1}. ${task.taskSlug} [${task.status}] ${task.
|
|
369
|
+
return tasks.map((task, index) => `${index + 1}. ${task.taskSlug} [${task.status}] ${task.branch}`).join("\n");
|
|
381
370
|
}
|
|
382
371
|
async function useTask(selector) {
|
|
383
372
|
const project = await ensureProject();
|
|
@@ -405,8 +394,7 @@ export function createGatewayService(deps) {
|
|
|
405
394
|
const project = await ensureProject();
|
|
406
395
|
const task = await deps.taskService.createTask(project.repoRoot, {
|
|
407
396
|
taskSlug,
|
|
408
|
-
title
|
|
409
|
-
createWorktree: true
|
|
397
|
+
title
|
|
410
398
|
});
|
|
411
399
|
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
412
400
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
@@ -460,7 +448,7 @@ export function createGatewayService(deps) {
|
|
|
460
448
|
return [
|
|
461
449
|
`Task created and initialized: ${task.taskSlug}`,
|
|
462
450
|
`branch: ${task.branch}`,
|
|
463
|
-
`worktree: ${task.worktreePath
|
|
451
|
+
`worktree: ${task.worktreePath}`,
|
|
464
452
|
`orchestration: ${template.autoOrchestration ? "auto" : "manual"}`,
|
|
465
453
|
`translation: ${preferences.translationEnabled ? "on" : "off"}`,
|
|
466
454
|
`sessions: ${startedRoles.join(", ")}`
|
|
@@ -523,7 +511,6 @@ export function createGatewayService(deps) {
|
|
|
523
511
|
deps.roundService.stopTask(taskSlug);
|
|
524
512
|
const result = await deps.taskService.cleanupTask(project.repoRoot, taskSlug, {
|
|
525
513
|
force: true,
|
|
526
|
-
deleteBranch: Boolean(task.worktreePath),
|
|
527
514
|
forceDeleteBranch: true
|
|
528
515
|
});
|
|
529
516
|
clearFailedTranslation(project.repoRoot, taskSlug);
|
package/dist/backend/server.js
CHANGED
|
@@ -73,7 +73,8 @@ export async function createServer(deps, options = {}) {
|
|
|
73
73
|
});
|
|
74
74
|
registerCodexTranslationRoutes(app, {
|
|
75
75
|
projectService: deps.projectService,
|
|
76
|
-
codexTranslationService: deps.codexTranslationService
|
|
76
|
+
codexTranslationService: deps.codexTranslationService,
|
|
77
|
+
sessionService: deps.sessionService
|
|
77
78
|
});
|
|
78
79
|
registerProjectRoutes(app, {
|
|
79
80
|
projectService: deps.projectService,
|
|
@@ -81,7 +82,8 @@ export async function createServer(deps, options = {}) {
|
|
|
81
82
|
});
|
|
82
83
|
registerHarnessRoutes(app, {
|
|
83
84
|
projectService: deps.projectService,
|
|
84
|
-
harnessService: deps.harnessService
|
|
85
|
+
harnessService: deps.harnessService,
|
|
86
|
+
taskService: deps.taskService
|
|
85
87
|
});
|
|
86
88
|
registerTaskRoutes(app, {
|
|
87
89
|
projectService: deps.projectService,
|
|
@@ -169,6 +171,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
169
171
|
const projectService = createProjectService({ fs, git, appSettings });
|
|
170
172
|
const harnessService = createHarnessService({
|
|
171
173
|
fs,
|
|
174
|
+
git,
|
|
172
175
|
runtime,
|
|
173
176
|
projectService,
|
|
174
177
|
apiUrl: options.apiUrl,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { DISPATCHABLE_ROLES
|
|
2
|
+
import { DISPATCHABLE_ROLES } from "../../shared/constants.js";
|
|
3
3
|
import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
5
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
@@ -25,25 +25,16 @@ export function createArtifactService(fs) {
|
|
|
25
25
|
return {
|
|
26
26
|
getHandoffPaths(_repoRoot, handoffDir) {
|
|
27
27
|
const roleCommandsDir = path.posix.join(handoffDir, "role-commands");
|
|
28
|
-
const logsDir = path.posix.join(handoffDir, "logs");
|
|
29
28
|
const messagesDir = path.posix.join(handoffDir, "messages");
|
|
30
29
|
return {
|
|
31
30
|
handoffDir,
|
|
32
31
|
roleCommandsDir,
|
|
33
|
-
logsDir,
|
|
34
32
|
messagesDir,
|
|
35
33
|
roleCommandPaths: {
|
|
36
34
|
architect: path.posix.join(roleCommandsDir, "architect.md"),
|
|
37
35
|
coder: path.posix.join(roleCommandsDir, "coder.md"),
|
|
38
36
|
reviewer: path.posix.join(roleCommandsDir, "reviewer.md")
|
|
39
37
|
},
|
|
40
|
-
roleLogPaths: {
|
|
41
|
-
"project-manager": path.posix.join(logsDir, "project-manager.log"),
|
|
42
|
-
architect: path.posix.join(logsDir, "architect.log"),
|
|
43
|
-
coder: path.posix.join(logsDir, "coder.log"),
|
|
44
|
-
reviewer: path.posix.join(logsDir, "reviewer.log"),
|
|
45
|
-
"codex-reviewer": path.posix.join(logsDir, "codex-reviewer.log")
|
|
46
|
-
},
|
|
47
38
|
messageRoutePaths: getDefaultMessageRoutePaths(messagesDir),
|
|
48
39
|
architecturePlanPath: path.posix.join(handoffDir, "architecture-plan.md"),
|
|
49
40
|
knownIssuesPath: path.posix.join(handoffDir, "known-issues.md"),
|
|
@@ -59,7 +50,6 @@ export function createArtifactService(fs) {
|
|
|
59
50
|
const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
|
|
60
51
|
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.handoffDir));
|
|
61
52
|
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.roleCommandsDir));
|
|
62
|
-
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.logsDir));
|
|
63
53
|
await fs.ensureDir(resolveRepoPath(input.repoRoot, paths.messagesDir));
|
|
64
54
|
return paths;
|
|
65
55
|
},
|
|
@@ -168,25 +158,6 @@ export function createArtifactService(fs) {
|
|
|
168
158
|
async saveRoleCommand(input) {
|
|
169
159
|
const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
|
|
170
160
|
await fs.writeText(resolveRepoPath(input.repoRoot, paths.roleCommandPaths[input.role]), input.content);
|
|
171
|
-
},
|
|
172
|
-
async appendRoleLog(input) {
|
|
173
|
-
if (!ROLE_NAMES.includes(input.role)) {
|
|
174
|
-
throw new VcmError({
|
|
175
|
-
code: "UNKNOWN_ROLE",
|
|
176
|
-
message: `Unknown role: ${input.role}`,
|
|
177
|
-
statusCode: 400
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
const paths = this.getHandoffPaths(input.repoRoot, input.handoffDir);
|
|
181
|
-
const roleLogPath = paths.roleLogPaths[input.role];
|
|
182
|
-
if (!roleLogPath) {
|
|
183
|
-
throw new VcmError({
|
|
184
|
-
code: "ROLE_LOG_UNAVAILABLE",
|
|
185
|
-
message: `${input.role} does not write a handoff log.`,
|
|
186
|
-
statusCode: 409
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
await fs.appendText(resolveRepoPath(input.repoRoot, roleLogPath), input.content);
|
|
190
161
|
}
|
|
191
162
|
};
|
|
192
163
|
}
|
|
@@ -18,6 +18,13 @@ export function createCodexHookService(deps) {
|
|
|
18
18
|
statusCode: 409
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
if (input.role === "codex-translator") {
|
|
22
|
+
return {
|
|
23
|
+
project,
|
|
24
|
+
config: undefined,
|
|
25
|
+
taskRepoRoot: undefined
|
|
26
|
+
};
|
|
27
|
+
}
|
|
21
28
|
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
22
29
|
const task = await deps.taskService.loadTask(project.repoRoot, input.taskSlug);
|
|
23
30
|
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
@@ -37,16 +44,30 @@ export function createCodexHookService(deps) {
|
|
|
37
44
|
});
|
|
38
45
|
}
|
|
39
46
|
const context = await getHookContext(input);
|
|
40
|
-
const session =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const session = input.role === "codex-translator"
|
|
48
|
+
? await deps.sessionService.recordProjectTranslatorHookEvent(context.project.repoRoot, {
|
|
49
|
+
eventName,
|
|
50
|
+
sessionId: stringOrUndefined(input.event.session_id),
|
|
51
|
+
transcriptPath: stringOrUndefined(input.event.transcript_path),
|
|
52
|
+
cwd: stringOrUndefined(input.event.cwd)
|
|
53
|
+
})
|
|
54
|
+
: await deps.sessionService.recordRoleHookEvent(context.project.repoRoot, {
|
|
55
|
+
taskSlug: input.taskSlug,
|
|
56
|
+
role: input.role,
|
|
57
|
+
eventName,
|
|
58
|
+
sessionId: stringOrUndefined(input.event.session_id),
|
|
59
|
+
transcriptPath: stringOrUndefined(input.event.transcript_path),
|
|
60
|
+
cwd: stringOrUndefined(input.event.cwd),
|
|
61
|
+
allowSessionMismatch: true
|
|
62
|
+
});
|
|
49
63
|
if (input.role === "codex-reviewer") {
|
|
64
|
+
if (!context.config || !context.taskRepoRoot) {
|
|
65
|
+
throw new VcmError({
|
|
66
|
+
code: "CODEX_HOOK_TASK_CONTEXT_MISSING",
|
|
67
|
+
message: "Codex Reviewer hook is missing task context.",
|
|
68
|
+
statusCode: 500
|
|
69
|
+
});
|
|
70
|
+
}
|
|
50
71
|
await deps.roundService.recordRoleTurnEvent({
|
|
51
72
|
repoRoot: context.project.repoRoot,
|
|
52
73
|
stateRepoRoot: context.taskRepoRoot,
|
|
@@ -178,7 +178,7 @@ export function createCodexTranslationService(deps) {
|
|
|
178
178
|
await saveQueue(repoRoot, queue);
|
|
179
179
|
return queued;
|
|
180
180
|
}
|
|
181
|
-
async function dispatchNext(repoRoot
|
|
181
|
+
async function dispatchNext(repoRoot) {
|
|
182
182
|
if (!deps.runtime || !deps.sessionService) {
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
@@ -207,7 +207,7 @@ export function createCodexTranslationService(deps) {
|
|
|
207
207
|
queue.updatedAt = next.updatedAt;
|
|
208
208
|
await saveQueue(repoRoot, queue);
|
|
209
209
|
}
|
|
210
|
-
const session = await ensureTranslatorSession(repoRoot,
|
|
210
|
+
const session = await ensureTranslatorSession(repoRoot, next.targetLanguage);
|
|
211
211
|
await submitTerminalInput(deps.runtime, session.id, batch?.prompt ?? await buildQueuePrompt(repoRoot, next));
|
|
212
212
|
const dispatchedAt = now();
|
|
213
213
|
for (const item of batch?.items ?? [next]) {
|
|
@@ -255,7 +255,7 @@ export function createCodexTranslationService(deps) {
|
|
|
255
255
|
await saveQueue(repoRoot, queue);
|
|
256
256
|
return { items: candidates, prompt };
|
|
257
257
|
}
|
|
258
|
-
async function ensureTranslatorSession(repoRoot,
|
|
258
|
+
async function ensureTranslatorSession(repoRoot, targetLanguage) {
|
|
259
259
|
void targetLanguage;
|
|
260
260
|
if (!deps.sessionService) {
|
|
261
261
|
throw new VcmError({
|
|
@@ -264,17 +264,7 @@ export function createCodexTranslationService(deps) {
|
|
|
264
264
|
statusCode: 500
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
|
-
|
|
268
|
-
if (existing?.status === "running") {
|
|
269
|
-
return existing;
|
|
270
|
-
}
|
|
271
|
-
if (existing?.claudeSessionId) {
|
|
272
|
-
return deps.sessionService.resumeRoleSession(repoRoot, taskSlug, CODEX_TRANSLATOR_ROLE, {
|
|
273
|
-
model: existing.model,
|
|
274
|
-
effort: existing.effort
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return deps.sessionService.startRoleSession(repoRoot, taskSlug, CODEX_TRANSLATOR_ROLE, {
|
|
267
|
+
return deps.sessionService.ensureProjectTranslatorSession(repoRoot, {
|
|
278
268
|
model: "gpt-5.5",
|
|
279
269
|
effort: "medium"
|
|
280
270
|
});
|
|
@@ -902,9 +892,7 @@ export function createCodexTranslationService(deps) {
|
|
|
902
892
|
const activeExisting = (await loadRuntimeFileJobs(repoRoot)).find((job) => fileTranslationReplacementKey(job) === replacementKey &&
|
|
903
893
|
isActiveFileTranslationJobStatus(job.status));
|
|
904
894
|
if (activeExisting) {
|
|
905
|
-
|
|
906
|
-
void dispatchNext(repoRoot, input.taskSlug);
|
|
907
|
-
}
|
|
895
|
+
void dispatchNext(repoRoot);
|
|
908
896
|
return activeExisting;
|
|
909
897
|
}
|
|
910
898
|
const timestamp = now();
|
|
@@ -1003,9 +991,7 @@ export function createCodexTranslationService(deps) {
|
|
|
1003
991
|
lastUpdatedAt: timestamp
|
|
1004
992
|
});
|
|
1005
993
|
await deps.fs.writeText(resolveRepoPath(repoRoot, job.reportPath), `# Translation Report\n\nStatus: queued\nJob: ${jobId}\n`);
|
|
1006
|
-
|
|
1007
|
-
void dispatchNext(repoRoot, input.taskSlug);
|
|
1008
|
-
}
|
|
994
|
+
void dispatchNext(repoRoot);
|
|
1009
995
|
return job;
|
|
1010
996
|
},
|
|
1011
997
|
async createBootstrapRun(repoRoot, input) {
|
|
@@ -1062,9 +1048,7 @@ export function createCodexTranslationService(deps) {
|
|
|
1062
1048
|
index.runs = [run, ...index.runs];
|
|
1063
1049
|
index.updatedAt = timestamp;
|
|
1064
1050
|
await saveBootstrapIndex(repoRoot, index);
|
|
1065
|
-
|
|
1066
|
-
void dispatchNext(repoRoot, input.taskSlug);
|
|
1067
|
-
}
|
|
1051
|
+
void dispatchNext(repoRoot);
|
|
1068
1052
|
return run;
|
|
1069
1053
|
},
|
|
1070
1054
|
async createMemoryUpdate(repoRoot, input) {
|
|
@@ -1120,9 +1104,7 @@ export function createCodexTranslationService(deps) {
|
|
|
1120
1104
|
"Delete or merge stale, duplicate, temporary, and task-local content."
|
|
1121
1105
|
]
|
|
1122
1106
|
});
|
|
1123
|
-
|
|
1124
|
-
void dispatchNext(repoRoot, input.taskSlug);
|
|
1125
|
-
}
|
|
1107
|
+
void dispatchNext(repoRoot);
|
|
1126
1108
|
return queueItem;
|
|
1127
1109
|
},
|
|
1128
1110
|
async readFileJobOutput(repoRoot, jobId) {
|
|
@@ -1156,14 +1138,12 @@ export function createCodexTranslationService(deps) {
|
|
|
1156
1138
|
});
|
|
1157
1139
|
}
|
|
1158
1140
|
const timestamp = now();
|
|
1159
|
-
const jobId = `conversation-${
|
|
1160
|
-
const jobRoot = `${CONVERSATION_RUNTIME_DIR}
|
|
1141
|
+
const jobId = `conversation-${Date.now()}-${createId().slice(0, 8)}`;
|
|
1142
|
+
const jobRoot = `${CONVERSATION_RUNTIME_DIR}/jobs/${jobId}`;
|
|
1161
1143
|
const sourceHash = `sha256:${sha256(sourceText)}`;
|
|
1162
1144
|
const targetLanguage = input.targetLanguage.trim() || "zh-CN";
|
|
1163
1145
|
const job = {
|
|
1164
1146
|
id: jobId,
|
|
1165
|
-
taskSlug: input.taskSlug,
|
|
1166
|
-
role: input.role,
|
|
1167
1147
|
direction: input.direction,
|
|
1168
1148
|
sourceHash,
|
|
1169
1149
|
sourceLanguage: input.sourceLanguage.trim() || "auto",
|
|
@@ -1187,15 +1167,10 @@ export function createCodexTranslationService(deps) {
|
|
|
1187
1167
|
version: 1,
|
|
1188
1168
|
baseRepoRoot: repoRoot,
|
|
1189
1169
|
pathBase: "baseRepoRoot",
|
|
1190
|
-
job,
|
|
1191
|
-
taskSlug: input.taskSlug,
|
|
1192
|
-
role: input.role,
|
|
1193
1170
|
direction: input.direction,
|
|
1194
|
-
sourceHash,
|
|
1195
1171
|
sourceLanguage: job.sourceLanguage,
|
|
1196
1172
|
targetLanguage,
|
|
1197
1173
|
translationProfile: input.translationProfile?.trim() || DEFAULT_PROFILE,
|
|
1198
|
-
contextText: input.contextText,
|
|
1199
1174
|
sourceContentBoundary: "VCM_TEXT",
|
|
1200
1175
|
sourceText,
|
|
1201
1176
|
absolutePaths: {
|
|
@@ -1208,8 +1183,8 @@ export function createCodexTranslationService(deps) {
|
|
|
1208
1183
|
format: "plain-text"
|
|
1209
1184
|
}
|
|
1210
1185
|
});
|
|
1211
|
-
if (
|
|
1212
|
-
void dispatchNext(repoRoot
|
|
1186
|
+
if (!input.deferDispatch) {
|
|
1187
|
+
void dispatchNext(repoRoot);
|
|
1213
1188
|
}
|
|
1214
1189
|
return job;
|
|
1215
1190
|
},
|
|
@@ -1290,6 +1265,7 @@ export function createCodexTranslationService(deps) {
|
|
|
1290
1265
|
return job;
|
|
1291
1266
|
},
|
|
1292
1267
|
async handleCodexHook(repoRoot, eventName, taskSlug) {
|
|
1268
|
+
void taskSlug;
|
|
1293
1269
|
if (eventName === "UserPromptSubmit") {
|
|
1294
1270
|
const queue = await loadQueue(repoRoot);
|
|
1295
1271
|
const active = queue.activeItemId
|
|
@@ -1306,10 +1282,11 @@ export function createCodexTranslationService(deps) {
|
|
|
1306
1282
|
}
|
|
1307
1283
|
if (eventName === "Stop") {
|
|
1308
1284
|
await validateActiveQueueItem(repoRoot);
|
|
1309
|
-
|
|
1310
|
-
await dispatchNext(repoRoot, taskSlug);
|
|
1311
|
-
}
|
|
1285
|
+
await dispatchNext(repoRoot);
|
|
1312
1286
|
}
|
|
1287
|
+
},
|
|
1288
|
+
ensureTranslatorSession(repoRoot) {
|
|
1289
|
+
return ensureTranslatorSession(repoRoot, "zh-CN");
|
|
1313
1290
|
}
|
|
1314
1291
|
};
|
|
1315
1292
|
}
|