vibe-coding-master 0.3.9 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -7
- package/dist/backend/api/codex-hook-routes.js +7 -0
- package/dist/backend/api/codex-translation-routes.js +50 -0
- package/dist/backend/cli/install-vcm-harness.js +29 -1
- package/dist/backend/gateway/gateway-settings-service.js +4 -3
- package/dist/backend/server.js +17 -2
- package/dist/backend/services/app-settings-service.js +2 -2
- package/dist/backend/services/artifact-service.js +2 -1
- package/dist/backend/services/codex-hook-service.js +12 -9
- package/dist/backend/services/codex-translation-service.js +964 -0
- package/dist/backend/services/harness-service.js +28 -1
- package/dist/backend/services/session-service.js +42 -21
- package/dist/backend/services/translation-service.js +120 -35
- package/dist/backend/templates/harness/codex-review.js +68 -3
- package/dist/backend/vcm-data-dir.js +7 -0
- package/dist/shared/constants.js +15 -1
- package/dist-frontend/assets/index-B13y-ZM8.css +32 -0
- package/dist-frontend/assets/index-C7Nb1xPJ.js +92 -0
- package/dist-frontend/index.html +2 -2
- package/docs/codex-review-gates.md +26 -7
- package/docs/codex-translation-plan.md +1164 -0
- package/docs/full-harness-baseline.md +1 -1
- package/docs/gateway-design.md +7 -4
- package/docs/product-design.md +10 -9
- package/docs/v0.2-implementation-plan.md +3 -3
- package/docs/vcm-cc-best-practices.md +3 -3
- package/package.json +1 -1
- package/dist-frontend/assets/index-CR1EOe-w.css +0 -32
- package/dist-frontend/assets/index-D-6FVz_K.js +0 -92
- package/docs/codex-file-translation-plan.md +0 -618
package/README.md
CHANGED
|
@@ -142,6 +142,7 @@ Important container notes:
|
|
|
142
142
|
- Make sure the container has network access to Claude services and to the translation provider if translation is enabled.
|
|
143
143
|
- VCM accepts normal Git repositories by checking `.git` directly. It also supports `.git` files that point to worktree gitdirs.
|
|
144
144
|
- VCM uses per-command `git -c safe.directory=...` for Git metadata reads and does not require global `git config --global --add safe.directory`.
|
|
145
|
+
- Set `VCM_SANDBOX=devcontainer` so VCM-managed Codex Reviewer sessions rely on the container boundary and do not start Codex's nested Linux sandbox.
|
|
145
146
|
- Treat the container as the sandbox boundary, especially when using relaxed Claude Code permission modes.
|
|
146
147
|
|
|
147
148
|
## Basic Usage
|
|
@@ -257,8 +258,8 @@ Gateway rules:
|
|
|
257
258
|
Gateway state is stored locally under:
|
|
258
259
|
|
|
259
260
|
```text
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
<vcmDataDir>/gateway/settings.json
|
|
262
|
+
<vcmDataDir>/gateway/audit.jsonl
|
|
262
263
|
```
|
|
263
264
|
|
|
264
265
|
### Bind Weixin
|
|
@@ -384,11 +385,13 @@ The task header does not include a manual `Refresh` button. Task status, role st
|
|
|
384
385
|
Translation settings are local and stored in:
|
|
385
386
|
|
|
386
387
|
```text
|
|
387
|
-
|
|
388
|
+
<vcmDataDir>/settings.json
|
|
388
389
|
```
|
|
389
390
|
|
|
390
391
|
The same file stores recent repository paths. The translation API key is stored locally under `translation.secrets.apiKey`; it is not written to the connected repository, `.ai/vcm/handoffs`, raw terminal logs, or git diffs.
|
|
391
392
|
|
|
393
|
+
VCM resolves `vcmDataDir` from `VCM_DATA_DIR`. If `VCM_DATA_DIR` is unset or empty, VCM uses `~/.vcm`. In Dev Containers, set `VCM_DATA_DIR=/workspace/.ai/vcm` and `VCM_SANDBOX=devcontainer` through `containerEnv` so VCM app state survives container rebuilds and VCM-managed Codex Reviewer sessions do not run a nested Codex sandbox.
|
|
394
|
+
|
|
392
395
|
The sidebar `Settings` section also stores the UI theme preference in this file. The default is `system`, which follows the OS/browser color-scheme preference; users can cycle between `System`, `Light`, and `Dark`.
|
|
393
396
|
|
|
394
397
|
The same sidebar also has a `Flow pause alert` toggle. It is on by default and controls the local alert that fires when VCM detects that the current role flow has stopped advancing. Short flows use a weak reminder: the soft two-note chime plays 3 times, 1.4 seconds apart. Flows lasting 2 minutes or longer use a strong reminder: VCM shows an alert dialog and repeats the chime until the user confirms it. The alert sound reuses one browser audio context so repeated reminders remain reliable in stricter browsers such as Safari. Safari users may still need to manually set `Safari > Website Settings > Auto-Play > Allow All Auto-Play`; Chrome is recommended for the most reliable alert sound behavior. The `Try alert` button always triggers the strong reminder for testing.
|
|
@@ -479,7 +482,7 @@ For `.gitignore`, VCM uses a gitignore-native managed block:
|
|
|
479
482
|
# VCM:END
|
|
480
483
|
```
|
|
481
484
|
|
|
482
|
-
`.ai/vcm/` is the active VCM local control area, and `.claude/worktrees/` is the Claude-compatible task worktree area. VCM keeps the task index in app-local project state under
|
|
485
|
+
`.ai/vcm/` is the active VCM local control area, and `.claude/worktrees/` is the Claude-compatible task worktree area. VCM keeps the task index in app-local project state under `<vcmDataDir>/projects/`; each task runtime repo keeps its own session, message, orchestration, and translation state.
|
|
483
486
|
|
|
484
487
|
VCM also JSON-merges `.claude/settings.json` to install Claude Code `PreToolUse`, `UserPromptSubmit`, `Stop`, and `PermissionRequest` hooks plus a managed `env.BASH_DEFAULT_TIMEOUT_MS` so foreground watch windows fit inside the Bash tool timeout. The hooks post directly to the local VCM backend, so roles do not need a VCM CLI command to confirm delivery or report turn completion. The `Stop` hook forwards the backend response to Claude Code, which lets VCM block turn-end while a validation job is still running. When Codex Review Gates are installed, VCM also writes `.ai/codex/.codex/config.toml` and `.ai/codex/.codex/hooks.json` so the embedded Codex Reviewer terminal can POST `UserPromptSubmit` and `Stop` events back to VCM.
|
|
485
488
|
|
|
@@ -606,8 +609,8 @@ Embedded terminal output is still written to raw role log files under `.ai/vcm/h
|
|
|
606
609
|
For a connected repository, VCM uses:
|
|
607
610
|
|
|
608
611
|
```text
|
|
609
|
-
|
|
610
|
-
|
|
612
|
+
<vcmDataDir>/projects/<project-id>/config.json
|
|
613
|
+
<vcmDataDir>/projects/<project-id>/tasks/<task>.json
|
|
611
614
|
<baseRepoRoot>/.claude/worktrees/<task>/
|
|
612
615
|
<taskRepoRoot>/.ai/vcm/sessions/<task>.json
|
|
613
616
|
<taskRepoRoot>/.ai/vcm/messages/<task>.jsonl
|
|
@@ -621,7 +624,17 @@ For a connected repository, VCM uses:
|
|
|
621
624
|
<taskRepoRoot>/.ai/vcm/handoffs/logs/{project-manager,architect,coder,reviewer}.log
|
|
622
625
|
```
|
|
623
626
|
|
|
624
|
-
The project config is stored under
|
|
627
|
+
The project config is stored under `vcmDataDir` so it is durable local app state. `vcmDataDir` is `VCM_DATA_DIR` when set, otherwise `~/.vcm`. For Dev Containers, prefer:
|
|
628
|
+
|
|
629
|
+
```json
|
|
630
|
+
{
|
|
631
|
+
"containerEnv": {
|
|
632
|
+
"VCM_DATA_DIR": "/workspace/.ai/vcm"
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
For worktree-backed tasks, `taskRepoRoot` is `<baseRepoRoot>/.claude/worktrees/<task>`; for inline tasks, `taskRepoRoot` is the connected base repo.
|
|
625
638
|
|
|
626
639
|
Because handoffs are scoped to `taskRepoRoot` without an extra task-name directory, VCM allows only one active inline task per connected repository. Use the default worktree mode for parallel tasks.
|
|
627
640
|
|
|
@@ -6,4 +6,11 @@ export function registerCodexHookRoutes(app, deps) {
|
|
|
6
6
|
await deps.codexHookService.handleStopHook(request.body);
|
|
7
7
|
return {};
|
|
8
8
|
});
|
|
9
|
+
app.post("/api/hooks/codex-translator", async (request) => {
|
|
10
|
+
return deps.codexHookService.handleHook(request.body);
|
|
11
|
+
});
|
|
12
|
+
app.post("/api/hooks/codex-translator/stop", async (request) => {
|
|
13
|
+
await deps.codexHookService.handleStopHook(request.body);
|
|
14
|
+
return {};
|
|
15
|
+
});
|
|
9
16
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { VcmError } from "../errors.js";
|
|
2
|
+
export function registerCodexTranslationRoutes(app, deps) {
|
|
3
|
+
app.get("/api/translation/codex/state", async () => {
|
|
4
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
5
|
+
return deps.codexTranslationService.getState(project.repoRoot);
|
|
6
|
+
});
|
|
7
|
+
app.get("/api/translation/codex/source-files", async (request) => {
|
|
8
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
9
|
+
return deps.codexTranslationService.browseSourceFiles(project.repoRoot, {
|
|
10
|
+
path: request.query.path,
|
|
11
|
+
query: request.query.query,
|
|
12
|
+
limit: request.query.limit === undefined ? undefined : Number(request.query.limit)
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
app.post("/api/translation/codex/files", async (request) => {
|
|
16
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
17
|
+
return deps.codexTranslationService.createFileJob(project.repoRoot, request.body);
|
|
18
|
+
});
|
|
19
|
+
app.post("/api/translation/codex/bootstrap", async (request) => {
|
|
20
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
21
|
+
return deps.codexTranslationService.createBootstrapRun(project.repoRoot, request.body);
|
|
22
|
+
});
|
|
23
|
+
app.get("/api/translation/codex/files/:jobId", async (request) => {
|
|
24
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
25
|
+
return deps.codexTranslationService.readFileJobOutput(project.repoRoot, request.params.jobId);
|
|
26
|
+
});
|
|
27
|
+
app.post("/api/translation/codex/files/:jobId/promote", async (request) => {
|
|
28
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
29
|
+
const targetPath = request.body?.targetPath?.trim();
|
|
30
|
+
if (!targetPath) {
|
|
31
|
+
throw new VcmError({
|
|
32
|
+
code: "TRANSLATION_PROMOTE_TARGET_REQUIRED",
|
|
33
|
+
message: "Promote target path is required.",
|
|
34
|
+
statusCode: 400
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return deps.codexTranslationService.promoteFileJob(project.repoRoot, request.params.jobId, targetPath);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function requireCurrentProject(projectService) {
|
|
41
|
+
const project = await projectService.getCurrentProject();
|
|
42
|
+
if (!project) {
|
|
43
|
+
throw new VcmError({
|
|
44
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
45
|
+
message: "Connect a repository first.",
|
|
46
|
+
statusCode: 409
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return project;
|
|
50
|
+
}
|
|
@@ -6,7 +6,7 @@ import process from "node:process";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
|
|
8
8
|
import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
|
|
9
|
-
import { renderCodexAgentsHarnessRules, renderCodexArchitecturePlanPrompt, renderCodexCliConfigHarnessRules, renderCodexConfigHarnessRules, renderCodexFinalDiffPrompt, renderCodexHooksHarnessRules, renderCodexReviewResultSchema, renderCodexValidationAdequacyPrompt, renderRequestCodexReviewTool, renderVcmCodexReviewGateSkillRules } from "../templates/harness/codex-review.js";
|
|
9
|
+
import { renderCodexAgentsHarnessRules, renderCodexArchitecturePlanPrompt, renderCodexCliConfigHarnessRules, renderCodexConfigHarnessRules, renderCodexFinalDiffPrompt, renderCodexHooksHarnessRules, renderCodexReviewResultSchema, renderCodexTranslatorAgentsHarnessRules, renderCodexTranslatorConfigHarnessRules, renderCodexValidationAdequacyPrompt, renderRequestCodexReviewTool, renderVcmCodexReviewGateSkillRules } from "../templates/harness/codex-review.js";
|
|
10
10
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
11
11
|
import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
|
|
12
12
|
import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
|
|
@@ -109,6 +109,13 @@ const MANAGED_FILES = [
|
|
|
109
109
|
commentStyle: "html",
|
|
110
110
|
category: "codex-reviewer-agent",
|
|
111
111
|
content: renderCodexAgentsHarnessRules()
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
path: ".ai/codex-translator/AGENTS.md",
|
|
115
|
+
title: "VCM Codex Translator",
|
|
116
|
+
commentStyle: "html",
|
|
117
|
+
category: "codex-translator-agent",
|
|
118
|
+
content: renderCodexTranslatorAgentsHarnessRules()
|
|
112
119
|
}
|
|
113
120
|
];
|
|
114
121
|
const DURABLE_DOC_TEMPLATES = [
|
|
@@ -186,6 +193,24 @@ const WHOLE_FILES = [
|
|
|
186
193
|
mode: 0o644,
|
|
187
194
|
content: renderCodexHooksHarnessRules()
|
|
188
195
|
},
|
|
196
|
+
{
|
|
197
|
+
path: ".ai/codex-translator/config.toml",
|
|
198
|
+
category: "codex-translator-config",
|
|
199
|
+
mode: 0o644,
|
|
200
|
+
content: renderCodexTranslatorConfigHarnessRules()
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
path: ".ai/codex-translator/.codex/config.toml",
|
|
204
|
+
category: "codex-translator-hooks",
|
|
205
|
+
mode: 0o644,
|
|
206
|
+
content: renderCodexCliConfigHarnessRules()
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
path: ".ai/codex-translator/.codex/hooks.json",
|
|
210
|
+
category: "codex-translator-hooks",
|
|
211
|
+
mode: 0o644,
|
|
212
|
+
content: renderCodexHooksHarnessRules("codex-translator")
|
|
213
|
+
},
|
|
189
214
|
{
|
|
190
215
|
path: ".ai/codex/prompts/architecture-plan-gate.md",
|
|
191
216
|
category: "codex-review-prompt",
|
|
@@ -432,6 +457,9 @@ function fixedDirectories() {
|
|
|
432
457
|
".ai/codex/.codex/",
|
|
433
458
|
".ai/codex/prompts/",
|
|
434
459
|
".ai/codex/schemas/",
|
|
460
|
+
".ai/codex-translator/",
|
|
461
|
+
".ai/codex-translator/.codex/",
|
|
462
|
+
".ai/vcm/translations/",
|
|
435
463
|
".ai/vcm/codex-reviews/",
|
|
436
464
|
".ai/tools/",
|
|
437
465
|
".ai/generated/"
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
3
3
|
const DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
4
4
|
const MAX_DEDUPE_IDS = 1000;
|
|
5
5
|
export function createGatewaySettingsService(deps) {
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const dataDir = resolveVcmDataDir();
|
|
7
|
+
const settingsPath = deps.settingsPath ?? path.join(dataDir, "gateway", "settings.json");
|
|
8
|
+
const auditPath = deps.auditPath ?? path.join(dataDir, "gateway", "audit.jsonl");
|
|
8
9
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
9
10
|
let cachedSettings = null;
|
|
10
11
|
async function loadSettings() {
|
package/dist/backend/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
|
12
12
|
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
13
13
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
14
14
|
import { createCodexReviewService } from "./services/codex-review-service.js";
|
|
15
|
+
import { createCodexTranslationService } from "./services/codex-translation-service.js";
|
|
15
16
|
import { createHarnessService, createScriptFixedHarnessInstaller } from "./services/harness-service.js";
|
|
16
17
|
import { createNodeFileSystemAdapter } from "./adapters/filesystem.js";
|
|
17
18
|
import { createNodePtyTerminalRuntime } from "./runtime/node-pty-runtime.js";
|
|
@@ -37,6 +38,7 @@ import { registerArtifactRoutes } from "./api/artifact-routes.js";
|
|
|
37
38
|
import { registerClaudeHookRoutes } from "./api/claude-hook-routes.js";
|
|
38
39
|
import { registerCodexHookRoutes } from "./api/codex-hook-routes.js";
|
|
39
40
|
import { registerCodexReviewRoutes } from "./api/codex-review-routes.js";
|
|
41
|
+
import { registerCodexTranslationRoutes } from "./api/codex-translation-routes.js";
|
|
40
42
|
import { registerHarnessRoutes } from "./api/harness-routes.js";
|
|
41
43
|
import { registerMessageRoutes } from "./api/message-routes.js";
|
|
42
44
|
import { registerProjectRoutes } from "./api/project-routes.js";
|
|
@@ -69,6 +71,10 @@ export async function createServer(deps, options = {}) {
|
|
|
69
71
|
projectService: deps.projectService,
|
|
70
72
|
codexReviewService: deps.codexReviewService
|
|
71
73
|
});
|
|
74
|
+
registerCodexTranslationRoutes(app, {
|
|
75
|
+
projectService: deps.projectService,
|
|
76
|
+
codexTranslationService: deps.codexTranslationService
|
|
77
|
+
});
|
|
72
78
|
registerProjectRoutes(app, { projectService: deps.projectService });
|
|
73
79
|
registerHarnessRoutes(app, {
|
|
74
80
|
projectService: deps.projectService,
|
|
@@ -169,7 +175,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
169
175
|
artifactService,
|
|
170
176
|
projectService,
|
|
171
177
|
taskService,
|
|
172
|
-
apiUrl: options.apiUrl
|
|
178
|
+
apiUrl: options.apiUrl,
|
|
179
|
+
sandboxMode: process.env.VCM_SANDBOX
|
|
173
180
|
});
|
|
174
181
|
const commandDispatcher = createCommandDispatcher({
|
|
175
182
|
runtime,
|
|
@@ -205,12 +212,18 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
205
212
|
sessionService,
|
|
206
213
|
roundService
|
|
207
214
|
});
|
|
215
|
+
const codexTranslationService = createCodexTranslationService({
|
|
216
|
+
fs,
|
|
217
|
+
runtime,
|
|
218
|
+
sessionService
|
|
219
|
+
});
|
|
208
220
|
const transcripts = createClaudeTranscriptService();
|
|
209
221
|
const translationService = createTranslationService({
|
|
210
222
|
runtime,
|
|
211
223
|
sessionRegistry: registry,
|
|
212
224
|
transcripts,
|
|
213
225
|
sessionService,
|
|
226
|
+
codexTranslationService,
|
|
214
227
|
fs,
|
|
215
228
|
projectService,
|
|
216
229
|
appSettings,
|
|
@@ -250,7 +263,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
250
263
|
projectService,
|
|
251
264
|
taskService,
|
|
252
265
|
sessionService,
|
|
253
|
-
roundService
|
|
266
|
+
roundService,
|
|
267
|
+
codexTranslationService
|
|
254
268
|
});
|
|
255
269
|
const diagnosticsService = createDiagnosticsService({
|
|
256
270
|
appRoot: getAppRoot(),
|
|
@@ -270,6 +284,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
270
284
|
codexHookService,
|
|
271
285
|
messageService,
|
|
272
286
|
codexReviewService,
|
|
287
|
+
codexTranslationService,
|
|
273
288
|
roundService,
|
|
274
289
|
statusService,
|
|
275
290
|
translationService,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { createHash } from "node:crypto";
|
|
4
3
|
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
5
4
|
import { CODEX_REVIEW_GATES } from "../../shared/types/codex-review.js";
|
|
6
5
|
import { createDefaultLaunchTemplate } from "../../shared/types/app-settings.js";
|
|
7
6
|
import { CLAUDE_MODEL_OPTIONS, SESSION_EFFORT_OPTIONS } from "../../shared/types/session.js";
|
|
7
|
+
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
8
8
|
const MAX_RECENT_REPOSITORIES = 5;
|
|
9
9
|
export function createAppSettingsService(deps) {
|
|
10
|
-
const settingsPath = deps.settingsPath ?? path.join(
|
|
10
|
+
const settingsPath = deps.settingsPath ?? path.join(resolveVcmDataDir(), "settings.json");
|
|
11
11
|
const settingsRoot = path.dirname(settingsPath);
|
|
12
12
|
const projectIndexPath = path.join(settingsRoot, "projects", "index.json");
|
|
13
13
|
let cachedSettings = null;
|
|
@@ -42,7 +42,8 @@ export function createArtifactService(fs) {
|
|
|
42
42
|
architect: path.posix.join(logsDir, "architect.log"),
|
|
43
43
|
coder: path.posix.join(logsDir, "coder.log"),
|
|
44
44
|
reviewer: path.posix.join(logsDir, "reviewer.log"),
|
|
45
|
-
"codex-reviewer": path.posix.join(logsDir, "codex-reviewer.log")
|
|
45
|
+
"codex-reviewer": path.posix.join(logsDir, "codex-reviewer.log"),
|
|
46
|
+
"codex-translator": path.posix.join(logsDir, "codex-translator.log")
|
|
46
47
|
},
|
|
47
48
|
messageRoutePaths: getDefaultMessageRoutePaths(messagesDir),
|
|
48
49
|
architecturePlanPath: path.posix.join(handoffDir, "architecture-plan.md"),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { isCodexRoleName } from "../../shared/constants.js";
|
|
1
2
|
import { VcmError } from "../errors.js";
|
|
2
3
|
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
3
|
-
const CODEX_REVIEWER_ROLE = "codex-reviewer";
|
|
4
4
|
export function createCodexHookService(deps) {
|
|
5
5
|
async function getHookContext(input) {
|
|
6
|
-
if (input.role
|
|
6
|
+
if (!isCodexRoleName(input.role)) {
|
|
7
7
|
throw new VcmError({
|
|
8
8
|
code: "CODEX_HOOK_ROLE_INVALID",
|
|
9
9
|
message: `Unknown Codex hook role: ${input.role}`,
|
|
@@ -14,7 +14,7 @@ export function createCodexHookService(deps) {
|
|
|
14
14
|
if (!project) {
|
|
15
15
|
throw new VcmError({
|
|
16
16
|
code: "PROJECT_NOT_CONNECTED",
|
|
17
|
-
message: "Connect a repository before accepting Codex
|
|
17
|
+
message: "Connect a repository before accepting Codex hooks.",
|
|
18
18
|
statusCode: 409
|
|
19
19
|
});
|
|
20
20
|
}
|
|
@@ -32,14 +32,14 @@ export function createCodexHookService(deps) {
|
|
|
32
32
|
if (eventName !== expectedEventName) {
|
|
33
33
|
throw new VcmError({
|
|
34
34
|
code: "CODEX_HOOK_EVENT_UNSUPPORTED",
|
|
35
|
-
message: `Unsupported Codex
|
|
35
|
+
message: `Unsupported Codex hook event for this endpoint: ${eventName}`,
|
|
36
36
|
statusCode: 400
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
const context = await getHookContext(input);
|
|
40
40
|
const session = await deps.sessionService.recordRoleHookEvent(context.project.repoRoot, {
|
|
41
41
|
taskSlug: input.taskSlug,
|
|
42
|
-
role:
|
|
42
|
+
role: input.role,
|
|
43
43
|
eventName,
|
|
44
44
|
sessionId: stringOrUndefined(input.event.session_id),
|
|
45
45
|
transcriptPath: stringOrUndefined(input.event.transcript_path),
|
|
@@ -51,14 +51,17 @@ export function createCodexHookService(deps) {
|
|
|
51
51
|
stateRepoRoot: context.taskRepoRoot,
|
|
52
52
|
stateRoot: context.config.stateRoot,
|
|
53
53
|
taskSlug: input.taskSlug,
|
|
54
|
-
role:
|
|
54
|
+
role: input.role,
|
|
55
55
|
eventName
|
|
56
56
|
});
|
|
57
|
+
if (input.role === "codex-translator") {
|
|
58
|
+
await deps.codexTranslationService?.handleCodexHook(context.project.repoRoot, eventName, input.taskSlug);
|
|
59
|
+
}
|
|
57
60
|
return {
|
|
58
61
|
ok: true,
|
|
59
62
|
eventName,
|
|
60
63
|
taskSlug: input.taskSlug,
|
|
61
|
-
role:
|
|
64
|
+
role: input.role,
|
|
62
65
|
sessionUpdated: Boolean(session)
|
|
63
66
|
};
|
|
64
67
|
}
|
|
@@ -77,9 +80,9 @@ function parseHookEvent(value) {
|
|
|
77
80
|
}
|
|
78
81
|
throw new VcmError({
|
|
79
82
|
code: "CODEX_HOOK_EVENT_UNSUPPORTED",
|
|
80
|
-
message: `Unsupported Codex
|
|
83
|
+
message: `Unsupported Codex hook event: ${String(value)}`,
|
|
81
84
|
statusCode: 400,
|
|
82
|
-
hint: "VCM accepts Codex
|
|
85
|
+
hint: "VCM accepts Codex UserPromptSubmit and Stop hooks only."
|
|
83
86
|
});
|
|
84
87
|
}
|
|
85
88
|
function stringOrUndefined(value) {
|