lark-remote 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/bridge/approval-coordinator.d.ts +1 -1
- package/dist/bridge/approval-coordinator.js +3 -3
- package/dist/bridge/inbound-media.js +10 -7
- package/dist/bridge/index.d.ts +9 -9
- package/dist/bridge/index.js +41 -41
- package/dist/bridge/queue-manager.d.ts +12 -12
- package/dist/bridge/queue-manager.js +19 -19
- package/dist/card/approval-render.d.ts +1 -1
- package/dist/card/approval-render.js +6 -6
- package/dist/card/bash-card-session.d.ts +3 -3
- package/dist/card/bash-card-session.js +3 -3
- package/dist/card/bash-renderer.js +1 -1
- package/dist/card/card-session.d.ts +4 -4
- package/dist/card/card-session.js +6 -6
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.d.ts +1 -1
- package/dist/card/run-renderer.js +14 -14
- package/dist/card/run-state.d.ts +2 -2
- package/dist/card/run-state.js +3 -3
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +6 -6
- package/dist/clone.d.ts +5 -3
- package/dist/clone.js +8 -4
- package/dist/common/truncate.d.ts +1 -1
- package/dist/common/truncate.js +2 -2
- package/dist/common/ttl-cache.d.ts +2 -2
- package/dist/common/ttl-cache.js +2 -2
- package/dist/config/codex-config.d.ts +9 -9
- package/dist/config/codex-config.js +18 -18
- package/dist/config/dir.js +5 -2
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -6
- package/dist/config/opencode-config.js +3 -3
- package/dist/connector/index.d.ts +5 -5
- package/dist/connector/index.js +24 -11
- package/dist/inbound/event.d.ts +1 -1
- package/dist/inbound/event.js +1 -1
- package/dist/inbound/placeholder.d.ts +2 -2
- package/dist/inbound/placeholder.js +2 -2
- package/dist/index.js +9 -9
- package/dist/instance-lock.js +4 -3
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.js +4 -4
- package/dist/persistence/atomic-write.d.ts +1 -1
- package/dist/persistence/atomic-write.js +6 -2
- package/dist/platform/agent-stopper.d.ts +1 -1
- package/dist/platform/identity.d.ts +7 -1
- package/dist/platform/identity.js +2 -2
- package/dist/platform/path.d.ts +6 -0
- package/dist/platform/path.js +14 -8
- package/dist/platform/select.d.ts +1 -1
- package/dist/platform/select.js +1 -1
- package/dist/platform/shell.d.ts +2 -3
- package/dist/platform/spawn.d.ts +2 -2
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/terminator-posix.js +1 -1
- package/dist/platform/terminator-win32.js +2 -2
- package/dist/platform/terminator.d.ts +3 -3
- package/dist/platform/terminator.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/restart.d.ts +1 -1
- package/dist/restart.js +5 -3
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +2 -2
- package/dist/router/config/opencode.js +1 -1
- package/dist/router/index.d.ts +9 -9
- package/dist/router/index.js +34 -34
- package/dist/router/order-exec-dispatch.js +2 -2
- package/dist/runner/bash/runner.d.ts +2 -2
- package/dist/runner/bash/runner.js +6 -6
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/claude/session.d.ts +2 -2
- package/dist/runner/claude/session.js +14 -14
- package/dist/runner/codex/app-server/runner.js +3 -3
- package/dist/runner/codex/app-server/translator.d.ts +2 -2
- package/dist/runner/codex/app-server/translator.js +5 -5
- package/dist/runner/common/acp/acp-approval.d.ts +2 -2
- package/dist/runner/common/acp/acp-approval.js +3 -3
- package/dist/runner/common/acp/base-acp-runner.js +1 -1
- package/dist/runner/common/acp/base-translator.d.ts +3 -3
- package/dist/runner/common/acp/base-translator.js +4 -4
- package/dist/runner/common/acp/protocol-types.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +1 -1
- package/dist/runner/common/connection-based-runner.js +2 -2
- package/dist/runner/common/jsonl-stream.d.ts +2 -2
- package/dist/runner/common/jsonl-stream.js +12 -12
- package/dist/runner/common/jsonrpc/client.d.ts +1 -1
- package/dist/runner/common/jsonrpc/client.js +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +1 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +2 -2
- package/dist/runner/common/spawning-runner.d.ts +21 -8
- package/dist/runner/common/spawning-runner.js +41 -15
- package/dist/runner/dsh/runner.js +2 -2
- package/dist/runner/kimi/acp/runner.d.ts +3 -3
- package/dist/runner/kimi/acp/runner.js +9 -9
- package/dist/runner/kimi/acp/translator.d.ts +2 -2
- package/dist/runner/kimi/acp/translator.js +4 -4
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +3 -3
- package/dist/runner/opencode/acp/translator.js +1 -1
- package/dist/runner/pi/rpc/runner.js +2 -2
- package/dist/runner/pi/rpc/translator.d.ts +1 -1
- package/dist/runner/pi/rpc/translator.js +4 -4
- package/dist/runner/types.d.ts +1 -1
- package/dist/session/claude/session-index.d.ts +12 -0
- package/dist/session/claude/session-index.js +20 -5
- package/dist/session/claude/sessions.js +10 -10
- package/dist/session/codex/rollout-reader.d.ts +4 -4
- package/dist/session/codex/rollout-reader.js +12 -12
- package/dist/session/common/two-pass.js +2 -2
- package/dist/session/common/usage-accumulator.d.ts +2 -2
- package/dist/session/common/usage-accumulator.js +2 -2
- package/dist/session/dsh/sessions.js +2 -2
- package/dist/session/kimi/sessions.d.ts +2 -2
- package/dist/session/kimi/sessions.js +11 -11
- package/dist/session/opencode/sessions.d.ts +3 -3
- package/dist/session/opencode/sessions.js +12 -10
- package/dist/session/pi/sessions.js +13 -12
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 入站消息结构占位符识别与剥离(纯函数,无 IO,便于表驱动测试)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 背景(事故 2026-09-15):`@larksuite/channel` 把非文本消息渲染成结构占位符,
|
|
5
5
|
* 富文本(`post`)里的图片是 `` —— 首字符恰好是 `!`,于是被
|
|
6
6
|
* `src/router/index.ts` 的裸前缀判定当成 bash 命令执行了整段消息。
|
|
7
7
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 取舍(有意为之):剥离对所有消息生效,所以用户在**纯文本**消息里手打
|
|
13
13
|
* `` / `<file key="…"/>` 这类字面量也会被当成占位符剥掉。代价是极少数
|
|
14
14
|
* 「讨论本工具自身语法」的消息会丢片段;换来的是任何入口都不可能把占位符当命令
|
|
15
|
-
*
|
|
15
|
+
* (事故正是 `` 的首字符 `!` 命中 bash 判定)。
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
18
|
* 可下载资源类占位符:这些占位符对应的资源会由媒体通道下载落盘,
|
package/dist/index.js
CHANGED
|
@@ -133,10 +133,10 @@ async function setupInstanceLockAndHandlers(instanceLock, logger, configDir) {
|
|
|
133
133
|
/** Initialize per-workspace runner factory + agent registry. */
|
|
134
134
|
function initializeRunner(config, configDir, cliArgs) {
|
|
135
135
|
const agentRegistry = new AgentRegistry();
|
|
136
|
-
//
|
|
136
|
+
// 单点化:runner factory 取最新 config 的唯一入口(语义保持,
|
|
137
137
|
// container 未接线时回退启动快照)。
|
|
138
138
|
const latest = () => agentRegistry.getConfigContainer()?.current ?? config;
|
|
139
|
-
//
|
|
139
|
+
// Claude factory reads from configContainer (not closure) so runtime
|
|
140
140
|
// config changes (model, effort, stopGraceMs) take effect after
|
|
141
141
|
// bridge.setConfig() + clearRunners(). Same pattern as codex/pi/kimi.
|
|
142
142
|
agentRegistry.register('claude', (ws) => {
|
|
@@ -256,7 +256,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
256
256
|
// Register DshRunner (HTTP-only DSH Web Host agent) and DshSessionReader.
|
|
257
257
|
const dshSessionReader = new DshSessionReader({
|
|
258
258
|
// hostProvider 每次调用读最新 config:/config 修改 host 后 runner 重建为
|
|
259
|
-
// 新 host,reader(/resume、用量、完成卡)也跟随新 host
|
|
259
|
+
// 新 host,reader(/resume、用量、完成卡)也跟随新 host。
|
|
260
260
|
hostProvider: () => getAgentConfig(latest(), 'dsh')?.host,
|
|
261
261
|
});
|
|
262
262
|
agentRegistry.register('dsh', (_ws) => {
|
|
@@ -308,7 +308,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
308
308
|
// 附件路径统一注入 prompt;无文本纯附件只回执(决策 2)。
|
|
309
309
|
const assembler = new InboundTurnAssembler({
|
|
310
310
|
onCommit: (turn, prompt) => {
|
|
311
|
-
//
|
|
311
|
+
// 入队时刻快照 cwd + agent/session 绑定,语义与旧路径一致
|
|
312
312
|
// (排队期间 /cd、/config 不再导致语义漂移)。
|
|
313
313
|
const messageId = turn.messageIds[turn.messageIds.length - 1] ?? '';
|
|
314
314
|
// turnMessageIds:终态收尾要覆盖本轮每一条消息(逐条挂的 Typing 得逐条撤)
|
|
@@ -347,7 +347,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
347
347
|
},
|
|
348
348
|
});
|
|
349
349
|
router.setInboundFlusher(() => assembler.flushAll('flush'));
|
|
350
|
-
//
|
|
350
|
+
// 入站媒体(图片/文件/视频/语音/表情):先认证后下载(修复)。
|
|
351
351
|
// connector 只上报"媒体到达",这里先过 owner + enabled 闸门,通过后才
|
|
352
352
|
// 下载——未认证/关闭配置时不会发生任何网络下载或内存/磁盘占用。
|
|
353
353
|
// 下载是异步的:立刻把在途 promise 交给装配器(`kind: 'media'`),
|
|
@@ -358,9 +358,9 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
358
358
|
return;
|
|
359
359
|
}
|
|
360
360
|
// 读 router.config(活引用):/config 保存后 setConfigValues 返回新对象,
|
|
361
|
-
// 启动时捕获的 config
|
|
361
|
+
// 启动时捕获的 config 参数会过期(修复)。
|
|
362
362
|
if (!router.config.inboundMedia.enabled) {
|
|
363
|
-
//
|
|
363
|
+
// 不静默:关闭时给 owner 明确反馈,避免发图后无任何反应。
|
|
364
364
|
// 不下载、不落盘,只进 rejected(随 turn 回执一起发出)。
|
|
365
365
|
assembler.ingest({
|
|
366
366
|
kind: 'rejected',
|
|
@@ -585,7 +585,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
585
585
|
// order.aliasInput / order.aliasRemove / order.textInput 也返回 toast+card,
|
|
586
586
|
// 需直返避免被吞(order.textInput 曾漏在列表外,编辑卡停留在编辑界面)。
|
|
587
587
|
// approval.planFeedback 同 answer 系列:附意见的 toast 需直返 + 不落串行队列。
|
|
588
|
-
//
|
|
588
|
+
// 直返名单以 router 的 DIRECT_RETURN_CMDS 为单一来源(历史注释:
|
|
589
589
|
// order.textInput 曾漏在列表外、answer 家族曾漏在直返列表外——两份拷贝漂移)。
|
|
590
590
|
if (DIRECT_RETURN_CMDS.has(actionValue.cmd)) {
|
|
591
591
|
return router.handleCardAction(fullValue, { userId, chatId, messageId });
|
|
@@ -605,7 +605,7 @@ function setupMessageHandlers(connector, router, bridge, sessionStore, workspace
|
|
|
605
605
|
}).catch((err) => logger.error('[control] order.exec dispatch failed:', err));
|
|
606
606
|
return;
|
|
607
607
|
}
|
|
608
|
-
//
|
|
608
|
+
// 非直返命令到这里已经没有回调响应可回(enqueue* 是 fire-and-forget),
|
|
609
609
|
// handler 返回的 toast 必须显式转成持久文本,否则失败毫无反馈。
|
|
610
610
|
const forwardActionFeedback = (res) => {
|
|
611
611
|
const text = actionFeedbackText(res);
|
package/dist/instance-lock.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { bestEffortUnlink } from './common/fs.js';
|
|
21
|
+
import { isWin32 } from './platform/select.js';
|
|
21
22
|
import { binaryName, verifyPidIdentityVerdict } from './platform/identity.js';
|
|
22
23
|
export class InstanceAlreadyRunningError extends Error {
|
|
23
24
|
pid;
|
|
@@ -29,7 +30,7 @@ export class InstanceAlreadyRunningError extends Error {
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
/**
|
|
32
|
-
* Probe whether a pid is alive.
|
|
33
|
+
* Probe whether a pid is alive.
|
|
33
34
|
* - process.kill(pid, 0) returning normally → alive.
|
|
34
35
|
* - ESRCH (no such process) → dead.
|
|
35
36
|
* - EPERM (operation not permitted) → the process EXISTS but is owned by
|
|
@@ -127,14 +128,14 @@ export class InstanceLock {
|
|
|
127
128
|
this.release();
|
|
128
129
|
process.exit(143);
|
|
129
130
|
});
|
|
130
|
-
if (
|
|
131
|
+
if (!isWin32()) {
|
|
131
132
|
process.on('SIGHUP', () => {
|
|
132
133
|
this.release();
|
|
133
134
|
process.exit(129);
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
else {
|
|
137
|
-
// win32 无 SIGHUP;CTRL_BREAK
|
|
138
|
+
// win32 无 SIGHUP;CTRL_BREAK 退出前也要释放锁(对照 cli.ts 信号转发)
|
|
138
139
|
process.on('SIGBREAK', () => {
|
|
139
140
|
this.release();
|
|
140
141
|
process.exit(149);
|
package/dist/logger/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare class Logger {
|
|
|
35
35
|
getCurrentLogFile(): string;
|
|
36
36
|
private ensureFile;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* remove daily subdirectories older than the retention window.
|
|
39
39
|
* Best-effort — any error is swallowed (logging must never throw).
|
|
40
40
|
*/
|
|
41
41
|
private pruneOldDirs;
|
package/dist/logger/index.js
CHANGED
|
@@ -21,7 +21,7 @@ function todayStr(d) {
|
|
|
21
21
|
return `${y}-${m}-${day}`;
|
|
22
22
|
}
|
|
23
23
|
function timestampStr(d) {
|
|
24
|
-
//
|
|
24
|
+
// local ISO 8601 with milliseconds and explicit offset, e.g.
|
|
25
25
|
// 2026-08-03T01:30:00.000+08:00. Previously this used toISOString() (UTC)
|
|
26
26
|
// while the daily directory boundary (todayStr) used local time — after
|
|
27
27
|
// local midnight in a +08:00 zone the new day's directory held lines
|
|
@@ -83,7 +83,7 @@ export class Logger {
|
|
|
83
83
|
fs.mkdirSync(dayDir, { recursive: true });
|
|
84
84
|
this.currentFile = path.join(dayDir, `${this.prefix}-${this.pid}.log`);
|
|
85
85
|
this.currentDate = dateStr;
|
|
86
|
-
//
|
|
86
|
+
// prune daily log directories older than the retention window so
|
|
87
87
|
// the logs directory does not grow without bound (single-day size cap is
|
|
88
88
|
// out of scope; bounded retention by day is the requested guard). Wrapped
|
|
89
89
|
// defensively so cleanup failure never blocks logging.
|
|
@@ -91,7 +91,7 @@ export class Logger {
|
|
|
91
91
|
return this.currentFile;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
*
|
|
94
|
+
* remove daily subdirectories older than the retention window.
|
|
95
95
|
* Best-effort — any error is swallowed (logging must never throw).
|
|
96
96
|
*/
|
|
97
97
|
pruneOldDirs(currentDateStr) {
|
|
@@ -125,7 +125,7 @@ export class Logger {
|
|
|
125
125
|
const message = format(...args);
|
|
126
126
|
const line = `${ts} [${LEVEL_UPPER[level]}] ${message}\n`;
|
|
127
127
|
const file = this.ensureFile(date);
|
|
128
|
-
//
|
|
128
|
+
// never let a disk error (EACCES / ENOSPC / EROFS) escape write().
|
|
129
129
|
// If logging itself throws — especially inside an uncaughtException handler
|
|
130
130
|
// that calls logger.error(...) — a re-throw would abort Node and skip
|
|
131
131
|
// instanceLock.release(), leaking the singleton lock. Logging must never
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Atomically write content (string or binary Buffer) to a file via tmp + rename.
|
|
3
3
|
* Falls back to copy+unlink when rename fails with EXDEV (cross-device).
|
|
4
|
-
* rename 目标被占用(win32 EPERM/EBUSY
|
|
4
|
+
* rename 目标被占用(win32 EPERM/EBUSY)时短暂退避重试;重试耗尽后
|
|
5
5
|
* 保留 tmp 现场并告警,再抛出原错误。
|
|
6
6
|
*/
|
|
7
7
|
export declare function atomicWrite(filePath: string, content: string | Buffer, encoding?: BufferEncoding): void;
|
|
@@ -5,13 +5,17 @@ import { getLogger } from '../logger/index.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Atomically write content (string or binary Buffer) to a file via tmp + rename.
|
|
7
7
|
* Falls back to copy+unlink when rename fails with EXDEV (cross-device).
|
|
8
|
-
* rename 目标被占用(win32 EPERM/EBUSY
|
|
8
|
+
* rename 目标被占用(win32 EPERM/EBUSY)时短暂退避重试;重试耗尽后
|
|
9
9
|
* 保留 tmp 现场并告警,再抛出原错误。
|
|
10
10
|
*/
|
|
11
11
|
export function atomicWrite(filePath, content, encoding = 'utf-8') {
|
|
12
12
|
const dir = path.dirname(filePath);
|
|
13
13
|
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
-
|
|
14
|
+
// tmp 命名:pid-ts-rand 后缀,避免固定
|
|
15
|
+
// `.tmp` 名在异常残留后被下一次写入复用/误判;必须与目标同目录(rename 原子性)
|
|
16
|
+
const tmpPath = path.join(dir, `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random()
|
|
17
|
+
.toString(36)
|
|
18
|
+
.slice(2, 10)}`);
|
|
15
19
|
// Buffer 内容忽略 encoding(writeFileSync 对 Buffer 自动以二进制写出)。
|
|
16
20
|
fs.writeFileSync(tmpPath, content, encoding);
|
|
17
21
|
// fsync the tmp file before rename so its data is durable on disk;
|
|
@@ -12,7 +12,13 @@ export interface QueryIdentityOptions {
|
|
|
12
12
|
export interface VerifyPidIdentityOptions extends QueryIdentityOptions {
|
|
13
13
|
/** 期望的二进制名(basename,如 'claude'/'node');win32/darwin 大小写不敏感 */
|
|
14
14
|
expectedBinary: string;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* pid 文件记录的 CreationDate;缺省则退化为仅命令行匹配。
|
|
17
|
+
* 现状:**无生产写入方**(pid 文件/锁文件都不记 CreationDate),仅测试
|
|
18
|
+
* 使用——接线需 win32 写侧在正常启动路径多一次 PowerShell 查询自身
|
|
19
|
+
* CreationDate,与 instance-lock「CIM 只在锁已存在路径查询」的约束冲突,
|
|
20
|
+
* 故保留该字段但暂不接线。
|
|
21
|
+
*/
|
|
16
22
|
expectedCreationDate?: string;
|
|
17
23
|
/** 匹配强度;默认 'executable'(见 {@link IdentityMatchMode}) */
|
|
18
24
|
matchMode?: IdentityMatchMode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pid
|
|
2
|
+
* pid 身份验证。
|
|
3
3
|
*
|
|
4
4
|
* POSIX 现状用 `ps -o command=` 读命令行,Windows 没有 ps,等价物是 CIM:
|
|
5
5
|
* PowerShell `Get-CimInstance Win32_Process` 取 CommandLine,并额外取
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - 只在查询失败/身份不匹配时返回 null / false,绝不抛给调用方——调用方
|
|
10
10
|
* (killOrphan、实例锁陈旧判定)一律「只有身份匹配才动手」;
|
|
11
11
|
* - win32 每次查询要拉起 PowerShell(冷启动约 1s),批量扫 pid 文件的场景
|
|
12
|
-
* (killOrphan
|
|
12
|
+
* (killOrphan)应按需调用,真机矩阵需实测整体耗时;
|
|
13
13
|
* - platform 显式注入,win32 语义可在任意宿主上单测。
|
|
14
14
|
*/
|
|
15
15
|
import { execFileSync, spawn } from 'node:child_process';
|
package/dist/platform/path.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export interface PathOptions {
|
|
2
2
|
platform?: NodeJS.Platform;
|
|
3
3
|
}
|
|
4
|
+
/**
|
|
5
|
+
* 路径的「判等键」形态:把 samePath 的平台归一规则单源化,供 Map/Set 键控
|
|
6
|
+
* 场景使用(按 cwd 建倒排索引时不能每次查询做 O(n) 两两比较)。不变量:
|
|
7
|
+
* `samePath(a, b)` 当且仅当 `pathKey(a) === pathKey(b)`。
|
|
8
|
+
*/
|
|
9
|
+
export declare function pathKey(p: string, opts?: PathOptions): string;
|
|
4
10
|
export declare function samePath(a: string, b: string, opts?: PathOptions): boolean;
|
|
5
11
|
/**
|
|
6
12
|
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
package/dist/platform/path.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PathKit —
|
|
2
|
+
* PathKit — 跨平台路径语义。
|
|
3
3
|
*
|
|
4
4
|
* 所有 cwd 相等性判断、会话目录编码、pid 文件名后缀、spawn cwd 前统一走本模块。
|
|
5
5
|
* 语义映射:
|
|
@@ -14,14 +14,18 @@ import { currentPlatform, isWin32 } from './select.js';
|
|
|
14
14
|
function resolveOptions(opts) {
|
|
15
15
|
return { platform: opts?.platform ?? currentPlatform };
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* 路径的「判等键」形态:把 samePath 的平台归一规则单源化,供 Map/Set 键控
|
|
19
|
+
* 场景使用(按 cwd 建倒排索引时不能每次查询做 O(n) 两两比较)。不变量:
|
|
20
|
+
* `samePath(a, b)` 当且仅当 `pathKey(a) === pathKey(b)`。
|
|
21
|
+
*/
|
|
22
|
+
export function pathKey(p, opts) {
|
|
18
23
|
const { platform } = resolveOptions(opts);
|
|
19
24
|
if (platform === 'linux')
|
|
20
|
-
return
|
|
25
|
+
return p;
|
|
21
26
|
if (isWin32(platform)) {
|
|
22
27
|
// win32:\ 与 / 都是分隔符,且路径大小写不敏感
|
|
23
|
-
|
|
24
|
-
return norm(a).toLowerCase() === norm(b).toLowerCase();
|
|
28
|
+
return p.replaceAll('\\', '/').replace(/\/+$/, '').toLowerCase();
|
|
25
29
|
}
|
|
26
30
|
// darwin:默认按大小写不敏感处理(macOS 默认文件系统 APFS 不敏感);
|
|
27
31
|
// 反斜杠是合法文件名字符,不动。分隔符只有 /。
|
|
@@ -29,9 +33,11 @@ export function samePath(a, b, opts) {
|
|
|
29
33
|
// 此处会判等(cwd 守卫假阳性)——影响面小(默认卷不敏感),如需精确需
|
|
30
34
|
// 逐目录 stat 探测卷大小写属性,暂不做。
|
|
31
35
|
// 尾部分隔符必须与 win32 同口径忽略,否则 `/cd ~/repo/` 与 `/cd ~/repo`
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
// 会被判成两个目录,迁移后直接表现为会话目录错配。
|
|
37
|
+
return p.replace(/\/+$/, '').toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
export function samePath(a, b, opts) {
|
|
40
|
+
return pathKey(a, opts) === pathKey(b, opts);
|
|
35
41
|
}
|
|
36
42
|
/**
|
|
37
43
|
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
package/dist/platform/select.js
CHANGED
package/dist/platform/shell.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ShellBackend:把 `!` 命令与 kimi ACP terminal 的 shell 依赖收进 seam
|
|
3
|
-
* (design.md §7.2)。
|
|
2
|
+
* ShellBackend:把 `!` 命令与 kimi ACP terminal 的 shell 依赖收进 seam。
|
|
4
3
|
*
|
|
5
4
|
* - posix:`bash -c <command>`(与现状 BashProcessRunner 同语义);
|
|
6
5
|
* - win32:默认 Git Bash(`bash.exe -c <command>`),powershell / cmd 仅作为
|
|
7
|
-
*
|
|
6
|
+
* 显式用户选择;
|
|
8
7
|
* - 首版不做命令翻译器:Git Bash 缺失时抛 {@link ShellUnavailableError},
|
|
9
8
|
* 由调用方转成明确错误卡(提示需要 Git Bash)。
|
|
10
9
|
*
|
package/dist/platform/spawn.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function spawnProcessSync(command: string, args?: readonly string
|
|
|
14
14
|
/**
|
|
15
15
|
* env 覆盖合并:先按不区分大小写删除 base 中的同名旧键,再设新值。
|
|
16
16
|
* Windows env 键大小写不敏感,`{...process.env, PATH:x}` 会产生 `PATH`/`Path`
|
|
17
|
-
*
|
|
17
|
+
* 双键。注意 posix 上并非普通 spread:base 中大小写不同名的键(如
|
|
18
18
|
* `Path`)也会被删除,而非与 `PATH` 并存——可接受(posix 语义键为全大写
|
|
19
19
|
* `PATH`,大小写变体本身就不是语义键),但语义与 spread 不同,勿混淆。
|
|
20
20
|
*/
|
|
@@ -30,7 +30,7 @@ export declare function mergeProcessEnv(base?: NodeJS.ProcessEnv, overrides?: No
|
|
|
30
30
|
* Windows 上 `DETACHED_PROCESS` 下 cmd.exe 另建控制台并重绑标准句柄,于是
|
|
31
31
|
* 子进程(npm 安装的 agent 全是 `.cmd` 垫片)的 stdout/stderr 永不抵达父进程,
|
|
32
32
|
* 表现为 agent 全程静默、turn 超时。实测 `windowsHide: true` 不能改善,
|
|
33
|
-
* 因此
|
|
33
|
+
* 因此 「保留 detached + windowsHide」的假设据此修正为「win32 不
|
|
34
34
|
* detached」。调用方应同时传 `windowsHide: true`,避免 cmd.exe 闪控制台窗口。
|
|
35
35
|
*/
|
|
36
36
|
export declare function useDetachedProcessGroup(platform?: NodeJS.Platform): boolean;
|
package/dist/platform/spawn.js
CHANGED
|
@@ -32,7 +32,7 @@ export function spawnProcessSync(command, args = [], options = {}) {
|
|
|
32
32
|
/**
|
|
33
33
|
* env 覆盖合并:先按不区分大小写删除 base 中的同名旧键,再设新值。
|
|
34
34
|
* Windows env 键大小写不敏感,`{...process.env, PATH:x}` 会产生 `PATH`/`Path`
|
|
35
|
-
*
|
|
35
|
+
* 双键。注意 posix 上并非普通 spread:base 中大小写不同名的键(如
|
|
36
36
|
* `Path`)也会被删除,而非与 `PATH` 并存——可接受(posix 语义键为全大写
|
|
37
37
|
* `PATH`,大小写变体本身就不是语义键),但语义与 spread 不同,勿混淆。
|
|
38
38
|
*/
|
|
@@ -61,7 +61,7 @@ const COMMAND_NOT_FOUND_PATTERN = /is not recognized as an internal or external
|
|
|
61
61
|
* Windows 上 `DETACHED_PROCESS` 下 cmd.exe 另建控制台并重绑标准句柄,于是
|
|
62
62
|
* 子进程(npm 安装的 agent 全是 `.cmd` 垫片)的 stdout/stderr 永不抵达父进程,
|
|
63
63
|
* 表现为 agent 全程静默、turn 超时。实测 `windowsHide: true` 不能改善,
|
|
64
|
-
* 因此
|
|
64
|
+
* 因此 「保留 detached + windowsHide」的假设据此修正为「win32 不
|
|
65
65
|
* detached」。调用方应同时传 `windowsHide: true`,避免 cmd.exe 闪控制台窗口。
|
|
66
66
|
*/
|
|
67
67
|
export function useDetachedProcessGroup(platform = currentPlatform) {
|
|
@@ -56,7 +56,7 @@ async function stopPosix(proc, opts, log) {
|
|
|
56
56
|
}
|
|
57
57
|
return { requested: true, via: 'taskkill' };
|
|
58
58
|
}
|
|
59
|
-
// 与 exit 事件竞速等 grace;用 once 避免重复 stop()
|
|
59
|
+
// 与 exit 事件竞速等 grace;用 once 避免重复 stop() 累积监听器
|
|
60
60
|
let graceTimer;
|
|
61
61
|
const exited = await Promise.race([
|
|
62
62
|
new Promise((resolve) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* win32 终止器:协议停止通道 → grace 等待 → taskkill
|
|
2
|
+
* win32 终止器:协议停止通道 → grace 等待 → taskkill 树杀。
|
|
3
3
|
*
|
|
4
4
|
* 状态机:
|
|
5
5
|
* 1. 进程已退出 → nothing(via='already-exited')
|
|
@@ -99,7 +99,7 @@ export function createWin32Terminator(deps) {
|
|
|
99
99
|
return { requested: true, via: 'taskkill' };
|
|
100
100
|
},
|
|
101
101
|
cleanupOnExit(proc) {
|
|
102
|
-
//
|
|
102
|
+
// 真机验证项(§9.2 矩阵):本方法在 process.on('exit') 里调用,而
|
|
103
103
|
// killTree 依赖 libuv uv_spawn 在 exit handler 内同步发起 taskkill——
|
|
104
104
|
// 大概率可用(uv_spawn 本身同步),但与 posix 路径(同步 process.kill)
|
|
105
105
|
// 可靠性不等价;失败表现为 win32 上 lark-remote 退出后 agent 变孤儿。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 平台终止器 seam
|
|
2
|
+
* 平台终止器 seam 的接口层。
|
|
3
3
|
*
|
|
4
4
|
* Windows 没有可拦截的跨进程 SIGTERM、负 PID 无效,因此把「进程组 + 信号」
|
|
5
5
|
* 换成「协议停止通道 + 进程树杀」。本文件只放接口与平台分发,实现见
|
|
@@ -14,7 +14,7 @@ export type { AgentStopper } from './agent-stopper.js';
|
|
|
14
14
|
* 进程终止器。
|
|
15
15
|
*
|
|
16
16
|
* `stop` 的 `immediate` 语义与原 `process-stopper` 的 `stop({immediate})` 对齐。
|
|
17
|
-
*
|
|
17
|
+
* 声明返回 `Promise<void>`,这里收窄为 {@link TerminateResult}:降级
|
|
18
18
|
* 必须可观测(优雅段被跳过、走了树杀、进程早已退出),否则 win32 上「等一个
|
|
19
19
|
* 不会来的退出事件」这类退化无法被发现。
|
|
20
20
|
*/
|
|
@@ -39,6 +39,6 @@ export interface TerminatorDeps {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* 唯一的终止器选择入口:posix 走组杀,win32 走「协议通道 + taskkill 树杀」。
|
|
42
|
-
* 其余模块一律拿这里的 Terminator,禁止散落平台 if
|
|
42
|
+
* 其余模块一律拿这里的 Terminator,禁止散落平台 if。
|
|
43
43
|
*/
|
|
44
44
|
export declare function createTerminator(deps: TerminatorDeps): Terminator;
|
|
@@ -17,12 +17,12 @@ function defaultTerminatorLog(level, message) {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* 唯一的终止器选择入口:posix 走组杀,win32 走「协议通道 + taskkill 树杀」。
|
|
20
|
-
* 其余模块一律拿这里的 Terminator,禁止散落平台 if
|
|
20
|
+
* 其余模块一律拿这里的 Terminator,禁止散落平台 if。
|
|
21
21
|
*/
|
|
22
22
|
export function createTerminator(deps) {
|
|
23
23
|
const platform = deps.platform ?? currentPlatform;
|
|
24
24
|
// 默认日志出口必须真的接上:win32 的「无协议通道 → 直接树杀」「协议通道抛错
|
|
25
|
-
// → 转树杀」以及 posix 的 grace
|
|
25
|
+
// → 转树杀」以及 posix 的 grace 超时都是设计要求的可观测降级信号,
|
|
26
26
|
// 缺省 undefined 会让这些信号全部静默消失。
|
|
27
27
|
const log = deps.log ?? defaultTerminatorLog;
|
|
28
28
|
if (isWin32(platform)) {
|
package/dist/platform/types.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface LaunchSpec {
|
|
|
11
11
|
/** 可执行文件路径(posix 绝对路径 / win32 含 .cmd 垫片路径) */
|
|
12
12
|
file: string;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/** 终止结果(win32 状态机产出;可观测降级用)。 */
|
|
15
15
|
export interface TerminateResult {
|
|
16
16
|
/** 进程是否已确认被请求终止(不代表已退出,退出由 exit 事件驱动) */
|
|
17
17
|
requested: boolean;
|
package/dist/restart.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare const RESTART_WAIT_PID_ENV = "LARK_REMOTE_RESTART_WAIT_PID";
|
|
|
16
16
|
* dir) propagate. Caller picks throw-vs-degrade semantics.
|
|
17
17
|
*
|
|
18
18
|
* Two disciplines live here so every caller inherits them for free:
|
|
19
|
-
* - windowsHide: win32 detached children would flash a console window
|
|
19
|
+
* - windowsHide: win32 detached children would flash a console window;
|
|
20
20
|
* - the 'error' handler is attached BEFORE the pid check — a failed spawn
|
|
21
21
|
* emits 'error' asynchronously (ENOENT/EACCES), and the synchronous pid
|
|
22
22
|
* check throws/returns first, so attaching after would leave the 'error'
|
package/dist/restart.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnProcess } from './platform/spawn.js';
|
|
1
|
+
import { mergeProcessEnv, spawnProcess } from './platform/spawn.js';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getLogger } from './logger/index.js';
|
|
@@ -23,7 +23,7 @@ const POLL_MS = 100;
|
|
|
23
23
|
* dir) propagate. Caller picks throw-vs-degrade semantics.
|
|
24
24
|
*
|
|
25
25
|
* Two disciplines live here so every caller inherits them for free:
|
|
26
|
-
* - windowsHide: win32 detached children would flash a console window
|
|
26
|
+
* - windowsHide: win32 detached children would flash a console window;
|
|
27
27
|
* - the 'error' handler is attached BEFORE the pid check — a failed spawn
|
|
28
28
|
* emits 'error' asynchronously (ENOENT/EACCES), and the synchronous pid
|
|
29
29
|
* check throws/returns first, so attaching after would leave the 'error'
|
|
@@ -58,7 +58,9 @@ export function spawnDetachedBridge(args, logFilePath, opts) {
|
|
|
58
58
|
*/
|
|
59
59
|
export function spawnReplacementBridge(logsDir) {
|
|
60
60
|
const pid = spawnDetachedBridge(process.argv.slice(1), path.join(logsDir, 'restart-child.log'), {
|
|
61
|
-
env
|
|
61
|
+
// env 注入统一走 mergeProcessEnv:Windows env 键大小写不敏感,
|
|
62
|
+
// 覆盖注入必须先按不区分大小写删旧键,避免双键
|
|
63
|
+
env: mergeProcessEnv(process.env, { [RESTART_WAIT_PID_ENV]: String(process.pid) }),
|
|
62
64
|
});
|
|
63
65
|
if (pid === null) {
|
|
64
66
|
throw new Error('spawn replacement lark-remote failed: no pid');
|
|
@@ -16,7 +16,7 @@ export declare class CodexConfigBuilder implements AgentConfigCardBuilder {
|
|
|
16
16
|
* - 否则优先 supported_reasoning_levels 中位 (len-1)/2;
|
|
17
17
|
* - 列表为空才用声明 default_reasoning_level;
|
|
18
18
|
* - 两者皆无 → value=undefined(清空档位 = 不传 effort;router setNestedValue
|
|
19
|
-
* 对 undefined
|
|
19
|
+
* 对 undefined 做键删除)。
|
|
20
20
|
*/
|
|
21
21
|
private effortPatchForModel;
|
|
22
22
|
}
|
|
@@ -41,7 +41,7 @@ export class CodexConfigBuilder {
|
|
|
41
41
|
key: 'agents.codex.reasoningEffort',
|
|
42
42
|
label: '推理强度',
|
|
43
43
|
type: 'select',
|
|
44
|
-
//
|
|
44
|
+
// 档位按目录实际声明透传:codex 支持 none/Custom,不再按标准枚举过滤
|
|
45
45
|
options: getReasoningEffortOptions(currentCodexModel),
|
|
46
46
|
currentValue: currentReasoningEffort,
|
|
47
47
|
});
|
|
@@ -108,7 +108,7 @@ export class CodexConfigBuilder {
|
|
|
108
108
|
* - 否则优先 supported_reasoning_levels 中位 (len-1)/2;
|
|
109
109
|
* - 列表为空才用声明 default_reasoning_level;
|
|
110
110
|
* - 两者皆无 → value=undefined(清空档位 = 不传 effort;router setNestedValue
|
|
111
|
-
* 对 undefined
|
|
111
|
+
* 对 undefined 做键删除)。
|
|
112
112
|
*/
|
|
113
113
|
effortPatchForModel(config, newModel) {
|
|
114
114
|
const currentReasoningEffort = config.agents?.codex?.reasoningEffort;
|
|
@@ -29,7 +29,7 @@ export class OpencodeConfigBuilder {
|
|
|
29
29
|
type: 'select',
|
|
30
30
|
options: modelOptions,
|
|
31
31
|
});
|
|
32
|
-
//
|
|
32
|
+
// mode select(build/plan,默认 build)——opencode 无运行期审批档位,
|
|
33
33
|
// mode 即审批粒度档位(build 逐项审批 / plan 规划模式)。
|
|
34
34
|
const currentMode = displayConfig.agents?.opencode?.mode ?? 'build';
|
|
35
35
|
fields.push({
|
package/dist/router/index.d.ts
CHANGED
|
@@ -222,7 +222,7 @@ export declare class CommandRouter {
|
|
|
222
222
|
/**
|
|
223
223
|
* Route a message: if it starts with /, handle as command; if it starts with !, execute as bash; otherwise forward to Claude.
|
|
224
224
|
*
|
|
225
|
-
* 命令识别前置条件(2026-09-15
|
|
225
|
+
* 命令识别前置条件(2026-09-15修复,`inbound-unified-input-design.md` §5.6):
|
|
226
226
|
* 裸前缀判定会被结构占位符命中 —— SDK 把富文本里的图片渲染成 ``,
|
|
227
227
|
* 首字符恰好是 `!`,整段用户消息被当 shell 命令执行(`executeBash "[image](img_v3_…"`)。
|
|
228
228
|
* 因此命令前缀只在「调用方声明纯文本 + 剥离占位符后非空且无占位符 + 首字符是 / 或 !」
|
|
@@ -304,7 +304,7 @@ export declare class CommandRouter {
|
|
|
304
304
|
*
|
|
305
305
|
* offset 恒重置为 0(新筛选从第 1 页开始)。路径校验与 TOCTOU 兜底照
|
|
306
306
|
* handleLsPage——校验失败只回错误 toast,绝不动卡片(否则用户看到一张空卡)。
|
|
307
|
-
* ls.browse / ls.switch 的 value 里刻意不带 q
|
|
307
|
+
* ls.browse / ls.switch 的 value 里刻意不带 q,换目录即清除筛选。
|
|
308
308
|
*/
|
|
309
309
|
private handleLsFilter;
|
|
310
310
|
/**
|
|
@@ -350,17 +350,17 @@ export declare class CommandRouter {
|
|
|
350
350
|
internalKey: string;
|
|
351
351
|
} | null;
|
|
352
352
|
/**
|
|
353
|
-
*
|
|
353
|
+
* 单源:重建 order/ws 列表卡(不投递,card 进 callback 响应体用)。
|
|
354
354
|
* `q` 只服务 ws 卡的关键词筛选;order 卡无此功能,不透传。
|
|
355
355
|
*/
|
|
356
356
|
private rebuildListCard;
|
|
357
357
|
/**
|
|
358
|
-
*
|
|
358
|
+
* 单源:「重建列表卡 → updateCardInPlace」原地刷新骨架(列表卡删除/
|
|
359
359
|
* 翻页/移除/排序/筛选动作共用)。返回重建后的卡片;无卡时返回 undefined 且跳过更新。
|
|
360
360
|
*/
|
|
361
361
|
private refreshListCard;
|
|
362
362
|
/**
|
|
363
|
-
*
|
|
363
|
+
* 单源:order 三个 input handler(aliasInput/aliasRemove/textInput)
|
|
364
364
|
* 的公共骨架——校验 orderId → reload+find → mutate → 成功后 toast + raw 卡。
|
|
365
365
|
* mutate 返回 error 时仅回 error toast,不重渲染。
|
|
366
366
|
* 注意:本 helper 只服务「toast + card 进响应体」语义(飞书需用响应体原地
|
|
@@ -472,7 +472,7 @@ export declare class CommandRouter {
|
|
|
472
472
|
setNestedValue(target: AppConfig, key: string, value: unknown): void;
|
|
473
473
|
diffConfig(original: AppConfig, pending: AppConfig): Record<string, string | undefined>;
|
|
474
474
|
/**
|
|
475
|
-
* config
|
|
475
|
+
* config 写盘后的运行时传播(自 config.save 卡片路径抽出,卡片保存与
|
|
476
476
|
* `/config <key> <value>` 文本直写两条路径共用)。
|
|
477
477
|
*
|
|
478
478
|
* 1. bridge 运行时值同步:setConfig + idle watchdog timeout;
|
|
@@ -480,7 +480,7 @@ export declare class CommandRouter {
|
|
|
480
480
|
* 配置值,/status 与真实 run 自相矛盾);
|
|
481
481
|
* 3. defaultAgent 切换 → 旧 agent session 存 previousSessions、新 agent 恢复
|
|
482
482
|
* 或清空(等效 /new);
|
|
483
|
-
* 4. 当前 agent 配置变更 → syncAgentChoices
|
|
483
|
+
* 4. 当前 agent 配置变更 → syncAgentChoices 并原子写盘(禁裸同步写盘,
|
|
484
484
|
* 写一半崩溃会截断 config.yaml 导致 bridge 起不来)。
|
|
485
485
|
*
|
|
486
486
|
* @param updates 以 config 路径形式给出的变更 key(卡片路径来自 diffConfig;
|
|
@@ -530,7 +530,7 @@ export declare class CommandRouter {
|
|
|
530
530
|
private buildConfigSwitchCard;
|
|
531
531
|
/**
|
|
532
532
|
* Whether a session of the given agent kind can be compacted from a resume
|
|
533
|
-
* card.
|
|
533
|
+
* card. duck-typing — check if the runner has `runCompact`
|
|
534
534
|
* (codex app-server, kimi acp), instead of hardcoding agentKind === 'codex'.
|
|
535
535
|
*/
|
|
536
536
|
private canCompactSession;
|
|
@@ -584,7 +584,7 @@ export declare class CommandRouter {
|
|
|
584
584
|
*/
|
|
585
585
|
cmdWs(args: string[], ctx: CommandContext, offset?: number, q?: string): CommandResult;
|
|
586
586
|
/**
|
|
587
|
-
*
|
|
587
|
+
* degraded card when a session's export is non-empty but unparseable
|
|
588
588
|
* (truncated by opencode's pipe cap on huge sessions, or otherwise corrupt).
|
|
589
589
|
* The session exists, so reuse listSessions metadata (title) instead of the
|
|
590
590
|
* misleading "未找到". Reuses the same card-builder helpers (markdownDiv) as
|