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/README.md +36 -67
- package/SECURITY.md +1 -1
- package/dist/cli.mjs +22031 -5268
- package/dist/daemon.mjs +49838 -26610
- package/dist/defaults.toml +51 -0
- package/dist/ui-server.mjs +31695 -12270
- package/package.json +7 -3
- package/schemas/config.v1.schema.json +4 -1
- package/schemas/data/sessions.v1.schema.json +1 -1
- package/scripts/build.js +4 -1
- package/scripts/check-npm-pack.js +12 -0
- package/scripts/daemon.sh +0 -3
- package/scripts/doctor.sh +102 -82
- package/scripts/real-feishu-e2e.ts +36 -48
- package/scripts/run-tests.js +42 -6
- package/scripts/setup-wizard-real-e2e.ts +71 -36
- package/scripts/setup-wizard-real-wizard-e2e.ts +342 -0
- package/scripts/supervisor-windows.ps1 +1 -50
- package/skills/codelark-question/SKILL.md +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codelark",
|
|
3
|
-
"version": "0.1.
|
|
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
|
-
"
|
|
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", "
|
|
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
|
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
|
-
|
|
4
|
-
|
|
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
|
|
27
|
-
check "Node.js >=
|
|
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 >=
|
|
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
|
-
# ---
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
CODEX_AUTH=
|
|
64
|
-
|
|
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
|
|
105
|
+
check "dist/daemon.mjs exists (not built; run 'npm run build')" 1
|
|
89
106
|
fi
|
|
90
107
|
|
|
91
|
-
# ---
|
|
92
|
-
if [ -f "$
|
|
93
|
-
check "config.
|
|
108
|
+
# --- Config files ---
|
|
109
|
+
if [ -f "$CONFIG_TOML" ]; then
|
|
110
|
+
check "config.toml exists" 0
|
|
94
111
|
else
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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.
|
|
122
|
+
check "config.toml permissions are 600" 0
|
|
103
123
|
else
|
|
104
|
-
check "config.
|
|
124
|
+
check "config.toml permissions are 600 (currently $PERMS)" 1
|
|
105
125
|
fi
|
|
106
126
|
fi
|
|
107
127
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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
|
|
180
|
-
echo " dist/daemon.mjs stale
|
|
181
|
-
echo " config.
|
|
182
|
-
echo " Stale PID file
|
|
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
|
|
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
|
|
909
|
-
|
|
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
|
-
|
|
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
|
|
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 ['全局配置', '
|
|
3428
|
+
if (command === '/set') return ['全局配置', '[runtime.codex]', 'runtime.codex.provider'];
|
|
3433
3429
|
if (command === `/set claudeProvider ${options.runtime === 'claude' ? options.provider : 'pty'}`) {
|
|
3434
|
-
return ['已更新全局配置', '
|
|
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:
|
|
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
|
-
|
|
4240
|
+
agent: options.runtime,
|
|
4240
4241
|
codex: {
|
|
4241
|
-
...(usesProxyBackedBasicDialogue(options) ? {
|
|
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
|
-
? {
|
|
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
|
-
|
|
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 {
|
package/scripts/run-tests.js
CHANGED
|
@@ -44,7 +44,45 @@ if (testFiles.length === 0) {
|
|
|
44
44
|
process.exit(1);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
} catch {
|
|
74
|
-
// ignore
|
|
75
|
-
}
|
|
110
|
+
terminateChild('SIGTERM');
|
|
111
|
+
cleanup();
|
|
76
112
|
|
|
77
113
|
if (signal) {
|
|
78
114
|
process.kill(process.pid, signal);
|