codelark 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codelark",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Installable CodeLark bridge with local setup UI and background service",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/huiyeruzhou/codelark#readme",
@@ -37,6 +37,7 @@
37
37
  "docs:preview": "vitepress preview docs --host 127.0.0.1",
38
38
  "logs:analyze": "node scripts/analyze-bridge-log.js",
39
39
  "real:setup-wizard:e2e": "tsx scripts/setup-wizard-real-e2e.ts",
40
+ "real:setup-wizard:wizard-e2e": "tsx scripts/setup-wizard-real-wizard-e2e.ts",
40
41
  "real:feishu:e2e": "tsx scripts/real-feishu-e2e.ts",
41
42
  "run": "node dist/cli.mjs run",
42
43
  "test": "node scripts/run-tests.js",
@@ -50,11 +51,14 @@
50
51
  "@larksuite/cli": "^1.0.45",
51
52
  "@larksuiteoapi/node-sdk": "^1.66.1",
52
53
  "@openai/codex-sdk": "^0.130.0",
54
+ "commander": "^7.2.0",
55
+ "config": "^4.4.1",
53
56
  "https-proxy-agent": "^9.0.0",
54
57
  "markdown-it": "^14.1.1",
55
58
  "pino": "^10.3.1",
56
- "qrcode": "^1.5.4",
57
- "ws": "^8.18.0"
59
+ "smol-toml": "^1.6.1",
60
+ "ws": "^8.18.0",
61
+ "zod": "^4.4.3"
58
62
  },
