@vibe-forge/tsconfigs 0.8.0 → 0.9.0
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/dist/apps/cli/__tests__/clear.spec.d.ts +1 -0
- package/dist/apps/cli/__tests__/clear.spec.js +72 -0
- package/dist/apps/cli/src/commands/clear.d.ts +4 -0
- package/dist/apps/cli/src/commands/clear.js +62 -39
- package/dist/apps/client/src/main.d.ts +1 -0
- package/dist/apps/client/src/main.js +1 -0
- package/dist/apps/server/__tests__/db/connection.spec.d.ts +1 -0
- package/dist/apps/server/__tests__/db/connection.spec.js +58 -0
- package/dist/apps/server/__tests__/db/index.spec.js +129 -5
- package/dist/apps/server/__tests__/db/schema.spec.js +3 -6
- package/dist/apps/server/__tests__/db/sqlite.spec.d.ts +1 -0
- package/dist/apps/server/__tests__/db/sqlite.spec.js +51 -0
- package/dist/apps/server/__tests__/services/session-start.spec.js +3 -3
- package/dist/apps/server/src/db/automation/repo.d.ts +2 -2
- package/dist/apps/server/src/db/channelSessions/repo.d.ts +2 -2
- package/dist/apps/server/src/db/connection.d.ts +2 -2
- package/dist/apps/server/src/db/connection.js +2 -2
- package/dist/apps/server/src/db/index.d.ts +2 -2
- package/dist/apps/server/src/db/schema.d.ts +3 -3
- package/dist/apps/server/src/db/sessions/messages.repo.d.ts +2 -2
- package/dist/apps/server/src/db/sessions/repo.d.ts +2 -2
- package/dist/apps/server/src/db/sessions/repo.js +1 -1
- package/dist/apps/server/src/db/sessions/tags.repo.d.ts +2 -2
- package/dist/apps/server/src/db/sqlite.d.ts +44 -0
- package/dist/apps/server/src/db/sqlite.js +83 -0
- package/dist/apps/server/src/index.js +1 -1
- package/dist/apps/server/src/routes/config.js +1 -1
- package/dist/apps/server/src/services/config/index.d.ts +2 -8
- package/dist/apps/server/src/services/config/index.js +3 -39
- package/dist/apps/server/src/services/session/index.js +1 -1
- package/dist/apps/server/src/services/session/notification.js +1 -1
- package/dist/packages/adapters/claude-code/__tests__/default-config.spec.js +15 -0
- package/dist/packages/adapters/claude-code/__tests__/prepare.spec.js +61 -1
- package/dist/packages/adapters/claude-code/__tests__/router-daemon.spec.d.ts +1 -0
- package/dist/packages/adapters/claude-code/__tests__/router-daemon.spec.js +183 -0
- package/dist/packages/adapters/claude-code/src/adapter-config.d.ts +1 -0
- package/dist/packages/adapters/claude-code/src/runtime/init.js +0 -45
- package/dist/packages/adapters/claude-code/src/runtime/prepare.d.ts +6 -9
- package/dist/packages/adapters/claude-code/src/runtime/prepare.js +25 -27
- package/dist/packages/adapters/claude-code/src/runtime/router-daemon.d.ts +19 -0
- package/dist/packages/adapters/claude-code/src/runtime/router-daemon.js +189 -0
- package/dist/packages/channels/lark/src/index.d.ts +4 -4
- package/dist/packages/config/__tests__/load.spec.js +219 -2
- package/dist/packages/config/__tests__/merge.spec.d.ts +1 -0
- package/dist/packages/config/__tests__/merge.spec.js +92 -0
- package/dist/packages/config/src/index.d.ts +1 -0
- package/dist/packages/config/src/index.js +1 -0
- package/dist/packages/config/src/load.d.ts +1 -1
- package/dist/packages/config/src/load.js +167 -53
- package/dist/packages/config/src/merge.d.ts +7 -0
- package/dist/packages/config/src/merge.js +92 -0
- package/dist/packages/tsconfigs/tsconfig.bundler.test.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.bundler.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.bundler.web.test.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.bundler.web.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.node.test.tsbuildinfo +1 -1
- package/dist/packages/tsconfigs/tsconfig.node.tsbuildinfo +1 -1
- package/dist/packages/types/src/config.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/adapter-asset-plan.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/adapter-asset-plan.spec.js +121 -0
- package/dist/packages/workspace-assets/__tests__/bundle.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/bundle.spec.js +61 -0
- package/dist/packages/workspace-assets/__tests__/prompt-selection.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/prompt-selection.spec.js +29 -0
- package/dist/packages/workspace-assets/__tests__/snapshot.d.ts +15 -0
- package/dist/packages/workspace-assets/__tests__/snapshot.js +203 -0
- package/dist/packages/workspace-assets/__tests__/test-helpers.d.ts +2 -0
- package/dist/packages/workspace-assets/__tests__/test-helpers.js +17 -0
- package/dist/packages/workspace-assets/__tests__/workspace-assets.snapshot.spec.d.ts +1 -0
- package/dist/packages/workspace-assets/__tests__/workspace-assets.snapshot.spec.js +172 -0
- package/package.json +1 -1
|
@@ -105,7 +105,7 @@ function createSessionsRepo(db) {
|
|
|
105
105
|
status: status ?? undefined
|
|
106
106
|
};
|
|
107
107
|
const stmt = db.prepare('INSERT INTO sessions (id, parentSessionId, title, createdAt, status) VALUES (?, ?, ?, ?, ?)');
|
|
108
|
-
stmt.run(session.id, session.parentSessionId ?? null, session.title, session.createdAt, session.status);
|
|
108
|
+
stmt.run(session.id, session.parentSessionId ?? null, session.title ?? null, session.createdAt, session.status ?? null);
|
|
109
109
|
return session;
|
|
110
110
|
};
|
|
111
111
|
const setTitle = (id, title) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export declare function createTagsRepo(db:
|
|
1
|
+
import type { SqliteDatabase } from '../sqlite';
|
|
2
|
+
export declare function createTagsRepo(db: SqliteDatabase): {
|
|
3
3
|
replace: (sessionId: string, tags: string[]) => void;
|
|
4
4
|
};
|
|
5
5
|
export type TagsRepo = ReturnType<typeof createTagsRepo>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
type DatabaseSyncOptions = import('node:sqlite').DatabaseSyncOptions;
|
|
2
|
+
type StatementSync = import('node:sqlite').StatementSync;
|
|
3
|
+
export type SqliteBindValue = null | number | bigint | string | NodeJS.ArrayBufferView;
|
|
4
|
+
export interface SqliteRunResult {
|
|
5
|
+
changes: number;
|
|
6
|
+
lastInsertRowid: number | bigint;
|
|
7
|
+
}
|
|
8
|
+
export interface SqliteStatement {
|
|
9
|
+
all: (...params: SqliteBindValue[]) => Record<string, unknown>[];
|
|
10
|
+
get: (...params: SqliteBindValue[]) => Record<string, unknown> | undefined;
|
|
11
|
+
run: (...params: SqliteBindValue[]) => SqliteRunResult;
|
|
12
|
+
}
|
|
13
|
+
type SqliteTransactionFn = (...args: unknown[]) => unknown;
|
|
14
|
+
export interface SqliteDatabase {
|
|
15
|
+
close: () => void;
|
|
16
|
+
exec: (sql: string) => void;
|
|
17
|
+
prepare: (sql: string) => SqliteStatement;
|
|
18
|
+
transaction: <T extends SqliteTransactionFn>(fn: T) => T;
|
|
19
|
+
}
|
|
20
|
+
declare class NodeSqliteStatement implements SqliteStatement {
|
|
21
|
+
private readonly statement;
|
|
22
|
+
constructor(statement: StatementSync);
|
|
23
|
+
all(...params: SqliteBindValue[]): {
|
|
24
|
+
[x: string]: unknown;
|
|
25
|
+
}[];
|
|
26
|
+
get(...params: SqliteBindValue[]): {
|
|
27
|
+
[x: string]: unknown;
|
|
28
|
+
} | undefined;
|
|
29
|
+
run(...params: SqliteBindValue[]): {
|
|
30
|
+
changes: number;
|
|
31
|
+
lastInsertRowid: number | bigint;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
declare class NodeSqliteDatabase implements SqliteDatabase {
|
|
35
|
+
private readonly database;
|
|
36
|
+
private savepointId;
|
|
37
|
+
constructor(database: DatabaseSync);
|
|
38
|
+
close(): void;
|
|
39
|
+
exec(sql: string): void;
|
|
40
|
+
prepare(sql: string): NodeSqliteStatement;
|
|
41
|
+
transaction<T extends SqliteTransactionFn>(fn: T): T;
|
|
42
|
+
}
|
|
43
|
+
export declare function createSqliteDatabase(path: string, options?: DatabaseSyncOptions): NodeSqliteDatabase;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSqliteDatabase = createSqliteDatabase;
|
|
4
|
+
const node_module_1 = require("node:module");
|
|
5
|
+
const require = (0, node_module_1.createRequire)(import.meta.url);
|
|
6
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
7
|
+
function cloneRow(row) {
|
|
8
|
+
return { ...row };
|
|
9
|
+
}
|
|
10
|
+
class NodeSqliteStatement {
|
|
11
|
+
statement;
|
|
12
|
+
constructor(statement) {
|
|
13
|
+
this.statement = statement;
|
|
14
|
+
}
|
|
15
|
+
all(...params) {
|
|
16
|
+
return this.statement
|
|
17
|
+
.all(...params)
|
|
18
|
+
.map(row => cloneRow(row));
|
|
19
|
+
}
|
|
20
|
+
get(...params) {
|
|
21
|
+
const row = this.statement.get(...params);
|
|
22
|
+
return row == null ? undefined : cloneRow(row);
|
|
23
|
+
}
|
|
24
|
+
run(...params) {
|
|
25
|
+
const result = this.statement.run(...params);
|
|
26
|
+
return {
|
|
27
|
+
changes: Number(result.changes),
|
|
28
|
+
lastInsertRowid: result.lastInsertRowid
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class NodeSqliteDatabase {
|
|
33
|
+
database;
|
|
34
|
+
savepointId = 0;
|
|
35
|
+
constructor(database) {
|
|
36
|
+
this.database = database;
|
|
37
|
+
}
|
|
38
|
+
close() {
|
|
39
|
+
if (this.database.isOpen) {
|
|
40
|
+
this.database.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exec(sql) {
|
|
44
|
+
this.database.exec(sql);
|
|
45
|
+
}
|
|
46
|
+
prepare(sql) {
|
|
47
|
+
return new NodeSqliteStatement(this.database.prepare(sql));
|
|
48
|
+
}
|
|
49
|
+
transaction(fn) {
|
|
50
|
+
return ((...args) => {
|
|
51
|
+
const savepointName = this.database.isTransaction
|
|
52
|
+
? `vf_tx_${++this.savepointId}`
|
|
53
|
+
: undefined;
|
|
54
|
+
this.database.exec(savepointName == null ? 'BEGIN' : `SAVEPOINT ${savepointName}`);
|
|
55
|
+
try {
|
|
56
|
+
const result = fn(...args);
|
|
57
|
+
this.database.exec(savepointName == null ? 'COMMIT' : `RELEASE SAVEPOINT ${savepointName}`);
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (savepointName == null) {
|
|
62
|
+
if (this.database.isTransaction) {
|
|
63
|
+
this.database.exec('ROLLBACK');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.database.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
|
68
|
+
this.database.exec(`RELEASE SAVEPOINT ${savepointName}`);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const defaultDatabaseOptions = {
|
|
76
|
+
enableForeignKeyConstraints: false
|
|
77
|
+
};
|
|
78
|
+
function createSqliteDatabase(path, options = {}) {
|
|
79
|
+
return new NodeSqliteDatabase(new DatabaseSync(path, {
|
|
80
|
+
...defaultDatabaseOptions,
|
|
81
|
+
...options
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
@@ -21,7 +21,7 @@ async function init() {
|
|
|
21
21
|
const server = node_http_1.default.createServer((req, res) => {
|
|
22
22
|
void handler(req, res);
|
|
23
23
|
});
|
|
24
|
-
const { projectConfig, userConfig } = await (0, index_js_1.
|
|
24
|
+
const { projectConfig, userConfig } = await (0, index_js_1.loadConfigState)();
|
|
25
25
|
const configs = [projectConfig, userConfig];
|
|
26
26
|
return { app, env, server, configs };
|
|
27
27
|
}
|
|
@@ -96,7 +96,7 @@ function configRouter() {
|
|
|
96
96
|
const router = new router_1.default();
|
|
97
97
|
router.get('/', async (ctx) => {
|
|
98
98
|
try {
|
|
99
|
-
const { workspaceFolder, projectConfig, userConfig, mergedConfig } = await (0, index_js_1.
|
|
99
|
+
const { workspaceFolder, projectConfig, userConfig, mergedConfig } = await (0, index_js_1.loadConfigState)();
|
|
100
100
|
const urls = {
|
|
101
101
|
repo: 'https://github.com/vibe-forge-ai/vibe-forge.ai',
|
|
102
102
|
docs: 'https://github.com/vibe-forge-ai/vibe-forge.ai',
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import type { Config } from '@vibe-forge/types';
|
|
2
2
|
export declare function getWorkspaceFolder(): string;
|
|
3
3
|
export declare function buildConfigJsonVariables(workspaceFolder?: string): Record<string, string | null | undefined>;
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function loadConfigSources(): Promise<{
|
|
4
|
+
export declare function loadConfigState(): Promise<{
|
|
6
5
|
workspaceFolder: string;
|
|
7
6
|
projectConfig: Config | undefined;
|
|
8
7
|
userConfig: Config | undefined;
|
|
9
|
-
|
|
10
|
-
export declare function loadMergedConfig(): Promise<{
|
|
11
|
-
workspaceFolder: string;
|
|
12
|
-
projectConfig: Config | undefined;
|
|
13
|
-
userConfig: Config | undefined;
|
|
14
|
-
mergedConfig: Config;
|
|
8
|
+
mergedConfig: Config | undefined;
|
|
15
9
|
}>;
|
|
@@ -2,61 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getWorkspaceFolder = getWorkspaceFolder;
|
|
4
4
|
exports.buildConfigJsonVariables = buildConfigJsonVariables;
|
|
5
|
-
exports.
|
|
6
|
-
exports.loadConfigSources = loadConfigSources;
|
|
7
|
-
exports.loadMergedConfig = loadMergedConfig;
|
|
5
|
+
exports.loadConfigState = loadConfigState;
|
|
8
6
|
const node_process_1 = require("node:process");
|
|
9
7
|
const config_1 = require("@vibe-forge/config");
|
|
10
|
-
const utils_1 = require("@vibe-forge/utils");
|
|
11
8
|
function getWorkspaceFolder() {
|
|
12
9
|
return node_process_1.env.__VF_PROJECT_WORKSPACE_FOLDER__ ?? (0, node_process_1.cwd)();
|
|
13
10
|
}
|
|
14
11
|
function buildConfigJsonVariables(workspaceFolder = getWorkspaceFolder()) {
|
|
15
12
|
return (0, config_1.buildConfigJsonVariables)(workspaceFolder, node_process_1.env);
|
|
16
13
|
}
|
|
17
|
-
|
|
18
|
-
if (left == null && right == null)
|
|
19
|
-
return undefined;
|
|
20
|
-
return {
|
|
21
|
-
...(left ?? {}),
|
|
22
|
-
...(right ?? {})
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
function mergeConfigs(project, user) {
|
|
26
|
-
return {
|
|
27
|
-
...project,
|
|
28
|
-
...user,
|
|
29
|
-
adapters: (0, utils_1.mergeAdapterConfigs)(project?.adapters, user?.adapters),
|
|
30
|
-
models: mergeRecord(project?.models, user?.models),
|
|
31
|
-
modelServices: mergeRecord(project?.modelServices, user?.modelServices),
|
|
32
|
-
channels: mergeRecord(project?.channels, user?.channels),
|
|
33
|
-
mcpServers: mergeRecord(project?.mcpServers, user?.mcpServers),
|
|
34
|
-
enabledPlugins: mergeRecord(project?.enabledPlugins, user?.enabledPlugins),
|
|
35
|
-
extraKnownMarketplaces: mergeRecord(project?.extraKnownMarketplaces, user?.extraKnownMarketplaces),
|
|
36
|
-
plugins: user?.plugins ?? project?.plugins,
|
|
37
|
-
shortcuts: mergeRecord(project?.shortcuts, user?.shortcuts),
|
|
38
|
-
conversation: mergeRecord(project?.conversation, user?.conversation),
|
|
39
|
-
notifications: mergeRecord(project?.notifications, user?.notifications)
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
async function loadConfigSources() {
|
|
14
|
+
async function loadConfigState() {
|
|
43
15
|
const workspaceFolder = getWorkspaceFolder();
|
|
44
16
|
const [projectConfig, userConfig] = await (0, config_1.loadConfig)({
|
|
45
17
|
cwd: workspaceFolder,
|
|
46
18
|
jsonVariables: buildConfigJsonVariables(workspaceFolder)
|
|
47
19
|
});
|
|
48
|
-
return {
|
|
49
|
-
workspaceFolder,
|
|
50
|
-
projectConfig,
|
|
51
|
-
userConfig
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
async function loadMergedConfig() {
|
|
55
|
-
const { workspaceFolder, projectConfig, userConfig } = await loadConfigSources();
|
|
56
20
|
return {
|
|
57
21
|
workspaceFolder,
|
|
58
22
|
projectConfig,
|
|
59
23
|
userConfig,
|
|
60
|
-
mergedConfig: mergeConfigs(projectConfig, userConfig)
|
|
24
|
+
mergedConfig: (0, config_1.mergeConfigs)(projectConfig, userConfig)
|
|
61
25
|
};
|
|
62
26
|
}
|
|
@@ -80,7 +80,7 @@ async function startAdapterSession(sessionId, options = {}) {
|
|
|
80
80
|
: [resolvedConfig.systemPrompt, options.systemPrompt]
|
|
81
81
|
.filter(Boolean)
|
|
82
82
|
.join('\n\n');
|
|
83
|
-
const { mergedConfig } = await (0, index_js_3.
|
|
83
|
+
const { mergedConfig } = await (0, index_js_3.loadConfigState)().catch(() => ({ mergedConfig: {} }));
|
|
84
84
|
const { modelLanguage } = mergedConfig;
|
|
85
85
|
const languagePrompt = modelLanguage == null
|
|
86
86
|
? undefined
|
|
@@ -36,7 +36,7 @@ async function maybeNotifySession(previousStatus, nextStatus, session) {
|
|
|
36
36
|
const notificationTrigger = toNotificationTrigger(nextStatus);
|
|
37
37
|
if (notificationTrigger == null)
|
|
38
38
|
return;
|
|
39
|
-
const { mergedConfig } = await (0, index_js_1.
|
|
39
|
+
const { mergedConfig } = await (0, index_js_1.loadConfigState)();
|
|
40
40
|
const { notifications, interfaceLanguage } = mergedConfig;
|
|
41
41
|
if (notifications?.disabled === true)
|
|
42
42
|
return;
|
|
@@ -94,6 +94,21 @@ describe('generateDefaultCCRConfigJSON', () => {
|
|
|
94
94
|
const config = JSON.parse(raw);
|
|
95
95
|
expect(config.API_TIMEOUT_MS).toBe(120000);
|
|
96
96
|
});
|
|
97
|
+
it('preserves explicit CCR router network options', () => {
|
|
98
|
+
const raw = generateDefaultCCRConfigJSON({
|
|
99
|
+
cwd: '/tmp/project',
|
|
100
|
+
userConfig: baseUserConfig,
|
|
101
|
+
adapterOptions: {
|
|
102
|
+
ccrOptions: {
|
|
103
|
+
PORT: '4123',
|
|
104
|
+
APIKEY: 'router-key'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const config = JSON.parse(raw);
|
|
109
|
+
expect(config.PORT).toBe('4123');
|
|
110
|
+
expect(config.APIKEY).toBe('router-key');
|
|
111
|
+
});
|
|
97
112
|
it('adds a maxtoken transformer for model service maxOutputTokens without clobbering existing transformers', () => {
|
|
98
113
|
const raw = generateDefaultCCRConfigJSON({
|
|
99
114
|
cwd: '/tmp/project',
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { NATIVE_HOOK_BRIDGE_ADAPTER_ENV } from '@vibe-forge/hooks';
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
ensureClaudeCodeRouterReady: vi.fn()
|
|
5
|
+
}));
|
|
6
|
+
vi.mock('../src/runtime/router-daemon', () => ({
|
|
7
|
+
ensureClaudeCodeRouterReady: mocks.ensureClaudeCodeRouterReady
|
|
8
|
+
}));
|
|
3
9
|
import { prepareClaudeExecution } from '../src/runtime/prepare';
|
|
4
10
|
const sessionId = '6cd99e50-d3be-4070-b408-8133cfc42750';
|
|
5
11
|
const createCtx = (resumeState) => ({
|
|
@@ -19,6 +25,15 @@ const createCtx = (resumeState) => ({
|
|
|
19
25
|
configs: [undefined, undefined]
|
|
20
26
|
});
|
|
21
27
|
describe('prepareClaudeExecution', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
mocks.ensureClaudeCodeRouterReady.mockResolvedValue({
|
|
31
|
+
host: '127.0.0.1',
|
|
32
|
+
port: 4123,
|
|
33
|
+
apiKey: 'router-key',
|
|
34
|
+
apiTimeoutMs: 120000
|
|
35
|
+
});
|
|
36
|
+
});
|
|
22
37
|
it('falls back to create mode when no resume state exists', async () => {
|
|
23
38
|
const result = await prepareClaudeExecution(createCtx(), {
|
|
24
39
|
type: 'resume',
|
|
@@ -28,6 +43,7 @@ describe('prepareClaudeExecution', () => {
|
|
|
28
43
|
onEvent: vi.fn()
|
|
29
44
|
});
|
|
30
45
|
expect(result.executionType).toBe('create');
|
|
46
|
+
expect(result.cliPath).toBe('claude');
|
|
31
47
|
expect(result.args).toContain('--session-id');
|
|
32
48
|
expect(result.args).toContain(sessionId);
|
|
33
49
|
expect(result.args).not.toContain('--resume');
|
|
@@ -41,10 +57,54 @@ describe('prepareClaudeExecution', () => {
|
|
|
41
57
|
onEvent: vi.fn()
|
|
42
58
|
});
|
|
43
59
|
expect(result.executionType).toBe('resume');
|
|
60
|
+
expect(result.cliPath).toBe('claude');
|
|
44
61
|
expect(result.args).toContain('--resume');
|
|
45
62
|
expect(result.args).toContain(sessionId);
|
|
46
63
|
expect(result.args).not.toContain('--session-id');
|
|
47
64
|
});
|
|
65
|
+
it('routes service-qualified models through the reusable CCR daemon via settings env only', async () => {
|
|
66
|
+
const ctx = createCtx();
|
|
67
|
+
const result = await prepareClaudeExecution(ctx, {
|
|
68
|
+
type: 'create',
|
|
69
|
+
runtime: 'server',
|
|
70
|
+
sessionId,
|
|
71
|
+
model: 'gpt-responses,gpt-5.2-codex-2026-01-14',
|
|
72
|
+
onEvent: vi.fn()
|
|
73
|
+
});
|
|
74
|
+
expect(result.cliPath).toBe('claude');
|
|
75
|
+
expect(result.args).toContain('--model');
|
|
76
|
+
expect(result.args).toContain('gpt-responses,gpt-5.2-codex-2026-01-14');
|
|
77
|
+
expect(result.env.ANTHROPIC_BASE_URL).toBeUndefined();
|
|
78
|
+
expect(result.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined();
|
|
79
|
+
expect(mocks.ensureClaudeCodeRouterReady).toHaveBeenCalledWith(ctx);
|
|
80
|
+
expect(ctx.cache.set).toHaveBeenCalledWith('adapter.claude-code.settings', expect.objectContaining({
|
|
81
|
+
env: expect.objectContaining({
|
|
82
|
+
ANTHROPIC_BASE_URL: 'http://127.0.0.1:4123',
|
|
83
|
+
ANTHROPIC_AUTH_TOKEN: 'router-key',
|
|
84
|
+
ANTHROPIC_API_KEY: '',
|
|
85
|
+
API_TIMEOUT_MS: '120000'
|
|
86
|
+
})
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
it('keeps native Claude execution untouched for non-CCR models', async () => {
|
|
90
|
+
const ctx = createCtx();
|
|
91
|
+
await prepareClaudeExecution(ctx, {
|
|
92
|
+
type: 'create',
|
|
93
|
+
runtime: 'server',
|
|
94
|
+
sessionId,
|
|
95
|
+
model: 'claude-sonnet-4-20250514',
|
|
96
|
+
onEvent: vi.fn()
|
|
97
|
+
});
|
|
98
|
+
expect(mocks.ensureClaudeCodeRouterReady).not.toHaveBeenCalled();
|
|
99
|
+
expect(ctx.cache.set).toHaveBeenCalledWith('adapter.claude-code.settings', expect.objectContaining({
|
|
100
|
+
env: expect.not.objectContaining({
|
|
101
|
+
ANTHROPIC_BASE_URL: expect.any(String),
|
|
102
|
+
ANTHROPIC_AUTH_TOKEN: expect.any(String),
|
|
103
|
+
ANTHROPIC_API_KEY: expect.any(String),
|
|
104
|
+
API_TIMEOUT_MS: expect.any(String)
|
|
105
|
+
})
|
|
106
|
+
}));
|
|
107
|
+
});
|
|
48
108
|
it('injects the shared native hook bridge adapter env when claude native hooks are enabled', async () => {
|
|
49
109
|
const ctx = createCtx();
|
|
50
110
|
ctx.env.__VF_PROJECT_AI_CLAUDE_NATIVE_HOOKS_AVAILABLE__ = '1';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { generateDefaultCCRConfigJSON } from '../src/ccr/default-config';
|
|
6
|
+
import { ensureClaudeCodeRouterReady } from '../src/runtime/router-daemon';
|
|
7
|
+
const tempDirs = [];
|
|
8
|
+
const createWorkspace = async () => {
|
|
9
|
+
const dir = await mkdtemp(join(tmpdir(), 'claude-code-router-'));
|
|
10
|
+
tempDirs.push(dir);
|
|
11
|
+
return dir;
|
|
12
|
+
};
|
|
13
|
+
const createCtx = (cwd, overrides) => ({
|
|
14
|
+
cwd,
|
|
15
|
+
env: {
|
|
16
|
+
TEST_ENV: 'router'
|
|
17
|
+
},
|
|
18
|
+
configs: [undefined, {
|
|
19
|
+
defaultModelService: 'gateway',
|
|
20
|
+
defaultModel: 'gpt-5.4',
|
|
21
|
+
modelServices: {
|
|
22
|
+
gateway: {
|
|
23
|
+
apiBaseUrl: 'https://example.test/chat/completions',
|
|
24
|
+
apiKey: 'provider-key',
|
|
25
|
+
models: ['gpt-5.4'],
|
|
26
|
+
timeoutMs: 120000
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
adapters: {
|
|
30
|
+
'claude-code': {
|
|
31
|
+
ccrOptions: {
|
|
32
|
+
PORT: '4123',
|
|
33
|
+
APIKEY: 'router-key'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
...(overrides ?? {})
|
|
38
|
+
}]
|
|
39
|
+
});
|
|
40
|
+
const getRouterPaths = (cwd) => ({
|
|
41
|
+
mockHome: join(cwd, '.ai', '.mock'),
|
|
42
|
+
routerHome: join(cwd, '.ai', '.mock', '.claude-code-router'),
|
|
43
|
+
configPath: join(cwd, '.ai', '.mock', '.claude-code-router', 'config.json'),
|
|
44
|
+
pidPath: join(cwd, '.ai', '.mock', '.claude-code-router', '.claude-code-router.pid')
|
|
45
|
+
});
|
|
46
|
+
describe('ensureClaudeCodeRouterReady', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
vi.clearAllMocks();
|
|
49
|
+
});
|
|
50
|
+
afterEach(async () => {
|
|
51
|
+
await Promise.all(tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })));
|
|
52
|
+
});
|
|
53
|
+
it('starts a detached router daemon when no pid file exists', async () => {
|
|
54
|
+
const workspace = await createWorkspace();
|
|
55
|
+
const ctx = createCtx(workspace);
|
|
56
|
+
const spawnDetached = vi.fn(async () => undefined);
|
|
57
|
+
const waitForReady = vi.fn(async () => undefined);
|
|
58
|
+
const connection = await ensureClaudeCodeRouterReady(ctx, {
|
|
59
|
+
resolveCliPath: () => '/bin/sh',
|
|
60
|
+
isProcessAlive: vi.fn(() => false),
|
|
61
|
+
spawnDetached,
|
|
62
|
+
stopProcess: vi.fn(async () => undefined),
|
|
63
|
+
waitForReady
|
|
64
|
+
});
|
|
65
|
+
const { configPath, mockHome } = getRouterPaths(workspace);
|
|
66
|
+
const config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
67
|
+
expect(connection).toEqual({
|
|
68
|
+
host: '127.0.0.1',
|
|
69
|
+
port: 4123,
|
|
70
|
+
apiKey: 'router-key',
|
|
71
|
+
apiTimeoutMs: 120000
|
|
72
|
+
});
|
|
73
|
+
expect(config.PORT).toBe('4123');
|
|
74
|
+
expect(config.APIKEY).toBe('router-key');
|
|
75
|
+
expect(config.API_TIMEOUT_MS).toBe(120000);
|
|
76
|
+
expect(spawnDetached).toHaveBeenCalledWith({
|
|
77
|
+
cliPath: '/bin/sh',
|
|
78
|
+
cwd: workspace,
|
|
79
|
+
env: expect.objectContaining({
|
|
80
|
+
TEST_ENV: 'router',
|
|
81
|
+
HOME: mockHome
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
expect(waitForReady).toHaveBeenCalledWith(4123, 15000);
|
|
85
|
+
});
|
|
86
|
+
it('restarts the daemon when the pid file is stale', async () => {
|
|
87
|
+
const workspace = await createWorkspace();
|
|
88
|
+
const ctx = createCtx(workspace);
|
|
89
|
+
const { pidPath, routerHome } = getRouterPaths(workspace);
|
|
90
|
+
const spawnDetached = vi.fn(async () => undefined);
|
|
91
|
+
const isProcessAlive = vi.fn(() => false);
|
|
92
|
+
await mkdir(routerHome, { recursive: true });
|
|
93
|
+
await writeFile(pidPath, '4321', 'utf8');
|
|
94
|
+
await ensureClaudeCodeRouterReady(ctx, {
|
|
95
|
+
resolveCliPath: () => '/bin/sh',
|
|
96
|
+
isProcessAlive,
|
|
97
|
+
spawnDetached,
|
|
98
|
+
stopProcess: vi.fn(async () => undefined),
|
|
99
|
+
waitForReady: vi.fn(async () => undefined)
|
|
100
|
+
});
|
|
101
|
+
await expect(readFile(pidPath, 'utf8')).rejects.toThrow();
|
|
102
|
+
expect(isProcessAlive).toHaveBeenCalledWith(4321);
|
|
103
|
+
expect(spawnDetached).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
it('reuses a live daemon when config is unchanged', async () => {
|
|
106
|
+
const workspace = await createWorkspace();
|
|
107
|
+
const ctx = createCtx(workspace);
|
|
108
|
+
const { configPath, pidPath, routerHome } = getRouterPaths(workspace);
|
|
109
|
+
const configText = generateDefaultCCRConfigJSON({
|
|
110
|
+
cwd: workspace,
|
|
111
|
+
userConfig: ctx.configs[1],
|
|
112
|
+
adapterOptions: ctx.configs[1].adapters['claude-code']
|
|
113
|
+
});
|
|
114
|
+
await mkdir(routerHome, { recursive: true });
|
|
115
|
+
await writeFile(configPath, configText, 'utf8');
|
|
116
|
+
await writeFile(pidPath, '2468', 'utf8');
|
|
117
|
+
const spawnDetached = vi.fn(async () => undefined);
|
|
118
|
+
const stopProcess = vi.fn(async () => undefined);
|
|
119
|
+
const waitForReady = vi.fn(async () => undefined);
|
|
120
|
+
const connection = await ensureClaudeCodeRouterReady(ctx, {
|
|
121
|
+
resolveCliPath: () => '/bin/sh',
|
|
122
|
+
isProcessAlive: vi.fn(() => true),
|
|
123
|
+
spawnDetached,
|
|
124
|
+
stopProcess,
|
|
125
|
+
waitForReady
|
|
126
|
+
});
|
|
127
|
+
expect(connection.port).toBe(4123);
|
|
128
|
+
expect(stopProcess).not.toHaveBeenCalled();
|
|
129
|
+
expect(spawnDetached).not.toHaveBeenCalled();
|
|
130
|
+
expect(waitForReady).toHaveBeenCalledWith(4123, 15000);
|
|
131
|
+
await expect(readFile(pidPath, 'utf8')).resolves.toBe('2468');
|
|
132
|
+
});
|
|
133
|
+
it('restarts a live daemon when config changes', async () => {
|
|
134
|
+
const workspace = await createWorkspace();
|
|
135
|
+
const ctx = createCtx(workspace);
|
|
136
|
+
const { configPath, pidPath, routerHome } = getRouterPaths(workspace);
|
|
137
|
+
const oldConfigText = generateDefaultCCRConfigJSON({
|
|
138
|
+
cwd: workspace,
|
|
139
|
+
userConfig: {
|
|
140
|
+
...ctx.configs[1],
|
|
141
|
+
adapters: {
|
|
142
|
+
'claude-code': {
|
|
143
|
+
ccrOptions: {
|
|
144
|
+
PORT: '4001',
|
|
145
|
+
APIKEY: 'old-router-key'
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
adapterOptions: {
|
|
151
|
+
ccrOptions: {
|
|
152
|
+
PORT: '4001',
|
|
153
|
+
APIKEY: 'old-router-key'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
await mkdir(routerHome, { recursive: true });
|
|
158
|
+
await writeFile(configPath, oldConfigText, 'utf8');
|
|
159
|
+
await writeFile(pidPath, '1357', 'utf8');
|
|
160
|
+
const spawnDetached = vi.fn(async () => undefined);
|
|
161
|
+
const stopProcess = vi.fn(async () => undefined);
|
|
162
|
+
const waitForReady = vi.fn(async () => undefined);
|
|
163
|
+
const connection = await ensureClaudeCodeRouterReady(ctx, {
|
|
164
|
+
resolveCliPath: () => '/bin/sh',
|
|
165
|
+
isProcessAlive: vi.fn(() => true),
|
|
166
|
+
spawnDetached,
|
|
167
|
+
stopProcess,
|
|
168
|
+
waitForReady
|
|
169
|
+
});
|
|
170
|
+
expect(connection).toEqual({
|
|
171
|
+
host: '127.0.0.1',
|
|
172
|
+
port: 4123,
|
|
173
|
+
apiKey: 'router-key',
|
|
174
|
+
apiTimeoutMs: 120000
|
|
175
|
+
});
|
|
176
|
+
expect(stopProcess).toHaveBeenCalledWith(1357);
|
|
177
|
+
expect(spawnDetached).toHaveBeenCalledTimes(1);
|
|
178
|
+
expect(stopProcess.mock.invocationCallOrder[0]).toBeLessThan(spawnDetached.mock.invocationCallOrder[0]);
|
|
179
|
+
expect(waitForReady).toHaveBeenCalledWith(4123, 15000);
|
|
180
|
+
await expect(readFile(pidPath, 'utf8')).rejects.toThrow();
|
|
181
|
+
expect(await readFile(configPath, 'utf8')).not.toBe(oldConfigText);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -1,49 +1,4 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { dirname, resolve } from 'node:path';
|
|
5
|
-
import process from 'node:process';
|
|
6
|
-
import { omitAdapterCommonConfig } from '@vibe-forge/utils';
|
|
7
|
-
import { generateDefaultCCRConfigJSON } from '../ccr/default-config';
|
|
8
|
-
import { resolveAdapterCliPath } from '../ccr/paths';
|
|
9
1
|
import { ensureClaudeNativeHooksInstalled } from './native-hooks';
|
|
10
2
|
export const initClaudeCodeAdapter = async (ctx) => {
|
|
11
|
-
const { cwd, env, configs: [config, userConfig] } = ctx;
|
|
12
|
-
const adapterOptions = omitAdapterCommonConfig({
|
|
13
|
-
...(config?.adapters?.['claude-code'] ?? {}),
|
|
14
|
-
...(userConfig?.adapters?.['claude-code'] ?? {})
|
|
15
|
-
});
|
|
16
|
-
const configPath = resolve(cwd, '.ai/.mock/.claude-code-router/config.json');
|
|
17
|
-
await mkdir(dirname(configPath), { recursive: true });
|
|
18
|
-
await writeFile(configPath, generateDefaultCCRConfigJSON({
|
|
19
|
-
cwd,
|
|
20
|
-
config,
|
|
21
|
-
userConfig,
|
|
22
|
-
adapterOptions
|
|
23
|
-
}));
|
|
24
3
|
await ensureClaudeNativeHooksInstalled(ctx);
|
|
25
|
-
const homePath = resolve(cwd, '.ai/.mock');
|
|
26
|
-
const cliPath = resolveAdapterCliPath();
|
|
27
|
-
if (!existsSync(cliPath)) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
await new Promise((resolvePromise, reject) => {
|
|
31
|
-
const proc = spawn(cliPath, ['restart'], {
|
|
32
|
-
env: {
|
|
33
|
-
...process.env,
|
|
34
|
-
...env,
|
|
35
|
-
HOME: homePath
|
|
36
|
-
},
|
|
37
|
-
cwd
|
|
38
|
-
});
|
|
39
|
-
proc.on('exit', (code) => {
|
|
40
|
-
if (code === 0) {
|
|
41
|
-
resolvePromise(null);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
reject(new Error(`ccr restart failed with code ${code}`));
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
proc.on('error', reject);
|
|
48
|
-
});
|
|
49
4
|
};
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import type { AdapterCtx, AdapterQueryOptions } from '@vibe-forge/types';
|
|
2
|
-
|
|
2
|
+
interface PreparedClaudeExecution {
|
|
3
3
|
cliPath: string;
|
|
4
4
|
args: string[];
|
|
5
|
-
env:
|
|
6
|
-
__VF_VIBE_FORGE_CLAUDE_HOOKS_ACTIVE__?: string | undefined;
|
|
7
|
-
__VF_VIBE_FORGE_HOOK_BRIDGE_ADAPTER__?: string | undefined;
|
|
8
|
-
__VF_CLAUDE_HOOK_RUNTIME__?: import("@vibe-forge/types").TaskRuntime | undefined;
|
|
9
|
-
__VF_CLAUDE_TASK_SESSION_ID__?: string | undefined;
|
|
10
|
-
};
|
|
5
|
+
env: Record<string, string | null | undefined>;
|
|
11
6
|
cwd: string;
|
|
12
7
|
sessionId: string;
|
|
13
|
-
executionType:
|
|
14
|
-
}
|
|
8
|
+
executionType: 'create' | 'resume';
|
|
9
|
+
}
|
|
10
|
+
export declare const prepareClaudeExecution: (ctx: AdapterCtx, options: AdapterQueryOptions) => Promise<PreparedClaudeExecution>;
|
|
11
|
+
export {};
|