lark-remote 0.1.2 → 0.1.3
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.en.md +2 -5
- package/README.md +1 -4
- package/dist/bridge/index.js +9 -1
- package/dist/card/card-budget.js +1 -2
- package/dist/card/run-renderer.js +1 -0
- package/dist/card/run-state.d.ts +4 -0
- package/dist/config/codex-config.d.ts +3 -5
- package/dist/config/codex-config.js +7 -7
- package/dist/config/dir.d.ts +1 -0
- package/dist/config/dir.js +4 -0
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -11
- package/dist/connector/index.js +28 -0
- package/dist/index.js +71 -20
- package/dist/router/config/codex.js +8 -12
- package/dist/router/index.d.ts +14 -1
- package/dist/router/index.js +276 -36
- package/dist/router/utils.d.ts +3 -0
- package/dist/router/utils.js +15 -3
- package/dist/runner/claude/runner.d.ts +0 -1
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/codex/runner.d.ts +0 -2
- package/dist/runner/codex/runner.js +1 -1
- package/dist/runner/common/spawning-runner.d.ts +0 -1
- package/dist/runner/common/spawning-runner.js +2 -4
- package/dist/runner/kimi/runner.d.ts +0 -1
- package/dist/runner/kimi/runner.js +1 -1
- package/dist/runner/opencode/runner.d.ts +0 -2
- package/dist/runner/opencode/runner.js +1 -1
- package/dist/runner/pi/runner.d.ts +0 -1
- package/dist/runner/pi/runner.js +1 -1
- package/dist/runner/probe.d.ts +45 -0
- package/dist/runner/probe.js +87 -0
- package/dist/runner/types.d.ts +3 -0
- package/dist/session/claude/sessions.d.ts +1 -1
- package/dist/session/claude/sessions.js +77 -31
- package/dist/session/codex/rollout-reader.js +9 -0
- package/dist/session/kimi/sessions.d.ts +3 -1
- package/dist/session/kimi/sessions.js +9 -3
- package/dist/session/opencode/sessions.d.ts +0 -2
- package/dist/session/opencode/sessions.js +1 -1
- package/dist/startup-contact.d.ts +4 -2
- package/dist/startup-contact.js +7 -4
- package/package.json +39 -4
package/README.en.md
CHANGED
|
@@ -64,7 +64,6 @@ feishu:
|
|
|
64
64
|
defaultAgent: claude
|
|
65
65
|
|
|
66
66
|
claude:
|
|
67
|
-
binary: claude
|
|
68
67
|
model: claude-opus-4-8
|
|
69
68
|
effort: medium # low | medium | high | xhigh | max
|
|
70
69
|
# permissionMode is hardcoded to bypassPermissions (inside the runner)
|
|
@@ -79,7 +78,7 @@ logging:
|
|
|
79
78
|
level: info # debug | info | warn | error
|
|
80
79
|
|
|
81
80
|
idle:
|
|
82
|
-
watchdogMinutes: 15 # 0 disables the idle
|
|
81
|
+
watchdogMinutes: 15 # 0 disables the idle timeout auto-stop
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
Override the config directory with `--config-dir <path>`; override the Claude settings path with `--settings <path>` or the `CLAUDE_SETTINGS_PATH` environment variable.
|
|
@@ -98,8 +97,6 @@ lark-remote --settings ~/.claude/settings.json # specify Claude settings file
|
|
|
98
97
|
|
|
99
98
|
The bridge prints nothing to the terminal after startup; logs go to `~/.lark-remote/logs/YYYY-MM-DD/lark-remote-<pid>.log` (rotated daily, one subdirectory per day). Only one `lark-remote` instance is allowed per `configDir` — a duplicate start fails with the existing pid. After connecting to Feishu, a startup notification (with start time and pid) is sent to the most recent private chat.
|
|
100
99
|
|
|
101
|
-
For automatic process restart, set up your own supervisor such as systemd, launchd, or pm2.
|
|
102
|
-
|
|
103
100
|
Each agent run creates one CardKit 2.0 card that updates in place with thinking, body text, and tool summaries in real time.
|
|
104
101
|
Timestamps are read from the JSONL and shown in local time as `YYYY-MM-DD HH:mm` for thinking, body, tool call/result, and session history events.
|
|
105
102
|
The card clearly shows completion, error, interruption, or idle timeout at the end; the in-card "⏹ Stop" button is equivalent to `/stop`.
|
|
@@ -152,7 +149,7 @@ Full docs under [`docs/`](docs/):
|
|
|
152
149
|
- Single-card streaming architecture and Feishu acceptance: [`docs/en/architecture/streaming-card.md`](docs/en/architecture/streaming-card.md)
|
|
153
150
|
- Template for adding a new agent: [`docs/en/guides/add-new-agent.md`](docs/en/guides/add-new-agent.md)
|
|
154
151
|
- Codex config card guide: [`docs/en/guides/codex-config.md`](docs/en/guides/codex-config.md)
|
|
155
|
-
- Feishu CardKit 2.0 component reference: [Feishu
|
|
152
|
+
- Feishu CardKit 2.0 component reference: [Official Feishu docs](https://open.feishu.cn/document/feishu-cards/card-json-v2-components/component-json-v2-overview)
|
|
156
153
|
- AI collaboration rules and red lines: [`CLAUDE.md`](CLAUDE.md)
|
|
157
154
|
|
|
158
155
|
> This package is a CLI tool only — no programmatic API is exposed.
|
package/README.md
CHANGED
|
@@ -64,7 +64,6 @@ feishu:
|
|
|
64
64
|
defaultAgent: claude
|
|
65
65
|
|
|
66
66
|
claude:
|
|
67
|
-
binary: claude
|
|
68
67
|
model: claude-opus-4-8
|
|
69
68
|
effort: medium # low | medium | high | xhigh | max
|
|
70
69
|
# permissionMode 硬编码为 bypassPermissions(runner 内部)
|
|
@@ -79,7 +78,7 @@ logging:
|
|
|
79
78
|
level: info # debug | info | warn | error
|
|
80
79
|
|
|
81
80
|
idle:
|
|
82
|
-
watchdogMinutes: 15 # 0
|
|
81
|
+
watchdogMinutes: 15 # 0 关闭空闲超时自动停止
|
|
83
82
|
```
|
|
84
83
|
|
|
85
84
|
配置目录可用 `--config-dir <path>` 覆盖;Claude settings 路径可用 `--settings <path>` 或 `CLAUDE_SETTINGS_PATH` 环境变量覆盖。
|
|
@@ -98,8 +97,6 @@ lark-remote --settings ~/.claude/settings.json # 指定 Claude 配置文件
|
|
|
98
97
|
|
|
99
98
|
bridge 启动后不在终端输出,运行日志写入 `~/.lark-remote/logs/YYYY-MM-DD/lark-remote-<pid>.log`(按日期轮转,每天一个子目录)。同一 `configDir` 只允许一个 `lark-remote` 实例,重复启动会直接报错并提示已有 pid。连接飞书成功后会向最近私聊用户发送启动通知,包含启动时间和进程号。
|
|
100
99
|
|
|
101
|
-
如需进程自动重启,可自行配置 systemd / launchd / pm2 等守护方案。
|
|
102
|
-
|
|
103
100
|
每次 agent 运行会创建一张 CardKit 2.0 卡片,并在原地实时更新 thinking、正文和工具摘要。
|
|
104
101
|
卡片会读取 JSONL 的 `timestamp`,以本地时间 `YYYY-MM-DD HH:mm` 显示 thinking、正文、tool call/result 和会话历史事件。
|
|
105
102
|
结束时卡片明确显示完成、出错、中断或空闲超时;运行中的「⏹ 终止」按钮与 `/stop` 等价。
|
package/dist/bridge/index.js
CHANGED
|
@@ -652,6 +652,7 @@ export class Bridge {
|
|
|
652
652
|
// Final usage read from jsonl after the run completes. live stream-json does
|
|
653
653
|
// not emit compact_boundary, so the live contextLength is unreliable.
|
|
654
654
|
let finalContextLength;
|
|
655
|
+
let finalContextLimit;
|
|
655
656
|
let finalCompactCount;
|
|
656
657
|
let finalCacheReadTokens;
|
|
657
658
|
let finalCacheCreationTokens;
|
|
@@ -825,6 +826,7 @@ export class Bridge {
|
|
|
825
826
|
? undefined
|
|
826
827
|
: this.resolveFinalUsage(finalSessionId, cwd, agentKind);
|
|
827
828
|
finalContextLength = finalUsage?.contextLength ?? contextLength;
|
|
829
|
+
finalContextLimit = finalUsage?.contextLimit;
|
|
828
830
|
finalCompactCount = finalUsage?.compactCount;
|
|
829
831
|
finalCumulativeTotalTokens = finalUsage?.cumulativeTotalTokens;
|
|
830
832
|
finalCumulativeInputTokens = finalUsage?.cumulativeInputTokens;
|
|
@@ -858,7 +860,7 @@ export class Bridge {
|
|
|
858
860
|
finalCacheCreationTokens = finalUsage?.cacheCreationTokens ?? finalCacheCreationTokens;
|
|
859
861
|
finalTotalTokens = finalUsage?.totalTokens ?? finalTotalTokens;
|
|
860
862
|
}
|
|
861
|
-
getLogger().info(`[bridge] final usage runId=${runId} contextLength=${finalContextLength} compactCount=${finalCompactCount ?? 'undefined'} cacheRead=${finalCacheReadTokens ?? 'undefined'} cacheCreate=${finalCacheCreationTokens ?? 'undefined'}`);
|
|
863
|
+
getLogger().info(`[bridge] final usage runId=${runId} contextLength=${finalContextLength} contextLimit=${finalContextLimit ?? 'undefined'} compactCount=${finalCompactCount ?? 'undefined'} cacheRead=${finalCacheReadTokens ?? 'undefined'} cacheCreate=${finalCacheCreationTokens ?? 'undefined'}`);
|
|
862
864
|
// for-await 自然结束 = stream 关闭 = CLI 进程退出。
|
|
863
865
|
// 进程退出(for-await end)= 唯一终态触发源。
|
|
864
866
|
// 1) finalizing(result 已收到)-> 转 done/error + usage meta
|
|
@@ -871,6 +873,7 @@ export class Bridge {
|
|
|
871
873
|
resultSubtype: finalState.resultSubtype,
|
|
872
874
|
errorMsg: finalState.errorMsg,
|
|
873
875
|
contextLength: finalContextLength,
|
|
876
|
+
contextLimit: finalContextLimit,
|
|
874
877
|
compactCount: finalCompactCount,
|
|
875
878
|
cacheReadTokens: finalCacheReadTokens,
|
|
876
879
|
cacheCreationTokens: finalCacheCreationTokens,
|
|
@@ -887,6 +890,7 @@ export class Bridge {
|
|
|
887
890
|
else if (finalState.terminal === 'running') {
|
|
888
891
|
await cardSession.finish('error', {
|
|
889
892
|
contextLength: finalContextLength,
|
|
893
|
+
contextLimit: finalContextLimit,
|
|
890
894
|
compactCount: finalCompactCount,
|
|
891
895
|
cacheReadTokens: finalCacheReadTokens,
|
|
892
896
|
cacheCreationTokens: finalCacheCreationTokens,
|
|
@@ -906,6 +910,7 @@ export class Bridge {
|
|
|
906
910
|
// 仅补充 usage meta(finalizing 期间被 /stop 或 idle 超时也展示 token 统计)
|
|
907
911
|
await cardSession.finish(finalState.terminal, {
|
|
908
912
|
contextLength: finalContextLength,
|
|
913
|
+
contextLimit: finalContextLimit,
|
|
909
914
|
compactCount: finalCompactCount,
|
|
910
915
|
cacheReadTokens: finalCacheReadTokens,
|
|
911
916
|
cacheCreationTokens: finalCacheCreationTokens,
|
|
@@ -937,6 +942,7 @@ export class Bridge {
|
|
|
937
942
|
if (catchTerminal === 'running' || catchTerminal === 'finalizing') {
|
|
938
943
|
await cardSession.finish('error', {
|
|
939
944
|
contextLength: finalContextLength,
|
|
945
|
+
contextLimit: finalContextLimit,
|
|
940
946
|
compactCount: finalCompactCount,
|
|
941
947
|
cacheReadTokens: finalCacheReadTokens,
|
|
942
948
|
cacheCreationTokens: finalCacheCreationTokens,
|
|
@@ -954,6 +960,7 @@ export class Bridge {
|
|
|
954
960
|
if (sawResult) {
|
|
955
961
|
await cardSession.finish(catchTerminal, {
|
|
956
962
|
contextLength: finalContextLength,
|
|
963
|
+
contextLimit: finalContextLimit,
|
|
957
964
|
compactCount: finalCompactCount,
|
|
958
965
|
cacheReadTokens: finalCacheReadTokens,
|
|
959
966
|
cacheCreationTokens: finalCacheCreationTokens,
|
|
@@ -1048,6 +1055,7 @@ export class Bridge {
|
|
|
1048
1055
|
return content.usage
|
|
1049
1056
|
? {
|
|
1050
1057
|
contextLength: content.usage.contextLength,
|
|
1058
|
+
contextLimit: content.usage.contextLimit,
|
|
1051
1059
|
compactCount: content.usage.compactCount,
|
|
1052
1060
|
cacheReadTokens: content.usage.cacheReadTokens,
|
|
1053
1061
|
cacheCreationTokens: content.usage.cacheCreationTokens,
|
package/dist/card/card-budget.js
CHANGED
|
@@ -46,8 +46,7 @@ export function enforceCardBudget(card, opts = {}) {
|
|
|
46
46
|
* when table truncation alone doesn't bring the card under the byte limit.
|
|
47
47
|
*
|
|
48
48
|
* 裁剪判定是**结构化**的:所有 collapsible_panel 都是待裁剪对象,不看面板
|
|
49
|
-
* 标题文本。历史教训(2026-08-08
|
|
50
|
-
* docs/zh/architecture/resume-card-budget-failure-analysis.md):旧实现用
|
|
49
|
+
* 标题文本。历史教训(2026-08-08):旧实现用
|
|
51
50
|
* emoji 标题模式(🤖👤💭🔧🟢🔴)识别「会话事件面板」,但真实
|
|
52
51
|
* sessionEventPanel 产出的标题是裸英文 type 名(emoji 在事件正文而非标题),
|
|
53
52
|
* 导致裁剪阶段对真实 resume 卡零命中,任何 >28KB 的卡片无裁剪直通极端降级,
|
|
@@ -776,6 +776,7 @@ function buildSummaryContent(state) {
|
|
|
776
776
|
const empty = !hasContent ? '\n\n(未返回内容)' : '';
|
|
777
777
|
const usageStatsStr = formatUsageStats({
|
|
778
778
|
contextLength: state.contextLength,
|
|
779
|
+
contextLimit: state.contextLimit,
|
|
779
780
|
compactCount: state.compactCount,
|
|
780
781
|
cacheReadTokens: state.cacheReadTokens,
|
|
781
782
|
cacheCreationTokens: state.cacheCreationTokens,
|
package/dist/card/run-state.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface RunState {
|
|
|
53
53
|
sessionId?: string;
|
|
54
54
|
resultSubtype?: 'success' | 'error';
|
|
55
55
|
contextLength?: number;
|
|
56
|
+
/** 当前模型 context window 上限(codex jsonl 提供);卡片据此渲染百分比。 */
|
|
57
|
+
contextLimit?: number;
|
|
56
58
|
errorMsg?: string;
|
|
57
59
|
idleTimeoutMinutes?: number;
|
|
58
60
|
compactCount?: number;
|
|
@@ -79,6 +81,8 @@ export interface RunState {
|
|
|
79
81
|
export interface FinishMeta {
|
|
80
82
|
resultSubtype?: 'success' | 'error';
|
|
81
83
|
contextLength?: number;
|
|
84
|
+
/** 当前模型 context window 上限;透传到 RunState 供 done 卡片显示百分比。 */
|
|
85
|
+
contextLimit?: number;
|
|
82
86
|
errorMsg?: string;
|
|
83
87
|
idleTimeoutMinutes?: number;
|
|
84
88
|
compactCount?: number;
|
|
@@ -76,7 +76,7 @@ export declare function parseCodexModelsOutput(stdout: string): BundledModelInfo
|
|
|
76
76
|
* (P3-4/P3-15/P3-5);成功结果 TTL 1h,失败/空结果短 TTL 负缓存;stat 异常时
|
|
77
77
|
* 指纹为空(P1-1/P3-10)。
|
|
78
78
|
*/
|
|
79
|
-
export declare function getCodexCatalogModels(
|
|
79
|
+
export declare function getCodexCatalogModels(codexHome?: string): BundledModelInfo[];
|
|
80
80
|
/**
|
|
81
81
|
* Test utility: clear the catalog cache. Tests that exercise the bundled
|
|
82
82
|
* command path (`--bundled`) directly use `invalidateCodexBundledTestCache`
|
|
@@ -88,18 +88,16 @@ export declare function invalidateCodexBundledCache(): void;
|
|
|
88
88
|
* @param codexHome 可选 codex 配置目录覆盖(与 loadCodexConfig 的 codexHome 对齐,
|
|
89
89
|
* 与 loadCodexConfig 的 codexHome 对齐);省略时走 $CODEX_HOME → ~/.codex。
|
|
90
90
|
*/
|
|
91
|
-
export declare function getReasoningEffortOptions(model: string,
|
|
91
|
+
export declare function getReasoningEffortOptions(model: string, codexHome?: string): readonly string[];
|
|
92
92
|
/**
|
|
93
93
|
* Get default reasoning effort for a model.
|
|
94
94
|
* 未声明/未知模型 → undefined(codex 语义:ModelPreset.default_reasoning_effort
|
|
95
95
|
* unwrap_or(ReasoningEffort::None),即不传 effort;P3-12)。
|
|
96
96
|
*/
|
|
97
|
-
export declare function getDefaultReasoningEffort(model: string,
|
|
97
|
+
export declare function getDefaultReasoningEffort(model: string, codexHome?: string): string | undefined;
|
|
98
98
|
interface LoadCodexConfigOpts {
|
|
99
99
|
/** Explicit codex home override. Default: $CODEX_HOME -> ~/.codex */
|
|
100
100
|
codexHome?: string;
|
|
101
|
-
/** codex binary path, for `debug models --bundled`. Default: 'codex' */
|
|
102
|
-
binary?: string;
|
|
103
101
|
}
|
|
104
102
|
/**
|
|
105
103
|
* Read codex config.toml, extract provider and model info
|
|
@@ -154,7 +154,8 @@ export function parseCodexModelsOutput(stdout) {
|
|
|
154
154
|
* (P3-4/P3-15/P3-5);成功结果 TTL 1h,失败/空结果短 TTL 负缓存;stat 异常时
|
|
155
155
|
* 指纹为空(P1-1/P3-10)。
|
|
156
156
|
*/
|
|
157
|
-
export function getCodexCatalogModels(
|
|
157
|
+
export function getCodexCatalogModels(codexHome) {
|
|
158
|
+
const binary = 'codex';
|
|
158
159
|
const home = resolveCodexHome(codexHome);
|
|
159
160
|
const catalogPath = readModelCatalogJsonPath(home);
|
|
160
161
|
const bundled = catalogPath === undefined || catalogPath === TOML_PARSE_FAILED_SENTINEL;
|
|
@@ -217,8 +218,8 @@ export function invalidateCodexBundledCache() {
|
|
|
217
218
|
* @param codexHome 可选 codex 配置目录覆盖(与 loadCodexConfig 的 codexHome 对齐,
|
|
218
219
|
* 与 loadCodexConfig 的 codexHome 对齐);省略时走 $CODEX_HOME → ~/.codex。
|
|
219
220
|
*/
|
|
220
|
-
export function getReasoningEffortOptions(model,
|
|
221
|
-
const models = getCodexCatalogModels(
|
|
221
|
+
export function getReasoningEffortOptions(model, codexHome) {
|
|
222
|
+
const models = getCodexCatalogModels(codexHome);
|
|
222
223
|
const found = models.find((m) => m.slug === model);
|
|
223
224
|
// 未命中(含未知模型,codex fallback 元数据 supported 为空)→ [],不虚构档位(P1)
|
|
224
225
|
return found?.supportedReasoningLevels ?? [];
|
|
@@ -228,8 +229,8 @@ export function getReasoningEffortOptions(model, binary = 'codex', codexHome) {
|
|
|
228
229
|
* 未声明/未知模型 → undefined(codex 语义:ModelPreset.default_reasoning_effort
|
|
229
230
|
* unwrap_or(ReasoningEffort::None),即不传 effort;P3-12)。
|
|
230
231
|
*/
|
|
231
|
-
export function getDefaultReasoningEffort(model,
|
|
232
|
-
const models = getCodexCatalogModels(
|
|
232
|
+
export function getDefaultReasoningEffort(model, codexHome) {
|
|
233
|
+
const models = getCodexCatalogModels(codexHome);
|
|
233
234
|
return models.find((m) => m.slug === model)?.defaultReasoningLevel;
|
|
234
235
|
}
|
|
235
236
|
/**
|
|
@@ -249,13 +250,12 @@ function extractCustomProvider(providers) {
|
|
|
249
250
|
* - anthropic: bundled models with 'claude-*' prefix
|
|
250
251
|
*/
|
|
251
252
|
export function loadCodexConfig(opts = {}) {
|
|
252
|
-
const binary = opts.binary ?? 'codex';
|
|
253
253
|
const codexHome = resolveCodexHome(opts.codexHome);
|
|
254
254
|
const configFile = path.join(codexHome, 'config.toml');
|
|
255
255
|
const catalogMode = isCodexCatalogMode(codexHome);
|
|
256
256
|
// 统一目录来源:catalog 模式 = 活动目录(model_catalog_json 整体替换内置目录);
|
|
257
257
|
// 否则 = bundled(与原行为一致)
|
|
258
|
-
const catalogSlugs = getCodexCatalogModels(
|
|
258
|
+
const catalogSlugs = getCodexCatalogModels(codexHome).map((m) => m.slug);
|
|
259
259
|
const allModels = catalogSlugs.length > 0 ? catalogSlugs : FALLBACK_MODELS;
|
|
260
260
|
const anthropicModels = allModels.filter((m) => m.startsWith('claude-'));
|
|
261
261
|
if (!fs.existsSync(configFile)) {
|
package/dist/config/dir.d.ts
CHANGED
package/dist/config/dir.js
CHANGED
|
@@ -49,6 +49,9 @@ export function parseCliArgs(args = process.argv.slice(2)) {
|
|
|
49
49
|
result.settings = args[++i];
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
else if (arg === '--dev') {
|
|
53
|
+
result.dev = true;
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
56
|
return result;
|
|
54
57
|
}
|
|
@@ -73,6 +76,7 @@ export function printHelp() {
|
|
|
73
76
|
'Options:',
|
|
74
77
|
' --config-dir <path> 自定义配置目录(默认 ~/.lark-remote,可用于同机多实例)',
|
|
75
78
|
' --settings <path> 指定 Claude 配置文件路径',
|
|
79
|
+
' --dev 开发模式:标记从源码 bun src/index.ts 启动(看门狗据此选择拉起方式)',
|
|
76
80
|
' -h, --help 显示本帮助信息',
|
|
77
81
|
' -v, --version 显示版本号',
|
|
78
82
|
];
|
package/dist/config/index.d.ts
CHANGED
|
@@ -15,7 +15,6 @@ export declare const MODEL_ID_TO_ALIAS: Record<string, string>;
|
|
|
15
15
|
/** alias → model ID 反向映射。 */
|
|
16
16
|
export declare const MODEL_ALIAS_TO_ID: Record<string, string>;
|
|
17
17
|
declare const ClaudeConfigSchema: z.ZodObject<{
|
|
18
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
19
18
|
model: z.ZodDefault<z.ZodString>;
|
|
20
19
|
effort: z.ZodDefault<z.ZodEnum<{
|
|
21
20
|
medium: "medium";
|
|
@@ -28,7 +27,6 @@ declare const ClaudeConfigSchema: z.ZodObject<{
|
|
|
28
27
|
}, z.core.$strip>;
|
|
29
28
|
/** Codex-specific configuration. */
|
|
30
29
|
export declare const CodexConfigSchema: z.ZodObject<{
|
|
31
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
32
30
|
model: z.ZodOptional<z.ZodString>;
|
|
33
31
|
modelProvider: z.ZodOptional<z.ZodString>;
|
|
34
32
|
reasoningEffort: z.ZodOptional<z.ZodString>;
|
|
@@ -36,13 +34,11 @@ export declare const CodexConfigSchema: z.ZodObject<{
|
|
|
36
34
|
}, z.core.$strip>;
|
|
37
35
|
/** OpenCode-specific configuration (run mode: opencode run --format json --auto). */
|
|
38
36
|
declare const OpencodeConfigSchema: z.ZodObject<{
|
|
39
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
40
37
|
providerID: z.ZodDefault<z.ZodString>;
|
|
41
38
|
modelID: z.ZodDefault<z.ZodString>;
|
|
42
39
|
}, z.core.$strip>;
|
|
43
40
|
/** Pi-specific configuration. pi is a spawn-per-message CLI like Claude. */
|
|
44
41
|
declare const PiConfigSchema: z.ZodObject<{
|
|
45
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
46
42
|
provider: z.ZodDefault<z.ZodString>;
|
|
47
43
|
model: z.ZodDefault<z.ZodString>;
|
|
48
44
|
thinking: z.ZodDefault<z.ZodEnum<{
|
|
@@ -57,7 +53,6 @@ declare const PiConfigSchema: z.ZodObject<{
|
|
|
57
53
|
}, z.core.$strip>;
|
|
58
54
|
/** Kimi-specific configuration. kimi is a spawn-per-message CLI. */
|
|
59
55
|
declare const KimiConfigSchema: z.ZodObject<{
|
|
60
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
61
56
|
model: z.ZodDefault<z.ZodString>;
|
|
62
57
|
thinkingEffort: z.ZodDefault<z.ZodEnum<{
|
|
63
58
|
max: "max";
|
|
@@ -71,7 +66,6 @@ export declare const AppConfigSchema: z.ZodObject<{
|
|
|
71
66
|
appSecret: z.ZodString;
|
|
72
67
|
}, z.core.$strip>;
|
|
73
68
|
claude: z.ZodDefault<z.ZodObject<{
|
|
74
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
75
69
|
model: z.ZodDefault<z.ZodString>;
|
|
76
70
|
effort: z.ZodDefault<z.ZodEnum<{
|
|
77
71
|
medium: "medium";
|
|
@@ -84,19 +78,16 @@ export declare const AppConfigSchema: z.ZodObject<{
|
|
|
84
78
|
}, z.core.$strip>>;
|
|
85
79
|
agents: z.ZodOptional<z.ZodObject<{
|
|
86
80
|
codex: z.ZodOptional<z.ZodObject<{
|
|
87
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
88
81
|
model: z.ZodOptional<z.ZodString>;
|
|
89
82
|
modelProvider: z.ZodOptional<z.ZodString>;
|
|
90
83
|
reasoningEffort: z.ZodOptional<z.ZodString>;
|
|
91
84
|
stopGraceMs: z.ZodDefault<z.ZodNumber>;
|
|
92
85
|
}, z.core.$strip>>;
|
|
93
86
|
opencode: z.ZodOptional<z.ZodObject<{
|
|
94
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
95
87
|
providerID: z.ZodDefault<z.ZodString>;
|
|
96
88
|
modelID: z.ZodDefault<z.ZodString>;
|
|
97
89
|
}, z.core.$strip>>;
|
|
98
90
|
pi: z.ZodOptional<z.ZodObject<{
|
|
99
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
100
91
|
provider: z.ZodDefault<z.ZodString>;
|
|
101
92
|
model: z.ZodDefault<z.ZodString>;
|
|
102
93
|
thinking: z.ZodDefault<z.ZodEnum<{
|
|
@@ -110,7 +101,6 @@ export declare const AppConfigSchema: z.ZodObject<{
|
|
|
110
101
|
tools: z.ZodDefault<z.ZodString>;
|
|
111
102
|
}, z.core.$strip>>;
|
|
112
103
|
kimi: z.ZodOptional<z.ZodObject<{
|
|
113
|
-
binary: z.ZodDefault<z.ZodString>;
|
|
114
104
|
model: z.ZodDefault<z.ZodString>;
|
|
115
105
|
thinkingEffort: z.ZodDefault<z.ZodEnum<{
|
|
116
106
|
max: "max";
|
package/dist/config/index.js
CHANGED
|
@@ -13,7 +13,6 @@ export const CLAUDE_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
|
13
13
|
export const PI_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh'];
|
|
14
14
|
/** 默认值常量:schema 与 template 共享,避免双源不一致(G25 Magic Numbers)。 */
|
|
15
15
|
const DEFAULTS = {
|
|
16
|
-
CLAUDE_BINARY: 'claude',
|
|
17
16
|
CLAUDE_MODEL: 'claude-opus-4-8',
|
|
18
17
|
STOP_GRACE_MS: 5000,
|
|
19
18
|
IDLE_WATCHDOG_MINUTES: 15,
|
|
@@ -43,7 +42,6 @@ const FeishuConfigSchema = z.object({
|
|
|
43
42
|
appSecret: z.string().min(1),
|
|
44
43
|
});
|
|
45
44
|
const ClaudeConfigSchema = z.object({
|
|
46
|
-
binary: z.string().default(DEFAULTS.CLAUDE_BINARY),
|
|
47
45
|
model: z.string().default(DEFAULTS.CLAUDE_MODEL),
|
|
48
46
|
effort: z.enum(CLAUDE_EFFORTS).default('medium'),
|
|
49
47
|
// permissionMode 硬编码为 bypassPermissions(runner 内部),不通过 config 配置
|
|
@@ -51,8 +49,6 @@ const ClaudeConfigSchema = z.object({
|
|
|
51
49
|
});
|
|
52
50
|
/** Codex-specific configuration. */
|
|
53
51
|
export const CodexConfigSchema = z.object({
|
|
54
|
-
/** Path to codex binary. Default: 'codex' (in PATH). */
|
|
55
|
-
binary: z.string().default('codex'),
|
|
56
52
|
/** model to use. Undefined → codex reads from its config.toml. */
|
|
57
53
|
model: z.string().optional(),
|
|
58
54
|
/** model provider, e.g. 'volcengine-coding-plan'. Undefined → codex reads from its config.toml. */
|
|
@@ -70,8 +66,6 @@ export const CodexConfigSchema = z.object({
|
|
|
70
66
|
});
|
|
71
67
|
/** OpenCode-specific configuration (run mode: opencode run --format json --auto). */
|
|
72
68
|
const OpencodeConfigSchema = z.object({
|
|
73
|
-
/** Path to opencode binary. Default: 'opencode' (in PATH). */
|
|
74
|
-
binary: z.string().default('opencode'),
|
|
75
69
|
/** Provider ID for the LLM backend (e.g. 'anthropic'). */
|
|
76
70
|
providerID: z.string().default('anthropic'),
|
|
77
71
|
/** Model ID for the LLM backend (e.g. 'claude-sonnet-4-20250514'). */
|
|
@@ -79,8 +73,6 @@ const OpencodeConfigSchema = z.object({
|
|
|
79
73
|
});
|
|
80
74
|
/** Pi-specific configuration. pi is a spawn-per-message CLI like Claude. */
|
|
81
75
|
const PiConfigSchema = z.object({
|
|
82
|
-
/** Path to pi binary. Default: 'pi' (in PATH). */
|
|
83
|
-
binary: z.string().default('pi'),
|
|
84
76
|
/** LLM provider, e.g. 'Volcano', 'anthropic', 'openai'. */
|
|
85
77
|
provider: z.string().default('Volcano'),
|
|
86
78
|
/** Model ID or alias, e.g. 'glm-5.2'. */
|
|
@@ -92,8 +84,6 @@ const PiConfigSchema = z.object({
|
|
|
92
84
|
});
|
|
93
85
|
/** Kimi-specific configuration. kimi is a spawn-per-message CLI. */
|
|
94
86
|
const KimiConfigSchema = z.object({
|
|
95
|
-
/** Path to kimi binary. Default: 'kimi' (in PATH). */
|
|
96
|
-
binary: z.string().default('kimi'),
|
|
97
87
|
/** Model ID or alias, e.g. 'kimi-code/k3'. */
|
|
98
88
|
model: z.string().default('kimi-code/k3'),
|
|
99
89
|
/** Thinking effort: 'on', 'max'. */
|
|
@@ -169,7 +159,6 @@ const TEMPLATE = `feishu:
|
|
|
169
159
|
appSecret: ""
|
|
170
160
|
|
|
171
161
|
claude:
|
|
172
|
-
binary: ${DEFAULTS.CLAUDE_BINARY}
|
|
173
162
|
model: ${DEFAULTS.CLAUDE_MODEL}
|
|
174
163
|
effort: medium
|
|
175
164
|
stopGraceMs: ${DEFAULTS.STOP_GRACE_MS}
|
package/dist/connector/index.js
CHANGED
|
@@ -53,6 +53,14 @@ function shouldRetrySendError(err) {
|
|
|
53
53
|
return true;
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Safely extract the im.v1.message.patch service from a LarkChannel.
|
|
58
|
+
* Returns undefined when the channel mock omits rawClient (unit tests).
|
|
59
|
+
*/
|
|
60
|
+
function tryGetPatchService(channel) {
|
|
61
|
+
const c = channel.rawClient;
|
|
62
|
+
return c?.im?.v1?.message;
|
|
63
|
+
}
|
|
56
64
|
export class FeishuConnector {
|
|
57
65
|
channel;
|
|
58
66
|
onMessage;
|
|
@@ -81,6 +89,26 @@ export class FeishuConnector {
|
|
|
81
89
|
},
|
|
82
90
|
},
|
|
83
91
|
});
|
|
92
|
+
// 观测探针(2026-08-11 run 卡定格事故):飞书业务码错误以 HTTP 200 + {code!=0}
|
|
93
|
+
// 返回时,lark SDK 正常 resolve、@larksuite/channel 的 patchCard 丢弃返回值,
|
|
94
|
+
// 导致终态卡 patch 被业务层拒绝时全链路无日志无兜底。这里只观测不改行为——
|
|
95
|
+
// 不 throw、不重试,返回值原样透传。
|
|
96
|
+
// 详见 .adversarial-tdd/prompt-patchcard-business-code-observability.md
|
|
97
|
+
// Guard: unit-test mocks may omit rawClient; skip probe installation in that case.
|
|
98
|
+
const messageService = tryGetPatchService(this.channel);
|
|
99
|
+
if (messageService?.patch) {
|
|
100
|
+
const origPatch = messageService.patch.bind(messageService);
|
|
101
|
+
messageService.patch = (async (request, options) => {
|
|
102
|
+
const res = await origPatch(request, options);
|
|
103
|
+
if (typeof res?.code === 'number' && res.code !== 0) {
|
|
104
|
+
const content = request?.data?.content;
|
|
105
|
+
const bytes = typeof content === 'string' ? Buffer.byteLength(content, 'utf8') : undefined;
|
|
106
|
+
getLogger().warn(`[feishu] message.patch business error code=${res.code} msg=${String(res.msg)} ` +
|
|
107
|
+
`messageId=${String(request?.path?.message_id)} bytes=${bytes ?? 'unknown'}`);
|
|
108
|
+
}
|
|
109
|
+
return res;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
84
112
|
this.channel.on('message', (msg) => {
|
|
85
113
|
if (msg.chatType !== 'p2p')
|
|
86
114
|
return;
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { parseCliArgs, resolveConfigDir, setConfigDir, printHelp, printVersion,
|
|
|
5
5
|
import { FeishuConnector } from './connector/index.js';
|
|
6
6
|
import { ClaudeRunner, CodexExecRunner, OpencodeExecRunner, PiRunner, KimiRunner, } from './runner/index.js';
|
|
7
7
|
import { AgentRegistry } from './runner/registry.js';
|
|
8
|
+
import { probeAllAgents } from './runner/probe.js';
|
|
8
9
|
import { SessionReaderRegistry, SessionStore } from './session/index.js';
|
|
9
10
|
import { ClaudeSessionReader, CodexSessionReader, OpencodeSessionReader, PiSessionReader, KimiSessionReader, } from './session/index.js';
|
|
10
11
|
import { CommandRouter, isImmediateAction, formatUsageStats, } from './router/index.js';
|
|
@@ -21,6 +22,7 @@ import { markdownDiv } from './card/collapsible.js';
|
|
|
21
22
|
import { sessionEventPanel } from './router/card-helpers.js';
|
|
22
23
|
import { newSessionButton, agentDisplayName } from './card/card-shared.js';
|
|
23
24
|
import path from 'node:path';
|
|
25
|
+
import fs from 'node:fs';
|
|
24
26
|
/** Send auto-resume card for a restored session */
|
|
25
27
|
async function sendAutoResumeCard(connector, contact, cwd, session, state) {
|
|
26
28
|
if (!contact.userId)
|
|
@@ -145,14 +147,13 @@ function setupInstanceLockAndHandlers(instanceLock, logger, configDir) {
|
|
|
145
147
|
function initializeRunner(config, configDir, cliArgs) {
|
|
146
148
|
const agentRegistry = new AgentRegistry();
|
|
147
149
|
// P1-15: Claude factory reads from configContainer (not closure) so runtime
|
|
148
|
-
// config changes (
|
|
150
|
+
// config changes (model, effort, stopGraceMs) take effect after
|
|
149
151
|
// bridge.setConfig() + clearRunners(). Same pattern as codex/pi/kimi.
|
|
150
152
|
agentRegistry.register('claude', (ws) => {
|
|
151
153
|
const container = agentRegistry.getConfigContainer();
|
|
152
154
|
const latestConfig = container?.current ?? config;
|
|
153
155
|
const claudeConfig = latestConfig.claude;
|
|
154
156
|
return new ClaudeRunner({
|
|
155
|
-
binary: claudeConfig.binary,
|
|
156
157
|
model: claudeConfig.model,
|
|
157
158
|
effort: claudeConfig.effort,
|
|
158
159
|
stopGraceMs: claudeConfig.stopGraceMs,
|
|
@@ -170,7 +171,6 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
170
171
|
const latestConfig = container?.current ?? config;
|
|
171
172
|
const codexConfig = getAgentConfig(latestConfig, 'codex');
|
|
172
173
|
return new CodexExecRunner({
|
|
173
|
-
binary: codexConfig?.binary ?? 'codex',
|
|
174
174
|
model: codexConfig?.model,
|
|
175
175
|
modelProvider: codexConfig?.modelProvider,
|
|
176
176
|
reasoningEffort: codexConfig?.reasoningEffort,
|
|
@@ -182,20 +182,17 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
182
182
|
});
|
|
183
183
|
// Register OpencodeExecRunner (run mode)
|
|
184
184
|
// Using `opencode run --format json --auto` instead of HTTP+SSE server mode
|
|
185
|
-
const opencodeConfig = getAgentConfig(config, 'opencode');
|
|
186
185
|
// Register session reader (same instance as used by runner)
|
|
187
186
|
const sessionReaderRegistry = new SessionReaderRegistry();
|
|
188
187
|
sessionReaderRegistry.register('claude', new ClaudeSessionReader());
|
|
189
188
|
sessionReaderRegistry.register('codex', codexSessionReader);
|
|
190
189
|
// Register OpencodeSessionReader (CLI version)
|
|
191
190
|
// Using `opencode session list` and `opencode export` instead of HTTP API
|
|
192
|
-
const opencodeSessionReader = new OpencodeSessionReader(
|
|
193
|
-
binary: opencodeConfig?.binary ?? 'opencode',
|
|
194
|
-
});
|
|
191
|
+
const opencodeSessionReader = new OpencodeSessionReader();
|
|
195
192
|
agentRegistry.register('opencode', (ws) => {
|
|
196
193
|
// Get latest config from container (set below for pi)
|
|
197
194
|
const container = agentRegistry.getConfigContainer();
|
|
198
|
-
const latestConfig = container?.current;
|
|
195
|
+
const latestConfig = container?.current ?? config;
|
|
199
196
|
const ocConfig = getAgentConfig(latestConfig, 'opencode');
|
|
200
197
|
// Build model string with validation: provider/model format
|
|
201
198
|
let model;
|
|
@@ -210,7 +207,6 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
210
207
|
}
|
|
211
208
|
}
|
|
212
209
|
return new OpencodeExecRunner({
|
|
213
|
-
binary: ocConfig?.binary ?? 'opencode',
|
|
214
210
|
model,
|
|
215
211
|
// P1-15: read stopGraceMs from latestConfig (not startup closure)
|
|
216
212
|
stopGraceMs: latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
@@ -230,10 +226,9 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
230
226
|
agentRegistry.register('pi', (ws) => {
|
|
231
227
|
// 每次 factory 调用时从 registry 获取最新 config
|
|
232
228
|
const container = agentRegistry.getConfigContainer();
|
|
233
|
-
const latestConfig = container?.current;
|
|
229
|
+
const latestConfig = container?.current ?? config;
|
|
234
230
|
const piConf = getAgentConfig(latestConfig, 'pi');
|
|
235
231
|
return new PiRunner({
|
|
236
|
-
binary: piConf?.binary ?? 'pi',
|
|
237
232
|
provider: piConf?.provider ?? 'Volcano',
|
|
238
233
|
model: piConf?.model ?? 'glm-5.2',
|
|
239
234
|
thinking: piConf?.thinking ?? 'medium',
|
|
@@ -252,12 +247,12 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
252
247
|
agentRegistry.register('kimi', (ws) => {
|
|
253
248
|
// 每次 factory 调用时从 registry 获取最新 config
|
|
254
249
|
const container = agentRegistry.getConfigContainer();
|
|
255
|
-
const latestConfig = container?.current;
|
|
250
|
+
const latestConfig = container?.current ?? config;
|
|
256
251
|
const kimiConf = getAgentConfig(latestConfig, 'kimi');
|
|
257
252
|
return new KimiRunner({
|
|
258
|
-
binary: kimiConf?.binary ?? 'kimi',
|
|
259
253
|
model: kimiConf?.model ?? 'kimi-code/k3',
|
|
260
254
|
thinkingEffort: kimiConf?.thinkingEffort ?? 'max',
|
|
255
|
+
// P1-15: read stopGraceMs from latestConfig (not startup closure)
|
|
261
256
|
stopGraceMs: latestConfig.claude?.stopGraceMs ?? DEFAULT_STOP_GRACE_MS,
|
|
262
257
|
pidDir: configDir,
|
|
263
258
|
workspace: ws,
|
|
@@ -273,10 +268,25 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
273
268
|
agentRegistry.registerDisplayName('kimi', 'Kimi');
|
|
274
269
|
// 设置全局 registry,供 agentDisplayName 等全局函数使用
|
|
275
270
|
AgentRegistry.setGlobalInstance(agentRegistry);
|
|
271
|
+
// Probe agent CLI availability (fire-and-forget, populates cache for /config card).
|
|
272
|
+
// Don't block startup — log unavailable agents as warnings.
|
|
273
|
+
probeAllAgents()
|
|
274
|
+
.then((availability) => {
|
|
275
|
+
const unavailable = [...availability.entries()].filter(([, ok]) => !ok).map(([kind]) => kind);
|
|
276
|
+
if (unavailable.length > 0) {
|
|
277
|
+
getLogger().warn(`[probe] agent CLI not found or not functional: ${unavailable.join(', ')}`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
getLogger().info('[probe] all agent CLIs available');
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
.catch(() => {
|
|
284
|
+
// Probe failure is non-fatal; /config card will retry on open.
|
|
285
|
+
});
|
|
276
286
|
return { agentRegistry, sessionReaderRegistry };
|
|
277
287
|
}
|
|
278
288
|
/** Setup message and card action handlers. */
|
|
279
|
-
function setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger) {
|
|
289
|
+
function setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger, config) {
|
|
280
290
|
connector.setMessageHandler((msg) => {
|
|
281
291
|
// 绑定/授权闸门:仅 owner 放行;非 owner 静默丢弃;未绑定时要求 PIN 认领
|
|
282
292
|
const decision = binder.classify(msg.userId, msg.content, msg.chatId);
|
|
@@ -286,9 +296,50 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
286
296
|
if (decision.kind === 'pin_wrong')
|
|
287
297
|
return;
|
|
288
298
|
if (decision.kind === 'bind_success') {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
299
|
+
// First-run onboarding: set default cwd + send welcome + Help card.
|
|
300
|
+
// setCwd is synchronous (outside the async closure) so that the user's
|
|
301
|
+
// next message — which will hit the `owner` branch now that
|
|
302
|
+
// startup-contact.json has been written — finds cwd already set.
|
|
303
|
+
// process.cwd() is the only sensible default: the user started
|
|
304
|
+
// lark-remote in the directory they want to work in.
|
|
305
|
+
// setCwd only when no cwd is set yet (e.g. fresh start after bind).
|
|
306
|
+
// Don't overwrite an existing cwd — the user may have already set one
|
|
307
|
+
// via /cd or a previous bind. Use realpath for canonical form (matching
|
|
308
|
+
// Claude JSONL cwd, e.g. /tmp → /private/tmp on macOS).
|
|
309
|
+
let resolvedCwd;
|
|
310
|
+
try {
|
|
311
|
+
resolvedCwd = fs.realpathSync(process.cwd());
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
resolvedCwd = process.cwd();
|
|
315
|
+
}
|
|
316
|
+
if (!sessionStore.getCwd(msg.userId)) {
|
|
317
|
+
sessionStore.setCwd(msg.userId, resolvedCwd);
|
|
318
|
+
}
|
|
319
|
+
void (async () => {
|
|
320
|
+
try {
|
|
321
|
+
// Message 1: bind confirmation + status (combined to avoid
|
|
322
|
+
// Feishu out-of-order delivery across three separate messages).
|
|
323
|
+
await connector.sendWithRetry(msg.chatId, {
|
|
324
|
+
text: `✅ 已绑定到本账号,此后仅你可使用本应用\n` +
|
|
325
|
+
`📂 当前工作目录: \`${resolvedCwd}\`\n` +
|
|
326
|
+
`🤖 默认 Agent: ${agentDisplayName(config.defaultAgent)}\n` +
|
|
327
|
+
`💡 直接输入消息即可开始对话,或 /cd 切换目录`,
|
|
328
|
+
});
|
|
329
|
+
// Message 2: Help card.
|
|
330
|
+
// Use router.cmdHelp() + manual send (without replyTo) so the Help
|
|
331
|
+
// card appears as an independent message, not a reply to the PIN
|
|
332
|
+
// message. router.handle('/help') would set replyTo=msg.messageId
|
|
333
|
+
// (the PIN), causing the card to nest under the PIN in Feishu UI.
|
|
334
|
+
const helpResult = router.cmdHelp();
|
|
335
|
+
if (helpResult.card) {
|
|
336
|
+
await connector.sendWithRetry(msg.chatId, { card: helpResult.card });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
logger.error('[binder] onboarding send failed:', err);
|
|
341
|
+
}
|
|
342
|
+
})();
|
|
292
343
|
return;
|
|
293
344
|
}
|
|
294
345
|
// decision.kind === 'owner':正常处理(不再每条覆盖 startup-contact)
|
|
@@ -486,7 +537,7 @@ async function main() {
|
|
|
486
537
|
logger.info('config loaded');
|
|
487
538
|
logger.info(`configDir = ${configDir}`);
|
|
488
539
|
logger.info(`feishu.appId = ${config.feishu.appId}`);
|
|
489
|
-
logger.info(`claude.
|
|
540
|
+
logger.info(`claude.model = ${config.claude.model}`);
|
|
490
541
|
if (cliArgs.settings) {
|
|
491
542
|
logger.info(`claude.settings = ${cliArgs.settings}`);
|
|
492
543
|
}
|
|
@@ -522,7 +573,7 @@ async function main() {
|
|
|
522
573
|
restartSpawner: () => spawnReplacementBridge(path.join(configDir, 'logs')),
|
|
523
574
|
sessionReaderRegistry,
|
|
524
575
|
});
|
|
525
|
-
setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger);
|
|
576
|
+
setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger, config);
|
|
526
577
|
try {
|
|
527
578
|
await connector.connect();
|
|
528
579
|
}
|
|
@@ -532,7 +583,7 @@ async function main() {
|
|
|
532
583
|
}
|
|
533
584
|
if (binder.isBound()) {
|
|
534
585
|
// 仅已绑定时发送启动通知;未绑定时不打扰(PIN 引导已在控制台输出)
|
|
535
|
-
await sendStartupHello(connector, startupContactStore);
|
|
586
|
+
await sendStartupHello(connector, startupContactStore, { dev: cliArgs.dev });
|
|
536
587
|
}
|
|
537
588
|
// Auto-restore: resume the persisted sessionId if available
|
|
538
589
|
const restoredContact = startupContactStore.getContact();
|