lark-remote 0.5.18 → 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 +32 -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 +120 -119
- package/dist/bridge/index.js +257 -695
- 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/bash-card-session.js +2 -0
- 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/card-session.d.ts +17 -0
- package/dist/card/card-session.js +35 -0
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-card-session.js +2 -0
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +77 -102
- package/dist/card/run-state.d.ts +35 -8
- package/dist/card/run-state.js +117 -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 +4 -2
- 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 +222 -202
- 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 +13 -8
- package/dist/runner/dsh/client.js +23 -15
- 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 +48 -2
- package/dist/runner/dsh/runner.js +288 -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 +25 -8
- 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
|
@@ -36,7 +36,7 @@ const SESSION_PANEL_TITLE_MAX = 42;
|
|
|
36
36
|
export function sessionEventPanel(ev, index, totalEvents, tailExpandedCount = 2, agentKind = 'claude') {
|
|
37
37
|
// reader 可以给事件一个"标题即摘要"的名字(工具调用:`🔧 bash — npm test`);
|
|
38
38
|
// 缺省用类型标签。标题连同时间戳一起收进一行显示宽度——手机上折叠面板只有
|
|
39
|
-
//
|
|
39
|
+
// 一行可用,长 URL 把标题撑成两行会断在 URL 中间。
|
|
40
40
|
const label = ev.title ?? eventLabel(ev, agentKind);
|
|
41
41
|
const ts = formatMonthDayClock(ev.timestamp);
|
|
42
42
|
const suffix = ts ? ` (${ts})` : '';
|
|
@@ -103,7 +103,7 @@ export function parseJumpOffset(inputValue, pageSize) {
|
|
|
103
103
|
* Build the CardKit 2.0 pagination block shared by /ls, /ws, /resume, /active,
|
|
104
104
|
* /order.
|
|
105
105
|
*
|
|
106
|
-
*
|
|
106
|
+
* 第一性原理(窄屏):分页栏要同时承载「页码信息」和「翻页
|
|
107
107
|
* 控件」两类需求。旧布局把两者塞进同一行 `column_set`(文案 weighted 4 : 输入
|
|
108
108
|
* weighted 2),手机窄屏下 auto 按钮先占走固有宽度,剩余宽度按 4:2 切分后文案
|
|
109
109
|
* 列只有百来像素、输入框只有几十像素——文案折行、输入框窄到没法点。
|
|
@@ -253,8 +253,8 @@ export function searchBar(opts) {
|
|
|
253
253
|
*
|
|
254
254
|
* 旧口径是 `events.slice(-max)`:按**原始事件条数**截尾。工具往返(bash 调用 →
|
|
255
255
|
* job 登记 → 结果)一段就能把窗口占满,于是重返会话的用户真正需要的两条信息
|
|
256
|
-
*
|
|
257
|
-
*
|
|
256
|
+
* ——最后一句我说了什么、最后一句它答了什么——被挤出窗口:自动恢复卡 5 个
|
|
257
|
+
* 面板全是 bash 调用,一条用户消息、一条 assistant 正文都没有。
|
|
258
258
|
*
|
|
259
259
|
* 契约:先取最新 `max` 条,再把最后一条 `user` 与最后一条 `assistant`(正文)
|
|
260
260
|
* 不在窗口内时补进来——补位时淘汰最早的**非锚点**事件。输出保持时间顺序,
|
|
@@ -313,7 +313,7 @@ export function buildSessionHistoryCard(state, opts) {
|
|
|
313
313
|
// (DSH 的 projections.title / claude 的 ai-title),后者是最近一条用户输入。
|
|
314
314
|
// claude 的 displayTitle 本身就取 aiTitle,两者相等 → 保持旧的「AI 标题」
|
|
315
315
|
// 单段文案;DSH 两者都有(标题 + 末轮 prompt)→ 各占一段,别把用户那句话
|
|
316
|
-
// 挂在「AI
|
|
316
|
+
// 挂在「AI 标题」下面。
|
|
317
317
|
if (displayTitle) {
|
|
318
318
|
if (aiTitle && aiTitle !== displayTitle) {
|
|
319
319
|
sections.push(`🏷️ **标题**\n${preview(aiTitle)}`);
|
|
@@ -357,7 +357,7 @@ export function buildSessionHistoryCard(state, opts) {
|
|
|
357
357
|
elements.push(markdownDiv(usageStr));
|
|
358
358
|
}
|
|
359
359
|
// Remove trailing hr
|
|
360
|
-
if (elements
|
|
360
|
+
if (elements[elements.length - 1].tag === 'hr') {
|
|
361
361
|
elements.pop();
|
|
362
362
|
}
|
|
363
363
|
if (opts.actions && opts.actions.length > 0) {
|
|
@@ -99,6 +99,9 @@ export declare abstract class CardSession<S, RO = unknown> {
|
|
|
99
99
|
* in-flight flush 完成后该事件无 follow-up render,停留为 stale 中间帧。
|
|
100
100
|
* finally 检查此标志后重新 scheduleFlush,保证 in-flight 期间累积的事件最终被渲染。 */
|
|
101
101
|
private pendingReschedule;
|
|
102
|
+
/** 终态帧是否已由 confirmTerminalFrame 确认落到飞书。settle 的兜底 patch
|
|
103
|
+
* 据此去重:流异常结束时若终态帧已经确认过,再 patch 一次只是幂等重发。 */
|
|
104
|
+
private terminalFrameConfirmed;
|
|
102
105
|
/** 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
|
|
103
106
|
* finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch 先落地、
|
|
104
107
|
* terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
|
|
@@ -121,5 +124,19 @@ export declare abstract class CardSession<S, RO = unknown> {
|
|
|
121
124
|
protected updateCard(): Promise<void>;
|
|
122
125
|
private waitForControllerReady;
|
|
123
126
|
protected prepareTerminalUpdate(): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* 把终态帧真正钉到飞书上。
|
|
129
|
+
*
|
|
130
|
+
* `controller.update()` 只把帧交给 @larksuite/channel 的 throttle/queue,
|
|
131
|
+
* resolve 时 `message.patch` 还没发出(见 `connector/index.ts` 的
|
|
132
|
+
* patchWithRetry 注释)。流若在下一次 throttle 到期前关闭,这一帧就可能
|
|
133
|
+
* 永远不出门——卡片停在运行态、底部「⏹ 停止」按钮还在:停止其实已经
|
|
134
|
+
* 生效,按钮仍可点,点了是空转。
|
|
135
|
+
*
|
|
136
|
+
* 终态后再走一次整卡 patch(幂等替换;一次 run 只多一次请求),成功打
|
|
137
|
+
* info 便于排障时区分「帧没发出去」与「发出去了飞书没刷」;失败只 warn,
|
|
138
|
+
* 不参与收尾决策——流本身已结束,不能让一次兜底 patch 把 finish 拖成异常。
|
|
139
|
+
*/
|
|
140
|
+
protected confirmTerminalFrame(label: string): Promise<void>;
|
|
124
141
|
protected release(): void;
|
|
125
142
|
}
|
|
@@ -128,6 +128,9 @@ export class CardSession {
|
|
|
128
128
|
* in-flight flush 完成后该事件无 follow-up render,停留为 stale 中间帧。
|
|
129
129
|
* finally 检查此标志后重新 scheduleFlush,保证 in-flight 期间累积的事件最终被渲染。 */
|
|
130
130
|
pendingReschedule = false;
|
|
131
|
+
/** 终态帧是否已由 confirmTerminalFrame 确认落到飞书。settle 的兜底 patch
|
|
132
|
+
* 据此去重:流异常结束时若终态帧已经确认过,再 patch 一次只是幂等重发。 */
|
|
133
|
+
terminalFrameConfirmed = false;
|
|
131
134
|
/** 当前 in-flight flush 的 promise(若 flush 正在 await updateCard)。
|
|
132
135
|
* finish() 在终态 patch 前 await 它,保证 in-flight 的 pre-terminal patch 先落地、
|
|
133
136
|
* terminal patch 后落地——否则两条 controller.update 并发无 FIFO,
|
|
@@ -185,6 +188,11 @@ export class CardSession {
|
|
|
185
188
|
getLogger().warn(`${this.logPrefix} stream did not complete cleanly:`, outcome.error);
|
|
186
189
|
if (!this.messageId)
|
|
187
190
|
return 'unsent';
|
|
191
|
+
// 终态帧已在 finish 里确认落地:兜底 patch 是幂等重发,跳过。
|
|
192
|
+
// 仍返回 'streamed' 而非 'unsent'——卡片确实已带终态内容,调用方不能
|
|
193
|
+
// 因此再发一张 fallback 卡(bridge 只对 'unsent' 兜底)。
|
|
194
|
+
if (this.terminalFrameConfirmed)
|
|
195
|
+
return 'streamed';
|
|
188
196
|
try {
|
|
189
197
|
await this.connector.updateCard(this.messageId, this.renderCurrentCard());
|
|
190
198
|
return 'updated';
|
|
@@ -247,6 +255,33 @@ export class CardSession {
|
|
|
247
255
|
if (this.flushP)
|
|
248
256
|
await this.flushP;
|
|
249
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* 把终态帧真正钉到飞书上。
|
|
260
|
+
*
|
|
261
|
+
* `controller.update()` 只把帧交给 @larksuite/channel 的 throttle/queue,
|
|
262
|
+
* resolve 时 `message.patch` 还没发出(见 `connector/index.ts` 的
|
|
263
|
+
* patchWithRetry 注释)。流若在下一次 throttle 到期前关闭,这一帧就可能
|
|
264
|
+
* 永远不出门——卡片停在运行态、底部「⏹ 停止」按钮还在:停止其实已经
|
|
265
|
+
* 生效,按钮仍可点,点了是空转。
|
|
266
|
+
*
|
|
267
|
+
* 终态后再走一次整卡 patch(幂等替换;一次 run 只多一次请求),成功打
|
|
268
|
+
* info 便于排障时区分「帧没发出去」与「发出去了飞书没刷」;失败只 warn,
|
|
269
|
+
* 不参与收尾决策——流本身已结束,不能让一次兜底 patch 把 finish 拖成异常。
|
|
270
|
+
*/
|
|
271
|
+
async confirmTerminalFrame(label) {
|
|
272
|
+
const messageId = this.messageId;
|
|
273
|
+
// 没拿到 messageId(流未建立/已降级)时无从 patch,交回 settle() 的兜底。
|
|
274
|
+
if (!messageId)
|
|
275
|
+
return;
|
|
276
|
+
try {
|
|
277
|
+
await this.connector.updateCard(messageId, this.renderCurrentCard());
|
|
278
|
+
this.terminalFrameConfirmed = true;
|
|
279
|
+
getLogger().info(`${this.logPrefix} terminal frame confirmed ${label} messageId=${messageId}`);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
getLogger().warn(`${this.logPrefix} terminal frame confirm failed ${label}:`, error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
250
285
|
release() {
|
|
251
286
|
if (this.released)
|
|
252
287
|
return;
|
|
@@ -50,6 +50,8 @@ export class RunCardSession extends CardSession {
|
|
|
50
50
|
await this.prepareTerminalUpdate();
|
|
51
51
|
this.state = finishRun(this.state, terminal, meta);
|
|
52
52
|
await this.updateCard();
|
|
53
|
+
// 终态帧必须真的落地(throttle 通道 resolve 时 patch 还没发)。
|
|
54
|
+
await this.confirmTerminalFrame(`runId=${this.state.runId} terminal=${this.state.terminal}`);
|
|
53
55
|
this.release();
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -55,7 +55,7 @@ type GroupContentPrepared = Map<BlockGroup, string>;
|
|
|
55
55
|
/**
|
|
56
56
|
* 状态行内容:**一行、一个 div、窄屏不换行**。
|
|
57
57
|
*
|
|
58
|
-
*
|
|
58
|
+
* 设计约束(手机端):旧版把「状态标签」和「耗时/指标」放进
|
|
59
59
|
* `column_set` 的 auto + grow 两列,窄屏上 auto 列被挤到只剩两个字宽,
|
|
60
60
|
* "运行中" 被拆成 "运行 / 中"。列式布局的可用宽度取决于对端渲染,服务端无法
|
|
61
61
|
* 保证;所以这里改成单 div,段间用 " · " 分隔——换行只会发生在分隔符处,
|
|
@@ -64,8 +64,8 @@ type GroupContentPrepared = Map<BlockGroup, string>;
|
|
|
64
64
|
* 状态标签(运行中/已完成…)不再重复:header 已经写了(`🔧 dsh · 调用工具`
|
|
65
65
|
* / `✅ dsh · 已完成`),终态 summary 还有一次。
|
|
66
66
|
*
|
|
67
|
-
* 终态也保留状态行:耗时定格,DSH
|
|
68
|
-
*
|
|
67
|
+
* 终态也保留状态行:耗时定格,DSH 的步数/上下文投影展示最后一次快照。
|
|
68
|
+
* summary 的最终 usage 与状态行并存。
|
|
69
69
|
*
|
|
70
70
|
* 导出供窄屏守卫测试断言分段宽度(与 `estimateCardBytes` 同一惯例)。
|
|
71
71
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRunActive, isRunTerminal, } from './run-state.js';
|
|
1
|
+
import { isLiveBackgroundJob, isRunActive, isRunTerminal, } from './run-state.js';
|
|
2
2
|
import { terminalToColor, terminalToLabel, newSessionButton, stopButton, compactButton, cardButton, agentDisplayName, } from './card-shared.js';
|
|
3
3
|
import { collapsibleMarkdownPanel, markdownDiv } from './collapsible.js';
|
|
4
4
|
import { toolBodyMd, toolHeaderText } from './tool-render.js';
|
|
@@ -8,7 +8,7 @@ import { countCardElements, MAX_CARD_ELEMENTS } from './card-budget.js';
|
|
|
8
8
|
import { displayWidth, truncateToDisplayWidth } from '../common/text-width.js';
|
|
9
9
|
import { isContentFilterFailure } from '../common/turn-failure.js';
|
|
10
10
|
import { formatClock, formatTimestamp } from './time.js';
|
|
11
|
-
import { formatUsageStats } from './usage-format.js';
|
|
11
|
+
import { formatTokenK, formatUsageStats } from './usage-format.js';
|
|
12
12
|
import { renderApprovalArea, renderApprovalAreaCompact } from './approval-render.js';
|
|
13
13
|
const REASONING_BYTES = 4_500;
|
|
14
14
|
const TEXT_BYTES = 10_000;
|
|
@@ -38,7 +38,7 @@ const EMPTY_MD_DIV_BYTES = measureJson(markdownDiv('', 'notation'));
|
|
|
38
38
|
* 能力门控(compactSupported !== false)与执行侧 `'runCompact' in runner`
|
|
39
39
|
* 鸭子探测对齐:claude 走 stream-json,不发射 turn_started,operationKind
|
|
40
40
|
* 恒 undefined——若以 operationKind==='turn' 作门控,claude 终态卡永远拿
|
|
41
|
-
*
|
|
41
|
+
* 不到按钮。compaction 卡的「防递归」仍由 operationKind
|
|
42
42
|
* 保证,两者是不同质的判断。
|
|
43
43
|
*/
|
|
44
44
|
function shouldShowCompactButton(state, options) {
|
|
@@ -121,37 +121,21 @@ function buildGoalSection(state) {
|
|
|
121
121
|
* This unifies timestamp position across all block types (thinking/plan/file_change/tool/text).
|
|
122
122
|
*
|
|
123
123
|
* Panel title: `💬 **输出** (ts)` when timestamp present, `💬 **输出**` otherwise.
|
|
124
|
-
*
|
|
124
|
+
* The panel is always expanded — streaming and finalized states render identically.
|
|
125
125
|
*
|
|
126
126
|
* @param content - The text content to render
|
|
127
127
|
* @param timestamp - Optional timestamp for the block
|
|
128
|
-
* @param finalized - Whether the run is in a terminal state
|
|
129
128
|
* @returns elements
|
|
130
129
|
*/
|
|
131
|
-
function renderTextBlock(content, timestamp
|
|
130
|
+
function renderTextBlock(content, timestamp) {
|
|
132
131
|
if (!content.trim()) {
|
|
133
132
|
return [];
|
|
134
133
|
}
|
|
135
134
|
const ts = formatTimestamp(timestamp);
|
|
136
|
-
//
|
|
135
|
+
// Render in collapsible_panel with expanded=true.
|
|
137
136
|
// NOTE: collapsible_panel does NOT shield markdown tables from Feishu's 11310 table count
|
|
138
137
|
// limit — tables inside lark_md text are still counted regardless of container.
|
|
139
138
|
// We must explicitly truncate excess tables via truncateMarkdownTables before rendering.
|
|
140
|
-
if (finalized) {
|
|
141
|
-
const title = '💬 **输出**';
|
|
142
|
-
const header = ts ? `${title} (${ts})` : title;
|
|
143
|
-
const safeContent = truncateMarkdownTables(content);
|
|
144
|
-
return [
|
|
145
|
-
collapsibleMarkdownPanel({
|
|
146
|
-
title: header,
|
|
147
|
-
expanded: true, // Always expanded in finalized state for full visibility
|
|
148
|
-
border: 'grey',
|
|
149
|
-
content: safeContent,
|
|
150
|
-
textSize: 'notation',
|
|
151
|
-
}),
|
|
152
|
-
];
|
|
153
|
-
}
|
|
154
|
-
// Running/finalizing: panel provides visual grouping with timestamp in header
|
|
155
139
|
const title = '💬 **输出**';
|
|
156
140
|
const header = ts ? `${title} (${ts})` : title;
|
|
157
141
|
const safeContent = truncateMarkdownTables(content);
|
|
@@ -302,7 +286,7 @@ function groupBlocks(blocks) {
|
|
|
302
286
|
*
|
|
303
287
|
* 早先试过"一行 + 服务端「详情」按钮":折叠不省字节所以正文不入 payload,展开
|
|
304
288
|
* 只能回服务端重出卡。代价是手机上要多点一次、按钮还占掉标题宽度(长 URL 被挤
|
|
305
|
-
*
|
|
289
|
+
* 成两行)。现在改成折叠面板 + **按工具数量缩放的正文截断**:
|
|
306
290
|
* 正文始终随卡下发(面板折叠时不显示),点标题即展开,无往返、无按钮。
|
|
307
291
|
*
|
|
308
292
|
* 展开态:运行中/失败的默认展开(用户在等结果、错误要立刻可见),其余折叠。
|
|
@@ -493,8 +477,6 @@ function assembleRunCard(state, options, elements) {
|
|
|
493
477
|
function insertAfterStatusRow(elements, extra) {
|
|
494
478
|
if (extra.length === 0)
|
|
495
479
|
return elements;
|
|
496
|
-
if (elements.length === 0)
|
|
497
|
-
return extra;
|
|
498
480
|
return [elements[0], ...extra, ...elements.slice(1)];
|
|
499
481
|
}
|
|
500
482
|
/**
|
|
@@ -573,16 +555,13 @@ function buildFallbackElements(state, options, tier) {
|
|
|
573
555
|
else if (group.kind === 'text') {
|
|
574
556
|
const content = truncateUtf8(group.content, tier.textBytes, true, '…(已截断)\n');
|
|
575
557
|
const ts = formatTimestamp(group.timestamp);
|
|
576
|
-
elements.push(...renderTextBlock(content, ts
|
|
558
|
+
elements.push(...renderTextBlock(content, ts));
|
|
577
559
|
}
|
|
578
560
|
else if (group.kind === 'tool') {
|
|
579
561
|
// Show tool individually (each tool is independent now)
|
|
580
562
|
elements.push(renderTool(group.tool, outputMax));
|
|
581
563
|
}
|
|
582
564
|
}
|
|
583
|
-
if (elements.length === 0 && tier === EXTREME_TIER) {
|
|
584
|
-
elements.push(markdownDiv('_暂无输出_'));
|
|
585
|
-
}
|
|
586
565
|
elements.push(...buildSummaryContent(state, { includeNotices: false }));
|
|
587
566
|
elements.push(...actionRow(state, options));
|
|
588
567
|
elements.push(...approvalArea(state));
|
|
@@ -606,7 +585,7 @@ function buildExtremeFallbackElements(state, options) {
|
|
|
606
585
|
/**
|
|
607
586
|
* 状态行内容:**一行、一个 div、窄屏不换行**。
|
|
608
587
|
*
|
|
609
|
-
*
|
|
588
|
+
* 设计约束(手机端):旧版把「状态标签」和「耗时/指标」放进
|
|
610
589
|
* `column_set` 的 auto + grow 两列,窄屏上 auto 列被挤到只剩两个字宽,
|
|
611
590
|
* "运行中" 被拆成 "运行 / 中"。列式布局的可用宽度取决于对端渲染,服务端无法
|
|
612
591
|
* 保证;所以这里改成单 div,段间用 " · " 分隔——换行只会发生在分隔符处,
|
|
@@ -615,8 +594,8 @@ function buildExtremeFallbackElements(state, options) {
|
|
|
615
594
|
* 状态标签(运行中/已完成…)不再重复:header 已经写了(`🔧 dsh · 调用工具`
|
|
616
595
|
* / `✅ dsh · 已完成`),终态 summary 还有一次。
|
|
617
596
|
*
|
|
618
|
-
* 终态也保留状态行:耗时定格,DSH
|
|
619
|
-
*
|
|
597
|
+
* 终态也保留状态行:耗时定格,DSH 的步数/上下文投影展示最后一次快照。
|
|
598
|
+
* summary 的最终 usage 与状态行并存。
|
|
620
599
|
*
|
|
621
600
|
* 导出供窄屏守卫测试断言分段宽度(与 `estimateCardBytes` 同一惯例)。
|
|
622
601
|
*/
|
|
@@ -629,7 +608,7 @@ export function statusLine(state) {
|
|
|
629
608
|
const elapsed = elapsedLabel(state);
|
|
630
609
|
if (elapsed)
|
|
631
610
|
parts.push(`⏱ ${elapsed}`);
|
|
632
|
-
//
|
|
611
|
+
// 运行期投影在终态也保留:DSH 卡片完成时,耗时/步数/
|
|
633
612
|
// 上下文百分比是这一跑留下的最后快照,撤掉后用户只能从 summary 的累计口径
|
|
634
613
|
// 重新推算。summary 仍展示最终 usage,两者口径不同、并存不冲突。
|
|
635
614
|
const live = state.liveStatus;
|
|
@@ -644,12 +623,12 @@ export function statusLine(state) {
|
|
|
644
623
|
parts.push(`📊 ~${pct}%`);
|
|
645
624
|
}
|
|
646
625
|
else if (live.contextUsed !== undefined) {
|
|
647
|
-
parts.push(`📊 ${
|
|
626
|
+
parts.push(`📊 ${formatTokenK(live.contextUsed)}`);
|
|
648
627
|
}
|
|
649
628
|
}
|
|
650
629
|
if (parts.length === 0) {
|
|
651
630
|
// 没有任何可用指标时退回状态词(骨架卡/测试构造的状态没有时间戳)。
|
|
652
|
-
return
|
|
631
|
+
return terminalToLabel(state.terminal);
|
|
653
632
|
}
|
|
654
633
|
return parts.join(' · ');
|
|
655
634
|
}
|
|
@@ -672,15 +651,6 @@ function elapsedLabel(state) {
|
|
|
672
651
|
return `${hours}h${String(minutes % 60).padStart(2, '0')}m`;
|
|
673
652
|
return `${Math.floor(hours / 24)}d${String(hours % 24).padStart(2, '0')}h`;
|
|
674
653
|
}
|
|
675
|
-
/** token 数量的紧凑写法:`950` / `12K` / `1.0M`。 */
|
|
676
|
-
function formatTokens(tokens) {
|
|
677
|
-
if (tokens < 1000)
|
|
678
|
-
return String(tokens);
|
|
679
|
-
// 千位边界取 M 档:999_500 起四舍五入会得到 "1000K",直接按 M 档展示。
|
|
680
|
-
if (tokens < 999_500)
|
|
681
|
-
return `${Math.round(tokens / 1000)}K`;
|
|
682
|
-
return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
683
|
-
}
|
|
684
654
|
/**
|
|
685
655
|
* 状态行元素(full/degraded/extreme/skeleton 共用)。
|
|
686
656
|
*
|
|
@@ -955,7 +925,7 @@ export const FINAL_ANSWER_OVERFLOW_MARKER = '_最终回答较长,完整内容
|
|
|
955
925
|
* 「卡片装不下正文」与「表格数超上限」是两件事,只有前者才走补发通道:
|
|
956
926
|
* 飞书单卡最多 5 张 markdown 表(11310),多出来的最旧表格会被删掉并在原位
|
|
957
927
|
* 留 `_💡 前 N 个表格已省略_`——正文仍在卡片里,再补发一遍就是把用户已经看到
|
|
958
|
-
*
|
|
928
|
+
* 的整篇回答重复。
|
|
959
929
|
*/
|
|
960
930
|
function buildFinalAnswerPriorityCard(state, options) {
|
|
961
931
|
const finalAnswer = state.finalAnswer;
|
|
@@ -963,12 +933,12 @@ function buildFinalAnswerPriorityCard(state, options) {
|
|
|
963
933
|
const prepared = prepareGroupContent(groups);
|
|
964
934
|
const safeAnswer = truncateMarkdownTables(finalAnswer.content);
|
|
965
935
|
const ts = formatTimestamp(finalAnswer.timestamp);
|
|
966
|
-
const answerElements = renderTextBlock(safeAnswer, ts
|
|
936
|
+
const answerElements = renderTextBlock(safeAnswer, ts);
|
|
967
937
|
const fixedStart = [statusRow(state), ...omittedBlocksHint(state)];
|
|
968
938
|
const summary = buildSummaryContent(state, { includeNotices: false });
|
|
969
939
|
const controls = [...actionRow(state, options), ...approvalArea(state)];
|
|
970
940
|
const buildWithProgress = (start, includeSummary) => {
|
|
971
|
-
const renderedProgress =
|
|
941
|
+
const renderedProgress = renderContentGroups(state, prepared, groups.slice(start));
|
|
972
942
|
const progress = start < groups.length
|
|
973
943
|
? [markdownDiv(`_💡 已省略 ${start} 个早期步骤_`), ...renderedProgress]
|
|
974
944
|
: renderedProgress;
|
|
@@ -1009,8 +979,17 @@ function buildFinalAnswerPriorityCard(state, options) {
|
|
|
1009
979
|
finalAnswerText: finalAnswer.content,
|
|
1010
980
|
};
|
|
1011
981
|
}
|
|
1012
|
-
/**
|
|
1013
|
-
|
|
982
|
+
/**
|
|
983
|
+
* 正常路径内容循环,时间线卡(buildChronologicalContent)与答案优先卡的进度
|
|
984
|
+
* 后缀(buildFinalAnswerPriorityCard)共用:notices 作为普通消息按到达时间
|
|
985
|
+
* 插入内容流(有内容时不再沉到 summary),tool/text/panel 组逐个渲染。
|
|
986
|
+
*
|
|
987
|
+
* prepared 的键是组对象引用:传入的组数组必须保持元素引用(slice 可以,重建
|
|
988
|
+
* 数组会让 prepared.get 永远 miss,退化为二次截断)。面板组描述经
|
|
989
|
+
* describePanelGroup 与估算共享同一份模板。
|
|
990
|
+
* 「已省略 N 个早期步骤」提示与空态兜底 `_暂无输出_` 由调用方各自负责。
|
|
991
|
+
*/
|
|
992
|
+
function renderContentGroups(state, prepared, visible) {
|
|
1014
993
|
const elements = [];
|
|
1015
994
|
const outputMax = toolOutputBudget(state);
|
|
1016
995
|
const contentItems = mergeNoticesIntoContent(visible, state.notices);
|
|
@@ -1027,7 +1006,7 @@ function buildAnswerProgressContent(state, prepared, visible) {
|
|
|
1027
1006
|
if (group.kind === 'text') {
|
|
1028
1007
|
const content = prepared.get(group) ?? '';
|
|
1029
1008
|
const ts = formatTimestamp(group.timestamp);
|
|
1030
|
-
elements.push(...renderTextBlock(content, ts
|
|
1009
|
+
elements.push(...renderTextBlock(content, ts));
|
|
1031
1010
|
continue;
|
|
1032
1011
|
}
|
|
1033
1012
|
const spec = describePanelGroup(group, prepared);
|
|
@@ -1058,46 +1037,12 @@ function withinRunCardBudget(card) {
|
|
|
1058
1037
|
}
|
|
1059
1038
|
/** Build content in chronological order using groupBlocks to interleave thinking/text/tools. */
|
|
1060
1039
|
function buildChronologicalContent(state, prepared, groups) {
|
|
1061
|
-
const elements = [];
|
|
1062
|
-
// finalized = 非 running(含 finalizing:主结果已出,thinking 折叠)
|
|
1063
|
-
const finalized = state.terminal !== 'running';
|
|
1064
1040
|
// 节点预算内从最新往回选择可见组:超限时只省略最旧的几步,不整卡降级。
|
|
1065
1041
|
const { visible, omitted } = selectVisibleGroups(groups);
|
|
1066
|
-
const
|
|
1042
|
+
const elements = [];
|
|
1067
1043
|
if (omitted > 0)
|
|
1068
1044
|
elements.push(markdownDiv(`_💡 已省略 ${omitted} 个早期步骤_`));
|
|
1069
|
-
|
|
1070
|
-
// 引用,重建数组会让 prepared.get 永远 miss,退化为二次截断)。
|
|
1071
|
-
// 面板组描述经 describePanelGroup 与估算共享同一份模板。
|
|
1072
|
-
// notices 作为普通消息按到达时间插入内容流(有内容时不再沉到 summary)。
|
|
1073
|
-
const contentItems = mergeNoticesIntoContent(visible, state.notices);
|
|
1074
|
-
for (const item of contentItems) {
|
|
1075
|
-
if (item.kind === 'notice') {
|
|
1076
|
-
elements.push(renderNoticeRow(item.notice));
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
const group = item;
|
|
1080
|
-
if (group.kind === 'tool') {
|
|
1081
|
-
elements.push(renderTool(group.tool, outputMax));
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1084
|
-
if (group.kind === 'text') {
|
|
1085
|
-
const content = prepared.get(group) ?? '';
|
|
1086
|
-
const ts = formatTimestamp(group.timestamp);
|
|
1087
|
-
elements.push(...renderTextBlock(content, ts, finalized));
|
|
1088
|
-
continue;
|
|
1089
|
-
}
|
|
1090
|
-
const spec = describePanelGroup(group, prepared);
|
|
1091
|
-
if (!spec)
|
|
1092
|
-
continue;
|
|
1093
|
-
elements.push(collapsibleMarkdownPanel({
|
|
1094
|
-
title: spec.title,
|
|
1095
|
-
expanded: spec.expanded,
|
|
1096
|
-
border: spec.border,
|
|
1097
|
-
content: spec.content,
|
|
1098
|
-
textSize: 'notation',
|
|
1099
|
-
}));
|
|
1100
|
-
}
|
|
1045
|
+
elements.push(...renderContentGroups(state, prepared, visible));
|
|
1101
1046
|
if (elements.length === 0) {
|
|
1102
1047
|
elements.push(markdownDiv('_暂无输出_'));
|
|
1103
1048
|
}
|
|
@@ -1154,8 +1099,8 @@ function buildSummaryContent(state, options = {}) {
|
|
|
1154
1099
|
const result = state.resultSubtype ?? 'success';
|
|
1155
1100
|
const hasContent = state.blocks.length > 0 || !!state.finalAnswer?.content.trim();
|
|
1156
1101
|
// 压缩(operationKind='compaction')不会返回 agent 正文——引擎只压缩上下文
|
|
1157
|
-
// 窗口,空内容属正常,不能沿用普通 run
|
|
1158
|
-
//
|
|
1102
|
+
// 窗口,空内容属正常,不能沿用普通 run 的「(未返回内容)」异常信号。
|
|
1103
|
+
// 所有会压缩的 agent(codex/claude/kimi/opencode/pi)
|
|
1159
1104
|
// 都经 streamCompact 走这条共享渲染路径,一处覆盖全部。
|
|
1160
1105
|
const empty = hasContent
|
|
1161
1106
|
? ''
|
|
@@ -1202,11 +1147,26 @@ const BACKGROUND_JOB_INLINE_MAX = 36;
|
|
|
1202
1147
|
const BACKGROUND_JOB_FOLD_DISPLAY_MAX = 180;
|
|
1203
1148
|
const BACKGROUND_JOB_FOLD_LINES = 6;
|
|
1204
1149
|
/**
|
|
1205
|
-
*
|
|
1150
|
+
* 任务状态标签:宿主的两态 + 桥侧观测出的「已结束 / 状态未知」。
|
|
1151
|
+
*
|
|
1152
|
+
* 「已结束」不是可有可无的修饰:卡片上曾经只有「运行中/停止中」,任务跑完之
|
|
1153
|
+
* 后卡片仍然这么写,用户只能靠点一次停止按钮看它报错来判断任务是否还在。
|
|
1206
1154
|
*/
|
|
1207
1155
|
function backgroundJobStatusLabel(status) {
|
|
1156
|
+
if (status === 'ended')
|
|
1157
|
+
return '已结束';
|
|
1158
|
+
if (status === 'unknown')
|
|
1159
|
+
return '状态未知';
|
|
1208
1160
|
return status === 'stopping' ? '停止中' : '运行中';
|
|
1209
1161
|
}
|
|
1162
|
+
/** 行尾状态(live 任务返回空串:运行中本来就是默认状态)。 */
|
|
1163
|
+
function backgroundJobStatusTail(status) {
|
|
1164
|
+
if (status === 'ended')
|
|
1165
|
+
return '✅ 已结束';
|
|
1166
|
+
if (status === 'unknown')
|
|
1167
|
+
return '⚠️ 状态未知';
|
|
1168
|
+
return '';
|
|
1169
|
+
}
|
|
1210
1170
|
/**
|
|
1211
1171
|
* 生成能包住任意反引号串的代码围栏。命令里的 shell 反引号不能替换成 `'`
|
|
1212
1172
|
* (那是改内容),只能选比内容里最长反引号串更长的围栏。
|
|
@@ -1228,7 +1188,7 @@ function codeFenceFor(content) {
|
|
|
1228
1188
|
/**
|
|
1229
1189
|
* 后台任务小节:命令独占一行,停止按钮单独一行。
|
|
1230
1190
|
*
|
|
1231
|
-
*
|
|
1191
|
+
* 布局约束(手机端):
|
|
1232
1192
|
* - 命令 + 按钮不再并排(grow + auto):窄屏下 auto 列会被挤成「🟥…」,
|
|
1233
1193
|
* 命令也被截断。按钮现在独占一个单列 column_set,宽度由按钮自身决定。
|
|
1234
1194
|
* - 命令不 truncate:短命令/单行直接内联;中等长度的多行命令用代码块
|
|
@@ -1244,24 +1204,40 @@ function buildBackgroundJobsSection(state) {
|
|
|
1244
1204
|
const jobs = state.backgroundJobs ?? [];
|
|
1245
1205
|
if (jobs.length === 0)
|
|
1246
1206
|
return [];
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1207
|
+
// 仍在跑的排前面:用户最先要知道的是"哪些还在占资源"。
|
|
1208
|
+
const running = jobs.filter((job) => isLiveBackgroundJob(job));
|
|
1209
|
+
const unknown = jobs.filter((job) => job.status === 'unknown');
|
|
1210
|
+
const ended = jobs.filter((job) => !isLiveBackgroundJob(job) && job.status !== 'unknown');
|
|
1211
|
+
const ordered = [...running, ...unknown, ...ended];
|
|
1212
|
+
// 计数逐维列出:只说「N 个任务」会让"都还在跑"与"都结束了"读起来一样。
|
|
1213
|
+
const counts = [];
|
|
1214
|
+
if (running.length > 0)
|
|
1215
|
+
counts.push(`${running.length} 个仍在运行`);
|
|
1216
|
+
if (unknown.length > 0)
|
|
1217
|
+
counts.push(`${unknown.length} 个状态未知`);
|
|
1218
|
+
if (ended.length > 0)
|
|
1219
|
+
counts.push(`${ended.length} 个已结束`);
|
|
1220
|
+
const headline = ended.length === jobs.length ? '已全部结束' : counts.join(' · ');
|
|
1221
|
+
const elements = [markdownDiv(`🧵 **后台任务** ${headline}`)];
|
|
1222
|
+
if (running.length + unknown.length > 0) {
|
|
1223
|
+
elements.push(markdownDiv('_本轮结束后继续,可逐个停止_', 'notation'));
|
|
1224
|
+
}
|
|
1225
|
+
ordered.slice(0, MAX_BACKGROUND_JOB_LINES).forEach((job, index) => {
|
|
1252
1226
|
const label = job.label.trim();
|
|
1227
|
+
const tail = backgroundJobStatusTail(job.status);
|
|
1253
1228
|
const lineCount = label === '' ? 0 : label.split(/\r?\n/).length;
|
|
1254
1229
|
const inline = label.length > 0 &&
|
|
1255
1230
|
lineCount === 1 &&
|
|
1256
1231
|
!label.includes('`') &&
|
|
1257
1232
|
displayWidth(label) <= BACKGROUND_JOB_INLINE_MAX;
|
|
1258
1233
|
if (inline) {
|
|
1259
|
-
elements.push(markdownDiv(`· \`${label}
|
|
1234
|
+
elements.push(markdownDiv(`· \`${label}\`${tail ? ` · ${tail}` : ''}`));
|
|
1260
1235
|
}
|
|
1261
1236
|
else {
|
|
1262
1237
|
// 多行/超宽/含反引号:代码块保留完整命令;只有超长才折叠。
|
|
1238
|
+
// 命令原文一个字都不改,状态出现在围栏之后。
|
|
1263
1239
|
const fence = codeFenceFor(label);
|
|
1264
|
-
const content = `${fence}\n${label}\n${fence}`;
|
|
1240
|
+
const content = `${fence}\n${label}\n${fence}${tail ? `\n\n${tail}` : ''}`;
|
|
1265
1241
|
const fold = lineCount > BACKGROUND_JOB_FOLD_LINES ||
|
|
1266
1242
|
displayWidth(label) > BACKGROUND_JOB_FOLD_DISPLAY_MAX;
|
|
1267
1243
|
elements.push(fold
|
|
@@ -1274,6 +1250,9 @@ function buildBackgroundJobsSection(state) {
|
|
|
1274
1250
|
})
|
|
1275
1251
|
: markdownDiv(content));
|
|
1276
1252
|
}
|
|
1253
|
+
// 已结束的任务不再给停止入口——那个按钮点下去只会回「可能已自行结束」。
|
|
1254
|
+
if (job.status === 'ended')
|
|
1255
|
+
return;
|
|
1277
1256
|
elements.push({
|
|
1278
1257
|
tag: 'column_set',
|
|
1279
1258
|
columns: [
|
|
@@ -1292,10 +1271,6 @@ function buildBackgroundJobsSection(state) {
|
|
|
1292
1271
|
}
|
|
1293
1272
|
return elements;
|
|
1294
1273
|
}
|
|
1295
|
-
/** Status tag label */
|
|
1296
|
-
function statusTagLabel(terminal) {
|
|
1297
|
-
return terminalToLabel(terminal);
|
|
1298
|
-
}
|
|
1299
1274
|
/** CardKit 2.0 header title — agent-aware via options.agentKind. */
|
|
1300
1275
|
function headerTitle(state, options = {}) {
|
|
1301
1276
|
const agent = agentDisplayName(options.agentKind ?? 'claude');
|
|
@@ -1325,7 +1300,7 @@ function headerTitle(state, options = {}) {
|
|
|
1325
1300
|
if (state.terminal === 'finalizing')
|
|
1326
1301
|
return `⏳ ${agent} · 完成中`;
|
|
1327
1302
|
// 审批等待期间 server 暂停 turn,命令工具停在 tool_running——标题必须提示
|
|
1328
|
-
//
|
|
1303
|
+
// 正在等人工决策,而不是「调用工具」。
|
|
1329
1304
|
if (hasPendingApproval(state))
|
|
1330
1305
|
return `✋ ${agent} · 等待审批`;
|
|
1331
1306
|
if (state.footer === 'thinking')
|
package/dist/card/run-state.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentEvent, ApprovalView, ToolHint, GoalView } from '../runner/index.js';
|
|
1
|
+
import type { AgentEvent, BackgroundJobRoster, ApprovalView, ToolHint, GoalView } from '../runner/index.js';
|
|
2
2
|
import type { UsageSnapshot } from '../session-contract.js';
|
|
3
3
|
export type RunTerminal = 'running' | 'finalizing' | 'done' | 'error' | 'interrupted' | 'idle_timeout'
|
|
4
4
|
/**
|
|
@@ -154,7 +154,7 @@ export interface RunState extends UsageSnapshot {
|
|
|
154
154
|
* 审批是否曾过期(桥侧 timer 触发,approval_expired 事件置位)。
|
|
155
155
|
* 独立于 approvals 数组持久保存:cancel 送达后 server 会回
|
|
156
156
|
* approval_resolved,把对应审批条目从数组移除,但过期原因必须活到
|
|
157
|
-
*
|
|
157
|
+
* 终态渲染(否则标记被消费后卡片误报「已被用户终止」)。
|
|
158
158
|
*/
|
|
159
159
|
approvalExpired?: boolean;
|
|
160
160
|
/**
|
|
@@ -172,13 +172,11 @@ export interface RunState extends UsageSnapshot {
|
|
|
172
172
|
/**
|
|
173
173
|
* run 结束时仍在后台运行的任务。跨 turn 生命周期:卡片必须说清楚"这一轮
|
|
174
174
|
* 结束了,但还有东西在跑",并给出逐个停止的入口。
|
|
175
|
+
*
|
|
176
|
+
* 收尾之后这份名单仍会被 `syncBackgroundJobs` 更新(宿主会继续推送任务
|
|
177
|
+
* 生命周期),所以它是"最后一次观测到的名单",不是"收尾那一刻的名单"。
|
|
175
178
|
*/
|
|
176
|
-
backgroundJobs?:
|
|
177
|
-
id: string;
|
|
178
|
-
label: string;
|
|
179
|
-
status?: string;
|
|
180
|
-
progress?: string;
|
|
181
|
-
}>;
|
|
179
|
+
backgroundJobs?: BackgroundJobRoster[];
|
|
182
180
|
/**
|
|
183
181
|
* 会话级长期目标(DSH goal)。跨 turn 生命周期:goal 仍 active 时,本轮结束
|
|
184
182
|
* 只是"一轮结束",宿主会自动续跑,卡片必须显示目标是什么、跑到第几轮。
|
|
@@ -208,5 +206,34 @@ export declare function reduceRunState(state: RunState, event: AgentEvent): RunS
|
|
|
208
206
|
* 写成「整体重建」,字段一多就会漏带。
|
|
209
207
|
*/
|
|
210
208
|
export declare function removeBackgroundJob(state: RunState, jobId: string): RunState;
|
|
209
|
+
/**
|
|
210
|
+
* 该任务是否仍在跑(`ended` / `unknown` 都不算)。
|
|
211
|
+
*
|
|
212
|
+
* 卡片据此决定要不要渲染「⏹ 停止」:给已经结束的任务留一个点了必然失败的
|
|
213
|
+
* 按钮,等于逼用户拿「停止」当查询用。
|
|
214
|
+
*/
|
|
215
|
+
export declare function isLiveBackgroundJob(job: BackgroundJobRoster): boolean;
|
|
216
|
+
/**
|
|
217
|
+
* 收尾后把宿主推来的整份任务名单同步进终态卡状态。
|
|
218
|
+
*
|
|
219
|
+
* 与 reducer 的分工:reducer 处理的是"收尾那一刻的名单",只在 run 还在消费
|
|
220
|
+
* 事件时有效;终态之后 `reduceRunState` 直接早退(终态幂等),名单只能由
|
|
221
|
+
* 这里更新。
|
|
222
|
+
*
|
|
223
|
+
* 三条规则:
|
|
224
|
+
* - 名单里消失 = 宿主已结算(`job/list` 是整份替换,摘掉即结束)→ `ended`;
|
|
225
|
+
* - 名单里还在、宿主却给了终态词 → `ended`(有些宿主会把已结束的行留在名单里);
|
|
226
|
+
* - `ended` 是终态,不回退成 running —— 同一个 job id 不会重新开始跑。
|
|
227
|
+
*
|
|
228
|
+
* 无变化时返回**同一个 state 引用**:bridge 靠它跳过无谓的卡片 patch。
|
|
229
|
+
*/
|
|
230
|
+
export declare function syncBackgroundJobs(state: RunState, roster: ReadonlyArray<BackgroundJobRoster>): RunState;
|
|
231
|
+
/**
|
|
232
|
+
* 订阅断了但任务仍未结算:把在跑的任务标成「状态未知」。
|
|
233
|
+
*
|
|
234
|
+
* 监控中断不等于任务结束——不标出来,卡片又会退化成"最后一次观测当现状",
|
|
235
|
+
* 正是这次要修的那个缺陷。未知态仍保留停止按钮(用户还能尝试停掉)。
|
|
236
|
+
*/
|
|
237
|
+
export declare function markBackgroundJobsUnknown(state: RunState): RunState;
|
|
211
238
|
export declare function finishRun(state: RunState, terminal: RunFinalTerminal, meta?: FinishMeta): RunState;
|
|
212
239
|
export {};
|