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/dist/backend/services/{ccr-integration-service.js → codex-bridge-integration-service.js}
RENAMED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { CCR_GATEWAY_BASE_URL, CCR_GPT_AUTO_COMPACT_PERCENT, CCR_GPT_AUTO_COMPACT_WINDOW_TOKENS, CCR_GPT_EFFECTIVE_CONTEXT_TOKENS, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { CODEX_BRIDGE_AUTO_COMPACT_PERCENT, CODEX_BRIDGE_AUTO_COMPACT_WINDOW_TOKENS, CODEX_BRIDGE_BASE_URL, CODEX_BRIDGE_EFFECTIVE_CONTEXT_TOKENS, createSessionModelOptions, getCodexBridgeModelId, isCodexBridgeSessionModel } from "../../shared/types/session.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
5
|
import { resolveVcmDataDir } from "../vcm-data-dir.js";
|
|
6
6
|
const DEFAULT_CACHE_TTL_MS = 10_000;
|
|
7
|
-
const DEFAULT_API_KEY_HELPER_PATH = fileURLToPath(new URL("../../../scripts/
|
|
8
|
-
export function
|
|
7
|
+
const DEFAULT_API_KEY_HELPER_PATH = fileURLToPath(new URL("../../../scripts/codex-bridge-api-key-helper.mjs", import.meta.url));
|
|
8
|
+
export function createCodexBridgeIntegrationService(deps) {
|
|
9
9
|
const now = deps.now ?? (() => new Date());
|
|
10
10
|
const cacheTtlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
11
11
|
const baseEnv = deps.baseEnv ?? process.env;
|
|
12
|
-
const configDir = deps.configDir ?? path.join(resolveVcmDataDir(baseEnv), "claude", "
|
|
12
|
+
const configDir = deps.configDir ?? path.join(resolveVcmDataDir(baseEnv), "claude", "codex-bridge");
|
|
13
13
|
let cachedProbe;
|
|
14
14
|
let inFlight;
|
|
15
15
|
async function probe(force = false) {
|
|
16
|
-
const settings = await deps.settings.
|
|
16
|
+
const settings = await deps.settings.getCodexBridgeIntegrationSettings();
|
|
17
17
|
if (!settings.apiKey) {
|
|
18
18
|
throw missingApiKeyError();
|
|
19
19
|
}
|
|
@@ -24,7 +24,7 @@ export function createCcrIntegrationService(deps) {
|
|
|
24
24
|
if (inFlight) {
|
|
25
25
|
return inFlight;
|
|
26
26
|
}
|
|
27
|
-
inFlight = deps.
|
|
27
|
+
inFlight = deps.bridge.probe(settings.apiKey).then((result) => {
|
|
28
28
|
const checkedAtDate = now();
|
|
29
29
|
cachedProbe = {
|
|
30
30
|
result,
|
|
@@ -38,14 +38,15 @@ export function createCcrIntegrationService(deps) {
|
|
|
38
38
|
return inFlight;
|
|
39
39
|
}
|
|
40
40
|
async function buildStatus(options = {}) {
|
|
41
|
-
const settings = await deps.settings.
|
|
41
|
+
const settings = await deps.settings.getCodexBridgeIntegrationSettings();
|
|
42
42
|
if (!settings.enabled) {
|
|
43
43
|
return createStatus({
|
|
44
44
|
enabled: false,
|
|
45
45
|
apiKeyConfigured: Boolean(settings.apiKey),
|
|
46
46
|
connectionState: "disabled",
|
|
47
47
|
modelAvailable: false,
|
|
48
|
-
|
|
48
|
+
models: [],
|
|
49
|
+
error: settings.apiKey ? undefined : "Save a Codex Bridge API key before enabling Codex models."
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
if (!cachedProbe && options.refreshIfMissing) {
|
|
@@ -56,7 +57,8 @@ export function createCcrIntegrationService(deps) {
|
|
|
56
57
|
enabled: true,
|
|
57
58
|
apiKeyConfigured: true,
|
|
58
59
|
connectionState: "checking",
|
|
59
|
-
modelAvailable: false
|
|
60
|
+
modelAvailable: false,
|
|
61
|
+
models: []
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
return createStatus({
|
|
@@ -64,13 +66,14 @@ export function createCcrIntegrationService(deps) {
|
|
|
64
66
|
apiKeyConfigured: true,
|
|
65
67
|
connectionState: cachedProbe.result.connectionState,
|
|
66
68
|
modelAvailable: cachedProbe.result.modelAvailable,
|
|
69
|
+
models: cachedProbe.result.models,
|
|
67
70
|
checkedAt: cachedProbe.checkedAt,
|
|
68
71
|
error: cachedProbe.result.error
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
74
|
return {
|
|
72
75
|
async initialize() {
|
|
73
|
-
const settings = await deps.settings.
|
|
76
|
+
const settings = await deps.settings.getCodexBridgeIntegrationSettings();
|
|
74
77
|
if (settings.enabled) {
|
|
75
78
|
await probe(true);
|
|
76
79
|
}
|
|
@@ -80,15 +83,15 @@ export function createCcrIntegrationService(deps) {
|
|
|
80
83
|
},
|
|
81
84
|
async updateSettings(input) {
|
|
82
85
|
if (input.apiKey !== undefined && typeof input.apiKey !== "string") {
|
|
83
|
-
throw invalidSettingsError("
|
|
86
|
+
throw invalidSettingsError("Codex Bridge API key must be a string.");
|
|
84
87
|
}
|
|
85
88
|
if (input.enabled !== undefined && typeof input.enabled !== "boolean") {
|
|
86
|
-
throw invalidSettingsError("
|
|
89
|
+
throw invalidSettingsError("Codex Bridge enabled state must be a boolean.");
|
|
87
90
|
}
|
|
88
91
|
if (input.clearApiKey !== undefined && typeof input.clearApiKey !== "boolean") {
|
|
89
|
-
throw invalidSettingsError("
|
|
92
|
+
throw invalidSettingsError("Codex Bridge clear-key state must be a boolean.");
|
|
90
93
|
}
|
|
91
|
-
const current = await deps.settings.
|
|
94
|
+
const current = await deps.settings.getCodexBridgeIntegrationSettings();
|
|
92
95
|
const clearApiKey = input.clearApiKey === true;
|
|
93
96
|
const nextApiKey = clearApiKey
|
|
94
97
|
? ""
|
|
@@ -99,7 +102,7 @@ export function createCcrIntegrationService(deps) {
|
|
|
99
102
|
if (nextEnabled && !nextApiKey) {
|
|
100
103
|
throw missingApiKeyError();
|
|
101
104
|
}
|
|
102
|
-
await deps.settings.
|
|
105
|
+
await deps.settings.updateCodexBridgeIntegrationSettings({
|
|
103
106
|
enabled: nextEnabled,
|
|
104
107
|
apiKey: nextApiKey
|
|
105
108
|
});
|
|
@@ -114,64 +117,66 @@ export function createCcrIntegrationService(deps) {
|
|
|
114
117
|
return buildStatus();
|
|
115
118
|
},
|
|
116
119
|
async checkConnection() {
|
|
117
|
-
const settings = await deps.settings.
|
|
120
|
+
const settings = await deps.settings.getCodexBridgeIntegrationSettings();
|
|
118
121
|
if (!settings.enabled) {
|
|
119
122
|
throw new VcmError({
|
|
120
|
-
code: "
|
|
121
|
-
message: "
|
|
123
|
+
code: "CODEX_BRIDGE_DISABLED",
|
|
124
|
+
message: "Codex Bridge models are disabled.",
|
|
122
125
|
statusCode: 409,
|
|
123
|
-
hint: "Save the
|
|
126
|
+
hint: "Save the Codex Bridge API key and enable Codex models before checking the connection."
|
|
124
127
|
});
|
|
125
128
|
}
|
|
126
129
|
await probe(true);
|
|
127
130
|
return buildStatus();
|
|
128
131
|
},
|
|
129
132
|
async getLaunchEnvironment(model) {
|
|
130
|
-
if (!
|
|
133
|
+
if (!isCodexBridgeSessionModel(model)) {
|
|
131
134
|
return buildNativeLaunchEnvironment(baseEnv);
|
|
132
135
|
}
|
|
133
|
-
const settings = await deps.settings.
|
|
136
|
+
const settings = await deps.settings.getCodexBridgeIntegrationSettings();
|
|
134
137
|
if (!settings.enabled) {
|
|
135
138
|
throw new VcmError({
|
|
136
|
-
code: "
|
|
137
|
-
message: "
|
|
139
|
+
code: "CODEX_BRIDGE_DISABLED",
|
|
140
|
+
message: "Codex Bridge models are disabled.",
|
|
138
141
|
statusCode: 409,
|
|
139
|
-
hint: "Enable
|
|
142
|
+
hint: "Enable Codex Bridge in VCM Settings before starting this session."
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
145
|
const checked = await probe();
|
|
143
|
-
|
|
146
|
+
const modelId = getCodexBridgeModelId(model);
|
|
147
|
+
const modelAvailable = checked.result.models.some((available) => available.id === modelId);
|
|
148
|
+
if (checked.result.connectionState !== "available" || !modelAvailable) {
|
|
144
149
|
throw new VcmError({
|
|
145
|
-
code: "
|
|
146
|
-
message: checked.result.error ?? `
|
|
150
|
+
code: "CODEX_BRIDGE_MODEL_UNAVAILABLE",
|
|
151
|
+
message: checked.result.error ?? `Codex Bridge model ${modelId} is unavailable.`,
|
|
147
152
|
statusCode: 409,
|
|
148
|
-
hint: "Check
|
|
153
|
+
hint: "Check Codex Bridge on port 3456, refresh its Codex login, and verify the selected model."
|
|
149
154
|
});
|
|
150
155
|
}
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
const noProxy = mergeNoProxy(deps.baseEnv?.NO_PROXY ?? deps.baseEnv?.no_proxy ?? process.env.NO_PROXY ?? process.env.no_proxy,
|
|
156
|
+
const bridgeBaseUrl = checked.result.baseUrl ?? CODEX_BRIDGE_BASE_URL;
|
|
157
|
+
const bridgeHost = new URL(bridgeBaseUrl).hostname;
|
|
158
|
+
const noProxy = mergeNoProxy(deps.baseEnv?.NO_PROXY ?? deps.baseEnv?.no_proxy ?? process.env.NO_PROXY ?? process.env.no_proxy, bridgeHost);
|
|
154
159
|
return {
|
|
155
|
-
ANTHROPIC_BASE_URL:
|
|
156
|
-
ANTHROPIC_API_BASE_URL:
|
|
157
|
-
CLAUDE_AGENT_API_BASE_URL:
|
|
160
|
+
ANTHROPIC_BASE_URL: bridgeBaseUrl,
|
|
161
|
+
ANTHROPIC_API_BASE_URL: bridgeBaseUrl,
|
|
162
|
+
CLAUDE_AGENT_API_BASE_URL: bridgeBaseUrl,
|
|
158
163
|
ANTHROPIC_AUTH_TOKEN: undefined,
|
|
159
164
|
ANTHROPIC_API_KEY: undefined,
|
|
160
|
-
ANTHROPIC_MODEL:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
ANTHROPIC_SMALL_FAST_MODEL: CCR_GPT_MODEL_ID,
|
|
165
|
+
ANTHROPIC_MODEL: modelId,
|
|
166
|
+
CODEX_BRIDGE_CLAUDE_MODEL: modelId,
|
|
167
|
+
ANTHROPIC_SMALL_FAST_MODEL: modelId,
|
|
164
168
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1",
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
CLAUDE_CODE_SUBAGENT_MODEL: modelId,
|
|
170
|
+
CLAUDE_CODE_MAX_CONTEXT_TOKENS: String(CODEX_BRIDGE_EFFECTIVE_CONTEXT_TOKENS),
|
|
171
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(CODEX_BRIDGE_AUTO_COMPACT_WINDOW_TOKENS),
|
|
172
|
+
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: String(CODEX_BRIDGE_AUTO_COMPACT_PERCENT),
|
|
168
173
|
CLAUDE_CONFIG_DIR: configDir,
|
|
169
174
|
NO_PROXY: noProxy,
|
|
170
175
|
no_proxy: noProxy
|
|
171
176
|
};
|
|
172
177
|
},
|
|
173
178
|
async getLaunchSettingsOverride(model) {
|
|
174
|
-
if (!
|
|
179
|
+
if (!isCodexBridgeSessionModel(model)) {
|
|
175
180
|
return undefined;
|
|
176
181
|
}
|
|
177
182
|
return {
|
|
@@ -180,51 +185,51 @@ export function createCcrIntegrationService(deps) {
|
|
|
180
185
|
}
|
|
181
186
|
};
|
|
182
187
|
}
|
|
183
|
-
const
|
|
188
|
+
const BRIDGE_BASE_URL_KEYS = [
|
|
184
189
|
"ANTHROPIC_BASE_URL",
|
|
185
190
|
"ANTHROPIC_API_BASE_URL",
|
|
186
191
|
"CLAUDE_AGENT_API_BASE_URL"
|
|
187
192
|
];
|
|
188
|
-
const
|
|
193
|
+
const BRIDGE_MODEL_KEYS = [
|
|
189
194
|
"ANTHROPIC_MODEL",
|
|
190
|
-
"ANTHROPIC_SMALL_FAST_MODEL"
|
|
195
|
+
"ANTHROPIC_SMALL_FAST_MODEL",
|
|
196
|
+
"CLAUDE_CODE_SUBAGENT_MODEL"
|
|
191
197
|
];
|
|
192
|
-
const
|
|
193
|
-
"
|
|
194
|
-
"CODEXL_CLAUDE_CODE_MODEL",
|
|
198
|
+
const BRIDGE_ONLY_ENV_KEYS = [
|
|
199
|
+
"CODEX_BRIDGE_CLAUDE_MODEL",
|
|
195
200
|
"CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"
|
|
196
201
|
];
|
|
197
202
|
export function buildNativeLaunchEnvironment(baseEnv) {
|
|
198
203
|
const environment = {};
|
|
199
|
-
let
|
|
204
|
+
let inheritedBridgeTakeover = false;
|
|
200
205
|
if (baseEnv.CLAUDE_CONFIG_DIR?.trim()) {
|
|
201
206
|
environment.CLAUDE_CONFIG_DIR = baseEnv.CLAUDE_CONFIG_DIR.trim();
|
|
202
207
|
}
|
|
203
|
-
for (const key of
|
|
204
|
-
if (
|
|
208
|
+
for (const key of BRIDGE_BASE_URL_KEYS) {
|
|
209
|
+
if (isCodexBridgeUrl(baseEnv[key])) {
|
|
205
210
|
environment[key] = undefined;
|
|
206
|
-
|
|
211
|
+
inheritedBridgeTakeover = true;
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
|
-
for (const key of
|
|
210
|
-
if (
|
|
214
|
+
for (const key of BRIDGE_MODEL_KEYS) {
|
|
215
|
+
if (isCodexBridgeModelName(baseEnv[key], baseEnv.CODEX_BRIDGE_CLAUDE_MODEL)) {
|
|
211
216
|
environment[key] = undefined;
|
|
212
|
-
|
|
217
|
+
inheritedBridgeTakeover = true;
|
|
213
218
|
}
|
|
214
219
|
}
|
|
215
|
-
for (const key of
|
|
220
|
+
for (const key of BRIDGE_ONLY_ENV_KEYS) {
|
|
216
221
|
if (baseEnv[key] !== undefined) {
|
|
217
222
|
environment[key] = undefined;
|
|
218
|
-
|
|
223
|
+
inheritedBridgeTakeover = true;
|
|
219
224
|
}
|
|
220
225
|
}
|
|
221
|
-
if (
|
|
226
|
+
if (inheritedBridgeTakeover) {
|
|
222
227
|
environment.ANTHROPIC_AUTH_TOKEN = undefined;
|
|
223
228
|
environment.ANTHROPIC_API_KEY = undefined;
|
|
224
229
|
}
|
|
225
230
|
return environment;
|
|
226
231
|
}
|
|
227
|
-
function
|
|
232
|
+
function isCodexBridgeUrl(value) {
|
|
228
233
|
if (!value) {
|
|
229
234
|
return false;
|
|
230
235
|
}
|
|
@@ -237,32 +242,29 @@ function isCcrGatewayUrl(value) {
|
|
|
237
242
|
return false;
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
return value === CCR_GPT_MODEL_ID
|
|
245
|
-
|| value.startsWith("anthropic/claude-ccr-h");
|
|
245
|
+
function isCodexBridgeModelName(value, bridgeModel) {
|
|
246
|
+
return Boolean(value && bridgeModel && value === bridgeModel);
|
|
246
247
|
}
|
|
247
248
|
function createStatus(input) {
|
|
249
|
+
const { models, ...status } = input;
|
|
248
250
|
const reason = input.error
|
|
249
|
-
?? (input.enabled ? "
|
|
251
|
+
?? (input.enabled ? "Codex Bridge models are unavailable." : "Enable Codex Bridge in Settings.");
|
|
250
252
|
return {
|
|
251
|
-
...
|
|
252
|
-
modelOptions: createSessionModelOptions(input.enabled && input.modelAvailable
|
|
253
|
+
...status,
|
|
254
|
+
modelOptions: createSessionModelOptions(models, input.enabled && input.modelAvailable ? undefined : reason)
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
257
|
function missingApiKeyError() {
|
|
256
258
|
return new VcmError({
|
|
257
|
-
code: "
|
|
258
|
-
message: "
|
|
259
|
+
code: "CODEX_BRIDGE_API_KEY_MISSING",
|
|
260
|
+
message: "Codex Bridge API key is not configured.",
|
|
259
261
|
statusCode: 400,
|
|
260
|
-
hint: "Save the
|
|
262
|
+
hint: "Save the Codex Bridge API key before enabling Codex models."
|
|
261
263
|
});
|
|
262
264
|
}
|
|
263
265
|
function invalidSettingsError(message) {
|
|
264
266
|
return new VcmError({
|
|
265
|
-
code: "
|
|
267
|
+
code: "CODEX_BRIDGE_SETTINGS_INVALID",
|
|
266
268
|
message,
|
|
267
269
|
statusCode: 400
|
|
268
270
|
});
|
|
@@ -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 },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isCodexBridgeSessionModel } from "../../shared/types/session.js";
|
|
5
5
|
import { VcmError } from "../errors.js";
|
|
6
6
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
7
7
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
@@ -598,21 +598,21 @@ export function createSessionService(deps) {
|
|
|
598
598
|
return migrateRunningProjectToolSessionCwd(repoRoot, resumed, targetCwd);
|
|
599
599
|
}
|
|
600
600
|
async function getModelLaunchEnvironment(model) {
|
|
601
|
-
if (!deps.
|
|
602
|
-
if (
|
|
601
|
+
if (!deps.codexBridgeIntegration) {
|
|
602
|
+
if (isCodexBridgeSessionModel(model)) {
|
|
603
603
|
throw new VcmError({
|
|
604
|
-
code: "
|
|
605
|
-
message: "
|
|
604
|
+
code: "CODEX_BRIDGE_UNAVAILABLE",
|
|
605
|
+
message: "Codex Bridge integration is not available in this VCM runtime.",
|
|
606
606
|
statusCode: 409,
|
|
607
|
-
hint: "Enable and configure
|
|
607
|
+
hint: "Enable and configure Codex Bridge before starting this session."
|
|
608
608
|
});
|
|
609
609
|
}
|
|
610
610
|
return {};
|
|
611
611
|
}
|
|
612
|
-
return deps.
|
|
612
|
+
return deps.codexBridgeIntegration.getLaunchEnvironment(model);
|
|
613
613
|
}
|
|
614
614
|
async function getModelLaunchSettingsOverride(model) {
|
|
615
|
-
return deps.
|
|
615
|
+
return deps.codexBridgeIntegration?.getLaunchSettingsOverride(model);
|
|
616
616
|
}
|
|
617
617
|
function isRuntimeSessionAlive(session) {
|
|
618
618
|
if (!session || isExitedStatus(session.status) || session.pid === undefined) {
|
|
@@ -1642,7 +1642,7 @@ function normalizeClaudeModel(value) {
|
|
|
1642
1642
|
|| value === "claude-opus-4-8"
|
|
1643
1643
|
|| value === "sonnet"
|
|
1644
1644
|
|| value === "fable"
|
|
1645
|
-
|| value
|
|
1645
|
+
|| isCodexBridgeSessionModel(value)) {
|
|
1646
1646
|
return value;
|
|
1647
1647
|
}
|
|
1648
1648
|
return "default";
|
|
@@ -1660,20 +1660,20 @@ function normalizeClaudeEffort(value) {
|
|
|
1660
1660
|
}
|
|
1661
1661
|
function assertResumeProviderCompatible(session, requestedModel) {
|
|
1662
1662
|
const persistedModel = normalizeClaudeModel(session.model);
|
|
1663
|
-
if (
|
|
1663
|
+
if (isCodexBridgeSessionModel(persistedModel) !== isCodexBridgeSessionModel(requestedModel)) {
|
|
1664
1664
|
throw new VcmError({
|
|
1665
1665
|
code: "SESSION_PROVIDER_SWITCH_REQUIRES_RESTART",
|
|
1666
1666
|
message: `Cannot resume ${session.role} with a different model provider.`,
|
|
1667
1667
|
statusCode: 409,
|
|
1668
|
-
hint: "Use Restart to switch between native Claude and
|
|
1668
|
+
hint: "Use Restart to switch between native Claude and Codex Bridge models."
|
|
1669
1669
|
});
|
|
1670
1670
|
}
|
|
1671
|
-
if (
|
|
1671
|
+
if (isCodexBridgeSessionModel(requestedModel) && !session.claudeConfigDir) {
|
|
1672
1672
|
throw new VcmError({
|
|
1673
|
-
code: "
|
|
1674
|
-
message: `${session.role} was created
|
|
1673
|
+
code: "CODEX_BRIDGE_SESSION_CONFIG_MISSING",
|
|
1674
|
+
message: `${session.role} was created without isolated Codex Bridge session storage.`,
|
|
1675
1675
|
statusCode: 409,
|
|
1676
|
-
hint: "Restart this role once to create an isolated
|
|
1676
|
+
hint: "Restart this role once to create an isolated Codex Bridge session."
|
|
1677
1677
|
});
|
|
1678
1678
|
}
|
|
1679
1679
|
}
|
|
@@ -1713,7 +1713,7 @@ function buildUsageTelemetryEnvironment(apiUrl, role, model) {
|
|
|
1713
1713
|
OTEL_LOG_TOOL_DETAILS: "0",
|
|
1714
1714
|
OTEL_LOG_RAW_API_BODIES: "0"
|
|
1715
1715
|
};
|
|
1716
|
-
if (!apiUrl ||
|
|
1716
|
+
if (!apiUrl || isCodexBridgeSessionModel(model)) {
|
|
1717
1717
|
return disabled;
|
|
1718
1718
|
}
|
|
1719
1719
|
return {
|
|
@@ -76,7 +76,7 @@ function extractApiRequestEvents(payload) {
|
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
78
78
|
const model = normalizeModel(readString(attributes.model));
|
|
79
|
-
if (
|
|
79
|
+
if (isCodexBridgeModel(model)) {
|
|
80
80
|
continue;
|
|
81
81
|
}
|
|
82
82
|
const sequence = readInteger(attributes["event.sequence"]);
|
|
@@ -334,9 +334,9 @@ function normalizeModel(input) {
|
|
|
334
334
|
const model = input?.slice(0, 200).trim();
|
|
335
335
|
return model && isSafeGroupKey(model) ? model : "unknown";
|
|
336
336
|
}
|
|
337
|
-
function
|
|
337
|
+
function isCodexBridgeModel(model) {
|
|
338
338
|
const normalized = model.toLowerCase();
|
|
339
|
-
return normalized.startsWith("gpt-") || normalized.startsWith("
|
|
339
|
+
return normalized.startsWith("gpt-") || normalized.startsWith("codex-bridge:");
|
|
340
340
|
}
|
|
341
341
|
function isSafeGroupKey(value) {
|
|
342
342
|
return value !== "__proto__" && value !== "prototype" && value !== "constructor";
|
|
@@ -125,6 +125,7 @@ ${renderRoleMemoryRules("architect")}
|
|
|
125
125
|
#### Code Scaffolding
|
|
126
126
|
|
|
127
127
|
- Use the Agent tool to invoke \`vcm-architect-scaffold-worker\` in the foreground after the plan and Scaffold Manifest are complete. Give it the exact plan path and require it to return before this Architect turn continues.
|
|
128
|
+
- Do not invoke any other subagent.
|
|
128
129
|
- Use one scaffold worker. Do not run it in the background or end the Architect turn while it is active.
|
|
129
130
|
- Review the worker commit, actual diff, callable surfaces, marker placement, ledger reconciliation, and L0 results yourself. Architect owns every final scaffold claim and must correct any worker error before marking planning complete.
|
|
130
131
|
- Create or update only the minimum module/file scaffolding needed to make boundaries, callable surfaces, and placeholders unambiguous. Minimum limits depth (no business implementation), never breadth: every \`create\`, \`change\`, and \`delete\` item must be scaffolded.
|
|
@@ -50,6 +50,7 @@ ${renderRoleMemoryRules("coder")}
|
|
|
50
50
|
### Parallel Worker Implementation
|
|
51
51
|
|
|
52
52
|
- Coder may use Claude Code subagents to invoke \`vcm-coder-worker\` for parallel implementation.
|
|
53
|
+
- Do not invoke any other subagent.
|
|
53
54
|
- Use workers when the task has at least 20 \`VCM:CODE\` markers and the marker distribution can form at least two worker-sized groups.
|
|
54
55
|
- Under a complete scaffold, marker implementations are order-independent — signatures, types, and cross-item contracts are frozen by the scaffold — so never serialize worker-sized groups for presumed implementation-order dependencies. When a group's module-scoped checks need peers that are still unimplemented, narrow that worker's assigned validation scope instead of serializing.
|
|
55
56
|
- An item counts as blocked only when a genuine implementation attempt has produced objective compile/check evidence already reported under the failure rules; prediction never blocks an item. A blocked marker item never exempts the remaining markers from worker dispatch.
|
|
@@ -52,32 +52,38 @@ export const CLAUDE_MODEL_OPTIONS = [
|
|
|
52
52
|
available: true
|
|
53
53
|
}
|
|
54
54
|
];
|
|
55
|
-
export const
|
|
56
|
-
export const
|
|
57
|
-
export const
|
|
58
|
-
export const
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
export const CODEX_BRIDGE_LOCAL_BASE_URL = "http://127.0.0.1:3456";
|
|
56
|
+
export const CODEX_BRIDGE_CONTAINER_BASE_URL = "http://host.docker.internal:3456";
|
|
57
|
+
export const CODEX_BRIDGE_BASE_URL = CODEX_BRIDGE_CONTAINER_BASE_URL;
|
|
58
|
+
export const CODEX_BRIDGE_BASE_URLS = [
|
|
59
|
+
CODEX_BRIDGE_LOCAL_BASE_URL,
|
|
60
|
+
CODEX_BRIDGE_CONTAINER_BASE_URL
|
|
61
61
|
];
|
|
62
|
-
export const
|
|
63
|
-
export const
|
|
64
|
-
export const
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
export const CODEX_BRIDGE_EFFECTIVE_CONTEXT_TOKENS = 258_400;
|
|
63
|
+
export const CODEX_BRIDGE_AUTO_COMPACT_WINDOW_TOKENS = CODEX_BRIDGE_EFFECTIVE_CONTEXT_TOKENS;
|
|
64
|
+
export const CODEX_BRIDGE_AUTO_COMPACT_PERCENT = 90;
|
|
65
|
+
export function isCodexBridgeSessionModel(model) {
|
|
66
|
+
return typeof model === "string"
|
|
67
|
+
&& model.startsWith("codex-bridge:")
|
|
68
|
+
&& model.length > "codex-bridge:".length;
|
|
69
69
|
}
|
|
70
|
-
export function
|
|
70
|
+
export function toCodexBridgeSessionModel(modelId) {
|
|
71
|
+
return `codex-bridge:${modelId}`;
|
|
72
|
+
}
|
|
73
|
+
export function getCodexBridgeModelId(model) {
|
|
74
|
+
return model.slice("codex-bridge:".length);
|
|
75
|
+
}
|
|
76
|
+
export function createSessionModelOptions(codexBridgeModels = [], unavailableReason) {
|
|
71
77
|
return [
|
|
72
78
|
...CLAUDE_MODEL_OPTIONS,
|
|
73
|
-
{
|
|
74
|
-
value:
|
|
75
|
-
label:
|
|
76
|
-
description:
|
|
77
|
-
source: "
|
|
78
|
-
available:
|
|
79
|
-
...(
|
|
80
|
-
}
|
|
79
|
+
...codexBridgeModels.map((model) => ({
|
|
80
|
+
value: toCodexBridgeSessionModel(model.id),
|
|
81
|
+
label: `${model.displayName ?? model.id} (Codex Bridge)`,
|
|
82
|
+
description: `${model.id} through the host Codex Bridge`,
|
|
83
|
+
source: "codex-bridge",
|
|
84
|
+
available: unavailableReason === undefined,
|
|
85
|
+
...(unavailableReason === undefined ? {} : { unavailableReason })
|
|
86
|
+
}))
|
|
81
87
|
];
|
|
82
88
|
}
|
|
83
89
|
const BASE_EFFORT_OPTIONS = [
|