vibe-coding-master 0.7.13 → 0.7.15
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 +14 -8
- package/dist/backend/adapters/ccr-gateway-adapter.js +73 -50
- package/dist/backend/adapters/claude-adapter.js +6 -2
- package/dist/backend/adapters/claude-settings-adapter.js +79 -0
- package/dist/backend/runtime/node-pty-runtime.js +5 -0
- package/dist/backend/server.js +4 -1
- package/dist/backend/services/ccr-integration-service.js +37 -5
- package/dist/backend/services/session-service.js +23 -8
- package/dist/shared/types/session.js +7 -1
- package/package.json +1 -1
- package/scripts/ccr-api-key-helper.mjs +21 -0
package/README.md
CHANGED
|
@@ -235,14 +235,16 @@ a host-running [Claude Code Router](https://github.com/musistudio/claude-code-ro
|
|
|
235
235
|
CCR must already be installed, authenticated, configured, and running on the
|
|
236
236
|
host. VCM does not manage the CCR process.
|
|
237
237
|
|
|
238
|
-
VCM
|
|
238
|
+
VCM automatically checks the local-host and DevContainer endpoints:
|
|
239
239
|
|
|
240
240
|
```text
|
|
241
|
+
http://127.0.0.1:3456
|
|
241
242
|
http://host.docker.internal:3456
|
|
242
243
|
```
|
|
243
244
|
|
|
244
|
-
Configure CCR to listen on port `3456` with an API key
|
|
245
|
-
from the container. In the VCM
|
|
245
|
+
Configure CCR to listen on port `3456` with an API key. When VCM runs in a
|
|
246
|
+
DevContainer, make the second endpoint reachable from the container. In the VCM
|
|
247
|
+
`Settings` section:
|
|
246
248
|
|
|
247
249
|
1. enter and save the CCR API key;
|
|
248
250
|
2. enable `CCR GPT models`;
|
|
@@ -250,11 +252,15 @@ from the container. In the VCM `Settings` section:
|
|
|
250
252
|
4. select `GPT-5.6 Sol (CCR)` in any Session model control.
|
|
251
253
|
|
|
252
254
|
The key is stored in global VCM state (`~/.vcm/settings.json`) with owner-only
|
|
253
|
-
permissions and is never returned by the settings API.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
permissions and is never returned by the settings API. It is used for CCR
|
|
256
|
+
checks, model discovery, and the GPT-only `apiKeyHelper`. GPT sessions receive a
|
|
257
|
+
child-only `--settings` override; VCM removes inherited Anthropic token
|
|
258
|
+
variables only from those child processes. VCM removes CCR-owned takeover
|
|
259
|
+
entries from global Claude settings, so native Claude selections keep their
|
|
260
|
+
normal launch and account authentication. If CCR is disabled, unreachable,
|
|
261
|
+
rejects the key, or does not expose
|
|
262
|
+
`Codex API/gpt-5.6-sol`, VCM blocks the new Start, Resume, or Restart and does
|
|
263
|
+
not fall back to another model.
|
|
258
264
|
|
|
259
265
|
## Launch Template
|
|
260
266
|
|
|
@@ -1,62 +1,84 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CCR_GATEWAY_BASE_URLS, CCR_GPT_MODEL_ID } from "../../shared/types/session.js";
|
|
2
2
|
const DEFAULT_TIMEOUT_MS = 3_000;
|
|
3
3
|
const CCR_ENCODED_MODEL_PREFIX = "anthropic/claude-ccr-h";
|
|
4
4
|
export function createCcrGatewayAdapter(deps = {}) {
|
|
5
|
-
const
|
|
5
|
+
const baseUrls = (deps.baseUrl ? [deps.baseUrl] : CCR_GATEWAY_BASE_URLS)
|
|
6
|
+
.map((baseUrl) => baseUrl.replace(/\/+$/, ""));
|
|
6
7
|
const fetchImpl = deps.fetch ?? globalThis.fetch;
|
|
7
8
|
const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
8
9
|
return {
|
|
9
10
|
async probe(apiKey) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const failures = [];
|
|
12
|
+
for (const baseUrl of baseUrls) {
|
|
13
|
+
const result = await probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs);
|
|
14
|
+
if (result.baseUrl) {
|
|
15
|
+
return result;
|
|
14
16
|
}
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
connectionState: "not-ccr",
|
|
18
|
-
modelAvailable: false,
|
|
19
|
-
error: `The fixed CCR endpoint ${baseUrl} did not identify a Claude Code Router gateway.`
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
const models = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, {
|
|
23
|
-
authorization: `Bearer ${apiKey}`,
|
|
24
|
-
"user-agent": "Claude Code"
|
|
25
|
-
});
|
|
26
|
-
if (models.response.status === 401 || models.response.status === 403) {
|
|
27
|
-
return {
|
|
28
|
-
connectionState: "unauthorized",
|
|
29
|
-
modelAvailable: false,
|
|
30
|
-
error: "CCR rejected the configured API key."
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
if (!models.response.ok) {
|
|
34
|
-
return invalidResponse(`CCR model discovery returned HTTP ${models.response.status}.`);
|
|
35
|
-
}
|
|
36
|
-
const modelDescriptors = readModelDescriptors(models.payload);
|
|
37
|
-
if (!modelDescriptors) {
|
|
38
|
-
return invalidResponse("CCR returned an invalid /v1/models response.");
|
|
39
|
-
}
|
|
40
|
-
const modelAvailable = modelDescriptors.some(isRequiredModel);
|
|
41
|
-
return {
|
|
42
|
-
connectionState: "available",
|
|
43
|
-
modelAvailable,
|
|
44
|
-
...(modelAvailable
|
|
45
|
-
? {}
|
|
46
|
-
: { error: `CCR does not expose the required model ${CCR_GPT_MODEL_ID}.` })
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
const timedOut = isAbortError(error);
|
|
51
|
-
return {
|
|
52
|
-
connectionState: "unreachable",
|
|
53
|
-
modelAvailable: false,
|
|
54
|
-
error: timedOut
|
|
55
|
-
? `CCR connection timed out after ${timeoutMs} ms at ${baseUrl}.`
|
|
56
|
-
: `CCR could not be reached from the VCM container backend at ${baseUrl}. Reason: ${errorMessage(error)}`
|
|
57
|
-
};
|
|
17
|
+
failures.push(result);
|
|
58
18
|
}
|
|
19
|
+
return combineProbeFailures(baseUrls, failures);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function probeEndpoint(fetchImpl, baseUrl, apiKey, timeoutMs) {
|
|
24
|
+
try {
|
|
25
|
+
const root = await requestJson(fetchImpl, `${baseUrl}/`, timeoutMs);
|
|
26
|
+
if (!root.response.ok) {
|
|
27
|
+
return invalidResponse(`Gateway root at ${baseUrl} returned HTTP ${root.response.status}.`);
|
|
28
|
+
}
|
|
29
|
+
if (!isCcrGateway(root.payload)) {
|
|
30
|
+
return {
|
|
31
|
+
connectionState: "not-ccr",
|
|
32
|
+
modelAvailable: false,
|
|
33
|
+
error: `${baseUrl} did not identify a Claude Code Router gateway.`
|
|
34
|
+
};
|
|
59
35
|
}
|
|
36
|
+
const models = await requestJson(fetchImpl, `${baseUrl}/v1/models`, timeoutMs, {
|
|
37
|
+
authorization: `Bearer ${apiKey}`,
|
|
38
|
+
"user-agent": "Claude Code"
|
|
39
|
+
});
|
|
40
|
+
if (models.response.status === 401 || models.response.status === 403) {
|
|
41
|
+
return {
|
|
42
|
+
connectionState: "unauthorized",
|
|
43
|
+
modelAvailable: false,
|
|
44
|
+
baseUrl,
|
|
45
|
+
error: "CCR rejected the configured API key."
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (!models.response.ok) {
|
|
49
|
+
return invalidResponse(`CCR model discovery at ${baseUrl} returned HTTP ${models.response.status}.`, baseUrl);
|
|
50
|
+
}
|
|
51
|
+
const modelDescriptors = readModelDescriptors(models.payload);
|
|
52
|
+
if (!modelDescriptors) {
|
|
53
|
+
return invalidResponse("CCR returned an invalid /v1/models response.", baseUrl);
|
|
54
|
+
}
|
|
55
|
+
const modelAvailable = modelDescriptors.some(isRequiredModel);
|
|
56
|
+
return {
|
|
57
|
+
connectionState: "available",
|
|
58
|
+
modelAvailable,
|
|
59
|
+
baseUrl,
|
|
60
|
+
...(modelAvailable
|
|
61
|
+
? {}
|
|
62
|
+
: { error: `CCR does not expose the required model ${CCR_GPT_MODEL_ID}.` })
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const timedOut = isAbortError(error);
|
|
67
|
+
return {
|
|
68
|
+
connectionState: "unreachable",
|
|
69
|
+
modelAvailable: false,
|
|
70
|
+
error: timedOut
|
|
71
|
+
? `CCR connection timed out after ${timeoutMs} ms at ${baseUrl}.`
|
|
72
|
+
: `CCR could not be reached from the VCM backend at ${baseUrl}. Reason: ${errorMessage(error)}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function combineProbeFailures(baseUrls, failures) {
|
|
77
|
+
const lastFailure = failures.at(-1);
|
|
78
|
+
return {
|
|
79
|
+
connectionState: lastFailure?.connectionState ?? "unreachable",
|
|
80
|
+
modelAvailable: false,
|
|
81
|
+
error: `CCR was not found at ${baseUrls.join(" or ")}. ${failures.map((failure) => failure.error).filter(Boolean).join(" ")}`.trim()
|
|
60
82
|
};
|
|
61
83
|
}
|
|
62
84
|
async function requestJson(fetchImpl, url, timeoutMs, headers = {}) {
|
|
@@ -131,10 +153,11 @@ function decodeCcrModelId(modelId) {
|
|
|
131
153
|
function normalizeModelName(value) {
|
|
132
154
|
return (value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
133
155
|
}
|
|
134
|
-
function invalidResponse(error) {
|
|
156
|
+
function invalidResponse(error, baseUrl) {
|
|
135
157
|
return {
|
|
136
158
|
connectionState: "invalid-response",
|
|
137
159
|
modelAvailable: false,
|
|
160
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
138
161
|
error
|
|
139
162
|
};
|
|
140
163
|
}
|
|
@@ -18,8 +18,9 @@ export function createClaudeAdapter(runner) {
|
|
|
18
18
|
}
|
|
19
19
|
return result.stdout.trim();
|
|
20
20
|
},
|
|
21
|
-
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default") {
|
|
21
|
+
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride) {
|
|
22
22
|
const args = ["--agent", role];
|
|
23
|
+
const sessionSettings = { ...settingsOverride };
|
|
23
24
|
if (claudeSessionId) {
|
|
24
25
|
args.push(resume ? "--resume" : "--session-id", claudeSessionId);
|
|
25
26
|
}
|
|
@@ -27,11 +28,14 @@ export function createClaudeAdapter(runner) {
|
|
|
27
28
|
args.push("--model", model);
|
|
28
29
|
}
|
|
29
30
|
if (effort === "ultracode") {
|
|
30
|
-
|
|
31
|
+
sessionSettings.ultracode = true;
|
|
31
32
|
}
|
|
32
33
|
else if (effort !== "default") {
|
|
33
34
|
args.push("--effort", effort);
|
|
34
35
|
}
|
|
36
|
+
if (Object.keys(sessionSettings).length > 0) {
|
|
37
|
+
args.push("--settings", JSON.stringify(sessionSettings));
|
|
38
|
+
}
|
|
35
39
|
if (permissionMode !== "default") {
|
|
36
40
|
args.push("--permission-mode", permissionMode);
|
|
37
41
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const CCR_ENV_KEYS = [
|
|
4
|
+
"CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
|
|
5
|
+
"ANTHROPIC_BASE_URL",
|
|
6
|
+
"ANTHROPIC_API_BASE_URL",
|
|
7
|
+
"CLAUDE_AGENT_API_BASE_URL",
|
|
8
|
+
"ANTHROPIC_MODEL",
|
|
9
|
+
"CCR_CLAUDE_CODE_MODEL",
|
|
10
|
+
"CODEXL_CLAUDE_CODE_MODEL",
|
|
11
|
+
"ANTHROPIC_SMALL_FAST_MODEL"
|
|
12
|
+
];
|
|
13
|
+
export function createClaudeSettingsAdapter(deps) {
|
|
14
|
+
const settingsPath = deps.settingsPath ?? resolveClaudeSettingsPath(deps.env ?? process.env);
|
|
15
|
+
return {
|
|
16
|
+
async restoreNativeSettings() {
|
|
17
|
+
if (!(await deps.fs.pathExists(settingsPath))) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const current = await deps.fs.readJson(settingsPath);
|
|
21
|
+
const restored = restoreNativeClaudeSettings(current);
|
|
22
|
+
if (!restored.changed) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
await deps.fs.writeJsonAtomic(settingsPath, restored.settings);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function restoreNativeClaudeSettings(value) {
|
|
31
|
+
const settings = isObject(value) ? structuredClone(value) : {};
|
|
32
|
+
const helperIsCcr = typeof settings.apiKeyHelper === "string"
|
|
33
|
+
&& settings.apiKeyHelper.includes(".claude-code-router");
|
|
34
|
+
const env = isObject(settings.env) ? settings.env : undefined;
|
|
35
|
+
const envUsesCcr = env
|
|
36
|
+
? ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_BASE_URL", "CLAUDE_AGENT_API_BASE_URL"]
|
|
37
|
+
.some((key) => isCcrGatewayUrl(env[key]))
|
|
38
|
+
: false;
|
|
39
|
+
if (!helperIsCcr && !envUsesCcr) {
|
|
40
|
+
return { changed: false, settings };
|
|
41
|
+
}
|
|
42
|
+
let changed = false;
|
|
43
|
+
if (helperIsCcr) {
|
|
44
|
+
delete settings.apiKeyHelper;
|
|
45
|
+
changed = true;
|
|
46
|
+
}
|
|
47
|
+
if (env) {
|
|
48
|
+
for (const key of CCR_ENV_KEYS) {
|
|
49
|
+
if (key in env) {
|
|
50
|
+
delete env[key];
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (Object.keys(env).length === 0) {
|
|
55
|
+
delete settings.env;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { changed, settings };
|
|
59
|
+
}
|
|
60
|
+
function resolveClaudeSettingsPath(env) {
|
|
61
|
+
const configDir = env.CLAUDE_CONFIG_DIR?.trim();
|
|
62
|
+
return path.join(configDir ? path.resolve(configDir) : path.join(homedir(), ".claude"), "settings.json");
|
|
63
|
+
}
|
|
64
|
+
function isCcrGatewayUrl(value) {
|
|
65
|
+
if (typeof value !== "string") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const url = new URL(value);
|
|
70
|
+
return url.port === "3456"
|
|
71
|
+
&& ["127.0.0.1", "localhost", "host.docker.internal"].includes(url.hostname);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isObject(value) {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
@@ -258,6 +258,11 @@ export function buildPtyEnvironment(baseEnv, inputEnv = {}) {
|
|
|
258
258
|
CLICOLOR: inputEnv.CLICOLOR ?? baseEnv.CLICOLOR ?? "1",
|
|
259
259
|
TERM_PROGRAM: inputEnv.TERM_PROGRAM ?? "VibeCodingMaster"
|
|
260
260
|
};
|
|
261
|
+
for (const [key, value] of Object.entries(inputEnv)) {
|
|
262
|
+
if (value === undefined) {
|
|
263
|
+
delete env[key];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
261
266
|
delete env.NO_COLOR;
|
|
262
267
|
return env;
|
|
263
268
|
}
|
package/dist/backend/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import fastifyStatic from "@fastify/static";
|
|
|
5
5
|
import { createArtifactService } from "./services/artifact-service.js";
|
|
6
6
|
import { createClaudeAdapter } from "./adapters/claude-adapter.js";
|
|
7
7
|
import { createCcrGatewayAdapter } from "./adapters/ccr-gateway-adapter.js";
|
|
8
|
+
import { createClaudeSettingsAdapter } from "./adapters/claude-settings-adapter.js";
|
|
8
9
|
import { createCommandRunner } from "./adapters/command-runner.js";
|
|
9
10
|
import { createCommandDispatcher } from "./services/command-dispatcher.js";
|
|
10
11
|
import { createClaudeHookService } from "./services/claude-hook-service.js";
|
|
@@ -208,9 +209,11 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
208
209
|
const git = createGitAdapter(runner);
|
|
209
210
|
const claude = createClaudeAdapter(runner);
|
|
210
211
|
const appSettings = createAppSettingsService({ fs });
|
|
212
|
+
const claudeSettings = createClaudeSettingsAdapter({ fs });
|
|
211
213
|
const ccrIntegration = createCcrIntegrationService({
|
|
212
214
|
settings: appSettings,
|
|
213
|
-
gateway: createCcrGatewayAdapter()
|
|
215
|
+
gateway: createCcrGatewayAdapter(),
|
|
216
|
+
restoreNativeClaudeSettings: () => claudeSettings.restoreNativeSettings()
|
|
214
217
|
});
|
|
215
218
|
const runtime = createNodePtyTerminalRuntime({ fs });
|
|
216
219
|
const registry = createSessionRegistry();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CCR_GATEWAY_BASE_URL, CCR_GPT_MODEL_ID, createSessionModelOptions, isCcrSessionModel } from "../../shared/types/session.js";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import { VcmError } from "../errors.js";
|
|
3
4
|
const DEFAULT_CACHE_TTL_MS = 10_000;
|
|
5
|
+
const DEFAULT_API_KEY_HELPER_PATH = fileURLToPath(new URL("../../../scripts/ccr-api-key-helper.mjs", import.meta.url));
|
|
4
6
|
export function createCcrIntegrationService(deps) {
|
|
5
7
|
const now = deps.now ?? (() => new Date());
|
|
6
8
|
const cacheTtlMs = deps.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
@@ -65,6 +67,9 @@ export function createCcrIntegrationService(deps) {
|
|
|
65
67
|
return {
|
|
66
68
|
async initialize() {
|
|
67
69
|
const settings = await deps.settings.getCcrIntegrationSettings();
|
|
70
|
+
if (settings.apiKey) {
|
|
71
|
+
await deps.restoreNativeClaudeSettings?.();
|
|
72
|
+
}
|
|
68
73
|
if (settings.enabled) {
|
|
69
74
|
await probe(true);
|
|
70
75
|
}
|
|
@@ -97,6 +102,9 @@ export function createCcrIntegrationService(deps) {
|
|
|
97
102
|
enabled: nextEnabled,
|
|
98
103
|
apiKey: nextApiKey
|
|
99
104
|
});
|
|
105
|
+
if (nextApiKey) {
|
|
106
|
+
await deps.restoreNativeClaudeSettings?.();
|
|
107
|
+
}
|
|
100
108
|
if (clearApiKey || !nextEnabled) {
|
|
101
109
|
cachedProbe = undefined;
|
|
102
110
|
return buildStatus();
|
|
@@ -142,12 +150,15 @@ export function createCcrIntegrationService(deps) {
|
|
|
142
150
|
hint: "Check that host CCR is running on port 3456 and exposes GPT-5.6 Sol."
|
|
143
151
|
});
|
|
144
152
|
}
|
|
145
|
-
const
|
|
153
|
+
const gatewayBaseUrl = checked.result.baseUrl ?? CCR_GATEWAY_BASE_URL;
|
|
154
|
+
const gatewayHost = new URL(gatewayBaseUrl).hostname;
|
|
155
|
+
const noProxy = mergeNoProxy(deps.baseEnv?.NO_PROXY ?? deps.baseEnv?.no_proxy ?? process.env.NO_PROXY ?? process.env.no_proxy, gatewayHost);
|
|
146
156
|
return {
|
|
147
|
-
ANTHROPIC_BASE_URL:
|
|
148
|
-
ANTHROPIC_API_BASE_URL:
|
|
149
|
-
CLAUDE_AGENT_API_BASE_URL:
|
|
150
|
-
ANTHROPIC_AUTH_TOKEN:
|
|
157
|
+
ANTHROPIC_BASE_URL: gatewayBaseUrl,
|
|
158
|
+
ANTHROPIC_API_BASE_URL: gatewayBaseUrl,
|
|
159
|
+
CLAUDE_AGENT_API_BASE_URL: gatewayBaseUrl,
|
|
160
|
+
ANTHROPIC_AUTH_TOKEN: undefined,
|
|
161
|
+
ANTHROPIC_API_KEY: undefined,
|
|
151
162
|
ANTHROPIC_MODEL: CCR_GPT_MODEL_ID,
|
|
152
163
|
CCR_CLAUDE_CODE_MODEL: CCR_GPT_MODEL_ID,
|
|
153
164
|
CODEXL_CLAUDE_CODE_MODEL: CCR_GPT_MODEL_ID,
|
|
@@ -156,6 +167,18 @@ export function createCcrIntegrationService(deps) {
|
|
|
156
167
|
NO_PROXY: noProxy,
|
|
157
168
|
no_proxy: noProxy
|
|
158
169
|
};
|
|
170
|
+
},
|
|
171
|
+
async getLaunchSettingsOverride(model) {
|
|
172
|
+
const settings = await deps.settings.getCcrIntegrationSettings();
|
|
173
|
+
if (settings.apiKey) {
|
|
174
|
+
await deps.restoreNativeClaudeSettings?.();
|
|
175
|
+
}
|
|
176
|
+
if (!isCcrSessionModel(model)) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
apiKeyHelper: buildApiKeyHelperCommand(deps.apiKeyHelperPath ?? DEFAULT_API_KEY_HELPER_PATH)
|
|
181
|
+
};
|
|
159
182
|
}
|
|
160
183
|
};
|
|
161
184
|
}
|
|
@@ -192,3 +215,12 @@ export function mergeNoProxy(current, host) {
|
|
|
192
215
|
}
|
|
193
216
|
return values.join(",");
|
|
194
217
|
}
|
|
218
|
+
export function buildApiKeyHelperCommand(helperPath) {
|
|
219
|
+
if (process.platform === "win32") {
|
|
220
|
+
return `${JSON.stringify(process.execPath)} ${JSON.stringify(helperPath)}`;
|
|
221
|
+
}
|
|
222
|
+
return `${quotePosixShell(process.execPath)} ${quotePosixShell(helperPath)}`;
|
|
223
|
+
}
|
|
224
|
+
function quotePosixShell(value) {
|
|
225
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
226
|
+
}
|
|
@@ -55,7 +55,10 @@ export function createSessionService(deps) {
|
|
|
55
55
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
56
56
|
const model = normalizeClaudeModel(input.model ?? persisted?.model);
|
|
57
57
|
const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort);
|
|
58
|
-
const modelEnvironment = await
|
|
58
|
+
const [modelEnvironment, modelSettingsOverride] = await Promise.all([
|
|
59
|
+
getModelLaunchEnvironment(model),
|
|
60
|
+
getModelLaunchSettingsOverride(model)
|
|
61
|
+
]);
|
|
59
62
|
const resumeClaudeSessionId = launchMode === "resume"
|
|
60
63
|
? persisted?.claudeSessionId
|
|
61
64
|
: undefined;
|
|
@@ -74,7 +77,7 @@ export function createSessionService(deps) {
|
|
|
74
77
|
? claudeTranscriptPath(taskRepoRoot, resumeClaudeSessionId)
|
|
75
78
|
: undefined;
|
|
76
79
|
const startCommand = {
|
|
77
|
-
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
|
|
80
|
+
...deps.claude.buildRoleStartCommand(role, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
|
|
78
81
|
cwd: taskRepoRoot
|
|
79
82
|
};
|
|
80
83
|
const runtimeSession = await deps.runtime.createSession({
|
|
@@ -160,7 +163,10 @@ export function createSessionService(deps) {
|
|
|
160
163
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
161
164
|
const model = normalizeClaudeModel(input.model ?? persisted?.model);
|
|
162
165
|
const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
|
|
163
|
-
const modelEnvironment = await
|
|
166
|
+
const [modelEnvironment, modelSettingsOverride] = await Promise.all([
|
|
167
|
+
getModelLaunchEnvironment(model),
|
|
168
|
+
getModelLaunchSettingsOverride(model)
|
|
169
|
+
]);
|
|
164
170
|
const resumeClaudeSessionId = launchMode === "resume"
|
|
165
171
|
? persisted?.claudeSessionId
|
|
166
172
|
: undefined;
|
|
@@ -191,7 +197,7 @@ export function createSessionService(deps) {
|
|
|
191
197
|
? claudeTranscriptPath(repoRoot, resumeClaudeSessionId)
|
|
192
198
|
: undefined;
|
|
193
199
|
const startCommand = {
|
|
194
|
-
...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
|
|
200
|
+
...deps.claude.buildRoleStartCommand(TRANSLATOR_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
|
|
195
201
|
cwd: launchCwd
|
|
196
202
|
};
|
|
197
203
|
const runtimeSession = await deps.runtime.createSession({
|
|
@@ -270,7 +276,10 @@ export function createSessionService(deps) {
|
|
|
270
276
|
const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
|
|
271
277
|
const model = normalizeClaudeModel(input.model ?? persisted?.model);
|
|
272
278
|
const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort ?? "medium");
|
|
273
|
-
const modelEnvironment = await
|
|
279
|
+
const [modelEnvironment, modelSettingsOverride] = await Promise.all([
|
|
280
|
+
getModelLaunchEnvironment(model),
|
|
281
|
+
getModelLaunchSettingsOverride(model)
|
|
282
|
+
]);
|
|
274
283
|
const resumeClaudeSessionId = launchMode === "resume"
|
|
275
284
|
? persisted?.claudeSessionId
|
|
276
285
|
: undefined;
|
|
@@ -297,7 +306,7 @@ export function createSessionService(deps) {
|
|
|
297
306
|
? claudeTranscriptPath(repoRoot, resumeClaudeSessionId)
|
|
298
307
|
: undefined;
|
|
299
308
|
const startCommand = {
|
|
300
|
-
...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort),
|
|
309
|
+
...deps.claude.buildRoleStartCommand(HARNESS_ENGINEER_ROLE, config.claudeCommand, permissionMode, resumeClaudeSessionId, launchMode === "resume", model, effort, modelSettingsOverride),
|
|
301
310
|
cwd: launchCwd
|
|
302
311
|
};
|
|
303
312
|
const runtimeSession = await deps.runtime.createSession({
|
|
@@ -480,14 +489,17 @@ export function createSessionService(deps) {
|
|
|
480
489
|
const permissionMode = normalizeClaudePermissionMode(session.permissionMode);
|
|
481
490
|
const model = normalizeClaudeModel(session.model);
|
|
482
491
|
const effort = normalizeClaudeEffort(session.effort);
|
|
483
|
-
const modelEnvironment = await
|
|
492
|
+
const [modelEnvironment, modelSettingsOverride] = await Promise.all([
|
|
493
|
+
getModelLaunchEnvironment(model),
|
|
494
|
+
getModelLaunchSettingsOverride(model)
|
|
495
|
+
]);
|
|
484
496
|
// Spawn (`claude --resume`) always anchors at the base repoRoot so resume works
|
|
485
497
|
// even if the persisted task cwd was deleted. `--resume` then restores the
|
|
486
498
|
// session's own last cwd (tracked on `session.cwd`), so the `/cd` migrate below
|
|
487
499
|
// fires only when that restored cwd differs from the target worktree.
|
|
488
500
|
const launchCwd = repoRoot;
|
|
489
501
|
const startCommand = {
|
|
490
|
-
...deps.claude.buildRoleStartCommand(session.role, config.claudeCommand, permissionMode, session.claudeSessionId, true, model, effort),
|
|
502
|
+
...deps.claude.buildRoleStartCommand(session.role, config.claudeCommand, permissionMode, session.claudeSessionId, true, model, effort, modelSettingsOverride),
|
|
491
503
|
cwd: launchCwd
|
|
492
504
|
};
|
|
493
505
|
const runtimeSession = await deps.runtime.createSession({
|
|
@@ -561,6 +573,9 @@ export function createSessionService(deps) {
|
|
|
561
573
|
}
|
|
562
574
|
return deps.ccrIntegration.getLaunchEnvironment(model);
|
|
563
575
|
}
|
|
576
|
+
async function getModelLaunchSettingsOverride(model) {
|
|
577
|
+
return deps.ccrIntegration?.getLaunchSettingsOverride(model);
|
|
578
|
+
}
|
|
564
579
|
function isRuntimeSessionAlive(session) {
|
|
565
580
|
if (!session || isExitedStatus(session.status) || session.pid === undefined) {
|
|
566
581
|
return false;
|
|
@@ -45,7 +45,13 @@ export const CLAUDE_MODEL_OPTIONS = [
|
|
|
45
45
|
available: true
|
|
46
46
|
}
|
|
47
47
|
];
|
|
48
|
-
export const
|
|
48
|
+
export const CCR_LOCAL_GATEWAY_BASE_URL = "http://127.0.0.1:3456";
|
|
49
|
+
export const CCR_CONTAINER_GATEWAY_BASE_URL = "http://host.docker.internal:3456";
|
|
50
|
+
export const CCR_GATEWAY_BASE_URL = CCR_CONTAINER_GATEWAY_BASE_URL;
|
|
51
|
+
export const CCR_GATEWAY_BASE_URLS = [
|
|
52
|
+
CCR_LOCAL_GATEWAY_BASE_URL,
|
|
53
|
+
CCR_CONTAINER_GATEWAY_BASE_URL
|
|
54
|
+
];
|
|
49
55
|
export const CCR_GPT_MODEL_ID = "Codex API/gpt-5.6-sol";
|
|
50
56
|
export const CCR_GPT_SESSION_MODEL = `ccr:${CCR_GPT_MODEL_ID}`;
|
|
51
57
|
export function isCcrSessionModel(model) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
const dataDir = process.env.VCM_DATA_DIR?.trim()
|
|
8
|
+
? path.resolve(process.env.VCM_DATA_DIR)
|
|
9
|
+
: path.join(homedir(), ".vcm");
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const settings = JSON.parse(await readFile(path.join(dataDir, "settings.json"), "utf8"));
|
|
13
|
+
const apiKey = settings?.ccr?.apiKey;
|
|
14
|
+
if (typeof apiKey !== "string" || !apiKey.trim()) {
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
} else {
|
|
17
|
+
process.stdout.write(`${apiKey.trim()}\n`);
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
}
|