lark-remote 0.5.6 → 0.5.8
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 +46 -6
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/bridge/index.d.ts +63 -1
- package/dist/bridge/index.js +147 -14
- package/dist/bridge/queue-manager.js +41 -58
- package/dist/card/approval-render.js +2 -1
- package/dist/card/bash-card-session.d.ts +0 -1
- package/dist/card/bash-card-session.js +0 -8
- package/dist/card/bash-renderer.js +2 -1
- package/dist/card/card-budget.d.ts +37 -0
- package/dist/card/card-budget.js +143 -24
- package/dist/card/card-session.d.ts +3 -13
- package/dist/card/card-session.js +4 -18
- package/dist/card/card-shared.d.ts +11 -0
- package/dist/card/card-shared.js +19 -13
- package/dist/card/run-card-session.d.ts +0 -1
- package/dist/card/run-card-session.js +0 -6
- package/dist/card/run-renderer.d.ts +18 -0
- package/dist/card/run-renderer.js +330 -62
- package/dist/card/run-state.d.ts +26 -0
- package/dist/card/run-state.js +45 -0
- package/dist/card/text-truncate.d.ts +3 -5
- package/dist/card/text-truncate.js +3 -5
- package/dist/card/time.d.ts +14 -0
- package/dist/card/time.js +30 -0
- package/dist/card/tool-render.d.ts +15 -2
- package/dist/card/tool-render.js +42 -82
- package/dist/common/text-width.d.ts +24 -0
- package/dist/common/text-width.js +93 -0
- package/dist/common/tool-summary.d.ts +19 -0
- package/dist/common/tool-summary.js +171 -0
- package/dist/common/turn-failure.d.ts +13 -0
- package/dist/common/turn-failure.js +18 -0
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/index.js +66 -13
- package/dist/order/index.d.ts +2 -0
- package/dist/order/index.js +2 -0
- package/dist/platform/command.js +1 -1
- package/dist/platform/shell.d.ts +2 -2
- package/dist/platform/spawn.d.ts +1 -1
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/router/card-helpers.d.ts +18 -16
- package/dist/router/card-helpers.js +88 -46
- package/dist/router/config/codex.js +2 -3
- package/dist/router/config/common/model-patch.d.ts +8 -0
- package/dist/router/config/common/model-patch.js +13 -0
- package/dist/router/config/common/render.js +30 -77
- package/dist/router/config/dsh.js +2 -1
- package/dist/router/config/kimi.js +3 -3
- package/dist/router/index.d.ts +9 -11
- package/dist/router/index.js +165 -356
- package/dist/router/order-exec-dispatch.js +2 -1
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +3 -3
- package/dist/runner/common/defaults.d.ts +2 -0
- package/dist/runner/common/defaults.js +3 -0
- 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 +3 -3
- package/dist/runner/common/spawning-runner.d.ts +1 -1
- package/dist/runner/common/spawning-runner.js +2 -2
- package/dist/runner/dsh/auth.js +2 -1
- package/dist/runner/dsh/client.d.ts +100 -3
- package/dist/runner/dsh/client.js +290 -6
- package/dist/runner/dsh/exit-cancel.d.ts +32 -0
- package/dist/runner/dsh/exit-cancel.js +72 -0
- package/dist/runner/dsh/inflight.d.ts +62 -0
- package/dist/runner/dsh/inflight.js +140 -0
- package/dist/runner/dsh/runner.d.ts +90 -4
- package/dist/runner/dsh/runner.js +436 -18
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/dsh/translator.js +103 -1
- package/dist/runner/dsh/types.d.ts +60 -0
- package/dist/runner/types.d.ts +95 -2
- package/dist/runner/types.js +9 -0
- package/dist/session/claude/session-index.d.ts +0 -4
- package/dist/session/claude/session-index.js +0 -8
- package/dist/session/claude/sessions.js +2 -2
- package/dist/session/dsh/sessions.js +157 -17
- package/dist/update/install.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,12 +5,15 @@ import { parseCliArgs, resolveConfigDir, setConfigDir, printHelp, printVersion,
|
|
|
5
5
|
import { FeishuConnector } from './connector/index.js';
|
|
6
6
|
import { ClaudeRunner, CodexAppServerRunner, OpencodeAcpRunner, PiRpcRunner, KimiAcpRunner, DshRunner, } from './runner/index.js';
|
|
7
7
|
import { AgentRegistry } from './runner/registry.js';
|
|
8
|
+
import { AGENT_DISPLAY_NAMES, AGENT_KINDS } from './runner/types.js';
|
|
8
9
|
import { probeAllAgents } from './runner/probe.js';
|
|
9
10
|
import { warmCodexCatalogCache } from './config/codex-config.js';
|
|
10
11
|
import { SessionReaderRegistry, SessionStore } from './session/index.js';
|
|
11
12
|
import { ClaudeSessionReader, CodexSessionReader, OpencodeSessionReader, PiSessionReader, KimiSessionReader, DshSessionReader, } from './session/index.js';
|
|
12
13
|
import { CommandRouter, isImmediateAction, DIRECT_RETURN_CMDS, SESSION_MUTATING_ACTION_CMDS, COMPACT_ACTION_CMDS, } from './router/index.js';
|
|
13
14
|
import { DshDaemon } from './runner/dsh/daemon.js';
|
|
15
|
+
import { DshClient } from './runner/dsh/client.js';
|
|
16
|
+
import { DshInflightStore, dshInflightPath, reconcileInflightDshRuns, } from './runner/dsh/inflight.js';
|
|
14
17
|
import { dispatchOrderExecForQueue } from './router/order-exec-dispatch.js';
|
|
15
18
|
import { buildCardActionFullValue } from './router/card-action-payload.js';
|
|
16
19
|
import { actionFeedbackText } from './router/card-action-feedback.js';
|
|
@@ -29,7 +32,7 @@ import { WorkspaceStore } from './workspace/index.js';
|
|
|
29
32
|
import { InboundTurnAssembler } from './inbound/turn-assembler.js';
|
|
30
33
|
import { InboundAuthorizer } from './inbound/authorize.js';
|
|
31
34
|
import { stripPlaceholders } from './inbound/placeholder.js';
|
|
32
|
-
import { buildSessionHistoryCard } from './router/card-helpers.js';
|
|
35
|
+
import { buildSessionHistoryCard, pickResumeCardEvents } from './router/card-helpers.js';
|
|
33
36
|
import { newSessionButton, resumeCompactButton, agentDisplayName } from './card/card-shared.js';
|
|
34
37
|
import path from 'node:path';
|
|
35
38
|
import fs from 'node:fs';
|
|
@@ -43,16 +46,18 @@ function isStopCommand(text) {
|
|
|
43
46
|
return t === '/stop' || t === '/t';
|
|
44
47
|
}
|
|
45
48
|
/** Send auto-resume card for a restored session */
|
|
46
|
-
async function sendAutoResumeCard(connector, contact, cwd, session, state, compactSupported = false) {
|
|
49
|
+
async function sendAutoResumeCard(connector, contact, cwd, session, state, compactSupported = false, stillRunning = false) {
|
|
47
50
|
if (!contact.userId)
|
|
48
51
|
return;
|
|
49
52
|
const recipient = contact.chatId ?? contact.userId;
|
|
50
53
|
if (!recipient)
|
|
51
54
|
return;
|
|
52
|
-
// Limit history events to prevent card size exceeded error (11310)
|
|
55
|
+
// Limit history events to prevent card size exceeded error (11310).
|
|
56
|
+
// 窗口按"最后 N 条"截,但强制保住最后一条用户消息与最后一条 assistant 正文,
|
|
57
|
+
// 否则工具往返会把它俩一起挤出卡(2026-10-19 恢复卡只剩 5 个 bash 面板)。
|
|
53
58
|
const MAX_HISTORY_EVENTS = 5;
|
|
54
|
-
const eventsToShow = state.events
|
|
55
|
-
const hiddenCount = state.events.length -
|
|
59
|
+
const eventsToShow = pickResumeCardEvents(state.events, MAX_HISTORY_EVENTS);
|
|
60
|
+
const hiddenCount = state.events.length - eventsToShow.length;
|
|
56
61
|
// Action buttons: Compact (runCompact-capable runner) + new session.
|
|
57
62
|
const agentKind = state.agentKind ?? 'claude';
|
|
58
63
|
const actionButtons = [];
|
|
@@ -73,6 +78,7 @@ async function sendAutoResumeCard(connector, contact, cwd, session, state, compa
|
|
|
73
78
|
headerText: `📂 \`${cwd}\`\n已恢复最近会话: **${session.sessionId}**`,
|
|
74
79
|
title: `🔁 自动恢复会话 · ${agentDisplayName(agentKind)}`,
|
|
75
80
|
hiddenCount,
|
|
81
|
+
stillRunning,
|
|
76
82
|
actions: actionButtons,
|
|
77
83
|
});
|
|
78
84
|
await connector.sendWithRetry(recipient, { card });
|
|
@@ -300,6 +306,9 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
300
306
|
provider: dshConf?.provider,
|
|
301
307
|
model: dshConf?.model,
|
|
302
308
|
reasoningEffort: dshConf?.reasoningEffort,
|
|
309
|
+
// Durable markers for turns that outlive this process; reconciled on the
|
|
310
|
+
// next startup (see reconcileInflightDshRuns).
|
|
311
|
+
inflightPath: dshInflightPath(configDir),
|
|
303
312
|
});
|
|
304
313
|
});
|
|
305
314
|
sessionReaderRegistry.register('dsh', dshSessionReader);
|
|
@@ -308,13 +317,10 @@ function initializeRunner(config, configDir, cliArgs) {
|
|
|
308
317
|
getLogger().warn(`[dsh] startup probe failed: ${error.message}`);
|
|
309
318
|
});
|
|
310
319
|
}
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
agentRegistry.registerDisplayName('pi', 'Pi');
|
|
316
|
-
agentRegistry.registerDisplayName('kimi', 'Kimi');
|
|
317
|
-
agentRegistry.registerDisplayName('dsh', 'DSH');
|
|
320
|
+
// 显示名单源在 runner/types.ts 的 AGENT_DISPLAY_NAMES(启动注册与回退共用)
|
|
321
|
+
for (const kind of AGENT_KINDS) {
|
|
322
|
+
agentRegistry.registerDisplayName(kind, AGENT_DISPLAY_NAMES[kind]);
|
|
323
|
+
}
|
|
318
324
|
// 设置全局 registry,供 agentDisplayName 等全局函数使用
|
|
319
325
|
AgentRegistry.setGlobalInstance(agentRegistry);
|
|
320
326
|
// Probe agent CLI availability (fire-and-forget, populates cache for /config card).
|
|
@@ -834,6 +840,33 @@ async function main() {
|
|
|
834
840
|
})();
|
|
835
841
|
}
|
|
836
842
|
}
|
|
843
|
+
// 上次进程可能死在 DSH turn 中途:清掉残留的待执行 prompt,并对仍在跑的
|
|
844
|
+
// turn 提示 owner(不自动 cancel——它可能是 Web UI 的 turn)。清理不依赖是否
|
|
845
|
+
// 已绑定;没有收件人时只是不发提示。
|
|
846
|
+
const dshConf = getAgentConfig(config, 'dsh');
|
|
847
|
+
if (dshConf) {
|
|
848
|
+
const contact = startupContactStore.getContact();
|
|
849
|
+
const recipient = contact?.chatId ?? contact?.userId;
|
|
850
|
+
void reconcileInflightDshRuns({
|
|
851
|
+
store: new DshInflightStore(dshInflightPath(configDir)),
|
|
852
|
+
client: new DshClient(dshConf.host ?? 'http://127.0.0.1:3080', {
|
|
853
|
+
dshHome: dshConf.dshHome,
|
|
854
|
+
token: dshConf.token,
|
|
855
|
+
credentialsFile: dshConf.credentialsFile,
|
|
856
|
+
// Host may be down: bound the probe instead of waiting the default 60s.
|
|
857
|
+
requestTimeoutMs: 3_000,
|
|
858
|
+
}),
|
|
859
|
+
...(recipient
|
|
860
|
+
? {
|
|
861
|
+
notify: async (text) => {
|
|
862
|
+
await connector.sendWithRetry(recipient, { text });
|
|
863
|
+
},
|
|
864
|
+
}
|
|
865
|
+
: {}),
|
|
866
|
+
}).catch((err) => {
|
|
867
|
+
logger.warn(`[startup] dsh inflight reconcile failed: ${err.message}`);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
837
870
|
// Auto-restore: resume the persisted sessionId if available
|
|
838
871
|
const restoredContact = startupContactStore.getContact();
|
|
839
872
|
if (restoredContact?.userId) {
|
|
@@ -845,6 +878,26 @@ async function main() {
|
|
|
845
878
|
const startupReader = sessionReaderRegistry.get(config.defaultAgent);
|
|
846
879
|
sessionStore.setSessionIdAndCwd(restoredContact.userId, config.defaultAgent, sessionIdToResume, restoredCwd);
|
|
847
880
|
const content = startupReader.readSessionContent(sessionIdToResume, restoredCwd);
|
|
881
|
+
// 该会话可能仍有在途 turn(上一个实例退出/崩溃后留在常驻 host 里)。
|
|
882
|
+
// 不取消、不接管,但必须在卡上说明,否则用户会把它当"卡住了"。
|
|
883
|
+
//
|
|
884
|
+
// 只有 DSH reader 的 isSessionActive() 是服务端的 running 事实。其他
|
|
885
|
+
// reader 的同名方法只表示"最近一段时间有写入",拿它当 turn 在跑会在
|
|
886
|
+
// 每次自动恢复时误报。
|
|
887
|
+
const stillRunning = config.defaultAgent === 'dsh'
|
|
888
|
+
? (() => {
|
|
889
|
+
try {
|
|
890
|
+
return startupReader.isSessionActive(sessionIdToResume, restoredCwd);
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
getLogger().warn(`[startup] isSessionActive failed: ${err.message}`);
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
})()
|
|
897
|
+
: false;
|
|
898
|
+
if (stillRunning) {
|
|
899
|
+
getLogger().warn(`[startup] session ${sessionIdToResume} still has a running turn; auto-resume card is a snapshot`);
|
|
900
|
+
}
|
|
848
901
|
// 完整透传 usage(含 inputTokens/outputTokens/totalTokens),
|
|
849
902
|
// 让 formatUsageStats 走真实值路径,与终态卡片//resume 卡片口径一致。
|
|
850
903
|
const usage = content.usage;
|
|
@@ -862,7 +915,7 @@ async function main() {
|
|
|
862
915
|
agentKind: config.defaultAgent,
|
|
863
916
|
},
|
|
864
917
|
// Compact 能力探测与 bridge 侧一致(runner 有 runCompact 才渲染)。
|
|
865
|
-
bridge.hasRunCompact(restoredCwd, config.defaultAgent));
|
|
918
|
+
bridge.hasRunCompact(restoredCwd, config.defaultAgent), stillRunning);
|
|
866
919
|
}
|
|
867
920
|
catch (err) {
|
|
868
921
|
getLogger().warn('[startup] sendAutoResumeCard failed:', err);
|
package/dist/order/index.d.ts
CHANGED
package/dist/order/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import path from 'node:path';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { atomicWriteJson } from '../persistence/atomic-write.js';
|
|
4
4
|
import { loadJsonFile } from '../persistence/load-json-file.js';
|
|
5
|
+
/** 指令已被删除/不存在时的统一用户提示。 */
|
|
6
|
+
export const ORDER_NOT_FOUND_TEXT = '指令不存在或已被删除';
|
|
5
7
|
const MAX_TEXT_LENGTH = 200;
|
|
6
8
|
const MAX_ORDERS = 50;
|
|
7
9
|
/** 别名名称规则:`[A-Za-z_][A-Za-z0-9_]*`,≤20 字符,不能数字开头。 */
|
package/dist/platform/command.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 自更新安装源检测 update/install.ts)。**执行**统一走 platform/spawn.ts 的
|
|
6
6
|
* cross-spawn 包装(PATHEXT 解析 + .cmd 经 cmd.exe 受控执行),本模块不再
|
|
7
7
|
* 解析 .cmd/.bat 垫片内容——垫片格式是 npm/pnpm 的上游实现细节(历史变过、
|
|
8
|
-
* pnpm
|
|
8
|
+
* pnpm 生成形态不同),手写解析器等于耦合该细节。
|
|
9
9
|
*
|
|
10
10
|
* posix:PATH 目录顺序 + X_OK(现状语义迁移)。
|
|
11
11
|
* win32:PATH 目录顺序 × PATHEXT(.exe/.cmd/.bat) 解析,.cmd 命中即报告垫片
|
package/dist/platform/shell.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 由调用方转成明确错误卡(提示需要 Git Bash)。
|
|
9
9
|
*
|
|
10
10
|
* kimi ACP 的 terminal/create 固定 `bash -c`,因此 Git Bash 不是 `!` 的专属
|
|
11
|
-
* 需求,而是 kimi agent 在 Windows
|
|
11
|
+
* 需求,而是 kimi agent 在 Windows 上可用的前置条件。
|
|
12
12
|
*/
|
|
13
13
|
import type { ChildProcess, SpawnOptions } from 'node:child_process';
|
|
14
14
|
import { type ResolveOptions, type LaunchSpec } from './command.js';
|
|
@@ -31,7 +31,7 @@ export interface ShellBackend {
|
|
|
31
31
|
* - posix:原样透传(与迁移前 `spawnProcess(command, args, ...)` 逐字一致);
|
|
32
32
|
* - win32:首 token 是 shell(bash/sh/zsh/dash)时按 Git Bash 解析,缺失则抛
|
|
33
33
|
* {@link ShellUnavailableError}——kimi agent 在 Windows 上可用的前置条件
|
|
34
|
-
*
|
|
34
|
+
* 。其余命令交给 cross-spawn 做 PATH/PATHEXT 解析,不做命令翻译。
|
|
35
35
|
*/
|
|
36
36
|
spawnArgv(file: string, args: readonly string[], opts?: {
|
|
37
37
|
cwd?: string;
|
package/dist/platform/spawn.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export declare function mergeProcessEnv(base?: NodeJS.ProcessEnv, overrides?: No
|
|
|
35
35
|
*/
|
|
36
36
|
export declare function useDetachedProcessGroup(platform?: NodeJS.Platform): boolean;
|
|
37
37
|
/**
|
|
38
|
-
* win32 command-not-found
|
|
38
|
+
* win32 command-not-found 行嗅探:命中即视为「命令缺失」失败而非
|
|
39
39
|
* 普通非零退出。platform 注入使其在任意宿主可测。
|
|
40
40
|
*/
|
|
41
41
|
export declare function isWindowsCommandNotFoundLine(line: string, platform?: NodeJS.Platform): boolean;
|
package/dist/platform/spawn.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 必须经它合并(先按不区分大小写删旧键再设新值);
|
|
11
11
|
* - isWindowsCommandNotFoundLine:win32 经 cmd 垫片启动失败不是 spawn 的
|
|
12
12
|
* error 事件 ENOENT,而是子进程往 stderr 打
|
|
13
|
-
* "'xxx' is not recognized as an internal or external command"
|
|
13
|
+
* "'xxx' is not recognized as an internal or external command"(见本文件 command-not-found 嗅探)。
|
|
14
14
|
*/
|
|
15
15
|
import crossSpawn from 'cross-spawn';
|
|
16
16
|
import { currentPlatform, isWin32 } from './select.js';
|
|
@@ -68,7 +68,7 @@ export function useDetachedProcessGroup(platform = currentPlatform) {
|
|
|
68
68
|
return !isWin32(platform);
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* win32 command-not-found
|
|
71
|
+
* win32 command-not-found 行嗅探:命中即视为「命令缺失」失败而非
|
|
72
72
|
* 普通非零退出。platform 注入使其在任意宿主可测。
|
|
73
73
|
*/
|
|
74
74
|
export function isWindowsCommandNotFoundLine(line, platform = currentPlatform) {
|
package/dist/platform/types.d.ts
CHANGED
|
@@ -10,22 +10,6 @@ import { markdownDiv } from '../card/collapsible.js';
|
|
|
10
10
|
import type { AgentSessionContentEvent } from '../runner/index.js';
|
|
11
11
|
import { formatUsageStats } from './usage-format.js';
|
|
12
12
|
type SessionUsageLike = Parameters<typeof formatUsageStats>[0];
|
|
13
|
-
/**
|
|
14
|
-
* Build a collapsible panel for a single session content event.
|
|
15
|
-
*
|
|
16
|
-
* Header carries a type emoji (👤 user / 🤖 assistant·text / 💭 thinking /
|
|
17
|
-
* 🔧 tool_use / 🟢🔴 tool_result) with optional timestamp.
|
|
18
|
-
* Body: the event's content (markdown).
|
|
19
|
-
*
|
|
20
|
-
* The last `tailExpandedCount` events are expanded by default so the user
|
|
21
|
-
* sees the most recent context without clicking; older events are collapsed.
|
|
22
|
-
*
|
|
23
|
-
* @param ev - Session content event
|
|
24
|
-
* @param index - Index in the events array
|
|
25
|
-
* @param totalEvents - Total number of events
|
|
26
|
-
* @param tailExpandedCount - How many events from the end to keep expanded (default 2)
|
|
27
|
-
* @param agentKind - Agent type for display name (e.g. 'claude', 'pi'). Defaults to 'claude'.
|
|
28
|
-
*/
|
|
29
13
|
export declare function sessionEventPanel(ev: AgentSessionContentEvent, index: number, totalEvents: number, tailExpandedCount?: number, agentKind?: string): object;
|
|
30
14
|
export { markdownDiv };
|
|
31
15
|
/** 页码输入非法时的统一提示文案(/ls /ws /resume /active /order 共用)。 */
|
|
@@ -115,6 +99,19 @@ export declare function searchBar(opts: {
|
|
|
115
99
|
currentQuery?: string;
|
|
116
100
|
extra?: Record<string, unknown>;
|
|
117
101
|
}): object;
|
|
102
|
+
/**
|
|
103
|
+
* 选出 resume/历史卡该展示的事件。
|
|
104
|
+
*
|
|
105
|
+
* 旧口径是 `events.slice(-max)`:按**原始事件条数**截尾。工具往返(bash 调用 →
|
|
106
|
+
* job 登记 → 结果)一段就能把窗口占满,于是重返会话的用户真正需要的两条信息
|
|
107
|
+
* ——最后一句我说了什么、最后一句它答了什么——被挤出窗口。2026-10-19 实测:
|
|
108
|
+
* 自动恢复卡 5 个面板全是 bash 调用,一条用户消息、一条 assistant 正文都没有。
|
|
109
|
+
*
|
|
110
|
+
* 契约:先取最新 `max` 条,再把最后一条 `user` 与最后一条 `assistant`(正文)
|
|
111
|
+
* 不在窗口内时补进来——补位时淘汰最早的**非锚点**事件。输出保持时间顺序,
|
|
112
|
+
* 长度不超过 `max`。
|
|
113
|
+
*/
|
|
114
|
+
export declare function pickResumeCardEvents(events: AgentSessionContentEvent[], max: number): AgentSessionContentEvent[];
|
|
118
115
|
/**
|
|
119
116
|
* Build a session-history resume card (shared by auto-restore, /resume <id>,
|
|
120
117
|
* config-switch resume, and run-completion notification).
|
|
@@ -145,6 +142,11 @@ export declare function buildSessionHistoryCard(state: {
|
|
|
145
142
|
emptyPlaceholder?: string;
|
|
146
143
|
/** Hidden earlier-events count (auto-restore only). */
|
|
147
144
|
hiddenCount?: number;
|
|
145
|
+
/**
|
|
146
|
+
* 该会话此刻仍有在途 turn(仅 DSH 的 `isSessionActive` 是服务端 running
|
|
147
|
+
* 事实)。为 true 时卡上显式提示"这是历史快照、实时输出不会落在这张卡上"。
|
|
148
|
+
*/
|
|
149
|
+
stillRunning?: boolean;
|
|
148
150
|
/** Action buttons to render (already built, protocol-specific). */
|
|
149
151
|
actions?: object[];
|
|
150
152
|
/** Optional card header template (e.g. 'green' for completion cards). */
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* specific builders (e.g. `sessionEventPanel`) so `router/index.ts` can fold
|
|
7
7
|
* long session histories into collapsible panels without duplicating logic.
|
|
8
8
|
*/
|
|
9
|
-
import { agentDisplayName } from '../card/card-shared.js';
|
|
9
|
+
import { agentDisplayName, cardButton } from '../card/card-shared.js';
|
|
10
10
|
import { collapsibleMarkdownPanel, markdownDiv } from '../card/collapsible.js';
|
|
11
|
-
import {
|
|
11
|
+
import { formatMonthDayClock } from '../card/time.js';
|
|
12
|
+
import { displayWidth, truncateMiddleToDisplayWidth } from '../common/text-width.js';
|
|
12
13
|
import { formatUsageStats } from './usage-format.js';
|
|
13
14
|
/**
|
|
14
15
|
* Build a collapsible panel for a single session content event.
|
|
@@ -26,10 +27,21 @@ import { formatUsageStats } from './usage-format.js';
|
|
|
26
27
|
* @param tailExpandedCount - How many events from the end to keep expanded (default 2)
|
|
27
28
|
* @param agentKind - Agent type for display name (e.g. 'claude', 'pi'). Defaults to 'claude'.
|
|
28
29
|
*/
|
|
30
|
+
/**
|
|
31
|
+
* resume / 历史卡片的事件面板标题宽度上限(半角单位)。
|
|
32
|
+
*
|
|
33
|
+
* 与 run 卡的工具面板同口径:手机上一行约 42 个半角宽,标题超了会折行。
|
|
34
|
+
*/
|
|
35
|
+
const SESSION_PANEL_TITLE_MAX = 42;
|
|
29
36
|
export function sessionEventPanel(ev, index, totalEvents, tailExpandedCount = 2, agentKind = 'claude') {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
// reader 可以给事件一个"标题即摘要"的名字(工具调用:`🔧 bash — npm test`);
|
|
38
|
+
// 缺省用类型标签。标题连同时间戳一起收进一行显示宽度——手机上折叠面板只有
|
|
39
|
+
// 一行可用(2026-10-19 工具标题被长 URL 撑成两行那次)。
|
|
40
|
+
const label = ev.title ?? eventLabel(ev, agentKind);
|
|
41
|
+
const ts = formatMonthDayClock(ev.timestamp);
|
|
42
|
+
const suffix = ts ? ` (${ts})` : '';
|
|
43
|
+
const labelMax = Math.max(8, SESSION_PANEL_TITLE_MAX - displayWidth(suffix));
|
|
44
|
+
const title = `${truncateMiddleToDisplayWidth(label, labelMax)}${suffix}`;
|
|
33
45
|
const expanded = index >= totalEvents - tailExpandedCount;
|
|
34
46
|
return collapsibleMarkdownPanel({
|
|
35
47
|
title,
|
|
@@ -130,18 +142,7 @@ export function paginationBar(opts) {
|
|
|
130
142
|
width: 'auto',
|
|
131
143
|
vertical_align: 'center',
|
|
132
144
|
elements: [
|
|
133
|
-
{
|
|
134
|
-
tag: 'button',
|
|
135
|
-
text: { tag: 'plain_text', content: prevText },
|
|
136
|
-
type: 'default',
|
|
137
|
-
size: 'small',
|
|
138
|
-
behaviors: [
|
|
139
|
-
{
|
|
140
|
-
type: 'callback',
|
|
141
|
-
value: { cmd: opts.cmd, offset: opts.offset - opts.pageSize, ...opts.extra },
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
+
cardButton(prevText, { cmd: opts.cmd, offset: opts.offset - opts.pageSize, ...opts.extra }, { type: 'default', size: 'small' }),
|
|
145
146
|
],
|
|
146
147
|
});
|
|
147
148
|
}
|
|
@@ -173,18 +174,7 @@ export function paginationBar(opts) {
|
|
|
173
174
|
width: 'auto',
|
|
174
175
|
vertical_align: 'center',
|
|
175
176
|
elements: [
|
|
176
|
-
{
|
|
177
|
-
tag: 'button',
|
|
178
|
-
text: { tag: 'plain_text', content: nextText },
|
|
179
|
-
type: 'default',
|
|
180
|
-
size: 'small',
|
|
181
|
-
behaviors: [
|
|
182
|
-
{
|
|
183
|
-
type: 'callback',
|
|
184
|
-
value: { cmd: opts.cmd, offset: opts.offset + opts.pageSize, ...opts.extra },
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
},
|
|
177
|
+
cardButton(nextText, { cmd: opts.cmd, offset: opts.offset + opts.pageSize, ...opts.extra }, { type: 'default', size: 'small' }),
|
|
188
178
|
],
|
|
189
179
|
});
|
|
190
180
|
}
|
|
@@ -252,23 +242,56 @@ export function searchBar(opts) {
|
|
|
252
242
|
width: 'auto',
|
|
253
243
|
vertical_align: 'center',
|
|
254
244
|
elements: [
|
|
255
|
-
{
|
|
256
|
-
tag: 'button',
|
|
257
|
-
text: { tag: 'plain_text', content: '清除筛选' },
|
|
258
|
-
type: 'default',
|
|
259
|
-
size: 'small',
|
|
260
|
-
behaviors: [
|
|
261
|
-
{
|
|
262
|
-
type: 'callback',
|
|
263
|
-
value: { cmd: opts.cmd, clearQuery: true, ...opts.extra },
|
|
264
|
-
},
|
|
265
|
-
],
|
|
266
|
-
},
|
|
245
|
+
cardButton('清除筛选', { cmd: opts.cmd, clearQuery: true, ...opts.extra }, { type: 'default', size: 'small' }),
|
|
267
246
|
],
|
|
268
247
|
});
|
|
269
248
|
}
|
|
270
249
|
return { tag: 'column_set', columns };
|
|
271
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* 选出 resume/历史卡该展示的事件。
|
|
253
|
+
*
|
|
254
|
+
* 旧口径是 `events.slice(-max)`:按**原始事件条数**截尾。工具往返(bash 调用 →
|
|
255
|
+
* job 登记 → 结果)一段就能把窗口占满,于是重返会话的用户真正需要的两条信息
|
|
256
|
+
* ——最后一句我说了什么、最后一句它答了什么——被挤出窗口。2026-10-19 实测:
|
|
257
|
+
* 自动恢复卡 5 个面板全是 bash 调用,一条用户消息、一条 assistant 正文都没有。
|
|
258
|
+
*
|
|
259
|
+
* 契约:先取最新 `max` 条,再把最后一条 `user` 与最后一条 `assistant`(正文)
|
|
260
|
+
* 不在窗口内时补进来——补位时淘汰最早的**非锚点**事件。输出保持时间顺序,
|
|
261
|
+
* 长度不超过 `max`。
|
|
262
|
+
*/
|
|
263
|
+
export function pickResumeCardEvents(events, max) {
|
|
264
|
+
if (max <= 0)
|
|
265
|
+
return [];
|
|
266
|
+
if (events.length <= max)
|
|
267
|
+
return events.slice();
|
|
268
|
+
const picked = new Set();
|
|
269
|
+
for (let i = events.length - 1; i >= 0 && picked.size < max; i -= 1)
|
|
270
|
+
picked.add(i);
|
|
271
|
+
const lastIndexOf = (predicate) => {
|
|
272
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
273
|
+
const ev = events[i];
|
|
274
|
+
if (ev && predicate(ev))
|
|
275
|
+
return i;
|
|
276
|
+
}
|
|
277
|
+
return -1;
|
|
278
|
+
};
|
|
279
|
+
const anchors = [
|
|
280
|
+
lastIndexOf((ev) => ev.type === 'user'),
|
|
281
|
+
lastIndexOf((ev) => ev.type === 'assistant'),
|
|
282
|
+
].filter((i) => i >= 0);
|
|
283
|
+
for (const anchor of anchors) {
|
|
284
|
+
if (picked.has(anchor))
|
|
285
|
+
continue;
|
|
286
|
+
// 腾位置时只淘汰非锚点:两个锚点之间不互相挤掉。
|
|
287
|
+
const victim = [...picked].filter((i) => !anchors.includes(i)).sort((a, b) => a - b)[0];
|
|
288
|
+
if (victim === undefined)
|
|
289
|
+
break;
|
|
290
|
+
picked.delete(victim);
|
|
291
|
+
picked.add(anchor);
|
|
292
|
+
}
|
|
293
|
+
return [...picked].sort((a, b) => a - b).map((i) => events[i]);
|
|
294
|
+
}
|
|
272
295
|
/**
|
|
273
296
|
* Build a session-history resume card (shared by auto-restore, /resume <id>,
|
|
274
297
|
* config-switch resume, and run-completion notification).
|
|
@@ -283,20 +306,39 @@ export function buildSessionHistoryCard(state, opts) {
|
|
|
283
306
|
const { displayTitle, aiTitle, recap, events, usage } = state;
|
|
284
307
|
let header = opts.headerText;
|
|
285
308
|
const sections = [];
|
|
309
|
+
const preview = (text) => (text.length > 200 ? text.slice(0, 197) + '...' : text);
|
|
286
310
|
// Reader 层不再截断 displayTitle/summary;统一在消费侧这里截到 200 字符。
|
|
311
|
+
//
|
|
312
|
+
// `aiTitle` 与 `displayTitle` 是两种东西:前者是 agent 自己起的会话标题
|
|
313
|
+
// (DSH 的 projections.title / claude 的 ai-title),后者是最近一条用户输入。
|
|
314
|
+
// claude 的 displayTitle 本身就取 aiTitle,两者相等 → 保持旧的「AI 标题」
|
|
315
|
+
// 单段文案;DSH 两者都有(标题 + 末轮 prompt)→ 各占一段,别把用户那句话
|
|
316
|
+
// 挂在「AI 标题」下面(2026-10-21)。
|
|
287
317
|
if (displayTitle) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
318
|
+
if (aiTitle && aiTitle !== displayTitle) {
|
|
319
|
+
sections.push(`🏷️ **标题**\n${preview(aiTitle)}`);
|
|
320
|
+
sections.push(`✏️ **最近输入**\n${preview(displayTitle)}`);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const label = aiTitle ? 'AI 标题' : '最近输入';
|
|
324
|
+
sections.push(`🏷️ **${label}**\n${preview(displayTitle)}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (aiTitle) {
|
|
328
|
+
sections.push(`🏷️ **AI 标题**\n${preview(aiTitle)}`);
|
|
291
329
|
}
|
|
292
330
|
if (recap) {
|
|
293
|
-
|
|
294
|
-
sections.push(`📝 **Recap**\n${recapPreview}`);
|
|
331
|
+
sections.push(`📝 **Recap**\n${preview(recap)}`);
|
|
295
332
|
}
|
|
296
333
|
if (sections.length > 0) {
|
|
297
334
|
header += '\n\n' + sections.join('\n\n──\n\n');
|
|
298
335
|
}
|
|
299
336
|
const elements = [markdownDiv(header), { tag: 'hr' }];
|
|
337
|
+
// 恢复时该会话仍有一个 turn 在跑(典型来源:上一个桥接实例退出/崩溃后,
|
|
338
|
+
// turn 留在常驻 host 里继续执行)。卡片只是快照,别让用户以为这就是全部。
|
|
339
|
+
if (opts.stillRunning) {
|
|
340
|
+
elements.push(markdownDiv('⚠️ **该会话仍有一个 turn 在运行**(可能来自已退出的实例):以下内容是历史快照,实时输出不会出现在这张卡上。'));
|
|
341
|
+
}
|
|
300
342
|
if (opts.hiddenCount && opts.hiddenCount > 0) {
|
|
301
343
|
elements.push(markdownDiv(`📜 还有 ${opts.hiddenCount} 个更早的事件未显示`));
|
|
302
344
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { loadCodexConfig, getReasoningEffortOptions, getDefaultReasoningEffort, } from '../../config/codex-config.js';
|
|
5
5
|
import { DEFAULT_TURN_IDLE_TIMEOUT_MINUTES } from '../../config/index.js';
|
|
6
|
-
import { resetModelPatch } from './common/model-patch.js';
|
|
6
|
+
import { pickMiddleValue, resetModelPatch } from './common/model-patch.js';
|
|
7
7
|
export class CodexConfigBuilder {
|
|
8
8
|
buildFields(displayConfig) {
|
|
9
9
|
const fields = [];
|
|
@@ -116,8 +116,7 @@ export class CodexConfigBuilder {
|
|
|
116
116
|
const isCurrentEffortValid = currentReasoningEffort && newModelSupportedEfforts.includes(currentReasoningEffort);
|
|
117
117
|
if (isCurrentEffortValid)
|
|
118
118
|
return null;
|
|
119
|
-
const
|
|
120
|
-
const newEffort = middle ?? getDefaultReasoningEffort(newModel);
|
|
119
|
+
const newEffort = pickMiddleValue(newModelSupportedEfforts) ?? getDefaultReasoningEffort(newModel);
|
|
121
120
|
return { key: 'agents.codex.reasoningEffort', value: newEffort };
|
|
122
121
|
}
|
|
123
122
|
}
|
|
@@ -19,3 +19,11 @@ export declare function resetModelPatch(modelKey: string, currentModel: string |
|
|
|
19
19
|
key: string;
|
|
20
20
|
value: unknown;
|
|
21
21
|
} | null;
|
|
22
|
+
/**
|
|
23
|
+
* 取档位列表的中位值(切换模型后当前档位不受支持时的兜底)。
|
|
24
|
+
*
|
|
25
|
+
* 偶数长度的中位口径由调用方显式选择,避免各 builder 各写一套公式后漂移:
|
|
26
|
+
* - `lower`:`(len-1)/2`,kimi/codex 既有口径;
|
|
27
|
+
* - `upper`:`len/2`,DSH 需要偏高的一档(off/low → low)。
|
|
28
|
+
*/
|
|
29
|
+
export declare function pickMiddleValue<T>(values: readonly T[], bias?: 'lower' | 'upper'): T | undefined;
|
|
@@ -22,4 +22,17 @@ export function resetModelPatch(modelKey, currentModel, newModelOptions) {
|
|
|
22
22
|
}
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 取档位列表的中位值(切换模型后当前档位不受支持时的兜底)。
|
|
27
|
+
*
|
|
28
|
+
* 偶数长度的中位口径由调用方显式选择,避免各 builder 各写一套公式后漂移:
|
|
29
|
+
* - `lower`:`(len-1)/2`,kimi/codex 既有口径;
|
|
30
|
+
* - `upper`:`len/2`,DSH 需要偏高的一档(off/low → low)。
|
|
31
|
+
*/
|
|
32
|
+
export function pickMiddleValue(values, bias = 'lower') {
|
|
33
|
+
if (values.length === 0)
|
|
34
|
+
return undefined;
|
|
35
|
+
const index = bias === 'upper' ? Math.floor(values.length / 2) : Math.floor((values.length - 1) / 2);
|
|
36
|
+
return values[index];
|
|
37
|
+
}
|
|
25
38
|
//# sourceMappingURL=model-patch.js.map
|