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.
Files changed (30) hide show
  1. package/README.md +20 -7
  2. package/dist/backend/api/codex-hook-routes.js +7 -0
  3. package/dist/backend/api/codex-translation-routes.js +50 -0
  4. package/dist/backend/cli/install-vcm-harness.js +29 -1
  5. package/dist/backend/gateway/gateway-settings-service.js +4 -3
  6. package/dist/backend/server.js +17 -2
  7. package/dist/backend/services/app-settings-service.js +2 -2
  8. package/dist/backend/services/artifact-service.js +2 -1
  9. package/dist/backend/services/codex-hook-service.js +12 -9
  10. package/dist/backend/services/codex-translation-service.js +964 -0
  11. package/dist/backend/services/harness-service.js +28 -1
  12. package/dist/backend/services/session-service.js +42 -21
  13. package/dist/backend/services/translation-service.js +120 -35
  14. package/dist/backend/templates/harness/codex-review.js +68 -3
  15. package/dist/backend/vcm-data-dir.js +7 -0
  16. package/dist/shared/constants.js +15 -1
  17. package/dist-frontend/assets/index-B13y-ZM8.css +32 -0
  18. package/dist-frontend/assets/index-C7Nb1xPJ.js +92 -0
  19. package/dist-frontend/index.html +2 -2
  20. package/docs/codex-review-gates.md +26 -7
  21. package/docs/codex-translation-plan.md +1164 -0
  22. package/docs/full-harness-baseline.md +1 -1
  23. package/docs/gateway-design.md +7 -4
  24. package/docs/product-design.md +10 -9
  25. package/docs/v0.2-implementation-plan.md +3 -3
  26. package/docs/vcm-cc-best-practices.md +3 -3
  27. package/package.json +1 -1
  28. package/dist-frontend/assets/index-CR1EOe-w.css +0 -32
  29. package/dist-frontend/assets/index-D-6FVz_K.js +0 -92
  30. 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
- ~/.vcm/gateway/settings.json
261
- ~/.vcm/gateway/audit.jsonl
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
- ~/.vcm/settings.json
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 `~/.vcm/projects/`; each task runtime repo keeps its own session, message, orchestration, and translation state.
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
- ~/.vcm/projects/<project-id>/config.json
610
- ~/.vcm/projects/<project-id>/tasks/<task>.json
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 `~/.vcm` so it is durable local app state and is not hidden inside a Git-ignored repository directory. For worktree-backed tasks, `taskRepoRoot` is `<baseRepoRoot>/.claude/worktrees/<task>`; for inline tasks, `taskRepoRoot` is the connected base repo.
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 { homedir } from "node:os";
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 settingsPath = deps.settingsPath ?? path.join(homedir(), ".vcm", "gateway", "settings.json");
7
- const auditPath = deps.auditPath ?? path.join(homedir(), ".vcm", "gateway", "audit.jsonl");
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() {
@@ -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(homedir(), ".vcm", "settings.json");
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 !== CODEX_REVIEWER_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 Reviewer hooks.",
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 Reviewer hook event for this endpoint: ${eventName}`,
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: CODEX_REVIEWER_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: CODEX_REVIEWER_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: CODEX_REVIEWER_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 Reviewer hook event: ${String(value)}`,
83
+ message: `Unsupported Codex hook event: ${String(value)}`,
81
84
  statusCode: 400,
82
- hint: "VCM accepts Codex Reviewer UserPromptSubmit and Stop hooks only."
85
+ hint: "VCM accepts Codex UserPromptSubmit and Stop hooks only."
83
86
  });
84
87
  }
85
88
  function stringOrUndefined(value) {