vibe-coding-master 0.7.44 → 0.7.46
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 +27 -27
- package/dist/backend/adapters/claude-adapter.js +2 -2
- package/dist/backend/adapters/codex-bridge-adapter.js +213 -0
- package/dist/backend/api/app-settings-routes.js +6 -6
- package/dist/backend/cli/install-vcm-harness.js +8 -0
- package/dist/backend/server.js +8 -8
- package/dist/backend/services/app-settings-service.js +11 -11
- package/dist/backend/services/{ccr-integration-service.js → codex-bridge-integration-service.js} +74 -72
- package/dist/backend/services/harness-service.js +2 -0
- package/dist/backend/services/session-service.js +16 -16
- package/dist/backend/services/usage-analytics-service.js +3 -3
- package/dist/backend/templates/harness/architect-agent.js +1 -0
- package/dist/backend/templates/harness/coder-agent.js +1 -0
- package/dist/shared/types/session.js +28 -22
- package/dist-frontend/assets/index-DDmygnV6.css +32 -0
- package/dist-frontend/assets/{index-BvCmrFlN.js → index-nAV6toi8.js} +24 -24
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/{ccr-api-key-helper.mjs → codex-bridge-api-key-helper.mjs} +1 -1
- package/scripts/harness-tools/vcm-subagent-guard +61 -0
- package/scripts/verify-package.mjs +1 -0
- package/dist/backend/adapters/ccr-gateway-adapter.js +0 -172
- package/dist-frontend/assets/index-B0d4Z6ny.css +0 -32
package/README.md
CHANGED
|
@@ -243,18 +243,18 @@ Open `Usage Analytics` in the sidebar `Task` section to inspect native Claude
|
|
|
243
243
|
Code usage for the active task. The report shows task totals and breakdowns by
|
|
244
244
|
role and model for input, output, cache-read, and cache-creation tokens plus
|
|
245
245
|
estimated USD cost. It combines every restart and resumed Claude session for
|
|
246
|
-
all seven roles.
|
|
246
|
+
all seven roles. Codex Bridge usage is excluded.
|
|
247
247
|
|
|
248
248
|
VCM retains only aggregate task data in
|
|
249
249
|
`<task-worktree>/.ai/vcm/telemetry/usage.json`. The file is temporary runtime
|
|
250
250
|
state and is removed with the task worktree when the task is closed.
|
|
251
251
|
|
|
252
|
-
### GPT Through
|
|
252
|
+
### GPT Through Codex Bridge
|
|
253
253
|
|
|
254
|
-
VCM can launch its normal Claude Code sessions with
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
VCM can launch its normal Claude Code sessions with models provided by a
|
|
255
|
+
host-running Codex Bridge. The Bridge must already be installed and running,
|
|
256
|
+
and Codex must be logged in on the host. VCM does not manage the Bridge process
|
|
257
|
+
or Codex login.
|
|
258
258
|
|
|
259
259
|
VCM automatically checks the local-host and DevContainer endpoints:
|
|
260
260
|
|
|
@@ -263,36 +263,36 @@ http://127.0.0.1:3456
|
|
|
263
263
|
http://host.docker.internal:3456
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
Configure
|
|
267
|
-
DevContainer,
|
|
268
|
-
`Settings` section:
|
|
266
|
+
Configure Codex Bridge to listen on port `3456`. When VCM runs in a
|
|
267
|
+
DevContainer, start it with a container-reachable bind address such as
|
|
268
|
+
`codex-bridge serve --host 0.0.0.0 --port 3456`. In the VCM `Settings` section:
|
|
269
269
|
|
|
270
|
-
1. enter and save the
|
|
271
|
-
2. enable `
|
|
270
|
+
1. enter and save the Codex Bridge API key;
|
|
271
|
+
2. enable `Codex Bridge models`;
|
|
272
272
|
3. confirm the status is `available`;
|
|
273
|
-
4. select
|
|
273
|
+
4. select any discovered Codex Bridge model in a Session model control.
|
|
274
274
|
|
|
275
275
|
The key is stored in global VCM state (`~/.vcm/settings.json`) with owner-only
|
|
276
|
-
permissions and is never returned by the settings API. It is used for
|
|
277
|
-
checks, model discovery, and the
|
|
276
|
+
permissions and is never returned by the settings API. It is used for Bridge
|
|
277
|
+
checks, model discovery, and the Bridge-only `apiKeyHelper`. Bridge sessions receive a
|
|
278
278
|
child-only `--settings` override and use the isolated Claude configuration root
|
|
279
|
-
`~/.vcm/claude/
|
|
279
|
+
`~/.vcm/claude/codex-bridge`. VCM never edits `~/.claude/settings.json`. Native Claude
|
|
280
280
|
sessions keep their normal configuration and account authentication; VCM only
|
|
281
|
-
removes inherited environment variables that clearly point at the local
|
|
282
|
-
|
|
283
|
-
global Claude Code or Claude App takeover if those clients should remain on
|
|
284
|
-
Anthropic.
|
|
281
|
+
removes inherited environment variables that clearly point at the local Bridge
|
|
282
|
+
from the native child process.
|
|
285
283
|
|
|
286
|
-
|
|
284
|
+
Codex Bridge child processes use a hard context limit and auto-compaction window of
|
|
287
285
|
`258400` tokens. VCM sets Claude Code's proactive compaction threshold to `90%`
|
|
288
|
-
(about `232560` tokens), leaving headroom before the
|
|
289
|
-
Claude sessions receive no VCM-owned context override.
|
|
286
|
+
(about `232560` tokens), leaving headroom before the Codex API limit. Native
|
|
287
|
+
Claude sessions receive no VCM-owned context override. Bridge-backed subagents
|
|
288
|
+
inherit the selected GPT model; native Architect scaffold workers continue to
|
|
289
|
+
use `opus`.
|
|
290
290
|
|
|
291
291
|
Resume keeps the provider recorded by the existing Session. Use Restart when
|
|
292
|
-
switching between
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
not fall back to another model.
|
|
292
|
+
switching between native Claude and Codex Bridge. If the Bridge is disabled,
|
|
293
|
+
unreachable, rejects the key, has unavailable Codex credentials, or no longer
|
|
294
|
+
exposes the selected model, VCM blocks the new Start, Resume, or Restart and
|
|
295
|
+
does not fall back to another model.
|
|
296
296
|
|
|
297
297
|
## Launch Template
|
|
298
298
|
|
|
@@ -482,7 +482,7 @@ Use it to:
|
|
|
482
482
|
appropriate
|
|
483
483
|
|
|
484
484
|
VCM bundles the Claude Code LSP bridge and loads it only for Architect sessions,
|
|
485
|
-
including
|
|
485
|
+
including Codex Bridge launches. The project environment must still
|
|
486
486
|
provide the language server for each detected language: `rust-analyzer`,
|
|
487
487
|
`typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
|
|
488
488
|
`jdtls`. Architect preloads `vcm-code-navigation` and uses LSP for semantic
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { roleRuntimeDisallowedTools, roleUsesLsp } from "../role-tool-policy.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isCodexBridgeSessionModel } from "../../shared/types/session.js";
|
|
3
3
|
import { VcmError } from "../errors.js";
|
|
4
4
|
export function createClaudeAdapter(runner) {
|
|
5
5
|
return {
|
|
@@ -33,7 +33,7 @@ export function createClaudeAdapter(runner) {
|
|
|
33
33
|
if (claudeSessionId) {
|
|
34
34
|
args.push(resume ? "--resume" : "--session-id", claudeSessionId);
|
|
35
35
|
}
|
|
36
|
-
if (!
|
|
36
|
+
if (!isCodexBridgeSessionModel(model)) {
|
|
37
37
|
args.push("--model", model);
|
|
38
38
|
}
|
|
39
39
|
if (effort === "ultracode") {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { CODEX_BRIDGE_BASE_URLS } from "../../shared/types/session.js";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 3_000;
|
|
3
|
+
export function createCodexBridgeAdapter(deps = {}) {
|
|
4
|
+
const baseUrls = (deps.baseUrl ? [deps.baseUrl] : CODEX_BRIDGE_BASE_URLS)
|
|
5
|
+
.map((baseUrl) => baseUrl.replace(/\/+$/, ""));
|
|
6
|
+
const fetchImpl = deps.fetch ?? globalThis.fetch;
|
|
7
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
8
|
+
return {
|
|
9
|
+
async probe(apiKey) {
|
|
10
|
+
const failures = [];
|
|
11
|
+
for (const baseUrl of baseUrls) {
|
|
12
|
+
const result = await probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs);
|
|
13
|
+
if (result.baseUrl) {
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
failures.push(result);
|
|
17
|
+
}
|
|
18
|
+
return combineProbeFailures(baseUrls, failures);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs) {
|
|
23
|
+
try {
|
|
24
|
+
const health = await requestJson(fetchImpl, `${baseUrl}/health`, timeoutMs);
|
|
25
|
+
if (!health.response.ok) {
|
|
26
|
+
return invalidResponse(`Codex Bridge health check at ${baseUrl} returned HTTP ${health.response.status}.`);
|
|
27
|
+
}
|
|
28
|
+
if (!isCodexBridgeHealth(health.payload)) {
|
|
29
|
+
return {
|
|
30
|
+
connectionState: "not-codex-bridge",
|
|
31
|
+
modelAvailable: false,
|
|
32
|
+
models: [],
|
|
33
|
+
error: `${baseUrl} did not identify as Codex Bridge.`
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const headers = {
|
|
37
|
+
authorization: `Bearer ${apiKey}`,
|
|
38
|
+
"user-agent": "VibeCodingMaster"
|
|
39
|
+
};
|
|
40
|
+
const auth = await requestJson(fetchImpl, `${baseUrl}/auth/status`, timeoutMs, headers);
|
|
41
|
+
if (auth.response.status === 401 || auth.response.status === 403) {
|
|
42
|
+
return {
|
|
43
|
+
connectionState: "unauthorized",
|
|
44
|
+
modelAvailable: false,
|
|
45
|
+
models: [],
|
|
46
|
+
baseUrl,
|
|
47
|
+
error: "Codex Bridge rejected the configured API key."
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (!auth.response.ok) {
|
|
51
|
+
return invalidResponse(responseError("Codex Bridge auth status", baseUrl, auth.response, auth.payload), baseUrl);
|
|
52
|
+
}
|
|
53
|
+
const authState = readAuthState(auth.payload);
|
|
54
|
+
if (!authState) {
|
|
55
|
+
return invalidResponse("Codex Bridge returned an invalid /auth/status response.", baseUrl);
|
|
56
|
+
}
|
|
57
|
+
if (authState.state !== "ready") {
|
|
58
|
+
return {
|
|
59
|
+
connectionState: "codex-auth-unavailable",
|
|
60
|
+
modelAvailable: false,
|
|
61
|
+
models: [],
|
|
62
|
+
baseUrl,
|
|
63
|
+
error: authState.message
|
|
64
|
+
?? `Codex authentication is ${authState.state}. Open Codex or run codex login, then retry.`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const modelsResponse = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, headers);
|
|
68
|
+
if (!modelsResponse.response.ok) {
|
|
69
|
+
const code = readErrorCode(modelsResponse.payload);
|
|
70
|
+
const connectionState = code?.startsWith("CODEX_AUTH_")
|
|
71
|
+
? "codex-auth-unavailable"
|
|
72
|
+
: modelsResponse.response.status === 401 || modelsResponse.response.status === 403
|
|
73
|
+
? "unauthorized"
|
|
74
|
+
: "invalid-response";
|
|
75
|
+
return {
|
|
76
|
+
connectionState,
|
|
77
|
+
modelAvailable: false,
|
|
78
|
+
models: [],
|
|
79
|
+
baseUrl,
|
|
80
|
+
error: responseError("Codex Bridge model discovery", baseUrl, modelsResponse.response, modelsResponse.payload)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const models = readModelDescriptors(modelsResponse.payload);
|
|
84
|
+
if (!models) {
|
|
85
|
+
return invalidResponse("Codex Bridge returned an invalid /v1/models response.", baseUrl);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
connectionState: "available",
|
|
89
|
+
modelAvailable: models.length > 0,
|
|
90
|
+
models,
|
|
91
|
+
baseUrl,
|
|
92
|
+
...(models.length > 0 ? {} : { error: "Codex Bridge did not expose any models." })
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const timedOut = isAbortError(error);
|
|
97
|
+
return {
|
|
98
|
+
connectionState: "unreachable",
|
|
99
|
+
modelAvailable: false,
|
|
100
|
+
models: [],
|
|
101
|
+
error: timedOut
|
|
102
|
+
? `Codex Bridge connection timed out after ${timeoutMs} ms at ${baseUrl}.`
|
|
103
|
+
: `Codex Bridge could not be reached from the VCM backend at ${baseUrl}. Reason: ${errorMessage(error)}`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function combineProbeFailures(baseUrls, failures) {
|
|
108
|
+
const lastFailure = failures.at(-1);
|
|
109
|
+
return {
|
|
110
|
+
connectionState: lastFailure?.connectionState ?? "unreachable",
|
|
111
|
+
modelAvailable: false,
|
|
112
|
+
models: [],
|
|
113
|
+
error: `Codex Bridge was not found at ${baseUrls.join(" or ")}. ${failures
|
|
114
|
+
.map((failure) => failure.error)
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join(" ")}`.trim()
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function requestJson(fetchImpl, url, timeoutMs, headers = {}) {
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetchImpl(url, {
|
|
124
|
+
method: "GET",
|
|
125
|
+
headers: {
|
|
126
|
+
accept: "application/json",
|
|
127
|
+
...headers
|
|
128
|
+
},
|
|
129
|
+
signal: controller.signal
|
|
130
|
+
});
|
|
131
|
+
let payload;
|
|
132
|
+
try {
|
|
133
|
+
payload = await response.json();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
payload = undefined;
|
|
137
|
+
}
|
|
138
|
+
return { response, payload };
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function isCodexBridgeHealth(value) {
|
|
145
|
+
return isObject(value)
|
|
146
|
+
&& value.status === "ok"
|
|
147
|
+
&& value.service === "codex-bridge";
|
|
148
|
+
}
|
|
149
|
+
function readAuthState(value) {
|
|
150
|
+
if (!isObject(value) || typeof value.state !== "string" || !value.state.trim()) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
state: value.state.trim(),
|
|
155
|
+
...(typeof value.message === "string" && value.message.trim()
|
|
156
|
+
? { message: value.message.trim() }
|
|
157
|
+
: {})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function readModelDescriptors(value) {
|
|
161
|
+
if (!isObject(value) || !Array.isArray(value.data)) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const models = [];
|
|
165
|
+
for (const item of value.data) {
|
|
166
|
+
if (!isObject(item) || typeof item.id !== "string" || !item.id.trim()) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
models.push({
|
|
170
|
+
id: item.id.trim(),
|
|
171
|
+
...(typeof item.display_name === "string" && item.display_name.trim()
|
|
172
|
+
? { displayName: item.display_name.trim() }
|
|
173
|
+
: {})
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return models;
|
|
177
|
+
}
|
|
178
|
+
function responseError(operation, baseUrl, response, payload) {
|
|
179
|
+
const message = readErrorMessage(payload);
|
|
180
|
+
return `${operation} at ${baseUrl} returned HTTP ${response.status}.${message ? ` Reason: ${message}` : ""}`;
|
|
181
|
+
}
|
|
182
|
+
function readErrorCode(value) {
|
|
183
|
+
return isObject(value)
|
|
184
|
+
&& isObject(value.error)
|
|
185
|
+
&& typeof value.error.code === "string"
|
|
186
|
+
? value.error.code
|
|
187
|
+
: undefined;
|
|
188
|
+
}
|
|
189
|
+
function readErrorMessage(value) {
|
|
190
|
+
return isObject(value)
|
|
191
|
+
&& isObject(value.error)
|
|
192
|
+
&& typeof value.error.message === "string"
|
|
193
|
+
? value.error.message
|
|
194
|
+
: undefined;
|
|
195
|
+
}
|
|
196
|
+
function invalidResponse(error, baseUrl) {
|
|
197
|
+
return {
|
|
198
|
+
connectionState: "invalid-response",
|
|
199
|
+
modelAvailable: false,
|
|
200
|
+
models: [],
|
|
201
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
202
|
+
error
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function isAbortError(error) {
|
|
206
|
+
return error instanceof Error && error.name === "AbortError";
|
|
207
|
+
}
|
|
208
|
+
function errorMessage(error) {
|
|
209
|
+
return error instanceof Error ? error.message : String(error);
|
|
210
|
+
}
|
|
211
|
+
function isObject(value) {
|
|
212
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
213
|
+
}
|
|
@@ -5,13 +5,13 @@ export function registerAppSettingsRoutes(app, deps) {
|
|
|
5
5
|
app.put("/api/settings/preferences", async (request) => {
|
|
6
6
|
return deps.appSettings.updatePreferences(request.body ?? {});
|
|
7
7
|
});
|
|
8
|
-
app.get("/api/settings/
|
|
9
|
-
return deps.
|
|
8
|
+
app.get("/api/settings/codex-bridge", async () => {
|
|
9
|
+
return deps.codexBridgeIntegration.getStatus();
|
|
10
10
|
});
|
|
11
|
-
app.put("/api/settings/
|
|
12
|
-
return deps.
|
|
11
|
+
app.put("/api/settings/codex-bridge", async (request) => {
|
|
12
|
+
return deps.codexBridgeIntegration.updateSettings(request.body ?? {});
|
|
13
13
|
});
|
|
14
|
-
app.post("/api/settings/
|
|
15
|
-
return deps.
|
|
14
|
+
app.post("/api/settings/codex-bridge/check", async () => {
|
|
15
|
+
return deps.codexBridgeIntegration.checkConnection();
|
|
16
16
|
});
|
|
17
17
|
}
|
|
@@ -53,11 +53,13 @@ const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM
|
|
|
53
53
|
const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,event}));});'"'"' | curl -fsS --retry 2 --retry-delay 1 --retry-all-errors --connect-timeout 1 --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
|
|
54
54
|
const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,runtimeSessionToken:process.env.VCM_RUNTIME_SESSION_TOKEN,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
|
|
55
55
|
const VCM_BASH_GUARD_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ]; then exit 0; fi; guard=""; repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"; if [ -n "$repo" ] && [ -f "$repo/.ai/tools/vcm-bash-guard" ]; then guard="$repo/.ai/tools/vcm-bash-guard"; else cwd="$(pwd -P 2>/dev/null || pwd)"; dir="$cwd"; while [ -n "$dir" ] && [ "$dir" != "/" ]; do if [ -f "$dir/.ai/tools/vcm-bash-guard" ]; then guard="$dir/.ai/tools/vcm-bash-guard"; break; fi; dir="$(dirname "$dir")"; done; if [ -z "$guard" ] && [ -n "\${CLAUDE_PROJECT_DIR:-}" ] && [ -f "\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard" ]; then guard="\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-bash-guard"; fi; fi; [ -n "$guard" ] || exit 0; python3 "$guard" || exit 0'`;
|
|
56
|
+
const VCM_SUBAGENT_GUARD_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ]; then exit 0; fi; guard=""; repo="$(git rev-parse --show-toplevel 2>/dev/null || true)"; if [ -n "$repo" ] && [ -f "$repo/.ai/tools/vcm-subagent-guard" ]; then guard="$repo/.ai/tools/vcm-subagent-guard"; else cwd="$(pwd -P 2>/dev/null || pwd)"; dir="$cwd"; while [ -n "$dir" ] && [ "$dir" != "/" ]; do if [ -f "$dir/.ai/tools/vcm-subagent-guard" ]; then guard="$dir/.ai/tools/vcm-subagent-guard"; break; fi; dir="$(dirname "$dir")"; done; if [ -z "$guard" ] && [ -n "\${CLAUDE_PROJECT_DIR:-}" ] && [ -f "\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-subagent-guard" ]; then guard="\${CLAUDE_PROJECT_DIR}/.ai/tools/vcm-subagent-guard"; fi; fi; [ -n "$guard" ] || { printf "%s\\n" "{\\"hookSpecificOutput\\":{\\"hookEventName\\":\\"PreToolUse\\",\\"permissionDecision\\":\\"deny\\",\\"permissionDecisionReason\\":\\"VCM subagent policy is unavailable. Refresh the VCM Harness before invoking subagents.\\"}}"; exit 0; }; python3 "$guard"'`;
|
|
56
57
|
const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
57
58
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
58
59
|
const VCM_HOOK_DEFINITIONS = [
|
|
59
60
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
60
61
|
{ eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
62
|
+
{ eventName: "PreToolUse", matcher: "Agent", command: VCM_SUBAGENT_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
61
63
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
62
64
|
{ eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
63
65
|
{ eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
@@ -416,6 +418,12 @@ const WHOLE_FILES = [
|
|
|
416
418
|
mode: 0o755,
|
|
417
419
|
templatePath: "scripts/harness-tools/vcm-bash-guard"
|
|
418
420
|
},
|
|
421
|
+
{
|
|
422
|
+
path: ".ai/tools/vcm-subagent-guard",
|
|
423
|
+
category: "runtime-tool",
|
|
424
|
+
mode: 0o755,
|
|
425
|
+
templatePath: "scripts/harness-tools/vcm-subagent-guard"
|
|
426
|
+
},
|
|
419
427
|
{
|
|
420
428
|
path: ".ai/tools/vcm-artifact",
|
|
421
429
|
category: "runtime-tool",
|
package/dist/backend/server.js
CHANGED
|
@@ -4,13 +4,13 @@ import Fastify from "fastify";
|
|
|
4
4
|
import fastifyStatic from "@fastify/static";
|
|
5
5
|
import { createArtifactService } from "./services/artifact-service.js";
|
|
6
6
|
import { createClaudeAdapter } from "./adapters/claude-adapter.js";
|
|
7
|
-
import {
|
|
7
|
+
import { createCodexBridgeAdapter } from "./adapters/codex-bridge-adapter.js";
|
|
8
8
|
import { createCommandRunner } from "./adapters/command-runner.js";
|
|
9
9
|
import { createCommandDispatcher } from "./services/command-dispatcher.js";
|
|
10
10
|
import { createClaudeHookService } from "./services/claude-hook-service.js";
|
|
11
11
|
import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
12
12
|
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
13
|
-
import {
|
|
13
|
+
import { createCodexBridgeIntegrationService } from "./services/codex-bridge-integration-service.js";
|
|
14
14
|
import { createAutoMemoryService } from "./services/auto-memory-service.js";
|
|
15
15
|
import { createArchitectRestartService } from "./services/architect-restart-service.js";
|
|
16
16
|
import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
|
|
@@ -88,7 +88,7 @@ export async function createServer(deps, options = {}) {
|
|
|
88
88
|
registerDiagnosticsRoutes(app, { diagnosticsService: deps.diagnosticsService });
|
|
89
89
|
registerAppSettingsRoutes(app, {
|
|
90
90
|
appSettings: deps.appSettings,
|
|
91
|
-
|
|
91
|
+
codexBridgeIntegration: deps.codexBridgeIntegration
|
|
92
92
|
});
|
|
93
93
|
registerClaudeHookRoutes(app, { claudeHookService: deps.claudeHookService });
|
|
94
94
|
registerGateReviewRoutes(app, {
|
|
@@ -186,7 +186,7 @@ export async function createServer(deps, options = {}) {
|
|
|
186
186
|
onManualInterrupt: (sessionId) => deps.terminalInterruptService.handleManualInterrupt(sessionId)
|
|
187
187
|
});
|
|
188
188
|
app.addHook("onReady", async () => {
|
|
189
|
-
await deps.
|
|
189
|
+
await deps.codexBridgeIntegration.initialize();
|
|
190
190
|
await cleanupRecentTranslationRuntime(deps);
|
|
191
191
|
deps.terminalProcessExitService.start();
|
|
192
192
|
deps.runtimeCoordinator.start();
|
|
@@ -235,9 +235,9 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
235
235
|
const git = createGitAdapter(runner);
|
|
236
236
|
const claude = createClaudeAdapter(runner);
|
|
237
237
|
const appSettings = createAppSettingsService({ fs });
|
|
238
|
-
const
|
|
238
|
+
const codexBridgeIntegration = createCodexBridgeIntegrationService({
|
|
239
239
|
settings: appSettings,
|
|
240
|
-
|
|
240
|
+
bridge: createCodexBridgeAdapter()
|
|
241
241
|
});
|
|
242
242
|
const runtime = createNodePtyTerminalRuntime({ fs });
|
|
243
243
|
const registry = createSessionRegistry();
|
|
@@ -255,7 +255,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
255
255
|
projectService,
|
|
256
256
|
taskService,
|
|
257
257
|
taskWorkflowService,
|
|
258
|
-
|
|
258
|
+
codexBridgeIntegration,
|
|
259
259
|
apiUrl: options.apiUrl
|
|
260
260
|
});
|
|
261
261
|
const harnessService = createHarnessService({
|
|
@@ -458,7 +458,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
458
458
|
});
|
|
459
459
|
return {
|
|
460
460
|
appSettings,
|
|
461
|
-
|
|
461
|
+
codexBridgeIntegration,
|
|
462
462
|
projectService,
|
|
463
463
|
taskService,
|
|
464
464
|
taskCloseService,
|
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { VCM_ROLE_NAMES } from "../../shared/constants.js";
|
|
4
4
|
import { GATE_REVIEW_GATES } from "../../shared/types/gate-review.js";
|
|
5
5
|
import { createDefaultLaunchTemplate, createDefaultToolSessionDefaults, DEFAULT_TRANSLATION_OUTPUT_MODE, DEFAULT_TRANSLATION_TARGET_LANGUAGE, TRANSLATION_OUTPUT_MODE_OPTIONS, TRANSLATION_TARGET_LANGUAGE_OPTIONS } from "../../shared/types/app-settings.js";
|
|
6
|
-
import { CLAUDE_MODEL_OPTIONS,
|
|
6
|
+
import { CLAUDE_MODEL_OPTIONS, isCodexBridgeSessionModel, SESSION_EFFORT_OPTIONS } from "../../shared/types/session.js";
|
|
7
7
|
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
8
8
|
const MAX_RECENT_REPOSITORIES = 5;
|
|
9
9
|
export function createAppSettingsService(deps) {
|
|
@@ -161,20 +161,20 @@ export function createAppSettingsService(deps) {
|
|
|
161
161
|
requiredGates: normalizedRequiredGates
|
|
162
162
|
};
|
|
163
163
|
},
|
|
164
|
-
async
|
|
165
|
-
return
|
|
164
|
+
async getCodexBridgeIntegrationSettings() {
|
|
165
|
+
return normalizeCodexBridgeIntegrationSettings((await loadSettings()).codexBridge);
|
|
166
166
|
},
|
|
167
|
-
async
|
|
167
|
+
async updateCodexBridgeIntegrationSettings(input) {
|
|
168
168
|
const current = await loadSettings();
|
|
169
|
-
const
|
|
170
|
-
...current.
|
|
169
|
+
const codexBridge = normalizeCodexBridgeIntegrationSettings({
|
|
170
|
+
...current.codexBridge,
|
|
171
171
|
...input
|
|
172
172
|
});
|
|
173
173
|
await saveSettings({
|
|
174
174
|
...current,
|
|
175
|
-
|
|
175
|
+
codexBridge
|
|
176
176
|
});
|
|
177
|
-
return
|
|
177
|
+
return codexBridge;
|
|
178
178
|
},
|
|
179
179
|
getSettingsPath() {
|
|
180
180
|
return settingsPath;
|
|
@@ -261,10 +261,10 @@ function normalizeSettingsFile(input) {
|
|
|
261
261
|
if (gateReview) {
|
|
262
262
|
settings.gateReview = gateReview;
|
|
263
263
|
}
|
|
264
|
-
settings.
|
|
264
|
+
settings.codexBridge = normalizeCodexBridgeIntegrationSettings(input.codexBridge);
|
|
265
265
|
return settings;
|
|
266
266
|
}
|
|
267
|
-
function
|
|
267
|
+
function normalizeCodexBridgeIntegrationSettings(input) {
|
|
268
268
|
const candidate = isObject(input) ? input : {};
|
|
269
269
|
const apiKey = typeof candidate.apiKey === "string" ? candidate.apiKey.trim() : "";
|
|
270
270
|
return {
|
|
@@ -355,7 +355,7 @@ function normalizeClaudeModel(input, fallback) {
|
|
|
355
355
|
if (typeof input !== "string") {
|
|
356
356
|
return fallback;
|
|
357
357
|
}
|
|
358
|
-
if (input
|
|
358
|
+
if (isCodexBridgeSessionModel(input)) {
|
|
359
359
|
return input;
|
|
360
360
|
}
|
|
361
361
|
const model = CLAUDE_MODEL_OPTIONS.find((option) => option.value === input);
|