lark-remote 0.1.4 → 0.1.5
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/CHANGELOG.md +21 -0
- package/README.en.md +4 -1
- package/README.md +4 -1
- package/dist/bridge/index.js +22 -0
- package/dist/card/run-renderer.js +1 -1
- package/dist/card/run-state.js +7 -0
- package/dist/config/dir.d.ts +1 -0
- package/dist/config/dir.js +5 -1
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.js +2 -0
- package/dist/connector/index.js +20 -0
- package/dist/index.js +51 -1
- package/dist/router/config/index.d.ts +0 -10
- package/dist/router/config/index.js +0 -17
- package/dist/router/index.d.ts +36 -1
- package/dist/router/index.js +231 -37
- package/dist/runner/codex/argv.d.ts +1 -1
- package/dist/runner/codex/argv.js +1 -1
- package/dist/runner/common/runner-utils.d.ts +12 -0
- package/dist/runner/common/runner-utils.js +21 -0
- package/dist/runner/common/spawning-runner.js +9 -1
- package/dist/session/claude/session-index.d.ts +93 -0
- package/dist/session/claude/session-index.js +374 -0
- package/dist/session/claude/sessions.d.ts +0 -18
- package/dist/session/claude/sessions.js +102 -319
- package/dist/session/codex/rollout-reader.js +3 -3
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.js +11 -0
- package/dist/update/install.d.ts +29 -0
- package/dist/update/install.js +93 -0
- package/dist/update/startup-hint.d.ts +5 -0
- package/dist/update/startup-hint.js +11 -0
- package/dist/update/version-check.d.ts +31 -0
- package/dist/update/version-check.js +149 -0
- package/package.json +1 -14
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.5] - 2026-08-14
|
|
9
|
+
|
|
10
|
+
### 新增
|
|
11
|
+
|
|
12
|
+
- `/update` / `/update check` 命令:检查 npm registry 最新版本并一键升级,升级后自动重启 bridge;开发模式(`--dev`)下自动拒绝自更新
|
|
13
|
+
- `--update` CLI 参数:非交互式升级(适用于 cron / 脚本自动化),升级后退出
|
|
14
|
+
- `checkUpdateOnStartup` 配置项:启动时静默检查版本更新并推送提示(默认关闭)
|
|
15
|
+
- 纯内存 session index(`session-index.ts`):替代旧的 `readCwdFromJsonl`,完整 `cwdSet` 支持 EnterWorktree 连续搬迁 A→B→C 场景
|
|
16
|
+
- `verify-test-classification.ts` 脚本:测试分类校验工具
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- `/ws`、`/order` 卡片分页从 5 条/页调整为 15 条/页,修复飞书 ErrCode 11310 元素超限
|
|
21
|
+
- CLI `--dev` 帮助文本内部术语"看门狗"替换为用户可理解的"空闲超时自动停止"
|
|
22
|
+
|
|
23
|
+
### 变更
|
|
24
|
+
|
|
25
|
+
- 移除 husky / lint-staged pre-commit 和 pre-push 钩子及相关 devDependencies
|
|
26
|
+
- `sessions.ts` 重构:迁移到 `SessionIndex` + `parseSessionJsonl`,移除 `readCwdFromJsonl`
|
|
27
|
+
|
|
8
28
|
## [0.1.4] - 2026-08-13
|
|
9
29
|
|
|
10
30
|
### 新增
|
|
@@ -115,3 +135,4 @@ Initial release.
|
|
|
115
135
|
[0.1.2]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.2
|
|
116
136
|
[0.1.3]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.3
|
|
117
137
|
[0.1.4]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.4
|
|
138
|
+
[0.1.5]: https://github.com/bungabungawoda/lark-remote/releases/tag/v0.1.5
|
package/README.en.md
CHANGED
|
@@ -150,10 +150,13 @@ Full docs under [`docs/`](docs/):
|
|
|
150
150
|
- Template for adding a new agent: [`docs/en/guides/add-new-agent.md`](docs/en/guides/add-new-agent.md)
|
|
151
151
|
- Codex config card guide: [`docs/en/guides/codex-config.md`](docs/en/guides/codex-config.md)
|
|
152
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)
|
|
153
|
-
- AI collaboration rules and red lines: [`CLAUDE.md`](CLAUDE.md)
|
|
154
153
|
|
|
155
154
|
> This package is a CLI tool only — no programmatic API is exposed.
|
|
156
155
|
|
|
156
|
+
## Acknowledgments
|
|
157
|
+
|
|
158
|
+
This project was inspired by [lark-coding-agent-bridge](https://github.com/zarazhangrui/lark-coding-agent-bridge) — thanks for demonstrating the feasibility of bridging Feishu with local coding agents.
|
|
159
|
+
|
|
157
160
|
## License
|
|
158
161
|
|
|
159
162
|
[MIT](LICENSE) © bungabungawoda
|
package/README.md
CHANGED
|
@@ -149,10 +149,13 @@ FEISHU_LIVE_TEST=1 bun run test tests/feishu-reaction-emoji-live.test.ts
|
|
|
149
149
|
- 新增 agent 接入模板:[`docs/zh/guides/add-new-agent.md`](docs/zh/guides/add-new-agent.md)
|
|
150
150
|
- Codex 配置卡片指南:[`docs/zh/guides/codex-config.md`](docs/zh/guides/codex-config.md)
|
|
151
151
|
- 飞书 CardKit 2.0 组件参考:[飞书开放平台官方文档](https://open.feishu.cn/document/feishu-cards/card-json-v2-components/component-json-v2-overview)
|
|
152
|
-
- AI 协作规则与红线:[`CLAUDE.md`](CLAUDE.md)
|
|
153
152
|
|
|
154
153
|
> 本包为纯 CLI 工具,不提供编程 API。
|
|
155
154
|
|
|
155
|
+
## 致谢
|
|
156
|
+
|
|
157
|
+
本项目受到了 [lark-coding-agent-bridge](https://github.com/zarazhangrui/lark-coding-agent-bridge) 的启发,感谢该项目展示了飞书与本地 Coding Agent 桥接的可行性。
|
|
158
|
+
|
|
156
159
|
## License
|
|
157
160
|
|
|
158
161
|
[MIT](LICENSE) © bungabungawoda
|
package/dist/bridge/index.js
CHANGED
|
@@ -725,6 +725,12 @@ export class Bridge {
|
|
|
725
725
|
// Extract agent-specific options from config
|
|
726
726
|
const agentOpts = this.getAgentRunOptions(agentKind);
|
|
727
727
|
const runOpts = { cwd, sessionId, ...agentOpts };
|
|
728
|
+
// Track whether system.init has been received for this run.
|
|
729
|
+
// Claude CLI --resume emits a stale result (from the previous turn)
|
|
730
|
+
// before sending system.init; bridge must not treat that as the run
|
|
731
|
+
// ending (sawResult, usage capture, or cardSession.push would all
|
|
732
|
+
// be wrong for the historical event).
|
|
733
|
+
let sawInit = false;
|
|
728
734
|
for await (const event of runner.run(message, runOpts)) {
|
|
729
735
|
// 只有终态才阻止事件处理。finalizing 是非终态,应该继续处理
|
|
730
736
|
// 这样在 result 事件后,后台任务输出仍能被正确处理
|
|
@@ -736,6 +742,7 @@ export class Bridge {
|
|
|
736
742
|
continue;
|
|
737
743
|
}
|
|
738
744
|
if (event.type === 'system' && event.subtype === 'init') {
|
|
745
|
+
sawInit = true;
|
|
739
746
|
// 代际守卫:run 在途时 /new(或 new-session 卡片、/cd、/resume、
|
|
740
747
|
// /config 切换)移动了 session 指针,此 init 的写回是 stale 的——
|
|
741
748
|
// 跳过,否则 /new 的清空会被在途 run 静默撤销(2026-08-09 事故:
|
|
@@ -773,6 +780,21 @@ export class Bridge {
|
|
|
773
780
|
contextLength = event.compactMetadata.postTokens;
|
|
774
781
|
}
|
|
775
782
|
if (event.type === 'result') {
|
|
783
|
+
// Pre-init result guard: Claude CLI --resume emits a stale result
|
|
784
|
+
// (from the previous turn) before system.init. Ignoring it prevents
|
|
785
|
+
// premature sawResult/usage capture and the run-state reducer (which
|
|
786
|
+
// also guards on sessionId === undefined) from transitioning to
|
|
787
|
+
// finalizing too early. The real result for this run arrives after
|
|
788
|
+
// system.init.
|
|
789
|
+
if (!sawInit) {
|
|
790
|
+
getLogger().info(`[bridge] pre-init result ignored (resume replay) runId=${runId}`);
|
|
791
|
+
// Still reset idle timer — the CLI is alive and producing events.
|
|
792
|
+
resetIdle();
|
|
793
|
+
// Do NOT push to cardSession: the run-state reducer would also
|
|
794
|
+
// skip it (sessionId === undefined), but skipping at the bridge
|
|
795
|
+
// layer avoids the push + render overhead for a no-op event.
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
776
798
|
sawResult = true;
|
|
777
799
|
resultWasError = event.subtype === 'error';
|
|
778
800
|
getLogger().info(`[bridge] result event received runId=${runId}`);
|
|
@@ -400,7 +400,7 @@ function buildSkeletonElements(state) {
|
|
|
400
400
|
// Summary (token stats etc.) — static, bounded size
|
|
401
401
|
elements.push(...buildSummaryContent(state));
|
|
402
402
|
// Bottom action row: stop (if running/finalizing) + new session (always).
|
|
403
|
-
// Degraded paths must keep the action buttons reachable (
|
|
403
|
+
// Degraded paths must keep the action buttons reachable (design constraint).
|
|
404
404
|
const actionButtons = [];
|
|
405
405
|
const showStop = state.terminal === 'running' || state.terminal === 'finalizing';
|
|
406
406
|
if (showStop) {
|
package/dist/card/run-state.js
CHANGED
|
@@ -61,6 +61,13 @@ function reduceSystemEvent(state, event) {
|
|
|
61
61
|
function reduceResultEvent(state, event) {
|
|
62
62
|
if (event.type !== 'result')
|
|
63
63
|
return state;
|
|
64
|
+
// Pre-init result guard: Claude CLI --resume emits a historical result (from
|
|
65
|
+
// the previous turn) before sending system.init for the new run. Without this
|
|
66
|
+
// guard, that stale result would prematurely transition to 'finalizing',
|
|
67
|
+
// freezing the card at "⏳ 等待进程退出" for the entire run duration.
|
|
68
|
+
// sessionId is set by system.init; undefined means init hasn't arrived yet.
|
|
69
|
+
if (state.sessionId === undefined)
|
|
70
|
+
return state;
|
|
64
71
|
const resultEvent = event;
|
|
65
72
|
const incomingSubtype = event.subtype;
|
|
66
73
|
const incomingErrorMsg = incomingSubtype === 'error' ? (resultEvent.errorMessage ?? 'Agent 返回错误结果') : undefined;
|
package/dist/config/dir.d.ts
CHANGED
package/dist/config/dir.js
CHANGED
|
@@ -52,6 +52,9 @@ export function parseCliArgs(args = process.argv.slice(2)) {
|
|
|
52
52
|
else if (arg === '--dev') {
|
|
53
53
|
result.dev = true;
|
|
54
54
|
}
|
|
55
|
+
else if (arg === '--update') {
|
|
56
|
+
result.update = true;
|
|
57
|
+
}
|
|
55
58
|
}
|
|
56
59
|
return result;
|
|
57
60
|
}
|
|
@@ -76,7 +79,8 @@ export function printHelp() {
|
|
|
76
79
|
'Options:',
|
|
77
80
|
' --config-dir <path> 自定义配置目录(默认 ~/.lark-remote,可用于同机多实例)',
|
|
78
81
|
' --settings <path> 指定 Claude 配置文件路径',
|
|
79
|
-
' --dev 开发模式:标记从源码 bun src/index.ts
|
|
82
|
+
' --dev 开发模式:标记从源码 bun src/index.ts 启动(空闲超时自动停止据此选择拉起方式)',
|
|
83
|
+
' --update 升级到最新版本后退出(用于 cron/脚本自动化升级)',
|
|
80
84
|
' -h, --help 显示本帮助信息',
|
|
81
85
|
' -v, --version 显示版本号',
|
|
82
86
|
];
|
package/dist/config/index.d.ts
CHANGED
|
@@ -151,6 +151,7 @@ export declare const AppConfigSchema: z.ZodObject<{
|
|
|
151
151
|
pi: "pi";
|
|
152
152
|
kimi: "kimi";
|
|
153
153
|
}>>;
|
|
154
|
+
checkUpdateOnStartup: z.ZodDefault<z.ZodBoolean>;
|
|
154
155
|
}, z.core.$strip>;
|
|
155
156
|
export type AppConfig = z.infer<typeof AppConfigSchema>;
|
|
156
157
|
export declare function loadConfig(configPath: string): AppConfig;
|
package/dist/config/index.js
CHANGED
|
@@ -153,6 +153,8 @@ export const AppConfigSchema = z.object({
|
|
|
153
153
|
logging: LoggingConfigSchema.default(LoggingConfigSchema.parse({})),
|
|
154
154
|
/** Default agent for new runs. */
|
|
155
155
|
defaultAgent: z.enum(['claude', 'codex', 'opencode', 'pi', 'kimi']).default('claude'),
|
|
156
|
+
/** Check for updates on bridge startup (default: false). */
|
|
157
|
+
checkUpdateOnStartup: z.boolean().default(false),
|
|
156
158
|
});
|
|
157
159
|
const TEMPLATE = `feishu:
|
|
158
160
|
appId: ""
|
package/dist/connector/index.js
CHANGED
|
@@ -3,7 +3,20 @@ import { getLogger } from '../logger/index.js';
|
|
|
3
3
|
import { MAX_FILE_UPLOAD_SIZE } from './file-limits.js';
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
|
+
import https from 'node:https';
|
|
6
7
|
import FormData from 'form-data';
|
|
8
|
+
/**
|
|
9
|
+
* Dedicated https.Agent with keepAlive disabled for sendFile's three axios
|
|
10
|
+
* calls (token, upload, send). Bun's Node.js http compat layer has a bug
|
|
11
|
+
* where it reuses keep-alive sockets that the server has already RST'd —
|
|
12
|
+
* the write goes into a dead socket and the caller gets ECONNRESET after
|
|
13
|
+
* ~30s. Node.js proper does not exhibit this because it checks socket
|
|
14
|
+
* health before reuse. Disabling keep-alive forces a fresh TCP+TLS
|
|
15
|
+
* handshake per request, eliminating the stale-socket path at the cost of
|
|
16
|
+
* one extra round-trip per file send (acceptable for an infrequent
|
|
17
|
+
* operation).
|
|
18
|
+
*/
|
|
19
|
+
const noKeepAliveAgent = new https.Agent({ keepAlive: false });
|
|
7
20
|
/**
|
|
8
21
|
* dedup 缓存 TTL(毫秒)。控制飞书事件去重窗口,全局作用于 message + cardAction。
|
|
9
22
|
*
|
|
@@ -288,18 +301,22 @@ export class FeishuConnector {
|
|
|
288
301
|
form.append('file_name', fileName);
|
|
289
302
|
form.append('file_type', 'stream');
|
|
290
303
|
// P2-18: timeout on the upload (large file, slow link) — 120s.
|
|
304
|
+
// httpsAgent: noKeepAlive — prevents Bun from reusing a server-RST'd
|
|
305
|
+
// keep-alive socket (ECONNRESET after ~30s; see noKeepAliveAgent docs).
|
|
291
306
|
const uploadResp = await axios.post('https://open.feishu.cn/open-apis/im/v1/files', form, {
|
|
292
307
|
headers: {
|
|
293
308
|
...form.getHeaders(),
|
|
294
309
|
Authorization: `Bearer ${accessToken}`,
|
|
295
310
|
},
|
|
296
311
|
timeout: 120000,
|
|
312
|
+
httpsAgent: noKeepAliveAgent,
|
|
297
313
|
});
|
|
298
314
|
if (uploadResp.data.code !== 0) {
|
|
299
315
|
throw new Error(`File upload failed: ${uploadResp.data.msg}`);
|
|
300
316
|
}
|
|
301
317
|
const fileKey = uploadResp.data.data.file_key;
|
|
302
318
|
// Send file message. P2-18: timeout 30s.
|
|
319
|
+
// httpsAgent: noKeepAlive — same Bun keep-alive fix as upload above.
|
|
303
320
|
const sendResp = await axios.post('https://open.feishu.cn/open-apis/im/v1/messages', {
|
|
304
321
|
receive_id: chatId,
|
|
305
322
|
msg_type: 'file',
|
|
@@ -311,6 +328,7 @@ export class FeishuConnector {
|
|
|
311
328
|
},
|
|
312
329
|
params: { receive_id_type: 'chat_id' },
|
|
313
330
|
timeout: 30000,
|
|
331
|
+
httpsAgent: noKeepAliveAgent,
|
|
314
332
|
});
|
|
315
333
|
if (sendResp.data.code !== 0) {
|
|
316
334
|
throw new Error(`File message failed: ${sendResp.data.msg}`);
|
|
@@ -347,12 +365,14 @@ export class FeishuConnector {
|
|
|
347
365
|
return this.cachedToken;
|
|
348
366
|
}
|
|
349
367
|
// P2-18: timeout 30s on the token request.
|
|
368
|
+
// httpsAgent: noKeepAlive — same Bun keep-alive fix as upload above.
|
|
350
369
|
const tokenResp = await axios.post('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
|
|
351
370
|
app_id: this.appId,
|
|
352
371
|
app_secret: this.appSecret,
|
|
353
372
|
}, {
|
|
354
373
|
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
355
374
|
timeout: 30000,
|
|
375
|
+
httpsAgent: noKeepAliveAgent,
|
|
356
376
|
});
|
|
357
377
|
if (tokenResp.data.code !== 0) {
|
|
358
378
|
throw new Error(`Failed to get tenant_access_token: ${tokenResp.data.msg ?? 'unknown error'}`);
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { StartupContactStore, sendStartupHello } from './startup-contact.js';
|
|
|
16
16
|
import { OwnerBinder, formatPinGuidance } from './binder.js';
|
|
17
17
|
import { InstanceAlreadyRunningError, InstanceLock } from './instance-lock.js';
|
|
18
18
|
import { spawnReplacementBridge, waitForPreviousInstance } from './restart.js';
|
|
19
|
+
import { checkLatestVersion, isNewer, runInstallLatest, formatUpdateHint } from './update/index.js';
|
|
19
20
|
import { classifyRejection } from './error-classification.js';
|
|
20
21
|
import { WorkspaceStore } from './workspace/index.js';
|
|
21
22
|
import { markdownDiv } from './card/collapsible.js';
|
|
@@ -444,7 +445,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
444
445
|
if (actionValue.cmd === 'stop') {
|
|
445
446
|
if (!actionValue.runId) {
|
|
446
447
|
logger.warn('[control] ignored stop card action without runId');
|
|
447
|
-
// Card button clicks must have visible feedback (
|
|
448
|
+
// Card button clicks must have visible feedback (design constraint: card button
|
|
448
449
|
// 点击必须有可见反馈,见 design.md §6.2)。
|
|
449
450
|
void bridge
|
|
450
451
|
.sendResult({ text: '⚠️ 无效的停止请求,缺少必要信息' }, { userId, chatId, messageId })
|
|
@@ -531,6 +532,32 @@ async function main() {
|
|
|
531
532
|
printVersion();
|
|
532
533
|
process.exit(0);
|
|
533
534
|
}
|
|
535
|
+
// --update: upgrade to latest version and exit (for cron/script automation)
|
|
536
|
+
if (cliArgs.update) {
|
|
537
|
+
// --update 在 acquireLock 之前执行;先解析 configDir 并初始化 logger,
|
|
538
|
+
// 让版本检查的日志落到正确目录(默认 ~/.lark-remote 或 --config-dir 指定)。
|
|
539
|
+
const updateConfigDir = resolveConfigDir(cliArgs.configDir);
|
|
540
|
+
initLogger({ dir: path.join(updateConfigDir, 'logs') });
|
|
541
|
+
try {
|
|
542
|
+
const { current, latest } = await checkLatestVersion();
|
|
543
|
+
if (!isNewer(current, latest)) {
|
|
544
|
+
console.log(`Already up to date: ${current}`);
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}
|
|
547
|
+
console.log(`Updating ${current} → ${latest} ...`);
|
|
548
|
+
const result = await runInstallLatest();
|
|
549
|
+
if (!result.success) {
|
|
550
|
+
console.error(`Update failed: ${result.error}`);
|
|
551
|
+
process.exit(1);
|
|
552
|
+
}
|
|
553
|
+
console.log(`✅ Updated to ${latest}. Restart lark-remote to use the new version.`);
|
|
554
|
+
process.exit(0);
|
|
555
|
+
}
|
|
556
|
+
catch (err) {
|
|
557
|
+
console.error(`Update failed: ${err.message}`);
|
|
558
|
+
process.exit(1);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
534
561
|
const { config, configDir, logger, instanceLock } = await initializeCliAndConfig();
|
|
535
562
|
// Restart handoff: if spawned as a /restart replacement, wait for the old
|
|
536
563
|
// bridge to die (and release the instance lock) before acquiring it.
|
|
@@ -574,6 +601,8 @@ async function main() {
|
|
|
574
601
|
ordersPath: path.join(configDir, 'orders.json'),
|
|
575
602
|
restartSpawner: () => spawnReplacementBridge(path.join(configDir, 'logs')),
|
|
576
603
|
sessionReaderRegistry,
|
|
604
|
+
devMode: cliArgs.dev,
|
|
605
|
+
updateCachePath: path.join(configDir, 'update-cache.json'),
|
|
577
606
|
});
|
|
578
607
|
setupMessageHandlers(connector, router, bridge, sessionStore, workspaceStore, binder, logger, config);
|
|
579
608
|
try {
|
|
@@ -586,6 +615,27 @@ async function main() {
|
|
|
586
615
|
if (binder.isBound()) {
|
|
587
616
|
// 仅已绑定时发送启动通知;未绑定时不打扰(PIN 引导已在控制台输出)
|
|
588
617
|
await sendStartupHello(connector, startupContactStore, { dev: cliArgs.dev });
|
|
618
|
+
// 启动时静默检查版本(由 checkUpdateOnStartup 控制,默认关闭)
|
|
619
|
+
if (config.checkUpdateOnStartup) {
|
|
620
|
+
void (async () => {
|
|
621
|
+
try {
|
|
622
|
+
const cachePath = path.join(configDir, 'update-cache.json');
|
|
623
|
+
const { current, latest } = await checkLatestVersion({ cachePath });
|
|
624
|
+
const hint = formatUpdateHint(current, latest);
|
|
625
|
+
if (hint) {
|
|
626
|
+
const contact = startupContactStore.getContact();
|
|
627
|
+
const recipient = contact?.chatId ?? contact?.userId;
|
|
628
|
+
if (recipient) {
|
|
629
|
+
await connector.sendWithRetry(recipient, { text: hint });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
catch (err) {
|
|
634
|
+
// Non-fatal: startup check failure must not crash the bridge
|
|
635
|
+
logger.warn(`[startup] update check failed: ${err.message}`);
|
|
636
|
+
}
|
|
637
|
+
})();
|
|
638
|
+
}
|
|
589
639
|
}
|
|
590
640
|
// Auto-restore: resume the persisted sessionId if available
|
|
591
641
|
const restoredContact = startupContactStore.getContact();
|
|
@@ -16,13 +16,3 @@ import type { AgentConfigCardBuilder } from './types.js';
|
|
|
16
16
|
export declare function getConfigBuilder(agent: AgentKind): AgentConfigCardBuilder;
|
|
17
17
|
/** List all registered agent kinds. */
|
|
18
18
|
export declare function listRegisteredAgents(): AgentKind[];
|
|
19
|
-
/**
|
|
20
|
-
* Sort agent kinds for UI lists: agents that are definitively unavailable
|
|
21
|
-
* (availability === false) sink to the bottom; everything else keeps its
|
|
22
|
-
* input order. The sort is stable, so relative order within both groups is
|
|
23
|
-
* preserved (e.g. registration order in the config card selector).
|
|
24
|
-
*
|
|
25
|
-
* `undefined` (probe cache missing/expired) is treated as "keep in place",
|
|
26
|
-
* so a stale cache cannot misplace installed agents.
|
|
27
|
-
*/
|
|
28
|
-
export declare function sortAgentsByAvailability(kinds: AgentKind[], isAvailable: (kind: AgentKind) => boolean | undefined): AgentKind[];
|
|
@@ -35,21 +35,4 @@ export function getConfigBuilder(agent) {
|
|
|
35
35
|
export function listRegisteredAgents() {
|
|
36
36
|
return Object.keys(builders);
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Sort agent kinds for UI lists: agents that are definitively unavailable
|
|
40
|
-
* (availability === false) sink to the bottom; everything else keeps its
|
|
41
|
-
* input order. The sort is stable, so relative order within both groups is
|
|
42
|
-
* preserved (e.g. registration order in the config card selector).
|
|
43
|
-
*
|
|
44
|
-
* `undefined` (probe cache missing/expired) is treated as "keep in place",
|
|
45
|
-
* so a stale cache cannot misplace installed agents.
|
|
46
|
-
*/
|
|
47
|
-
export function sortAgentsByAvailability(kinds, isAvailable) {
|
|
48
|
-
const unavailable = new Set(kinds.filter((kind) => isAvailable(kind) === false));
|
|
49
|
-
return [...kinds].sort((a, b) => {
|
|
50
|
-
const rankA = unavailable.has(a) ? 1 : 0;
|
|
51
|
-
const rankB = unavailable.has(b) ? 1 : 0;
|
|
52
|
-
return rankA - rankB;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
38
|
//# sourceMappingURL=index.js.map
|
package/dist/router/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { SessionStore, SessionReaderRegistry } from '../session/index.js';
|
|
|
2
2
|
import type { AppConfig } from '../config/index.js';
|
|
3
3
|
import type { Bridge } from '../bridge/index.js';
|
|
4
4
|
import type { AgentBinding } from '../bridge/queue-manager.js';
|
|
5
|
+
import { type VersionCheckResult, type InstallResult } from '../update/index.js';
|
|
5
6
|
interface CommandContext {
|
|
6
7
|
userId: string;
|
|
7
8
|
chatId: string;
|
|
@@ -91,6 +92,12 @@ export declare class CommandRouter {
|
|
|
91
92
|
*/
|
|
92
93
|
private restartSpawner?;
|
|
93
94
|
private idleTimeoutMs;
|
|
95
|
+
/** Dev mode flag: --dev means the bridge was started from source (bun src/index.ts). */
|
|
96
|
+
private devMode;
|
|
97
|
+
/** Cache file for version checks (<configDir>/update-cache.json). */
|
|
98
|
+
private updateCachePath?;
|
|
99
|
+
/** Injected update functions (for testability; defaults to real implementation). */
|
|
100
|
+
private updateFns;
|
|
94
101
|
private pendingConfig;
|
|
95
102
|
/**
|
|
96
103
|
* Monotonic counter minting unique internal keys for order.exec enqueue
|
|
@@ -135,6 +142,19 @@ export declare class CommandRouter {
|
|
|
135
142
|
idleTimeoutMs?: number;
|
|
136
143
|
/** Session reader registry (required). */
|
|
137
144
|
sessionReaderRegistry: SessionReaderRegistry;
|
|
145
|
+
/** Dev mode: bridge was started from source (--dev flag). */
|
|
146
|
+
devMode?: boolean;
|
|
147
|
+
/** Version check cache path (<configDir>/update-cache.json). */
|
|
148
|
+
updateCachePath?: string;
|
|
149
|
+
/** Override update functions for testability. */
|
|
150
|
+
updateFns?: {
|
|
151
|
+
checkLatestVersion?: (opts?: {
|
|
152
|
+
cachePath?: string;
|
|
153
|
+
bypassCache?: boolean;
|
|
154
|
+
}) => Promise<VersionCheckResult>;
|
|
155
|
+
isNewer?: (current: string, latest: string) => boolean | null;
|
|
156
|
+
runInstallLatest?: () => Promise<InstallResult>;
|
|
157
|
+
};
|
|
138
158
|
});
|
|
139
159
|
/**
|
|
140
160
|
* Resolve the active session reader for the current defaultAgent.
|
|
@@ -307,7 +327,7 @@ export declare class CommandRouter {
|
|
|
307
327
|
*
|
|
308
328
|
* @param updates 以 config 路径形式给出的变更 key(卡片路径来自 diffConfig;
|
|
309
329
|
* 直写路径传 mapAgentKey(key),保证 pi./codex./opencode./kimi. 命中 agents.*)。
|
|
310
|
-
* @returns
|
|
330
|
+
* @returns 结构化切换结果(含 notice 文案 + 新 agent/sessionId 信息)
|
|
311
331
|
*/
|
|
312
332
|
private propagateConfigSave;
|
|
313
333
|
private executeCommand;
|
|
@@ -317,6 +337,14 @@ export declare class CommandRouter {
|
|
|
317
337
|
private cmdStop;
|
|
318
338
|
private cmdExit;
|
|
319
339
|
private cmdRestart;
|
|
340
|
+
/**
|
|
341
|
+
* /update: check for newer version and upgrade if available.
|
|
342
|
+
* /update check: only check, don't upgrade.
|
|
343
|
+
*
|
|
344
|
+
* Flow: check version → if newer, install → spawn replacement bridge → exit.
|
|
345
|
+
* Reuses /restart's restartSpawner + pendingExit mechanism.
|
|
346
|
+
*/
|
|
347
|
+
private cmdUpdate;
|
|
320
348
|
private cmdNew;
|
|
321
349
|
private cmdCd;
|
|
322
350
|
private readSessionDisplayState;
|
|
@@ -330,6 +358,13 @@ export declare class CommandRouter {
|
|
|
330
358
|
private switchCwdAndNotify;
|
|
331
359
|
/** Build card for auto-resumed session with new session button */
|
|
332
360
|
private buildAutoResumeCard;
|
|
361
|
+
/**
|
|
362
|
+
* Build card for config.save agent switch.
|
|
363
|
+
* When the new agent has a restored session, renders a Resume card (mirrors
|
|
364
|
+
* buildAutoResumeCard) with session history + stop/new buttons.
|
|
365
|
+
* When the session was cleared, renders a compact notice card instead.
|
|
366
|
+
*/
|
|
367
|
+
private buildConfigSwitchCard;
|
|
333
368
|
private static readonly LS_PAGE_SIZE;
|
|
334
369
|
private cmdLs;
|
|
335
370
|
private cmdWs;
|