59
63
  "devDependencies": {
60
64
  "@types/markdown-it": "^14.1.2",
@@ -86,7 +86,7 @@
86
86
  "type": "object",
87
87
  "properties": {
88
88
  "provider": {
89
- "enum": ["pty", "sdk"]
89
+ "enum": ["sdk", "pty", "tmux"]
90
90
  },
91
91
  "executable": {
92
92
  "enum": ["claude", "ccr"]
@@ -97,6 +97,9 @@
97
97
  "permissionMode": {
98
98
  "enum": ["default", "acceptEdits", "bypassPermissions", "plan"]
99
99
  },
100
+ "reasoningEffort": {
101
+ "enum": ["low", "medium", "high", "xhigh", "max"]
102
+ },
100
103
  "idleTimeoutMinutes": {
101
104
  "type": "integer",
102
105
  "minimum": 0
@@ -202,7 +202,7 @@
202
202
  "type": "string"
203
203
  },
204
204
  "provider": {
205
- "enum": ["pty", "sdk"]
205
+ "enum": ["sdk", "pty", "tmux"]
206
206
  },
207
207
  "permissionMode": {
208
208
  "type": "string"
package/scripts/build.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  findMissingPackageJsonRuntimeDependencies,
3
3
  formatMissingRuntimeDependenciesMessage,
4
4
  } from './build-preflight.js';
5
+ import { copyFile, mkdir } from 'node:fs/promises';
5
6
 
6
7
  const nodeMajor = Number((process.versions.node || '0').split('.')[0]);
7
8
  if (!Number.isFinite(nodeMajor) || nodeMajor < 24) {
@@ -49,5 +50,7 @@ async function build(entryPoint, outfile) {
49
50
  await build('src/entrypoints/daemon.ts', 'dist/daemon.mjs');
50
51
  await build('src/operator-ui/server.ts', 'dist/ui-server.mjs');
51
52
  await build('src/entrypoints/cli.ts', 'dist/cli.mjs');
53
+ await mkdir('dist', { recursive: true });
54
+ await copyFile('src/configuration/defaults.toml', 'dist/defaults.toml');
52
55
 
53
- console.log('Built dist/daemon.mjs, dist/ui-server.mjs, dist/cli.mjs');
56
+ console.log('Built dist/daemon.mjs, dist/ui-server.mjs, dist/cli.mjs, dist/defaults.toml');
@@ -34,6 +34,10 @@ const files = packEntries.flatMap((entry) => entry.files?.map((file) => file.pat
34
34
  const forbiddenMatches = files.filter((file) =>
35
35
  forbiddenFiles.has(file) || forbiddenPrefixes.some((prefix) => file.startsWith(prefix)),
36
36
  );
37
+ const requiredFiles = new Set([
38
+ 'dist/defaults.toml',
39
+ ]);
40
+ const missingRequiredFiles = [...requiredFiles].filter((file) => !files.includes(file));
37
41
 
38
42
  if (forbiddenMatches.length > 0) {
39
43
  console.error('Unexpected files would be included in the npm package:');
@@ -43,4 +47,12 @@ if (forbiddenMatches.length > 0) {
43
47
  process.exit(1);
44
48
  }
45
49
 
50
+ if (missingRequiredFiles.length > 0) {
51
+ console.error('Required files are missing from the npm package:');
52
+ for (const file of missingRequiredFiles) {
53
+ console.error(`- ${file}`);
54
+ }
55
+ process.exit(1);
56
+ }
57
+
46
58
  console.log(`npm package dry-run passed: ${files.length} files, no docs or example env files included.`);
package/scripts/daemon.sh CHANGED
@@ -115,9 +115,6 @@ case "${1:-help}" in
115
115
  exit 1
116
116
  fi
117
117
 
118
- # Source config.env BEFORE clean_env so CODELARK_* flags are available.
119
- [ -f "$CODELARK_HOME/config.env" ] && set -a && source "$CODELARK_HOME/config.env" && set +a
120
-
121
118
  clean_env
122
119
  echo "Starting bridge..."
123
120
  supervisor_start
package/scripts/doctor.sh CHANGED
@@ -1,9 +1,13 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
- CODELARK_HOME="${CODELARK_HOME:-${CODELARK_HOME:-$HOME/.codelark}}"
4
- CONFIG_FILE="$CODELARK_HOME/config.env"
3
+
4
+ CODELARK_HOME="${CODELARK_HOME:-$HOME/.codelark}"
5
+ CONFIG_TOML="$CODELARK_HOME/config.toml"
6
+ LEGACY_CONFIG_ENV="$CODELARK_HOME/config.env"
7
+ LEGACY_CONFIG_JSON="$CODELARK_HOME/config.json"
5
8
  PID_FILE="$CODELARK_HOME/runtime/bridge.pid"
6
9
  LOG_FILE="$CODELARK_HOME/logs/bridge.log"
10
+ SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
11
 
8
12
  PASS=0
9
13
  FAIL=0
@@ -20,60 +24,73 @@ check() {
20
24
  fi
21
25
  }
22
26
 
27
+ toml_value() {
28
+ local key="$1"
29
+ awk -F= -v key="$key" '
30
+ $1 ~ "^[[:space:]]*" key "[[:space:]]*$" {
31
+ value=$2
32
+ sub(/^[[:space:]]*/, "", value)
33
+ sub(/[[:space:]]*$/, "", value)
34
+ gsub(/^"/, "", value)
35
+ gsub(/"$/, "", value)
36
+ print value
37
+ exit
38
+ }
39
+ ' "$CONFIG_TOML" 2>/dev/null || true
40
+ }
41
+
42
+ toml_bool() {
43
+ local key="$1"
44
+ toml_value "$key" | tr '[:upper:]' '[:lower:]'
45
+ }
46
+
23
47
  # --- Node.js version ---
24
48
  if command -v node &>/dev/null; then
25
49
  NODE_VER=$(node -v | sed 's/v//' | cut -d. -f1)
26
- if [ "$NODE_VER" -ge 20 ] 2>/dev/null; then
27
- check "Node.js >= 20 (found v$(node -v | sed 's/v//'))" 0
50
+ if [ "$NODE_VER" -ge 24 ] 2>/dev/null; then
51
+ check "Node.js >= 24 (found v$(node -v | sed 's/v//'))" 0
28
52
  else
29
- check "Node.js >= 20 (found v$(node -v | sed 's/v//'), need >= 20)" 1
53
+ check "Node.js >= 24 (found v$(node -v | sed 's/v//'), need >= 24)" 1
30
54
  fi
31
55
  else
32
56
  check "Node.js installed" 1
33
57
  fi
34
58
 
35
- # --- Helper: read a value from config.env ---
36
- get_config() { grep "^$1=" "$CONFIG_FILE" 2>/dev/null | head -1 | cut -d= -f2- | sed 's/^["'"'"']//;s/["'"'"']$//'; }
37
-
38
- # --- Read runtime setting ---
39
- SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
40
- CODELARK_RUNTIME=$(get_config CODELARK_RUNTIME)
41
- CODELARK_RUNTIME="codex"
42
- echo "Runtime: $CODELARK_RUNTIME"
59
+ # --- Runtime setting ---
60
+ CODELARK_AGENT="$(toml_value agent)"
61
+ CODELARK_AGENT="${CODELARK_AGENT:-codex}"
62
+ echo "Runtime agent: $CODELARK_AGENT"
43
63
  echo ""
44
64
 
45
65
  # --- Codex checks ---
46
- if command -v codex &>/dev/null; then
47
- CODEX_VER=$(codex --version 2>/dev/null || echo "unknown")
48
- check "Codex CLI available (${CODEX_VER})" 0
49
- else
50
- check "Codex CLI available (not found in PATH)" 1
51
- fi
66
+ if command -v codex &>/dev/null; then
67
+ CODEX_VER=$(codex --version 2>/dev/null || echo "unknown")
68
+ check "Codex CLI available (${CODEX_VER})" 0
69
+ else
70
+ check "Codex CLI available (not found in PATH)" 1
71
+ fi
52
72
 
53
- # Check @openai/codex-sdk
54
- CODEX_SDK="$SKILL_DIR/node_modules/@openai/codex-sdk"
55
- if [ -d "$CODEX_SDK" ]; then
56
- check "@openai/codex-sdk installed" 0
57
- else
58
- check "@openai/codex-sdk installed (not found — run 'npm install' in $SKILL_DIR)" 1
59
- fi
73
+ CODEX_SDK="$SKILL_DIR/node_modules/@openai/codex-sdk"
74
+ if [ -d "$CODEX_SDK" ]; then
75
+ check "@openai/codex-sdk installed" 0
76
+ else
77
+ check "@openai/codex-sdk installed (not found; run 'npm install' in $SKILL_DIR)" 1
78
+ fi
60
79
 
61
- # Check Codex auth: any of CODELARK_CODEX_API_KEY / CODEX_API_KEY / OPENAI_API_KEY,
62
- # or `codex auth status` showing logged-in (interactive login).
63
- CODEX_AUTH=1
64
- if [ -n "${CODELARK_CODEX_API_KEY:-}" ] || [ -n "${CODEX_API_KEY:-}" ] || [ -n "${OPENAI_API_KEY:-}" ]; then
80
+ CODEX_AUTH=1
81
+ if [ -n "${CODELARK_CODEX_API_KEY:-}" ] || [ -n "${CODEX_API_KEY:-}" ] || [ -n "${OPENAI_API_KEY:-}" ]; then
82
+ CODEX_AUTH=0
83
+ elif command -v codex &>/dev/null; then
84
+ CODEX_AUTH_OUT=$(codex auth status 2>&1 || true)
85
+ if echo "$CODEX_AUTH_OUT" | grep -qiE 'logged.in|authenticated'; then
65
86
  CODEX_AUTH=0
66
- elif command -v codex &>/dev/null; then
67
- CODEX_AUTH_OUT=$(codex auth status 2>&1 || true)
68
- if echo "$CODEX_AUTH_OUT" | grep -qiE 'logged.in|authenticated'; then
69
- CODEX_AUTH=0
70
- fi
71
- fi
72
- if [ "$CODEX_AUTH" = "0" ]; then
73
- check "Codex auth available (API key or login)" 0
74
- else
75
- check "Codex auth available (set OPENAI_API_KEY or run 'codex auth login')" 1
76
87
  fi
88
+ fi
89
+ if [ "$CODEX_AUTH" = "0" ]; then
90
+ check "Codex auth available (API key or login)" 0
91
+ else
92
+ check "Codex auth available (set OPENAI_API_KEY or run 'codex auth login')" 1
93
+ fi
77
94
 
78
95
  # --- dist/daemon.mjs freshness ---
79
96
  DAEMON_MJS="$SKILL_DIR/dist/daemon.mjs"
@@ -85,57 +102,60 @@ if [ -f "$DAEMON_MJS" ]; then
85
102
  check "dist/daemon.mjs is stale (src changed, run 'npm run build')" 1
86
103
  fi
87
104
  else
88
- check "dist/daemon.mjs exists (not built run 'npm run build')" 1
105
+ check "dist/daemon.mjs exists (not built; run 'npm run build')" 1
89
106
  fi
90
107
 
91
- # --- config.env exists ---
92
- if [ -f "$CONFIG_FILE" ]; then
93
- check "config.env exists" 0
108
+ # --- Config files ---
109
+ if [ -f "$CONFIG_TOML" ]; then
110
+ check "config.toml exists" 0
94
111
  else
95
- check "config.env exists ($CONFIG_FILE not found)" 1
112
+ if [ -f "$LEGACY_CONFIG_JSON" ] || [ -f "$LEGACY_CONFIG_ENV" ]; then
113
+ check "config.toml exists (legacy config will migrate on next startup)" 0
114
+ else
115
+ check "config.toml exists ($CONFIG_TOML not found)" 1
116
+ fi
96
117
  fi
97
118
 
98
- # --- config.env permissions ---
99
- if [ -f "$CONFIG_FILE" ]; then
100
- PERMS=$(stat -f "%Lp" "$CONFIG_FILE" 2>/dev/null || stat -c "%a" "$CONFIG_FILE" 2>/dev/null || echo "unknown")
119
+ if [ -f "$CONFIG_TOML" ]; then
120
+ PERMS=$(stat -f "%Lp" "$CONFIG_TOML" 2>/dev/null || stat -c "%a" "$CONFIG_TOML" 2>/dev/null || echo "unknown")
101
121
  if [ "$PERMS" = "600" ]; then
102
- check "config.env permissions are 600" 0
122
+ check "config.toml permissions are 600" 0
103
123
  else
104
- check "config.env permissions are 600 (currently $PERMS)" 1
124
+ check "config.toml permissions are 600 (currently $PERMS)" 1
105
125
  fi
106
126
  fi
107
127
 
108
- # --- Load config for channel checks ---
109
- if [ -f "$CONFIG_FILE" ]; then
110
- CODELARK_CHANNELS=$(get_config CODELARK_ENABLED_CHANNELS)
111
-
112
- # --- Feishu ---
113
- if echo "$CODELARK_CHANNELS" | grep -q feishu; then
114
- FS_APP_ID=$(get_config CODELARK_FEISHU_APP_ID)
115
- FS_SECRET=$(get_config CODELARK_FEISHU_APP_SECRET)
116
- FS_SITE=$(get_config CODELARK_FEISHU_SITE)
117
- case "$FS_SITE" in
118
- lark|*open.larksuite.com*)
119
- FS_DOMAIN="https://open.larksuite.com"
120
- ;;
121
- *)
122
- FS_DOMAIN="https://open.feishu.cn"
123
- ;;
124
- esac
125
- if [ -n "$FS_APP_ID" ] && [ -n "$FS_SECRET" ]; then
126
- FEISHU_RESULT=$(curl -s --max-time 5 -X POST "${FS_DOMAIN}/open-apis/auth/v3/tenant_access_token/internal" \
127
- -H "Content-Type: application/json" \
128
- -d "{\"app_id\":\"${FS_APP_ID}\",\"app_secret\":\"${FS_SECRET}\"}" 2>/dev/null || echo '{"code":1}')
129
- if echo "$FEISHU_RESULT" | grep -q '"code"[[:space:]]*:[[:space:]]*0'; then
130
- check "Feishu app credentials are valid" 0
131
- else
132
- check "Feishu app credentials are valid (token request failed)" 1
133
- fi
128
+ if [ -f "$LEGACY_CONFIG_ENV" ] || [ -f "$LEGACY_CONFIG_JSON" ]; then
129
+ check "legacy config.env/config.json are migration inputs only" 0
130
+ echo " Legacy config files are not sourced by daemon scripts; startup migration writes config.toml."
131
+ fi
132
+
133
+ # --- Feishu credentials, best-effort from home TOML ---
134
+ CHANNEL_ENABLED="$(toml_bool enabled)"
135
+ if [ -f "$CONFIG_TOML" ] && [ "$CHANNEL_ENABLED" = "true" ]; then
136
+ FS_APP_ID="$(toml_value app_id)"
137
+ FS_SECRET="$(toml_value app_secret)"
138
+ FS_SITE="$(toml_value site)"
139
+ case "$FS_SITE" in
140
+ lark|*open.larksuite.com*)
141
+ FS_DOMAIN="https://open.larksuite.com"
142
+ ;;
143
+ *)
144
+ FS_DOMAIN="https://open.feishu.cn"
145
+ ;;
146
+ esac
147
+ if [ -n "$FS_APP_ID" ] && [ -n "$FS_SECRET" ]; then
148
+ FEISHU_RESULT=$(curl -s --max-time 5 -X POST "${FS_DOMAIN}/open-apis/auth/v3/tenant_access_token/internal" \
149
+ -H "Content-Type: application/json" \
150
+ -d "{\"app_id\":\"${FS_APP_ID}\",\"app_secret\":\"${FS_SECRET}\"}" 2>/dev/null || echo '{"code":1}')
151
+ if echo "$FEISHU_RESULT" | grep -q '"code"[[:space:]]*:[[:space:]]*0'; then
152
+ check "Feishu app credentials are valid" 0
134
153
  else
135
- check "Feishu app credentials configured" 1
154
+ check "Feishu app credentials are valid (token request failed)" 1
136
155
  fi
156
+ else
157
+ check "Feishu app credentials configured" 1
137
158
  fi
138
-
139
159
  fi
140
160
 
141
161
  # --- Log directory writable ---
@@ -176,10 +196,10 @@ echo "Results: $PASS passed, $FAIL failed"
176
196
  if [ "$FAIL" -gt 0 ]; then
177
197
  echo ""
178
198
  echo "Common fixes:"
179
- echo " SDK cli.js missing cd $SKILL_DIR && npm install"
180
- echo " dist/daemon.mjs stale cd $SKILL_DIR && npm run build"
181
- echo " config.env missing run setup wizard"
182
- echo " Stale PID file run stop, then start"
199
+ echo " SDK cli.js missing -> cd $SKILL_DIR && npm install"
200
+ echo " dist/daemon.mjs stale -> cd $SKILL_DIR && npm run build"
201
+ echo " config.toml missing -> run setup wizard or start once to migrate legacy config"
202
+ echo " Stale PID file -> run stop, then start"
183
203
  fi
184
204
 
185
205
  [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -9,7 +9,12 @@ import path from 'node:path';
9
9
  import { promisify } from 'node:util';
10
10
  import { WebSocketServer } from 'ws';
11
11
 
12
- import { DEFAULT_WORKSPACE_ROOT, feishuSiteToApiBaseUrl, type ClaudeExecutable, type FeishuSite } from '../src/configuration/index.js';
12
+ import type { FeishuSite } from '../src/channels/types.js';
13
+ import { feishuSiteToApiBaseUrl } from '../src/channels/feishu/site.js';
14
+ import { createConfigService } from '../src/configuration/service.js';
15
+ import { DEFAULT_WORKSPACE_ROOT } from '../src/configuration/paths.js';
16
+ import type { ClaudeExecutable } from '../src/runtime/options.js';
17
+ import type { ConfigPatch } from '../src/configuration/schema.js';
13
18
  import {
14
19
  basicDialogueStreamCardCheckpointIssues,
15
20
  collectRealE2eDump,
@@ -26,6 +31,11 @@ const TEST_CHAT_REGISTRY_PATH = process.env.CODELARK_REAL_FEISHU_TEST_CHAT_REGIS
26
31
  || path.join(os.tmpdir(), 'codelark-real-feishu-e2e-chats.json');
27
32
  const BASIC_DIALOGUE_MODEL_PROXY_CHUNK_DELAY_MS = 120;
28
33
 
34
+ function defaultRealFeishuTestEnvFile(): string {
35
+ const codelarkHome = process.env.CODELARK_HOME || path.join(os.homedir(), '.codelark');
36
+ return path.join(codelarkHome, 'real-feishu-e2e', 'test.env');
37
+ }
38
+
29
39
  interface CliOptions {
30
40
  dryRun: boolean;
31
41
  dumpOnly: boolean;
@@ -905,13 +915,14 @@ function parseEnvLine(line: string): { key: string; value: string } | null {
905
915
  }
906
916
 
907
917
  function loadRealFeishuTestEnvFile(argv: string[]): string {
908
- const envFile = valueArg(argv, '--test-env-file', '');
909
- if (!envFile) return '';
918
+ const explicitEnvFile = valueArg(argv, '--test-env-file', '');
919
+ const envFile = explicitEnvFile || defaultRealFeishuTestEnvFile();
910
920
  const resolved = path.resolve(envFile);
911
921
  let content = '';
912
922
  try {
913
923
  content = fs.readFileSync(resolved, 'utf-8');
914
924
  } catch (error) {
925
+ if (!explicitEnvFile) return '';
915
926
  throw new Error(`Unable to read real Feishu E2E test env file: ${resolved}`);
916
927
  }
917
928
  for (const line of content.split(/\r?\n/)) {
@@ -977,7 +988,7 @@ function parseOptions(argv: string[]): CliOptions {
977
988
  keepGroup: hasFlag(argv, '--keep-group'),
978
989
  keepCodelarkHome: hasFlag(argv, '--keep-clk-home'),
979
990
  allowConcurrentApp: hasFlag(argv, '--allow-concurrent-app'),
980
- testEnvFile: valueArg(argv, '--test-env-file', ''),
991
+ testEnvFile: valueArg(argv, '--test-env-file', defaultRealFeishuTestEnvFile()),
981
992
  runId,
982
993
  channelType: valueArg(argv, '--channel-type', 'feishu-env'),
983
994
  channelAlias: valueArg(argv, '--channel-alias', 'Real Feishu E2E'),
@@ -1033,7 +1044,7 @@ function printUsage(): void {
1033
1044
  ' --stop-test-bridge Stop a previous isolated real Feishu E2E bridge for --run-root/--clk-home',
1034
1045
  ' --launch-bridge Start a test-only bridge child process with an isolated CODELARK_HOME',
1035
1046
  ' --allow-concurrent-app Skip same-app bridge lock; unsafe only when launching another bridge for the same app',
1036
- ' --test-env-file <path> Load CODELARK_REAL_FEISHU_TEST_* values from a private test env file before parsing options',
1047
+ ` --test-env-file <path> Load CODELARK_REAL_FEISHU_TEST_* values from a private test env file; default ${defaultRealFeishuTestEnvFile()}`,
1037
1048
  ' --create-chat Create a new Feishu group and invite the test/live bridge bot',
1038
1049
  ' --fake-ccr Run true ccr/Claude Code against a local fake OpenAI-compatible backend',
1039
1050
  ' --fake-ccr-response <txt> Expected fake backend response text',
@@ -1282,29 +1293,14 @@ function isPidAlive(pid: unknown): boolean {
1282
1293
  }
1283
1294
 
1284
1295
  function listConfiguredFeishuAppIds(codelarkHome: string): string[] {
1285
- const configPath = path.join(codelarkHome, 'config.json');
1286
- const config = readJsonIfExists<{
1287
- channels?: Array<{ provider?: string; enabled?: boolean; config?: { appId?: string } }>;
1288
- }>(configPath, {});
1296
+ const config = createConfigService({ codelarkHome, env: {} }).snapshot().config;
1289
1297
  const appIds = new Set<string>();
1290
- for (const channel of config.channels || []) {
1298
+ for (const channel of config.channels) {
1291
1299
  if (channel.provider !== 'feishu') continue;
1292
1300
  if (channel.enabled === false) continue;
1293
1301
  const appId = channel.config?.appId?.trim();
1294
1302
  if (appId) appIds.add(appId);
1295
1303
  }
1296
- const envPath = path.join(codelarkHome, 'config.env');
1297
- try {
1298
- const content = fs.readFileSync(envPath, 'utf-8');
1299
- for (const line of content.split(/\r?\n/)) {
1300
- const parsed = parseEnvLine(line);
1301
- if (parsed?.key === 'CODELARK_FEISHU_APP_ID' && parsed.value.trim()) {
1302
- appIds.add(parsed.value.trim());
1303
- }
1304
- }
1305
- } catch {
1306
- // config.env is optional in v2 installs.
1307
- }
1308
1304
  return [...appIds];
1309
1305
  }
1310
1306
 
@@ -3429,9 +3425,9 @@ function sessionManagementExpectedTexts(options: CliOptions, text: string): stri
3429
3425
  ];
3430
3426
  }
3431
3427
  if (command === '/help') return ['命令速览', 'Bridge 控制', 'SessionRuntime 配置'];
3432
- if (command === '/set') return ['全局配置', 'GlobalRuntime / Codex', 'GlobalRuntime / Claude', 'Bridge 控制'];
3428
+ if (command === '/set') return ['全局配置', '[runtime.codex]', 'runtime.codex.provider'];
3433
3429
  if (command === `/set claudeProvider ${options.runtime === 'claude' ? options.provider : 'pty'}`) {
3434
- return ['已更新全局配置', '默认 Claude Provider', options.runtime === 'claude' ? options.provider : 'pty'];
3430
+ return ['已更新全局配置', 'runtime.claude.provider', options.runtime === 'claude' ? options.provider : 'pty'];
3435
3431
  }
3436
3432
  if (command === `/new mgmt-${options.runId} ${options.workDir}`) {
3437
3433
  return ['已创建群聊会话', `mgmt-${options.runId}`, options.workDir];
@@ -4230,15 +4226,21 @@ function writeReport(report: unknown, outputPath: string): void {
4230
4226
  }
4231
4227
 
4232
4228
  function writeIsolatedBridgeConfig(options: CliOptions): void {
4233
- const timestamp = new Date().toISOString();
4234
4229
  fs.mkdirSync(options.codelarkHome, { recursive: true, mode: 0o700 });
4235
4230
  fs.mkdirSync(options.workDir, { recursive: true });
4236
- const config = {
4237
- schemaVersion: 1,
4231
+ const config: ConfigPatch = {
4232
+ schemaVersion: 2,
4233
+ session: {
4234
+ workspace: options.workDir,
4235
+ },
4236
+ bridge: {
4237
+ defaultWorkspace: options.workDir,
4238
+ },
4238
4239
  runtime: {
4239
- provider: 'codex',
4240
+ agent: options.runtime,
4240
4241
  codex: {
4241
- ...(usesProxyBackedBasicDialogue(options) ? { defaultModel: options.codexModel } : {}),
4242
+ ...(usesProxyBackedBasicDialogue(options) ? { model: options.codexModel } : {}),
4243
+ provider: options.runtime === 'codex' ? options.provider : (process.env.CODELARK_DEFAULT_CODEX_PROVIDER || 'pty'),
4242
4244
  skipGitRepoCheck: true,
4243
4245
  sandboxMode: 'workspace-write',
4244
4246
  networkAccess: true,
@@ -4249,43 +4251,29 @@ function writeIsolatedBridgeConfig(options: CliOptions): void {
4249
4251
  executable: options.claudeExecutable,
4250
4252
  permissionMode: process.env.CODELARK_CLAUDE_PERMISSION_MODE || 'default',
4251
4253
  ...(process.env.CODELARK_CLAUDE_DEFAULT_MODEL
4252
- ? { defaultModel: process.env.CODELARK_CLAUDE_DEFAULT_MODEL }
4254
+ ? { model: process.env.CODELARK_CLAUDE_DEFAULT_MODEL }
4253
4255
  : {}),
4254
4256
  },
4255
- bridgeControl: {
4256
- defaultCodexProvider: options.runtime === 'codex'
4257
- ? options.provider
4258
- : (process.env.CODELARK_DEFAULT_CODEX_PROVIDER || 'pty'),
4259
- },
4260
- bridge: {
4261
- defaultWorkspaceRoot: options.workDir,
4262
- historyMessageLimit: 8,
4263
- streamStatusIdleStartSeconds: 30,
4264
- streamStatusCheckIntervalSeconds: 5,
4265
- },
4266
4257
  },
4267
4258
  channels: [{
4268
4259
  id: options.channelType,
4269
4260
  alias: options.channelAlias,
4270
4261
  provider: 'feishu',
4271
4262
  enabled: true,
4272
- createdAt: timestamp,
4273
- updatedAt: timestamp,
4274
4263
  config: {
4275
4264
  appId: options.testFeishuAppId,
4276
4265
  appSecret: options.testFeishuAppSecret,
4277
4266
  site: options.feishuSite,
4267
+ historyMessageLimit: 8,
4268
+ streamStatusIdleStartSeconds: 30,
4269
+ streamStatusCheckIntervalSeconds: 5,
4278
4270
  streamingEnabled: true,
4279
4271
  feedbackMarkdownEnabled: true,
4280
4272
  requireMention: false,
4281
4273
  },
4282
4274
  }],
4283
4275
  };
4284
- fs.writeFileSync(
4285
- path.join(options.codelarkHome, 'config.json'),
4286
- JSON.stringify(config, null, 2) + '\n',
4287
- { mode: 0o600 },
4288
- );
4276
+ createConfigService({ codelarkHome: options.codelarkHome, env: {}, migrate: false }).replace({ kind: 'home' }, config);
4289
4277
  }
4290
4278
 
4291
4279
  function readJsonIfExists<T>(filePath: string, fallback: T): T {
@@ -44,7 +44,45 @@ if (testFiles.length === 0) {
44
44
  process.exit(1);
45
45
  }
46
46
 
47
- const child = spawn(
47
+ let child;
48
+ let cleaned = false;
49
+
50
+ function cleanup() {
51
+ if (cleaned) return;
52
+ cleaned = true;
53
+ try {
54
+ fs.rmSync(tempHome, { recursive: true, force: true });
55
+ } catch {
56
+ // ignore
57
+ }
58
+ }
59
+
60
+ function terminateChild(signal = 'SIGTERM') {
61
+ if (!child?.pid) return;
62
+ try {
63
+ if (process.platform !== 'win32') {
64
+ process.kill(-child.pid, signal);
65
+ return;
66
+ }
67
+ } catch {
68
+ // fall back to killing the direct child below
69
+ }
70
+ try {
71
+ child.kill(signal);
72
+ } catch {
73
+ // ignore
74
+ }
75
+ }
76
+
77
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
78
+ process.on(signal, () => {
79
+ terminateChild(signal);
80
+ cleanup();
81
+ process.exit(1);
82
+ });
83
+ }
84
+
85
+ child = spawn(
48
86
  process.execPath,
49
87
  [
50
88
  '--test',
@@ -56,6 +94,7 @@ const child = spawn(
56
94
  ],
57
95
  {
58
96
  stdio: 'inherit',
97
+ detached: process.platform !== 'win32',
59
98
  env: {
60
99
  ...process.env,
61
100
  HOME: runtimeHome,
@@ -68,11 +107,8 @@ const child = spawn(
68
107
  );
69
108
 
70
109
  child.on('exit', (code, signal) => {
71
- try {
72
- fs.rmSync(tempHome, { recursive: true, force: true });
73
- } catch {
74
- // ignore
75
- }
110
+ terminateChild('SIGTERM');
111
+ cleanup();
76
112
 
77
113
  if (signal) {
78
114
  process.kill(process.pid, signal);