lark-remote 0.5.5 → 0.5.7
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 +48 -7
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/bridge/approval-coordinator.d.ts +13 -0
- package/dist/bridge/approval-coordinator.js +34 -1
- package/dist/bridge/index.d.ts +48 -1
- package/dist/bridge/index.js +86 -14
- package/dist/card/approval-render.js +23 -1
- package/dist/card/card-budget.d.ts +37 -0
- package/dist/card/card-budget.js +128 -5
- package/dist/card/run-renderer.d.ts +24 -0
- package/dist/card/run-renderer.js +256 -60
- package/dist/card/run-state.d.ts +26 -0
- package/dist/card/run-state.js +45 -0
- 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 +40 -66
- package/dist/common/text-width.d.ts +24 -0
- package/dist/common/text-width.js +93 -0
- package/dist/common/tool-summary.d.ts +11 -0
- package/dist/common/tool-summary.js +153 -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 +63 -8
- 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 +65 -4
- package/dist/router/index.d.ts +9 -0
- package/dist/router/index.js +57 -7
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +3 -3
- 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 +124 -6
- package/dist/runner/dsh/client.js +398 -12
- 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 +117 -7
- package/dist/runner/dsh/runner.js +607 -21
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/dsh/translator.js +103 -1
- package/dist/runner/dsh/types.d.ts +91 -1
- package/dist/runner/types.d.ts +86 -2
- package/dist/session/claude/sessions.js +2 -2
- package/dist/session/dsh/sessions.js +100 -15
- package/dist/update/install.js +1 -1
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* completed → success; aborted/interrupted → interrupted;
|
|
13
13
|
* blocked/error/max-tokens → error (errorMessage carries the reason)
|
|
14
14
|
*
|
|
15
|
-
* Token accounting (ccusage
|
|
15
|
+
* Token accounting (ccusage 口径): DSH emits ONE usage per model
|
|
16
16
|
* call (per step) — a tool-looping turn has several `assistant/message` events.
|
|
17
17
|
* The result event therefore carries **the sum of the turn's samplings**, not the
|
|
18
18
|
* last one (codex `token_count` has the same shape). `lastUsage` is kept only as
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* completed → success; aborted/interrupted → interrupted;
|
|
13
13
|
* blocked/error/max-tokens → error (errorMessage carries the reason)
|
|
14
14
|
*
|
|
15
|
-
* Token accounting (ccusage
|
|
15
|
+
* Token accounting (ccusage 口径): DSH emits ONE usage per model
|
|
16
16
|
* call (per step) — a tool-looping turn has several `assistant/message` events.
|
|
17
17
|
* The result event therefore carries **the sum of the turn's samplings**, not the
|
|
18
18
|
* last one (codex `token_count` has the same shape). `lastUsage` is kept only as
|
|
@@ -118,6 +118,48 @@ export class DshTranslator {
|
|
|
118
118
|
}
|
|
119
119
|
return [];
|
|
120
120
|
}
|
|
121
|
+
case 'tool/call': {
|
|
122
|
+
// DSH 把「模型发起调用」落成独立事件(name/arguments 是模型原始输出)。
|
|
123
|
+
// 这是卡片工具行的唯一来源:assistant/message 里只有完成后的正文块,
|
|
124
|
+
// 不含调用轨迹。
|
|
125
|
+
const call = ev.data;
|
|
126
|
+
const id = typeof call.callId === 'string' ? call.callId : '';
|
|
127
|
+
const name = typeof call.name === 'string' ? call.name : '';
|
|
128
|
+
if (!id || !name)
|
|
129
|
+
return [];
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
type: 'assistant',
|
|
133
|
+
message: {
|
|
134
|
+
content: [{ type: 'tool_use', id, name, input: parseToolArguments(call.arguments) }],
|
|
135
|
+
},
|
|
136
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
case 'tool/result': {
|
|
141
|
+
const data = ev.data;
|
|
142
|
+
const msg = data.message;
|
|
143
|
+
const toolCallId = typeof msg?.toolCallId === 'string' ? msg.toolCallId : '';
|
|
144
|
+
if (!toolCallId)
|
|
145
|
+
return [];
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
type: 'user',
|
|
149
|
+
message: {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: 'tool_result',
|
|
153
|
+
tool_use_id: toolCallId,
|
|
154
|
+
content: toolResultText(msg?.content, data.error),
|
|
155
|
+
is_error: msg?.isError === true,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
}
|
|
121
163
|
case 'turn/end': {
|
|
122
164
|
const turnReason = ev.data.reason;
|
|
123
165
|
const reason = turnReason?.kind;
|
|
@@ -153,6 +195,66 @@ export class DshTranslator {
|
|
|
153
195
|
}
|
|
154
196
|
}
|
|
155
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Parse a `tool/call.arguments` payload (a JSON string on the wire) into the
|
|
200
|
+
* object shape the card renderer consumes. Non-JSON arguments (a truncated or
|
|
201
|
+
* provider-mangled payload) are kept verbatim so the header can still show
|
|
202
|
+
* something instead of silently rendering an empty summary.
|
|
203
|
+
*/
|
|
204
|
+
function parseToolArguments(raw) {
|
|
205
|
+
if (typeof raw !== 'string')
|
|
206
|
+
return raw ?? {};
|
|
207
|
+
const trimmed = raw.trim();
|
|
208
|
+
if (!trimmed)
|
|
209
|
+
return {};
|
|
210
|
+
try {
|
|
211
|
+
return JSON.parse(trimmed);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return trimmed;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Flatten a `tool/result.message.content` block list into display text.
|
|
219
|
+
*
|
|
220
|
+
* DSH content is a block array (`{type:'text',text}` / `{type:'image',...}`),
|
|
221
|
+
* the same shape the LLM sees. Images collapse to a marker: the card cannot
|
|
222
|
+
* render them and the raw base64 would blow the byte budget.
|
|
223
|
+
*/
|
|
224
|
+
function toolResultText(content, error) {
|
|
225
|
+
const err = error;
|
|
226
|
+
const errorHint = typeof err?.message === 'string' ? err.message : '';
|
|
227
|
+
if (content == null)
|
|
228
|
+
return errorHint;
|
|
229
|
+
if (typeof content === 'string')
|
|
230
|
+
return content;
|
|
231
|
+
if (Array.isArray(content)) {
|
|
232
|
+
const parts = [];
|
|
233
|
+
for (const block of content) {
|
|
234
|
+
if (typeof block === 'string') {
|
|
235
|
+
parts.push(block);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!block || typeof block !== 'object')
|
|
239
|
+
continue;
|
|
240
|
+
const rec = block;
|
|
241
|
+
if (typeof rec.text === 'string') {
|
|
242
|
+
parts.push(rec.text);
|
|
243
|
+
}
|
|
244
|
+
else if (rec.type === 'image') {
|
|
245
|
+
parts.push('[image]');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const text = parts.join('\n');
|
|
249
|
+
return text || errorHint;
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
return JSON.stringify(content);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return String(content);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
156
258
|
/** Map DSH TokenUsage → ResultEvent.usage (cacheWrite = cache creation). */
|
|
157
259
|
export function mapUsage(u) {
|
|
158
260
|
return mapAggregatedUsage({
|
|
@@ -60,6 +60,27 @@ export interface DshSessionSummary {
|
|
|
60
60
|
export interface DshSessionListValue {
|
|
61
61
|
items: DshSessionSummary[];
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* One pending inbox occurrence from the `inbox` projection.
|
|
65
|
+
*
|
|
66
|
+
* DSH 在 prompt 被受理但还没被 turn 取走时,把它记录成 `inbox.next-turn[]`
|
|
67
|
+
* 的一项;`source.rpcId` 回显 `session/prompt` 的 requestId,因此调用方可以
|
|
68
|
+
* 精确找到“我这条还没开始的消息”。`session/updateQueue` 用 `id` 删除它。
|
|
69
|
+
*/
|
|
70
|
+
export interface DshInboxItem {
|
|
71
|
+
id: string;
|
|
72
|
+
source?: {
|
|
73
|
+
kind?: string;
|
|
74
|
+
rpcId?: string;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** `session/projections` → value(只声明本仓库用到的部分)。 */
|
|
78
|
+
export interface DshProjectionSnapshot {
|
|
79
|
+
asOfSeq?: number;
|
|
80
|
+
values: Record<string, unknown>;
|
|
81
|
+
}
|
|
82
|
+
/** Whether the host currently has a turn open for a session. */
|
|
83
|
+
export type DshOpenTurnState = 'open' | 'closed' | 'unknown';
|
|
63
84
|
/** session/page → value.records[]. */
|
|
64
85
|
export interface DshPageRecord {
|
|
65
86
|
event: DshSessionEvent;
|
|
@@ -218,7 +239,32 @@ export type DshRemoteEventOutcome = {
|
|
|
218
239
|
details?: unknown;
|
|
219
240
|
};
|
|
220
241
|
};
|
|
221
|
-
/**
|
|
242
|
+
/** One option of a DSH `ask_user_question` prompt. */
|
|
243
|
+
export interface DshQuestionOption {
|
|
244
|
+
label: string;
|
|
245
|
+
description?: string;
|
|
246
|
+
}
|
|
247
|
+
/** One question of a DSH `ask_user_question` prompt (`user-questions/request`). */
|
|
248
|
+
export interface DshQuestion {
|
|
249
|
+
id: string;
|
|
250
|
+
question: string;
|
|
251
|
+
/**
|
|
252
|
+
* Supporting detail kept out of the question text. `intent.kind:'plan-review'`
|
|
253
|
+
* questions carry the plan markdown here, so it must reach the card.
|
|
254
|
+
*/
|
|
255
|
+
detail?: string;
|
|
256
|
+
header?: string;
|
|
257
|
+
options?: DshQuestionOption[];
|
|
258
|
+
multiSelect?: boolean;
|
|
259
|
+
}
|
|
260
|
+
/** One answer item of the `$events/result` payload for a question waterfall. */
|
|
261
|
+
export interface DshQuestionAnswer {
|
|
262
|
+
id: string;
|
|
263
|
+
selected: string[];
|
|
264
|
+
/** Free-text answer when the user picked none of the offered labels. */
|
|
265
|
+
custom?: string;
|
|
266
|
+
}
|
|
267
|
+
/** Approval/question items yielded while consuming `$events`. */
|
|
222
268
|
export type DshApprovalStreamItem = {
|
|
223
269
|
kind: 'ready';
|
|
224
270
|
clientId: string;
|
|
@@ -229,6 +275,11 @@ export type DshApprovalStreamItem = {
|
|
|
229
275
|
toolName: string;
|
|
230
276
|
callId?: string;
|
|
231
277
|
reason?: string;
|
|
278
|
+
} | {
|
|
279
|
+
kind: 'question';
|
|
280
|
+
eventId: string;
|
|
281
|
+
clientId: string;
|
|
282
|
+
questions: DshQuestion[];
|
|
232
283
|
} | {
|
|
233
284
|
kind: 'cancelled';
|
|
234
285
|
eventId: string;
|
|
@@ -279,3 +330,42 @@ export type DshStreamItem = {
|
|
|
279
330
|
callId?: string;
|
|
280
331
|
reason?: string;
|
|
281
332
|
};
|
|
333
|
+
/**
|
|
334
|
+
* One finished projection value from the Host-wide `session/control` stream.
|
|
335
|
+
*
|
|
336
|
+
* DSH 的 `session/control` 是唯一的"服务端变一次推一次"通道:连接先收到一份
|
|
337
|
+
* baseline(覆盖当前所有活跃 session 的全部投影值),之后每个投影变化推一帧
|
|
338
|
+
* `{type:'projection', sessionId, key, value, seq}`。调用方按 sessionId 过滤。
|
|
339
|
+
*/
|
|
340
|
+
export interface DshProjectionItem {
|
|
341
|
+
kind: 'projection';
|
|
342
|
+
sessionId: string;
|
|
343
|
+
key: string;
|
|
344
|
+
value: unknown;
|
|
345
|
+
}
|
|
346
|
+
/** `contextPressure` 投影(只声明本仓库消费的字段)。 */
|
|
347
|
+
export interface DshContextPressure {
|
|
348
|
+
/** 上一次采样实际占用的 token。 */
|
|
349
|
+
pressureTokens?: number;
|
|
350
|
+
/** 下一次请求的预估占用(官方 UI 优先用它算百分比)。 */
|
|
351
|
+
projectedTokens?: number;
|
|
352
|
+
/** 当前模型上下文窗口。 */
|
|
353
|
+
contextWindow?: number;
|
|
354
|
+
}
|
|
355
|
+
/** `sessionStats` 投影(只声明本仓库消费的字段)。 */
|
|
356
|
+
export interface DshSessionStats {
|
|
357
|
+
turns?: number;
|
|
358
|
+
steps?: number;
|
|
359
|
+
toolMs?: number;
|
|
360
|
+
}
|
|
361
|
+
/** One background job row from the `job/list` stream (fields lark-remote consumes). */
|
|
362
|
+
export interface DshJobView {
|
|
363
|
+
id: string;
|
|
364
|
+
kind?: string;
|
|
365
|
+
label?: string;
|
|
366
|
+
status?: 'running' | 'stopping' | 'completed' | 'killed' | 'failed';
|
|
367
|
+
progress?: string;
|
|
368
|
+
detail?: string;
|
|
369
|
+
startedAt?: number;
|
|
370
|
+
finishedAt?: number;
|
|
371
|
+
}
|
package/dist/runner/types.d.ts
CHANGED
|
@@ -124,6 +124,11 @@ export interface UserQuestion {
|
|
|
124
124
|
header?: string;
|
|
125
125
|
multiSelect?: boolean;
|
|
126
126
|
options: UserQuestionOption[];
|
|
127
|
+
/**
|
|
128
|
+
* 题面外的补充正文(DSH `AskUserQuestionItem.detail`):plan-review 提问用它
|
|
129
|
+
* 承载计划全文。渲染层折叠展示并截断,不能与 header / question 混写。
|
|
130
|
+
*/
|
|
131
|
+
detail?: string;
|
|
127
132
|
/** 是否允许自定义答案(Codex isOther 预留;当前单选渲染层恒提供 Other 输入)。 */
|
|
128
133
|
isOther?: boolean;
|
|
129
134
|
/** 敏感输入(Codex isSecret 预留)。 */
|
|
@@ -159,6 +164,11 @@ export interface ApprovalView {
|
|
|
159
164
|
* 概要行用于展示完整题干。
|
|
160
165
|
*/
|
|
161
166
|
intro?: string;
|
|
167
|
+
/**
|
|
168
|
+
* 提问超时后是否还能在别处回答的提示(数据驱动,避免共享渲染层写死某个
|
|
169
|
+
* agent 的语义:DSH 提问超时后 Web UI 仍可回答,Claude/Codex 不能)。
|
|
170
|
+
*/
|
|
171
|
+
expiredHint?: string;
|
|
162
172
|
command?: string;
|
|
163
173
|
commandCwd?: string;
|
|
164
174
|
/**
|
|
@@ -315,6 +325,45 @@ export interface SessionInfoEvent {
|
|
|
315
325
|
mode?: string;
|
|
316
326
|
timestamp?: string;
|
|
317
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* 运行期状态投影(上下文水位 / 会话累计统计)。
|
|
330
|
+
*
|
|
331
|
+
* 不是内容块:卡片把它渲染在状态行(statusRow),不进内容流,因此不参与
|
|
332
|
+
* blocks 分组与 MAX_BLOCKS 裁剪。字段缺省表示该维度本次未变。
|
|
333
|
+
*
|
|
334
|
+
* 数据源是 agent 服务端自己算好的投影(DSH `session/control` 的
|
|
335
|
+
* contextPressure / sessionStats),bridge 不重算口径——百分比只在两端都
|
|
336
|
+
* 有值时才渲染。
|
|
337
|
+
*/
|
|
338
|
+
export interface RunStatusEvent {
|
|
339
|
+
type: 'run_status';
|
|
340
|
+
/** 已投影 token(官方口径 projectedTokens ?? pressureTokens)。 */
|
|
341
|
+
contextUsed?: number;
|
|
342
|
+
/** 上下文窗口上限(contextWindow)。 */
|
|
343
|
+
contextLimit?: number;
|
|
344
|
+
/** 会话累计步骤数(sessionStats.steps)。 */
|
|
345
|
+
steps?: number;
|
|
346
|
+
/** 会话累计工具耗时(毫秒,sessionStats.toolMs)。 */
|
|
347
|
+
toolMs?: number;
|
|
348
|
+
timestamp?: string;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* 运行结束后仍在后台运行的任务(终态卡展示 + 逐个停止入口)。
|
|
352
|
+
*
|
|
353
|
+
* 为什么必须出现在 run 卡片上:后台任务的生命周期跨 turn(`bash
|
|
354
|
+
* run_in_background` 起的东西能活过 run 结束),卡片收尾时如果不说,用户就
|
|
355
|
+
* 以为这一轮真的结束了——机器上却还在跑。
|
|
356
|
+
*/
|
|
357
|
+
export interface BackgroundJobsEvent {
|
|
358
|
+
type: 'background_jobs';
|
|
359
|
+
jobs: Array<{
|
|
360
|
+
id: string;
|
|
361
|
+
label: string;
|
|
362
|
+
status?: string;
|
|
363
|
+
progress?: string;
|
|
364
|
+
}>;
|
|
365
|
+
timestamp?: string;
|
|
366
|
+
}
|
|
318
367
|
/** File change event — for agents that report file modifications (e.g. Codex). */
|
|
319
368
|
export interface FileChangeEvent {
|
|
320
369
|
type: 'file_change';
|
|
@@ -382,7 +431,7 @@ export interface TurnDiffEvent {
|
|
|
382
431
|
export type RunnerLifetime = 'turn' | 'workspace';
|
|
383
432
|
/** 工具渲染 hint:runner 自愿声明,渲染层只消费不推断。 */
|
|
384
433
|
export type ToolHint = 'shell' | 'file' | 'search' | 'web' | 'patch';
|
|
385
|
-
export type AgentEvent = SystemInitEvent | SystemCompactEvent | ApprovalRequestedEvent | ApprovalResolvedEvent | ApprovalViewUpdatedEvent | ApprovalExpiredEvent | AssistantEvent | UserEvent | ResultEvent | PlanEvent | FileChangeEvent | TurnStartedEvent | TurnDiffEvent | NoticeEvent | SessionInfoEvent;
|
|
434
|
+
export type AgentEvent = SystemInitEvent | SystemCompactEvent | ApprovalRequestedEvent | ApprovalResolvedEvent | ApprovalViewUpdatedEvent | ApprovalExpiredEvent | AssistantEvent | UserEvent | ResultEvent | PlanEvent | FileChangeEvent | TurnStartedEvent | TurnDiffEvent | NoticeEvent | SessionInfoEvent | RunStatusEvent | BackgroundJobsEvent;
|
|
386
435
|
export interface SpawnOptions {
|
|
387
436
|
cwd: string;
|
|
388
437
|
sessionId?: string;
|
|
@@ -392,6 +441,29 @@ export interface SpawnOptions {
|
|
|
392
441
|
reasoningEffort?: string;
|
|
393
442
|
settings?: string;
|
|
394
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* What a stop request actually achieved.
|
|
446
|
+
*
|
|
447
|
+
* Local-process runners (`claude`/`codex`/…) can answer this from the kill
|
|
448
|
+
* itself and keep returning `void`. DSH turns live in the shared Web Host, so
|
|
449
|
+
* its stop is a session-scoped request: the outcome is the only way callers can
|
|
450
|
+
* tell "the turn is gone" apart from "we asked, but it is still running".
|
|
451
|
+
*
|
|
452
|
+
* - `withdrawn` — the prompt had not started yet and was removed from the
|
|
453
|
+
* host's inbox; no turn was cancelled.
|
|
454
|
+
* - `cancelled` — the active turn (or maintenance job) was cancelled and the
|
|
455
|
+
* host reported the session idle.
|
|
456
|
+
* - `unconfirmed` — the cancel was rejected/lost, or the session still reports
|
|
457
|
+
* an open turn afterwards. A turn may still be running.
|
|
458
|
+
* - `noop` — there was no session to stop (e.g. stop during setup).
|
|
459
|
+
*/
|
|
460
|
+
export type StopStatus = 'withdrawn' | 'cancelled' | 'unconfirmed' | 'noop';
|
|
461
|
+
export interface StopOutcome {
|
|
462
|
+
status: StopStatus;
|
|
463
|
+
sessionId?: string;
|
|
464
|
+
/** Human-readable reason for `unconfirmed` (logged, not shown verbatim). */
|
|
465
|
+
detail?: string;
|
|
466
|
+
}
|
|
395
467
|
/**
|
|
396
468
|
* Seam (§3): the contract any agent runner (claude/codex/opencode/pi/kimi/dsh)
|
|
397
469
|
* must satisfy for the router/bridge.
|
|
@@ -407,10 +479,14 @@ export interface Runner {
|
|
|
407
479
|
* Stop the running process. SIGTERM → grace → SIGKILL on the per-workspace
|
|
408
480
|
* proc. `immediate: true` skips the grace period and SIGKILLs directly
|
|
409
481
|
* (used by user-initiated `/stop`).
|
|
482
|
+
*
|
|
483
|
+
* Runners whose work is remote (DSH) return a {@link StopOutcome} instead of
|
|
484
|
+
* `void` so callers can tell a confirmed stop from a best-effort request;
|
|
485
|
+
* process runners keep returning `void`.
|
|
410
486
|
*/
|
|
411
487
|
stop(opts?: {
|
|
412
488
|
immediate?: boolean;
|
|
413
|
-
}): Promise<void>;
|
|
489
|
+
}): Promise<void | StopOutcome>;
|
|
414
490
|
killOrphan(): void;
|
|
415
491
|
registerExitHandlers(): void;
|
|
416
492
|
/**
|
|
@@ -457,7 +533,15 @@ export interface AgentSession {
|
|
|
457
533
|
/** A single renderable event from session history. */
|
|
458
534
|
export interface AgentSessionContentEvent {
|
|
459
535
|
type: string;
|
|
536
|
+
/** 正文(展开后的面板内容)。 */
|
|
460
537
|
content: string;
|
|
538
|
+
/**
|
|
539
|
+
* 面板标题(可选,覆盖 reader 的默认标签)。
|
|
540
|
+
*
|
|
541
|
+
* 折叠面板默认只显示标题:工具调用这种"标题即全部信息"的事件必须把
|
|
542
|
+
* `bash — npm test` 这类摘要放进标题,否则折叠态只剩 `🔧 工具调用`。
|
|
543
|
+
*/
|
|
544
|
+
title?: string;
|
|
461
545
|
timestamp?: string;
|
|
462
546
|
}
|
|
463
547
|
export interface AgentSessionUsage {
|
|
@@ -15,8 +15,8 @@ import { SessionIndex, extractText, isTaskNotificationText, CLAUDE_MAPPING, } fr
|
|
|
15
15
|
* win32 上 `\` 同样是分隔符、`:` 是非法文件名字符,一并归一为 `-`
|
|
16
16
|
* (`C:\Users\x\proj` -> `C--Users-x-proj`);见 encodeProjectDirName。
|
|
17
17
|
*
|
|
18
|
-
* Note: this encoding is LOSSY (`
|
|
19
|
-
* `
|
|
18
|
+
* Note: this encoding is LOSSY (`my_data/foo` and `my-data/foo` both encode to
|
|
19
|
+
* `my-data-foo`). It is only safe to use the result to **locate** files; never
|
|
20
20
|
* to **decode** a directory name back to a cwd. The cwd must always be read
|
|
21
21
|
* from the JSONL content (regression 2026-06-21 /resume & /cd 路径错乱).
|
|
22
22
|
*/
|
|
@@ -24,6 +24,7 @@ import { dshArgs, extractDshToken } from '../../runner/dsh/client.js';
|
|
|
24
24
|
import { DSH_COOKIE_LIFETIME_MS, isDshCredentialsNotFound, mintDshBrowserCookie, readDshBrowserSecretSync, resolveDshCredentialsFile, } from '../../runner/dsh/auth.js';
|
|
25
25
|
import { capEvents, paginate } from '../common/pagination.js';
|
|
26
26
|
import { truncateUtf8, TOOL_RESULT_MAX_BYTES } from '../../common/truncate.js';
|
|
27
|
+
import { summarizeToolInput } from '../../common/tool-summary.js';
|
|
27
28
|
/**
|
|
28
29
|
* history 拉取上限。DSH session/page 的 maxMessages 同时限制返回条数,
|
|
29
30
|
* 若用它同时充当展示截断,>50 事件的会话累计 usage 会少算。这里拉取一个足够大的
|
|
@@ -218,6 +219,8 @@ export class DshSessionReader {
|
|
|
218
219
|
// null = 历史里没有 turn 标记(旧数据/截断)→ 退回末条 usage 语义。
|
|
219
220
|
let turnUsage = null;
|
|
220
221
|
let lastTurnUsage = null;
|
|
222
|
+
/** 等结果的工具调用(见 MappedEvent.pendingTool)。 */
|
|
223
|
+
let pendingTool;
|
|
221
224
|
for (const entry of value.records ?? []) {
|
|
222
225
|
const ev = entry.event;
|
|
223
226
|
if (!ev)
|
|
@@ -231,7 +234,30 @@ export class DshSessionReader {
|
|
|
231
234
|
turnUsage = null;
|
|
232
235
|
}
|
|
233
236
|
const contentEvents = mapEvent(ev);
|
|
234
|
-
|
|
237
|
+
if (contentEvents.pendingTool) {
|
|
238
|
+
// 未完成的工具调用最多同时一个(DSH 按 step 串行执行;并行调用时按
|
|
239
|
+
// 到达顺序配对,最坏情况是两次调用共用一个面板,不会丢信息)。
|
|
240
|
+
if (pendingTool)
|
|
241
|
+
events.push(pendingTool);
|
|
242
|
+
pendingTool = contentEvents.pendingTool;
|
|
243
|
+
}
|
|
244
|
+
else if (ev.type === 'tool/result' && pendingTool && contentEvents.events[0]) {
|
|
245
|
+
const result = contentEvents.events[0];
|
|
246
|
+
// 标题沿用调用的 `🔧 name — summary`,失败改成 ❌(折叠态一眼看出结果)。
|
|
247
|
+
const failed = result.title?.startsWith('🔴') === true;
|
|
248
|
+
events.push({
|
|
249
|
+
type: 'tool_use',
|
|
250
|
+
title: failed
|
|
251
|
+
? (pendingTool.title ?? '❌ 工具').replace('🔧', '❌')
|
|
252
|
+
: pendingTool.title,
|
|
253
|
+
content: result.content,
|
|
254
|
+
timestamp: pendingTool.timestamp,
|
|
255
|
+
});
|
|
256
|
+
pendingTool = undefined;
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
events.push(...contentEvents.events);
|
|
260
|
+
}
|
|
235
261
|
if (contentEvents.title)
|
|
236
262
|
displayTitle = contentEvents.title;
|
|
237
263
|
const u = contentEvents.usage;
|
|
@@ -278,6 +304,9 @@ export class DshSessionReader {
|
|
|
278
304
|
}),
|
|
279
305
|
};
|
|
280
306
|
}
|
|
307
|
+
// 中断/仍在执行的调用没有结果:把它当独立事件留下,别让这一条消失。
|
|
308
|
+
if (pendingTool)
|
|
309
|
+
events.push(pendingTool);
|
|
281
310
|
return {
|
|
282
311
|
// 展示仍按 maxEvents 尾截断(默认 50,保持原行为),usage 已在上面基于
|
|
283
312
|
// 完整 history 累加。
|
|
@@ -327,6 +356,25 @@ function sessionPreset(item) {
|
|
|
327
356
|
function emptyUsageTotals() {
|
|
328
357
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
329
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* `user/message.source.kind` 里属于**纯上下文注入**的种类:没有回顾价值,且
|
|
361
|
+
* 会挤掉真正的对话(5 条事件的窗口里被它们占满就什么都看不到了)。
|
|
362
|
+
*/
|
|
363
|
+
const CONTEXT_INJECTION_KINDS = new Set([
|
|
364
|
+
'time-context',
|
|
365
|
+
'agent-instructions',
|
|
366
|
+
'runtime-context',
|
|
367
|
+
'skill-catalog',
|
|
368
|
+
'compact-checkpoint',
|
|
369
|
+
]);
|
|
370
|
+
/** 系统通知(后台任务完成、目标变更)在回顾卡里的正文上限。 */
|
|
371
|
+
const NOTICE_MAX_BYTES = 400;
|
|
372
|
+
/** 单条思考在 resume 卡里的上限:过程信息,留头几百字即可。 */
|
|
373
|
+
const REASONING_MAX_BYTES = 800;
|
|
374
|
+
/** 单条 assistant 正文上限(最终答复通常最重要,给足空间)。 */
|
|
375
|
+
const TEXT_MAX_BYTES = 2_000;
|
|
376
|
+
/** 工具原始参数上限(标题已有摘要,正文只用于看完整命令)。 */
|
|
377
|
+
const TOOL_INPUT_MAX_BYTES = 600;
|
|
330
378
|
/** Map one DSH SessionEvent to display content events (+ title/usage side effects). */
|
|
331
379
|
function mapEvent(ev) {
|
|
332
380
|
const out = { events: [] };
|
|
@@ -337,42 +385,79 @@ function mapEvent(ev) {
|
|
|
337
385
|
.filter((c) => c.type === 'text')
|
|
338
386
|
.map((c) => c.text ?? '')
|
|
339
387
|
.join('');
|
|
340
|
-
|
|
388
|
+
if (!text.trim())
|
|
389
|
+
break;
|
|
390
|
+
// DSH 把上下文注入也写成 user/message,靠 `source.kind` 区分。真实会话里
|
|
391
|
+
// 30 条 user/message 中只有 8 条是用户本人说的(其余是 time-context /
|
|
392
|
+
// agent-instructions / skill-catalog / goal / tool-jobs…)——照单全收会让
|
|
393
|
+
// 回顾卡里塞满"Time sampled while preparing turn…",还把系统提示标成 👤 你。
|
|
394
|
+
const source = ev.data.source;
|
|
395
|
+
const kind = typeof source?.kind === 'string' ? source.kind : undefined;
|
|
396
|
+
if (kind && CONTEXT_INJECTION_KINDS.has(kind))
|
|
397
|
+
break;
|
|
398
|
+
const isNotice = kind !== undefined && kind !== 'user';
|
|
399
|
+
out.events.push({
|
|
400
|
+
type: isNotice ? 'notice' : 'user',
|
|
401
|
+
...(isNotice ? { title: '🔔 通知' } : {}),
|
|
402
|
+
content: isNotice ? truncateUtf8(text.trim(), NOTICE_MAX_BYTES) : text,
|
|
403
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
404
|
+
});
|
|
341
405
|
break;
|
|
342
406
|
}
|
|
343
407
|
case 'assistant/message': {
|
|
344
408
|
const content = ev.data.message
|
|
345
409
|
?.content ?? [];
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
410
|
+
const timestamp = new Date(ev.time).toISOString();
|
|
411
|
+
// DSH 的块类型是 text / reasoning / tool-call(不是 Anthropic 的
|
|
412
|
+
// thinking)。只认 text 会让 resume 卡"只有工具调用、没有思考与输出"
|
|
413
|
+
// (2026-10-19 反馈:一个 31 turn 的会话里 reasoning 块 371 个、text 12 个,
|
|
414
|
+
// 全被丢掉)。tool-call 块不在这里取——独立的 `tool/call` 事件带完整参数。
|
|
415
|
+
for (const block of content) {
|
|
416
|
+
const text = typeof block.text === 'string' ? block.text.trim() : '';
|
|
417
|
+
if (!text)
|
|
418
|
+
continue;
|
|
419
|
+
if (block.type === 'text') {
|
|
420
|
+
out.events.push({
|
|
421
|
+
type: 'assistant',
|
|
422
|
+
content: truncateUtf8(text, TEXT_MAX_BYTES),
|
|
423
|
+
timestamp,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
else if (block.type === 'reasoning') {
|
|
427
|
+
out.events.push({
|
|
428
|
+
type: 'thinking',
|
|
429
|
+
// 思考是过程信息:resume 卡里留头几百字足够判断"当时在想什么"。
|
|
430
|
+
content: truncateUtf8(text, REASONING_MAX_BYTES),
|
|
431
|
+
timestamp,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
}
|
|
356
435
|
const usage = ev.data.usage;
|
|
357
436
|
if (usage && typeof usage === 'object')
|
|
358
437
|
out.usage = usage;
|
|
359
438
|
break;
|
|
360
439
|
}
|
|
361
440
|
case 'tool/call': {
|
|
441
|
+
// 调用本身先不产出事件:DSH 的 tool/call 与 tool/result 是一对,resume 卡
|
|
442
|
+
// 里合成一个面板(`🔧 bash — cmd` + 输出),而不是两个各占一屏。
|
|
362
443
|
const name = String(ev.data.name ?? '');
|
|
363
444
|
const args = typeof ev.data.arguments === 'string' ? ev.data.arguments : '';
|
|
364
|
-
|
|
445
|
+
const summary = summarizeToolInput(name, args);
|
|
446
|
+
out.pendingTool = {
|
|
365
447
|
type: 'tool_use',
|
|
366
|
-
|
|
448
|
+
title: summary ? `🔧 ${name} — ${summary}` : `🔧 ${name}`,
|
|
449
|
+
content: truncateUtf8(args, TOOL_INPUT_MAX_BYTES),
|
|
367
450
|
timestamp: new Date(ev.time).toISOString(),
|
|
368
|
-
}
|
|
451
|
+
};
|
|
369
452
|
break;
|
|
370
453
|
}
|
|
371
454
|
case 'tool/result': {
|
|
372
455
|
const msg = ev.data.message;
|
|
373
456
|
const raw = stringifyContent(msg?.content);
|
|
457
|
+
const isError = msg?.isError === true;
|
|
374
458
|
out.events.push({
|
|
375
459
|
type: 'tool_result',
|
|
460
|
+
title: isError ? '🔴 结果(失败)' : '🟢 结果',
|
|
376
461
|
content: truncateUtf8(raw, TOOL_RESULT_MAX_BYTES),
|
|
377
462
|
timestamp: new Date(ev.time).toISOString(),
|
|
378
463
|
});
|
package/dist/update/install.js
CHANGED
|
@@ -102,7 +102,7 @@ export function detectPackageManager(scriptPath) {
|
|
|
102
102
|
return inferred;
|
|
103
103
|
}
|
|
104
104
|
// 3. availability fallback: 纯 Node PATH 解析(win32 PATHEXT),不再依赖外部 which
|
|
105
|
-
// (Windows 无 which);win32 再用 where.exe
|
|
105
|
+
// (Windows 无 which);win32 再用 where.exe 兜底罕见的解析失败场景
|
|
106
106
|
const candidates = ['npm', 'bun', 'pnpm'];
|
|
107
107
|
for (const cmd of candidates) {
|
|
108
108
|
if (resolveExecutable(cmd))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-remote",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|