lark-remote 0.5.19 → 0.5.20
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 +19 -0
- package/dist/bridge/approval-actions.d.ts +1 -1
- package/dist/bridge/approval-actions.js +1 -1
- package/dist/bridge/bash-run.d.ts +71 -0
- package/dist/bridge/bash-run.js +217 -0
- package/dist/bridge/compact.d.ts +155 -0
- package/dist/bridge/compact.js +450 -0
- package/dist/bridge/final-usage.js +4 -4
- package/dist/bridge/inbound-media.js +1 -1
- package/dist/bridge/index.d.ts +97 -153
- package/dist/bridge/index.js +182 -809
- package/dist/bridge/job-watch.d.ts +68 -0
- package/dist/bridge/job-watch.js +146 -0
- package/dist/bridge/queue-manager.d.ts +3 -3
- package/dist/bridge/queue-manager.js +10 -29
- package/dist/card/approval-render.d.ts +2 -2
- package/dist/card/approval-render.js +109 -97
- package/dist/card/card-budget.js +1 -1
- package/dist/card/card-helpers.d.ts +3 -3
- package/dist/card/card-helpers.js +6 -6
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +34 -93
- package/dist/card/run-state.d.ts +1 -1
- package/dist/card/run-state.js +28 -90
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +1 -1
- package/dist/card/usage-format.d.ts +12 -0
- package/dist/card/usage-format.js +8 -3
- package/dist/cli.js +15 -3
- package/dist/clone.js +2 -2
- package/dist/config/codex-config.js +130 -106
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.js +23 -8
- package/dist/connector/index.js +6 -6
- package/dist/error-classification.js +3 -2
- package/dist/inbound/placeholder.d.ts +1 -1
- package/dist/inbound/placeholder.js +1 -1
- package/dist/index.js +203 -133
- package/dist/logger/index.js +6 -6
- package/dist/message-handlers.d.ts +0 -5
- package/dist/message-handlers.js +1 -1
- package/dist/model-catalog.d.ts +2 -2
- package/dist/model-catalog.js +3 -8
- package/dist/platform/identity.js +1 -1
- package/dist/platform/path.d.ts +1 -1
- package/dist/platform/path.js +1 -1
- package/dist/platform/sleep-blocker.js +1 -1
- package/dist/platform/terminator-win32.js +2 -1
- package/dist/router/commands/resume.js +2 -20
- package/dist/router/config/dsh.d.ts +4 -8
- package/dist/router/config/dsh.js +3 -10
- package/dist/router/config/kimi.js +1 -1
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/config-diff.js +1 -1
- package/dist/router/index.d.ts +56 -11
- package/dist/router/index.js +219 -200
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +6 -4
- package/dist/runner/codex/app-server/runner.d.ts +9 -2
- package/dist/runner/codex/app-server/runner.js +30 -25
- package/dist/runner/codex/app-server/translator.d.ts +0 -5
- package/dist/runner/codex/app-server/translator.js +4 -17
- package/dist/runner/common/acp/acp-approval.d.ts +1 -1
- package/dist/runner/common/acp/acp-approval.js +1 -1
- package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
- package/dist/runner/common/acp/base-acp-runner.js +26 -65
- package/dist/runner/common/acp/base-translator.d.ts +12 -2
- package/dist/runner/common/acp/base-translator.js +31 -9
- package/dist/runner/common/acp/protocol-types.d.ts +1 -1
- package/dist/runner/common/acp/protocol-types.js +2 -2
- package/dist/runner/common/connection-based-runner.d.ts +32 -1
- package/dist/runner/common/connection-based-runner.js +32 -2
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
- package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
- package/dist/runner/common/spawning-runner.d.ts +1 -1
- package/dist/runner/common/spawning-runner.js +1 -1
- package/dist/runner/dsh/catalog-probe.d.ts +17 -0
- package/dist/runner/dsh/catalog-probe.js +10 -0
- package/dist/runner/dsh/client.d.ts +1 -3
- package/dist/runner/dsh/client.js +2 -4
- package/dist/runner/dsh/exit-cancel.d.ts +2 -2
- package/dist/runner/dsh/exit-cancel.js +2 -2
- package/dist/runner/dsh/inflight.d.ts +1 -1
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/runner.d.ts +40 -1
- package/dist/runner/dsh/runner.js +272 -199
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/dsh/translator.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -2
- package/dist/runner/kimi/acp/runner.js +1 -2
- package/dist/runner/kimi/acp/translator.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +3 -4
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
- package/dist/runner/pi/rpc/protocol-types.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +3 -0
- package/dist/runner/pi/rpc/runner.js +27 -42
- package/dist/runner/types.d.ts +2 -2
- package/dist/runner/types.js +1 -1
- package/dist/session/claude/sessions.d.ts +45 -0
- package/dist/session/claude/sessions.js +97 -71
- package/dist/session/codex/rollout-reader.js +4 -4
- package/dist/session/common/throw-session-list-error.d.ts +10 -0
- package/dist/session/common/throw-session-list-error.js +16 -0
- package/dist/session/dsh/sessions.js +11 -5
- package/dist/session/kimi/sessions.d.ts +40 -1
- package/dist/session/kimi/sessions.js +244 -192
- package/dist/session/opencode/sessions.d.ts +153 -1
- package/dist/session/opencode/sessions.js +354 -321
- package/dist/session/pi/sessions.js +4 -5
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/dist/update/version-check.js +2 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadConfig, getAgentConfig } from './config/index.js';
|
|
1
|
+
import { loadConfig, getAgentConfig, PI_DEFAULTS, KIMI_DEFAULTS, } from './config/index.js';
|
|
2
2
|
import { resolveAgentChoices } from './runner/index.js';
|
|
3
3
|
import { ensureConfig } from './config/wizard.js';
|
|
4
4
|
import { parseCliArgs, resolveConfigDir, setConfigDir, printHelp, printVersion, } from './config/dir.js';
|
|
@@ -29,6 +29,13 @@ import { buildSessionHistoryCard, pickResumeCardEvents } from './card/card-helpe
|
|
|
29
29
|
import { newSessionButton, resumeCompactButton, agentDisplayName } from './card/card-shared.js';
|
|
30
30
|
import path from 'node:path';
|
|
31
31
|
import { setupMessageHandlers } from './message-handlers.js';
|
|
32
|
+
/**
|
|
33
|
+
* 分钟 → 毫秒(turn idle timeout 等 config 字段共用)。
|
|
34
|
+
* 未配置(undefined/null)→ undefined = 不设该超时,与原三处内联三元逐字等价。
|
|
35
|
+
*/
|
|
36
|
+
function minutesToMs(minutes) {
|
|
37
|
+
return minutes != null ? minutes * 60_000 : undefined;
|
|
38
|
+
}
|
|
32
39
|
/** Send auto-resume card for a restored session */
|
|
33
40
|
async function sendAutoResumeCard(connector, contact, cwd, session, state, compactSupported = false, stillRunning = false) {
|
|
34
41
|
if (!contact.userId)
|
|
@@ -38,7 +45,7 @@ async function sendAutoResumeCard(connector, contact, cwd, session, state, compa
|
|
|
38
45
|
return;
|
|
39
46
|
// Limit history events to prevent card size exceeded error (11310).
|
|
40
47
|
// 窗口按"最后 N 条"截,但强制保住最后一条用户消息与最后一条 assistant 正文,
|
|
41
|
-
//
|
|
48
|
+
// 否则工具往返多时(如连续 bash 面板占满窗口)会把这两条挤出卡。
|
|
42
49
|
const MAX_HISTORY_EVENTS = 5;
|
|
43
50
|
const eventsToShow = pickResumeCardEvents(state.events, MAX_HISTORY_EVENTS);
|
|
44
51
|
const hiddenCount = state.events.length - eventsToShow.length;
|
|
@@ -122,25 +129,45 @@ async function setupInstanceLockAndHandlers(instanceLock, logger, configDir) {
|
|
|
122
129
|
}
|
|
123
130
|
});
|
|
124
131
|
}
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const latest = () => agentRegistry.getConfigContainer()?.current ?? config;
|
|
131
|
-
// One global DSH per machine/DSH_HOME. This resolver reuses a listening
|
|
132
|
-
// process or starts `dsh web` detached; it never owns the child's lifetime.
|
|
133
|
-
const dshDaemon = new DshDaemon({
|
|
134
|
-
host: () => getAgentConfig(latest(), 'dsh')?.host,
|
|
135
|
-
dshHome: () => getAgentConfig(latest(), 'dsh')?.dshHome,
|
|
136
|
-
dshCommand: () => getAgentConfig(latest(), 'dsh')?.dshCommand,
|
|
137
|
-
manageProcess: () => getAgentConfig(latest(), 'dsh')?.manageProcess ?? true,
|
|
138
|
-
startTimeoutMs: () => getAgentConfig(latest(), 'dsh')?.startTimeoutMs,
|
|
139
|
-
});
|
|
132
|
+
/**
|
|
133
|
+
* 创建全部 session reader。runner 侧不 import session 层(否则 runner ↔ session
|
|
134
|
+
* 互相依赖),统一由组合根注入。
|
|
135
|
+
*/
|
|
136
|
+
function createSessionReaders(config, latest) {
|
|
140
137
|
// claude 的会话读取器与其余 agent 一样由组合根注入:runner 侧不 import session
|
|
141
138
|
// 层(否则 runner ↔ session 互相依赖),且 bridge 读 usage / router 列 session
|
|
142
139
|
// 用的是同一个实例(缓存共用)。
|
|
143
|
-
const
|
|
140
|
+
const claude = new ClaudeSessionReader();
|
|
141
|
+
const codex = new CodexSessionReader({ codexHome: process.env.CODEX_HOME });
|
|
142
|
+
// Register OpencodeSessionReader (CLI version)
|
|
143
|
+
// Using `opencode session list` and `session export` instead of HTTP API.
|
|
144
|
+
// Share the configured binary with the ACP runner so `opencode2` (v2) can
|
|
145
|
+
// be used without the reader silently falling back to a v1 `opencode`.
|
|
146
|
+
// The reader is registered once; changing acp.binary at runtime requires a
|
|
147
|
+
// lark-remote restart.
|
|
148
|
+
const opencode = new OpencodeSessionReader({
|
|
149
|
+
binary: getAgentConfig(config, 'opencode')?.acp?.binary,
|
|
150
|
+
});
|
|
151
|
+
const pi = new PiSessionReader();
|
|
152
|
+
const kimi = new KimiSessionReader();
|
|
153
|
+
const dsh = new DshSessionReader({
|
|
154
|
+
// hostProvider 每次调用读最新 config:/config 修改 host 后 runner 重建为
|
|
155
|
+
// 新 host,reader(/resume、用量、完成卡)也跟随新 host。
|
|
156
|
+
hostProvider: () => getAgentConfig(latest(), 'dsh')?.host,
|
|
157
|
+
dshHomeProvider: () => getAgentConfig(latest(), 'dsh')?.dshHome,
|
|
158
|
+
tokenProvider: () => getAgentConfig(latest(), 'dsh')?.token,
|
|
159
|
+
credentialsFileProvider: () => getAgentConfig(latest(), 'dsh')?.credentialsFile,
|
|
160
|
+
});
|
|
161
|
+
return { claude, codex, opencode, pi, kimi, dsh };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 按 agent 家族注册 runner factory。
|
|
165
|
+
*
|
|
166
|
+
* factory 一律从 configContainer 读最新 config(bridge.setConfig() +
|
|
167
|
+
* clearRunners() 后运行时改动生效);`latest()` 未接线时回退启动快照。
|
|
168
|
+
*/
|
|
169
|
+
function registerRunners(agentRegistry, deps) {
|
|
170
|
+
const { configDir, cliArgs, latest, dshDaemon, readers } = deps;
|
|
144
171
|
// Claude factory reads from configContainer (not closure) so runtime
|
|
145
172
|
// config changes (model, effort, stopGraceMs) take effect after
|
|
146
173
|
// bridge.setConfig() + clearRunners(). Same pattern as codex/pi/kimi.
|
|
@@ -153,7 +180,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
153
180
|
settings: cliArgs.settings,
|
|
154
181
|
pidDir: configDir,
|
|
155
182
|
cwd: ws,
|
|
156
|
-
sessionReader:
|
|
183
|
+
sessionReader: readers.claude,
|
|
157
184
|
permissionMode: claudeConfig.permissionMode,
|
|
158
185
|
idleTtlMs: claudeConfig.idleTtlMinutes != null ? claudeConfig.idleTtlMinutes * 60_000 : undefined,
|
|
159
186
|
});
|
|
@@ -162,7 +189,6 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
162
189
|
// config from container.current so runtime config changes (model, sandbox,
|
|
163
190
|
// approvalPolicy, reasoningEffort, etc.) take effect after bridge.setConfig()
|
|
164
191
|
// + clearRunners().
|
|
165
|
-
const codexSessionReader = new CodexSessionReader({ codexHome: process.env.CODEX_HOME });
|
|
166
192
|
agentRegistry.register('codex', (_ws) => {
|
|
167
193
|
const codexConfig = getAgentConfig(latest(), 'codex');
|
|
168
194
|
return new CodexAppServerRunner({
|
|
@@ -170,31 +196,16 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
170
196
|
model: codexConfig?.model,
|
|
171
197
|
modelProvider: codexConfig?.modelProvider,
|
|
172
198
|
reasoningEffort: codexConfig?.reasoningEffort,
|
|
173
|
-
sessionReader:
|
|
199
|
+
sessionReader: readers.codex,
|
|
174
200
|
sandbox: codexConfig?.sandbox,
|
|
175
201
|
approvalPolicy: codexConfig?.approvalPolicy,
|
|
176
202
|
binary: codexConfig?.appServer?.binary,
|
|
177
203
|
requestTimeoutMs: codexConfig?.appServer?.requestTimeoutMs,
|
|
178
204
|
idleTtlMs: codexConfig?.appServer?.idleTtlMs,
|
|
179
|
-
turnTimeoutMs: codexConfig?.appServer?.turnIdleTimeoutMinutes
|
|
180
|
-
? codexConfig.appServer.turnIdleTimeoutMinutes * 60_000
|
|
181
|
-
: undefined,
|
|
205
|
+
turnTimeoutMs: minutesToMs(codexConfig?.appServer?.turnIdleTimeoutMinutes),
|
|
182
206
|
});
|
|
183
207
|
});
|
|
184
208
|
// Register OpencodeAcpRunner (pure ACP mode, `opencode acp` JSON-RPC over stdio)
|
|
185
|
-
// Register session reader (same instance as used by runner)
|
|
186
|
-
const sessionReaderRegistry = new SessionReaderRegistry();
|
|
187
|
-
sessionReaderRegistry.register('claude', claudeSessionReader);
|
|
188
|
-
sessionReaderRegistry.register('codex', codexSessionReader);
|
|
189
|
-
// Register OpencodeSessionReader (CLI version)
|
|
190
|
-
// Using `opencode session list` and `session export` instead of HTTP API.
|
|
191
|
-
// Share the configured binary with the ACP runner so `opencode2` (v2) can
|
|
192
|
-
// be used without the reader silently falling back to a v1 `opencode`.
|
|
193
|
-
// The reader is registered once; changing acp.binary at runtime requires a
|
|
194
|
-
// lark-remote restart.
|
|
195
|
-
const opencodeSessionReader = new OpencodeSessionReader({
|
|
196
|
-
binary: getAgentConfig(config, 'opencode')?.acp?.binary,
|
|
197
|
-
});
|
|
198
209
|
agentRegistry.register('opencode', (_ws) => {
|
|
199
210
|
const ocConfig = getAgentConfig(latest(), 'opencode');
|
|
200
211
|
// Build model string with validation: provider/model format
|
|
@@ -212,68 +223,50 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
212
223
|
return new OpencodeAcpRunner({
|
|
213
224
|
kind: 'opencode',
|
|
214
225
|
model,
|
|
215
|
-
sessionReader:
|
|
226
|
+
sessionReader: readers.opencode,
|
|
216
227
|
mode: ocConfig?.mode ?? 'build',
|
|
217
228
|
binary: ocConfig?.acp?.binary,
|
|
218
229
|
requestTimeoutMs: ocConfig?.acp?.requestTimeoutMs,
|
|
219
230
|
idleTtlMs: ocConfig?.acp?.idleTtlMs,
|
|
220
|
-
turnIdleTimeoutMs: ocConfig?.acp?.turnIdleTimeoutMinutes
|
|
221
|
-
? ocConfig.acp.turnIdleTimeoutMinutes * 60_000
|
|
222
|
-
: undefined,
|
|
231
|
+
turnIdleTimeoutMs: minutesToMs(ocConfig?.acp?.turnIdleTimeoutMinutes),
|
|
223
232
|
});
|
|
224
233
|
});
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
const configContainer = { current: config };
|
|
231
|
-
agentRegistry.setConfigContainer(configContainer);
|
|
232
|
-
const piSessionReader = new PiSessionReader();
|
|
234
|
+
// Register PiRpcRunner.
|
|
235
|
+
// factory 闭包必须读最新 config 而不是启动时的快照,否则 /config 保存后
|
|
236
|
+
// provider 等字段对 pi 不生效:config 引用放在可变容器里(见上文
|
|
237
|
+
// setConfigContainer),factory 每次从容器取最新值。
|
|
238
|
+
// PiSessionReader 的创建与注册见 registerSessionReaders。
|
|
233
239
|
agentRegistry.register('pi', (ws) => {
|
|
234
240
|
const piConf = getAgentConfig(latest(), 'pi');
|
|
235
241
|
return new PiRpcRunner({
|
|
236
|
-
provider: piConf?.provider ??
|
|
237
|
-
model: piConf?.model ??
|
|
238
|
-
thinking: piConf?.thinking ??
|
|
239
|
-
tools: (piConf?.tools ??
|
|
242
|
+
provider: piConf?.provider ?? PI_DEFAULTS.provider,
|
|
243
|
+
model: piConf?.model ?? PI_DEFAULTS.model,
|
|
244
|
+
thinking: piConf?.thinking ?? PI_DEFAULTS.thinking,
|
|
245
|
+
tools: (piConf?.tools ?? PI_DEFAULTS.tools)
|
|
240
246
|
.split(',')
|
|
241
247
|
.map((t) => t.trim())
|
|
242
248
|
.filter(Boolean),
|
|
243
249
|
cwd: ws,
|
|
244
|
-
sessionReader:
|
|
250
|
+
sessionReader: readers.pi,
|
|
245
251
|
});
|
|
246
252
|
});
|
|
247
|
-
|
|
248
|
-
// Register KimiAcpRunner (pure ACP mode) and KimiSessionReader
|
|
249
|
-
const kimiSessionReader = new KimiSessionReader();
|
|
253
|
+
// Register KimiAcpRunner (pure ACP mode)
|
|
250
254
|
agentRegistry.register('kimi', (_ws) => {
|
|
251
255
|
const kimiConf = getAgentConfig(latest(), 'kimi');
|
|
252
256
|
const acpConf = kimiConf?.acp;
|
|
253
257
|
return new KimiAcpRunner({
|
|
254
258
|
kind: 'kimi',
|
|
255
|
-
sessionReader:
|
|
259
|
+
sessionReader: readers.kimi,
|
|
256
260
|
binary: acpConf?.binary ?? 'kimi',
|
|
257
261
|
requestTimeoutMs: acpConf?.requestTimeoutMs,
|
|
258
262
|
idleTtlMs: acpConf?.idleTtlMs,
|
|
259
|
-
turnIdleTimeoutMs: acpConf?.turnIdleTimeoutMinutes
|
|
260
|
-
|
|
261
|
-
: undefined,
|
|
262
|
-
model: kimiConf?.model ?? 'kimi-code/k3',
|
|
263
|
+
turnIdleTimeoutMs: minutesToMs(acpConf?.turnIdleTimeoutMinutes),
|
|
264
|
+
model: kimiConf?.model ?? KIMI_DEFAULTS.model,
|
|
263
265
|
thinkingEffort: kimiConf?.thinkingEffort,
|
|
264
266
|
permissionMode: kimiConf?.permissionMode ?? 'manual',
|
|
265
267
|
});
|
|
266
268
|
});
|
|
267
|
-
|
|
268
|
-
// Register DshRunner (HTTP-only DSH Web Host agent) and DshSessionReader.
|
|
269
|
-
const dshSessionReader = new DshSessionReader({
|
|
270
|
-
// hostProvider 每次调用读最新 config:/config 修改 host 后 runner 重建为
|
|
271
|
-
// 新 host,reader(/resume、用量、完成卡)也跟随新 host。
|
|
272
|
-
hostProvider: () => getAgentConfig(latest(), 'dsh')?.host,
|
|
273
|
-
dshHomeProvider: () => getAgentConfig(latest(), 'dsh')?.dshHome,
|
|
274
|
-
tokenProvider: () => getAgentConfig(latest(), 'dsh')?.token,
|
|
275
|
-
credentialsFileProvider: () => getAgentConfig(latest(), 'dsh')?.credentialsFile,
|
|
276
|
-
});
|
|
269
|
+
// Register DshRunner (HTTP-only DSH Web Host agent).
|
|
277
270
|
agentRegistry.register('dsh', (_ws) => {
|
|
278
271
|
const dshConf = getAgentConfig(latest(), 'dsh');
|
|
279
272
|
// Warm the shared process in the background so the first message and
|
|
@@ -284,7 +277,7 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
284
277
|
});
|
|
285
278
|
return new DshRunner({
|
|
286
279
|
kind: 'dsh',
|
|
287
|
-
sessionReader:
|
|
280
|
+
sessionReader: readers.dsh,
|
|
288
281
|
daemon: dshDaemon,
|
|
289
282
|
host: dshConf?.host,
|
|
290
283
|
dshHome: dshConf?.dshHome,
|
|
@@ -300,7 +293,37 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
300
293
|
inflightPath: dshInflightPath(configDir),
|
|
301
294
|
});
|
|
302
295
|
});
|
|
303
|
-
|
|
296
|
+
}
|
|
297
|
+
/** 按 agent 家族注册 session reader(与 runner factory 共享同一实例)。 */
|
|
298
|
+
function registerSessionReaders(registry, readers) {
|
|
299
|
+
registry.register('claude', readers.claude);
|
|
300
|
+
registry.register('codex', readers.codex);
|
|
301
|
+
registry.register('opencode', readers.opencode);
|
|
302
|
+
registry.register('pi', readers.pi);
|
|
303
|
+
registry.register('kimi', readers.kimi);
|
|
304
|
+
registry.register('dsh', readers.dsh);
|
|
305
|
+
}
|
|
306
|
+
/** Initialize per-workspace runner factory + agent registry. */
|
|
307
|
+
function initializeRunner(config, configDir, cliArgs) {
|
|
308
|
+
const agentRegistry = new AgentRegistry();
|
|
309
|
+
// 单点化:runner factory 取最新 config 的唯一入口(语义保持,
|
|
310
|
+
// container 未接线时回退启动快照)。
|
|
311
|
+
const latest = () => agentRegistry.getConfigContainer()?.current ?? config;
|
|
312
|
+
// 用可变容器存储 config 引用,registry 持有容器引用,factory 从容器读取最新值。
|
|
313
|
+
agentRegistry.setConfigContainer({ current: config });
|
|
314
|
+
// One global DSH per machine/DSH_HOME. This resolver reuses a listening
|
|
315
|
+
// process or starts `dsh web` detached; it never owns the child's lifetime.
|
|
316
|
+
const dshDaemon = new DshDaemon({
|
|
317
|
+
host: () => getAgentConfig(latest(), 'dsh')?.host,
|
|
318
|
+
dshHome: () => getAgentConfig(latest(), 'dsh')?.dshHome,
|
|
319
|
+
dshCommand: () => getAgentConfig(latest(), 'dsh')?.dshCommand,
|
|
320
|
+
manageProcess: () => getAgentConfig(latest(), 'dsh')?.manageProcess ?? true,
|
|
321
|
+
startTimeoutMs: () => getAgentConfig(latest(), 'dsh')?.startTimeoutMs,
|
|
322
|
+
});
|
|
323
|
+
const readers = createSessionReaders(config, latest);
|
|
324
|
+
registerRunners(agentRegistry, { configDir, cliArgs, latest, dshDaemon, readers });
|
|
325
|
+
const sessionReaderRegistry = new SessionReaderRegistry();
|
|
326
|
+
registerSessionReaders(sessionReaderRegistry, readers);
|
|
304
327
|
if (config.defaultAgent === 'dsh') {
|
|
305
328
|
void dshDaemon.ensure().catch((error) => {
|
|
306
329
|
getLogger().warn(`[dsh] startup probe failed: ${error.message}`);
|
|
@@ -328,8 +351,14 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
328
351
|
warmCodexCatalogCache();
|
|
329
352
|
return { agentRegistry, sessionReaderRegistry };
|
|
330
353
|
}
|
|
331
|
-
|
|
332
|
-
|
|
354
|
+
/**
|
|
355
|
+
* CLI 早期退出分支:--help / --version / --update。
|
|
356
|
+
*
|
|
357
|
+
* 红线:三者都必须在 config 加载与单例锁之前完成——--help/--version 是纯信息
|
|
358
|
+
* 查询,另一实例在跑、或还没有 config 时也必须能应答;--update 不是守护类命令,
|
|
359
|
+
* 等锁只会误报 "already running"。返回 true 表示进程应停止。
|
|
360
|
+
*/
|
|
361
|
+
async function handleCliEarlyExit(cliArgs) {
|
|
333
362
|
// --help/--version must bypass config loading and singleton lock — it's a pure
|
|
334
363
|
// informational query that must work even when another instance is running
|
|
335
364
|
// or no config exists yet.
|
|
@@ -367,6 +396,14 @@ async function main() {
|
|
|
367
396
|
process.exit(1);
|
|
368
397
|
}
|
|
369
398
|
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Bootstrap:configDir → config → logger → 单例锁 → 防休眠 → 启动配置日志。
|
|
403
|
+
* 顺序红线:waitForPreviousInstance 必须先于 instanceLock.acquire()(/restart
|
|
404
|
+
* 替补进程要等旧实例释放锁才能拿到单例锁);PID 存活校验在 acquire 内完成。
|
|
405
|
+
*/
|
|
406
|
+
async function bootstrap(cliArgs) {
|
|
370
407
|
const { config, configDir, logger, instanceLock } = await initializeCliAndConfig();
|
|
371
408
|
// Restart handoff: if spawned as a /restart replacement, wait for the old
|
|
372
409
|
// lark-remote to die (and release the instance lock) before acquiring it.
|
|
@@ -387,6 +424,11 @@ async function main() {
|
|
|
387
424
|
if (cliArgs.settings) {
|
|
388
425
|
logger.info(`claude.settings = ${cliArgs.settings}`);
|
|
389
426
|
}
|
|
427
|
+
return { cliArgs, config, configDir, logger };
|
|
428
|
+
}
|
|
429
|
+
/** Wiring:装配 connector / stores / bridge / router / 消息处理器(不连接)。 */
|
|
430
|
+
function wire(boot) {
|
|
431
|
+
const { cliArgs, config, configDir, logger } = boot;
|
|
390
432
|
const { agentRegistry, sessionReaderRegistry } = initializeRunner(config, configDir, cliArgs);
|
|
391
433
|
const connector = new FeishuConnector(config);
|
|
392
434
|
const startupContactStore = new StartupContactStore(path.join(configDir, 'startup-contact.json'));
|
|
@@ -438,6 +480,89 @@ async function main() {
|
|
|
438
480
|
config,
|
|
439
481
|
cloneSession,
|
|
440
482
|
});
|
|
483
|
+
return {
|
|
484
|
+
cliArgs,
|
|
485
|
+
config,
|
|
486
|
+
configDir,
|
|
487
|
+
logger,
|
|
488
|
+
connector,
|
|
489
|
+
startupContactStore,
|
|
490
|
+
binder,
|
|
491
|
+
sessionStore,
|
|
492
|
+
bridge,
|
|
493
|
+
sessionReaderRegistry,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Auto-restore: resume the persisted sessionId if available.
|
|
498
|
+
* 非关键启动优化:发送失败只告警(logger 已初始化),不允许经
|
|
499
|
+
* main().catch → process.exit(1) 拖垮整个桥接。
|
|
500
|
+
*/
|
|
501
|
+
async function restoreLastSession(app) {
|
|
502
|
+
const { config, logger, sessionStore, sessionReaderRegistry, bridge, connector, startupContactStore, } = app;
|
|
503
|
+
const restoredContact = startupContactStore.getContact();
|
|
504
|
+
if (!restoredContact?.userId)
|
|
505
|
+
return;
|
|
506
|
+
const restoredCwd = sessionStore.getCwd(restoredContact.userId);
|
|
507
|
+
if (!restoredCwd)
|
|
508
|
+
return;
|
|
509
|
+
const sessionIdToResume = sessionStore.getSessionId(restoredContact.userId, config.defaultAgent);
|
|
510
|
+
if (!sessionIdToResume)
|
|
511
|
+
return;
|
|
512
|
+
logger.info(`[startup] auto-resuming persisted session ${sessionIdToResume} in ${restoredCwd}`);
|
|
513
|
+
const startupReader = sessionReaderRegistry.get(config.defaultAgent);
|
|
514
|
+
sessionStore.setSessionIdAndCwd(restoredContact.userId, config.defaultAgent, sessionIdToResume, restoredCwd);
|
|
515
|
+
const content = startupReader.readSessionContent(sessionIdToResume, restoredCwd);
|
|
516
|
+
// 该会话可能仍有在途 turn(上一个实例退出/崩溃后留在常驻 host 里)。
|
|
517
|
+
// 不取消、不接管,但必须在卡上说明,否则用户会把它当"卡住了"。
|
|
518
|
+
//
|
|
519
|
+
// 只有 DSH reader 的 isSessionActive() 是服务端的 running 事实。其他
|
|
520
|
+
// reader 的同名方法只表示"最近一段时间有写入",拿它当 turn 在跑会在
|
|
521
|
+
// 每次自动恢复时误报。
|
|
522
|
+
const stillRunning = config.defaultAgent === 'dsh'
|
|
523
|
+
? (() => {
|
|
524
|
+
try {
|
|
525
|
+
return startupReader.isSessionActive(sessionIdToResume, restoredCwd);
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
getLogger().warn(`[startup] isSessionActive failed: ${err.message}`);
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
})()
|
|
532
|
+
: false;
|
|
533
|
+
if (stillRunning) {
|
|
534
|
+
getLogger().warn(`[startup] session ${sessionIdToResume} still has a running turn; auto-resume card is a snapshot`);
|
|
535
|
+
}
|
|
536
|
+
// 完整透传 usage(含 inputTokens/outputTokens/totalTokens),
|
|
537
|
+
// 让 formatUsageStats 走真实值路径,与终态卡片//resume 卡片口径一致。
|
|
538
|
+
const usage = content.usage;
|
|
539
|
+
// Build auto-resume card and send to the user. Auto-resume is a
|
|
540
|
+
// non-critical startup nicety: a sendWithRetry failure here (e.g.
|
|
541
|
+
// transient feishu API error) must not crash the bridge via
|
|
542
|
+
// main().catch → process.exit(1). Logger is already initialized.
|
|
543
|
+
try {
|
|
544
|
+
await sendAutoResumeCard(connector, restoredContact, restoredCwd, { sessionId: sessionIdToResume, summary: '' }, {
|
|
545
|
+
events: content.events,
|
|
546
|
+
usage,
|
|
547
|
+
aiTitle: content.aiTitle,
|
|
548
|
+
recap: content.recap,
|
|
549
|
+
displayTitle: content.displayTitle,
|
|
550
|
+
agentKind: config.defaultAgent,
|
|
551
|
+
},
|
|
552
|
+
// Compact 能力探测与 bridge 侧一致(runner 有 runCompact 才渲染)。
|
|
553
|
+
bridge.hasRunCompact(restoredCwd, config.defaultAgent), stillRunning);
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
getLogger().warn('[startup] sendAutoResumeCard failed:', err);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function main() {
|
|
560
|
+
const cliArgs = parseCliArgs();
|
|
561
|
+
if (await handleCliEarlyExit(cliArgs))
|
|
562
|
+
return;
|
|
563
|
+
const boot = await bootstrap(cliArgs);
|
|
564
|
+
const app = wire(boot);
|
|
565
|
+
const { config, configDir, logger, connector, startupContactStore, binder } = app;
|
|
441
566
|
try {
|
|
442
567
|
await connector.connect();
|
|
443
568
|
}
|
|
@@ -447,7 +572,7 @@ async function main() {
|
|
|
447
572
|
}
|
|
448
573
|
if (binder.isBound()) {
|
|
449
574
|
// 仅已绑定时发送启动通知;未绑定时不打扰(PIN 引导已在控制台输出)
|
|
450
|
-
await sendStartupHello(connector, startupContactStore, { dev: cliArgs.dev });
|
|
575
|
+
await sendStartupHello(connector, startupContactStore, { dev: app.cliArgs.dev });
|
|
451
576
|
// 启动时静默检查版本(由 checkUpdateOnStartup 控制,默认关闭)
|
|
452
577
|
if (config.checkUpdateOnStartup) {
|
|
453
578
|
void (async () => {
|
|
@@ -497,62 +622,7 @@ async function main() {
|
|
|
497
622
|
logger.warn(`[startup] dsh inflight reconcile failed: ${err.message}`);
|
|
498
623
|
});
|
|
499
624
|
}
|
|
500
|
-
|
|
501
|
-
const restoredContact = startupContactStore.getContact();
|
|
502
|
-
if (restoredContact?.userId) {
|
|
503
|
-
const restoredCwd = sessionStore.getCwd(restoredContact.userId);
|
|
504
|
-
if (restoredCwd) {
|
|
505
|
-
const sessionIdToResume = sessionStore.getSessionId(restoredContact.userId, config.defaultAgent);
|
|
506
|
-
if (sessionIdToResume) {
|
|
507
|
-
logger.info(`[startup] auto-resuming persisted session ${sessionIdToResume} in ${restoredCwd}`);
|
|
508
|
-
const startupReader = sessionReaderRegistry.get(config.defaultAgent);
|
|
509
|
-
sessionStore.setSessionIdAndCwd(restoredContact.userId, config.defaultAgent, sessionIdToResume, restoredCwd);
|
|
510
|
-
const content = startupReader.readSessionContent(sessionIdToResume, restoredCwd);
|
|
511
|
-
// 该会话可能仍有在途 turn(上一个实例退出/崩溃后留在常驻 host 里)。
|
|
512
|
-
// 不取消、不接管,但必须在卡上说明,否则用户会把它当"卡住了"。
|
|
513
|
-
//
|
|
514
|
-
// 只有 DSH reader 的 isSessionActive() 是服务端的 running 事实。其他
|
|
515
|
-
// reader 的同名方法只表示"最近一段时间有写入",拿它当 turn 在跑会在
|
|
516
|
-
// 每次自动恢复时误报。
|
|
517
|
-
const stillRunning = config.defaultAgent === 'dsh'
|
|
518
|
-
? (() => {
|
|
519
|
-
try {
|
|
520
|
-
return startupReader.isSessionActive(sessionIdToResume, restoredCwd);
|
|
521
|
-
}
|
|
522
|
-
catch (err) {
|
|
523
|
-
getLogger().warn(`[startup] isSessionActive failed: ${err.message}`);
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
})()
|
|
527
|
-
: false;
|
|
528
|
-
if (stillRunning) {
|
|
529
|
-
getLogger().warn(`[startup] session ${sessionIdToResume} still has a running turn; auto-resume card is a snapshot`);
|
|
530
|
-
}
|
|
531
|
-
// 完整透传 usage(含 inputTokens/outputTokens/totalTokens),
|
|
532
|
-
// 让 formatUsageStats 走真实值路径,与终态卡片//resume 卡片口径一致。
|
|
533
|
-
const usage = content.usage;
|
|
534
|
-
// Build auto-resume card and send to the user. Auto-resume is a
|
|
535
|
-
// non-critical startup nicety: a sendWithRetry failure here (e.g.
|
|
536
|
-
// transient feishu API error) must not crash the bridge via
|
|
537
|
-
// main().catch → process.exit(1). Logger is already initialized.
|
|
538
|
-
try {
|
|
539
|
-
await sendAutoResumeCard(connector, restoredContact, restoredCwd, { sessionId: sessionIdToResume, summary: '' }, {
|
|
540
|
-
events: content.events,
|
|
541
|
-
usage,
|
|
542
|
-
aiTitle: content.aiTitle,
|
|
543
|
-
recap: content.recap,
|
|
544
|
-
displayTitle: content.displayTitle,
|
|
545
|
-
agentKind: config.defaultAgent,
|
|
546
|
-
},
|
|
547
|
-
// Compact 能力探测与 bridge 侧一致(runner 有 runCompact 才渲染)。
|
|
548
|
-
bridge.hasRunCompact(restoredCwd, config.defaultAgent), stillRunning);
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
getLogger().warn('[startup] sendAutoResumeCard failed:', err);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
625
|
+
await restoreLastSession(app);
|
|
556
626
|
logger.info('lark-remote is running, press Ctrl+C to exit');
|
|
557
627
|
}
|
|
558
628
|
main().catch((err) => {
|
package/dist/logger/index.js
CHANGED
|
@@ -21,12 +21,12 @@ function todayStr(d) {
|
|
|
21
21
|
return `${y}-${m}-${day}`;
|
|
22
22
|
}
|
|
23
23
|
function timestampStr(d) {
|
|
24
|
-
//
|
|
25
|
-
// 2026-08-03T01:30:00.000+08:00.
|
|
26
|
-
//
|
|
27
|
-
// local midnight
|
|
28
|
-
//
|
|
29
|
-
//
|
|
24
|
+
// Local ISO 8601 with milliseconds and explicit offset, e.g.
|
|
25
|
+
// 2026-08-03T01:30:00.000+08:00. Must match the daily directory boundary
|
|
26
|
+
// (todayStr), which is local time: a UTC timestamp would put lines written
|
|
27
|
+
// after local midnight into the previous UTC day's file. Local offset keeps
|
|
28
|
+
// the timestamp's calendar date consistent with the directory date across
|
|
29
|
+
// all timezones.
|
|
30
30
|
const pad = (n) => String(n).padStart(2, '0');
|
|
31
31
|
const y = d.getFullYear();
|
|
32
32
|
const mo = pad(d.getMonth() + 1);
|
|
@@ -7,11 +7,6 @@ import type { SessionStore } from './session/index.js';
|
|
|
7
7
|
import type { WorkspaceStore } from './workspace/index.js';
|
|
8
8
|
import type { OwnerBinder } from './binder.js';
|
|
9
9
|
import type { CloneSession } from './clone.js';
|
|
10
|
-
/**
|
|
11
|
-
* /stop 命令别名单源:普通消息的停止分支与 clone 活跃期拦截的排除条件
|
|
12
|
-
* 共用同一判定,防止将来加别名时两处漂移。
|
|
13
|
-
*/
|
|
14
|
-
export declare function isStopCommand(text: string): boolean;
|
|
15
10
|
export interface MessageHandlerDeps {
|
|
16
11
|
connector: FeishuConnector;
|
|
17
12
|
router: CommandRouter;
|
package/dist/message-handlers.js
CHANGED
|
@@ -14,7 +14,7 @@ const SESSION_MUTATING_FLUSH_BUDGET_MS = 2_000;
|
|
|
14
14
|
* /stop 命令别名单源:普通消息的停止分支与 clone 活跃期拦截的排除条件
|
|
15
15
|
* 共用同一判定,防止将来加别名时两处漂移。
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
function isStopCommand(text) {
|
|
18
18
|
const t = text.trim().toLowerCase();
|
|
19
19
|
return t === '/stop' || t === '/t';
|
|
20
20
|
}
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* 「地基层被 runner 反向 import」;挪到这里后三方共同引用同一份。
|
|
7
7
|
*
|
|
8
8
|
* 表内容来自 Claude CLI 的模型别名(alias 是 CLI 侧的稳定名字,model ID 会随
|
|
9
|
-
*
|
|
9
|
+
* 版本变),新增一行只改 MODEL_ID_TO_ALIAS,反向表自动派生。
|
|
10
10
|
*/
|
|
11
11
|
/** 模型 ID → alias 映射。 */
|
|
12
12
|
export declare const MODEL_ID_TO_ALIAS: Record<string, string>;
|
|
13
|
-
/** alias → model ID
|
|
13
|
+
/** alias → model ID 反向映射(从正向表派生,新增别名只改上面一张表)。 */
|
|
14
14
|
export declare const MODEL_ALIAS_TO_ID: Record<string, string>;
|
package/dist/model-catalog.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 「地基层被 runner 反向 import」;挪到这里后三方共同引用同一份。
|
|
7
7
|
*
|
|
8
8
|
* 表内容来自 Claude CLI 的模型别名(alias 是 CLI 侧的稳定名字,model ID 会随
|
|
9
|
-
*
|
|
9
|
+
* 版本变),新增一行只改 MODEL_ID_TO_ALIAS,反向表自动派生。
|
|
10
10
|
*/
|
|
11
11
|
/** 模型 ID → alias 映射。 */
|
|
12
12
|
export const MODEL_ID_TO_ALIAS = {
|
|
@@ -15,11 +15,6 @@ export const MODEL_ID_TO_ALIAS = {
|
|
|
15
15
|
'claude-haiku-4-5-20250501': 'haiku',
|
|
16
16
|
'claude-fable-5': 'fable',
|
|
17
17
|
};
|
|
18
|
-
/** alias → model ID
|
|
19
|
-
export const MODEL_ALIAS_TO_ID =
|
|
20
|
-
opus: 'claude-opus-4-8',
|
|
21
|
-
sonnet: 'claude-sonnet-4-6',
|
|
22
|
-
haiku: 'claude-haiku-4-5-20250501',
|
|
23
|
-
fable: 'claude-fable-5',
|
|
24
|
-
};
|
|
18
|
+
/** alias → model ID 反向映射(从正向表派生,新增别名只改上面一张表)。 */
|
|
19
|
+
export const MODEL_ALIAS_TO_ID = Object.fromEntries(Object.entries(MODEL_ID_TO_ALIAS).map(([id, alias]) => [alias, id]));
|
|
25
20
|
//# sourceMappingURL=model-catalog.js.map
|
|
@@ -154,7 +154,7 @@ export function binaryName(token, platform = currentPlatform) {
|
|
|
154
154
|
return caseInsensitive(platform) ? name.toLowerCase() : name;
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
157
|
+
* 命令行与期望二进制是否属于同一程序。
|
|
158
158
|
*
|
|
159
159
|
* 1. 可执行位匹配:首 token 的 basename 相等——`/usr/local/bin/claude --verbose`
|
|
160
160
|
* 匹配 claude;native 单文件 CLI(claude 的 `bin/claude.exe`)也走这条;
|
package/dist/platform/path.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function samePath(a: string, b: string, opts?: PathOptions): bool
|
|
|
21
21
|
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
|
22
22
|
*
|
|
23
23
|
* 只可用于**定位**目录,绝不可反解回 cwd——cwd 必须从 JSONL 内容读取
|
|
24
|
-
*
|
|
24
|
+
* (否则 /resume & /cd 路径会错乱)。
|
|
25
25
|
*
|
|
26
26
|
* posix:`/` → `-`(`_` 同样归一,与 Claude Code 一致)。
|
|
27
27
|
* win32:`\` 与 `/` **都是**分隔符、`:` 是非法文件名字符,三者都必须归一,
|
package/dist/platform/path.js
CHANGED
|
@@ -63,7 +63,7 @@ export function samePath(a, b, opts) {
|
|
|
63
63
|
* cwd → 会话项目目录名(Claude/pi 的 on-disk 编码,lossy N-to-N)。
|
|
64
64
|
*
|
|
65
65
|
* 只可用于**定位**目录,绝不可反解回 cwd——cwd 必须从 JSONL 内容读取
|
|
66
|
-
*
|
|
66
|
+
* (否则 /resume & /cd 路径会错乱)。
|
|
67
67
|
*
|
|
68
68
|
* posix:`/` → `-`(`_` 同样归一,与 Claude Code 一致)。
|
|
69
69
|
* win32:`\` 与 `/` **都是**分隔符、`:` 是非法文件名字符,三者都必须归一,
|
|
@@ -6,7 +6,7 @@ import { getLogger } from '../logger/index.js';
|
|
|
6
6
|
* **必须按无符号 32 位取值**:`0x80000001` = 2147483649 超出 Int32 正数上界
|
|
7
7
|
* (2147483647),而 PowerShell 的数字字面量按 Int32 解析 —— 十六进制写法一旦
|
|
8
8
|
* 进了脚本就会被解析成 **-2147483647**,传给 P/Invoke 声明的 `uint` 形参时抛
|
|
9
|
-
* `MethodArgumentConversionInvalidCastArgument
|
|
9
|
+
* `MethodArgumentConversionInvalidCastArgument`。
|
|
10
10
|
*/
|
|
11
11
|
const ES_FLAGS_UINT32 = (0x80000000 | 0x00000001) >>> 0;
|
|
12
12
|
/**
|
|
@@ -42,8 +42,9 @@ export function createWin32Terminator(deps) {
|
|
|
42
42
|
stdio: 'ignore',
|
|
43
43
|
windowsHide: true,
|
|
44
44
|
});
|
|
45
|
-
killer?.on?.('error', () => {
|
|
45
|
+
killer?.on?.('error', (err) => {
|
|
46
46
|
// taskkill 拉起失败不致命:调用方仍可走 killOrphan / 陈旧锁自愈
|
|
47
|
+
log?.(`win32 树杀失败:taskkill error 事件 ${err?.message ?? err}`);
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isAgentKind } from '../../agent-kinds.js';
|
|
2
|
-
import { agentDisplayName } from '../../card/card-shared.js';
|
|
2
|
+
import { agentDisplayName, cardButton } from '../../card/card-shared.js';
|
|
3
3
|
import { buildSessionHistoryCard, paginationBar } from '../../card/card-helpers.js';
|
|
4
4
|
import { pageInfo } from '../paging.js';
|
|
5
5
|
import { clampInt } from '../../common/clamp.js';
|
|
@@ -194,25 +194,7 @@ export function buildResumeCommand(args, ctx, offset, deps) {
|
|
|
194
194
|
tag: 'column',
|
|
195
195
|
width: 'auto',
|
|
196
196
|
elements: [
|
|
197
|
-
{
|
|
198
|
-
tag: 'button',
|
|
199
|
-
text: {
|
|
200
|
-
tag: 'plain_text',
|
|
201
|
-
content: isCurrent ? '✓ 当前会话' : '恢复此会话',
|
|
202
|
-
},
|
|
203
|
-
type: isCurrent ? 'default' : 'primary',
|
|
204
|
-
disabled: isCurrent,
|
|
205
|
-
behaviors: [
|
|
206
|
-
{
|
|
207
|
-
type: 'callback',
|
|
208
|
-
value: {
|
|
209
|
-
cmd: 'resume.use',
|
|
210
|
-
sessionId: session.sessionId,
|
|
211
|
-
agent: agentKind,
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
},
|
|
197
|
+
cardButton(isCurrent ? '✓ 当前会话' : '恢复此会话', { cmd: 'resume.use', sessionId: session.sessionId, agent: agentKind }, { type: isCurrent ? 'default' : 'primary', disabled: isCurrent }),
|
|
216
198
|
],
|
|
217
199
|
},
|
|
218
200
|
],
|