lark-remote 0.5.20 → 0.5.21
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 +10 -0
- package/dist/bridge/inbound-media.js +71 -45
- package/dist/bridge/index.d.ts +9 -0
- package/dist/bridge/index.js +230 -203
- package/dist/card/approval-render.js +32 -33
- package/dist/card/card-budget.js +123 -98
- package/dist/card/run-renderer.js +82 -68
- package/dist/card/run-state.d.ts +0 -14
- package/dist/card/run-state.js +335 -207
- package/dist/card/tool-render.js +20 -16
- package/dist/card/usage-format.d.ts +12 -9
- package/dist/card/usage-format.js +111 -97
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/index.js +226 -190
- package/dist/message-handlers.js +253 -236
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +189 -133
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/index.js +192 -201
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +287 -221
- package/dist/runner/common/acp/base-translator.js +19 -17
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/spawning-runner.d.ts +5 -0
- package/dist/runner/common/spawning-runner.js +8 -3
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/client.d.ts +7 -9
- package/dist/runner/dsh/client.js +356 -287
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +4 -3
- package/dist/runner/dsh/runner.js +88 -64
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/kimi/acp/runner.js +81 -44
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/session/claude/sessions.js +113 -101
- package/dist/session/codex/rollout-reader.js +179 -180
- package/dist/session/dsh/sessions.js +281 -231
- package/dist/session/kimi/sessions.d.ts +0 -7
- package/dist/session/kimi/sessions.js +129 -109
- package/dist/session/opencode/sessions.d.ts +0 -11
- package/dist/session/opencode/sessions.js +161 -137
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.js +98 -98
- package/package.json +1 -1
|
@@ -10,7 +10,56 @@
|
|
|
10
10
|
* `result` from that response.
|
|
11
11
|
* - `agent_settled` is ignored for compaction turns (no double result).
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { produceTurnStartedEvent } from '../../common/turn-started.js';
|
|
14
|
+
/** 首见:push 一个空 text/thinking 块并把游标移到它。 */
|
|
15
|
+
function textLikeStart(kind, state, _evt) {
|
|
16
|
+
state.content.push(kind === 'text' ? { type: 'text', text: '' } : { type: 'thinking', thinking: '' });
|
|
17
|
+
state.currentContentIndex++;
|
|
18
|
+
}
|
|
19
|
+
/** 增量:把 delta 追加到当前 text/thinking 块(类型不匹配则忽略)。 */
|
|
20
|
+
function textLikeDelta(kind, state, evt) {
|
|
21
|
+
const idx = state.currentContentIndex;
|
|
22
|
+
const block = state.content[idx];
|
|
23
|
+
if (idx >= 0 && block?.type === kind) {
|
|
24
|
+
block[kind] += evt.delta ?? '';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** 收尾:以全量 content 覆盖当前 text/thinking 块(无 content 则忽略)。 */
|
|
28
|
+
function textLikeEnd(kind, state, evt) {
|
|
29
|
+
const idx = state.currentContentIndex;
|
|
30
|
+
const block = state.content[idx];
|
|
31
|
+
if (idx >= 0 && block?.type === kind && evt.content) {
|
|
32
|
+
block[kind] = evt.content;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function toolCallStart(state, _evt) {
|
|
36
|
+
// 信息保真 不再 push 空壳 tool_use(空 id/name 会被渲染成
|
|
37
|
+
// 无意义工具块)。只保留 bookkeeping,toolcall_end 补发完整事件。
|
|
38
|
+
state.pendingToolCall = true;
|
|
39
|
+
}
|
|
40
|
+
function toolCallEnd(state, evt) {
|
|
41
|
+
if (state.pendingToolCall && evt.toolCall) {
|
|
42
|
+
state.content.push({
|
|
43
|
+
type: 'tool_use',
|
|
44
|
+
id: evt.toolCall.id,
|
|
45
|
+
name: evt.toolCall.name,
|
|
46
|
+
input: evt.toolCall.arguments,
|
|
47
|
+
});
|
|
48
|
+
state.currentContentIndex = state.content.length - 1;
|
|
49
|
+
state.pendingToolCall = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** 查表分派 message_update;toolcall_delta 等无专属 handler 的类型为 no-op。 */
|
|
53
|
+
const PI_UPDATE_HANDLERS = new Map([
|
|
54
|
+
['text_start', textLikeStart.bind(null, 'text')],
|
|
55
|
+
['text_delta', textLikeDelta.bind(null, 'text')],
|
|
56
|
+
['text_end', textLikeEnd.bind(null, 'text')],
|
|
57
|
+
['thinking_start', textLikeStart.bind(null, 'thinking')],
|
|
58
|
+
['thinking_delta', textLikeDelta.bind(null, 'thinking')],
|
|
59
|
+
['thinking_end', textLikeEnd.bind(null, 'thinking')],
|
|
60
|
+
['toolcall_start', toolCallStart],
|
|
61
|
+
['toolcall_end', toolCallEnd],
|
|
62
|
+
]);
|
|
14
63
|
export class PiRpcTranslator {
|
|
15
64
|
content = [];
|
|
16
65
|
currentContentIndex = -1;
|
|
@@ -135,8 +184,9 @@ export class PiRpcTranslator {
|
|
|
135
184
|
}
|
|
136
185
|
/** Self-produced turn_started (no wire equivalent). */
|
|
137
186
|
produceTurnStarted(sessionId, turnId) {
|
|
138
|
-
this.
|
|
139
|
-
|
|
187
|
+
return produceTurnStartedEvent(sessionId, turnId, this.operationKind, (s) => {
|
|
188
|
+
this.sessionId = s;
|
|
189
|
+
});
|
|
140
190
|
}
|
|
141
191
|
/** Result for a completed normal turn (agent_settled). */
|
|
142
192
|
produceResultFromSettled() {
|
|
@@ -236,63 +286,14 @@ export class PiRpcTranslator {
|
|
|
236
286
|
this.lastTotalTokens = u.totalTokens;
|
|
237
287
|
}
|
|
238
288
|
applyUpdate(evt) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
this.content[this.currentContentIndex].text += evt.delta ?? '';
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
case 'text_end':
|
|
251
|
-
if (this.currentContentIndex >= 0 &&
|
|
252
|
-
this.content[this.currentContentIndex]?.type === 'text' &&
|
|
253
|
-
evt.content) {
|
|
254
|
-
this.content[this.currentContentIndex].text = evt.content;
|
|
255
|
-
}
|
|
256
|
-
break;
|
|
257
|
-
case 'thinking_start':
|
|
258
|
-
this.content.push({ type: 'thinking', thinking: '' });
|
|
259
|
-
this.currentContentIndex++;
|
|
260
|
-
break;
|
|
261
|
-
case 'thinking_delta':
|
|
262
|
-
if (this.currentContentIndex >= 0 &&
|
|
263
|
-
this.content[this.currentContentIndex]?.type === 'thinking') {
|
|
264
|
-
this.content[this.currentContentIndex].thinking +=
|
|
265
|
-
evt.delta ?? '';
|
|
266
|
-
}
|
|
267
|
-
break;
|
|
268
|
-
case 'thinking_end':
|
|
269
|
-
if (this.currentContentIndex >= 0 &&
|
|
270
|
-
this.content[this.currentContentIndex]?.type === 'thinking' &&
|
|
271
|
-
evt.content) {
|
|
272
|
-
this.content[this.currentContentIndex].thinking = evt.content;
|
|
273
|
-
}
|
|
274
|
-
break;
|
|
275
|
-
case 'toolcall_start':
|
|
276
|
-
// 信息保真 不再 push 空壳 tool_use(空 id/name 会被渲染成
|
|
277
|
-
// 无意义工具块)。只保留 bookkeeping,toolcall_end 补发完整事件。
|
|
278
|
-
this.pendingToolCall = true;
|
|
279
|
-
break;
|
|
280
|
-
case 'toolcall_end':
|
|
281
|
-
if (this.pendingToolCall && evt.toolCall) {
|
|
282
|
-
this.content.push({
|
|
283
|
-
type: 'tool_use',
|
|
284
|
-
id: evt.toolCall.id,
|
|
285
|
-
name: evt.toolCall.name,
|
|
286
|
-
input: evt.toolCall.arguments,
|
|
287
|
-
});
|
|
288
|
-
this.currentContentIndex = this.content.length - 1;
|
|
289
|
-
this.pendingToolCall = false;
|
|
290
|
-
}
|
|
291
|
-
break;
|
|
292
|
-
default:
|
|
293
|
-
// toolcall_delta: pi provides complete arguments at toolcall_end, ignore.
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
289
|
+
const state = {
|
|
290
|
+
content: this.content,
|
|
291
|
+
currentContentIndex: this.currentContentIndex,
|
|
292
|
+
pendingToolCall: this.pendingToolCall,
|
|
293
|
+
};
|
|
294
|
+
PI_UPDATE_HANDLERS.get(evt.type)?.(state, evt);
|
|
295
|
+
this.currentContentIndex = state.currentContentIndex;
|
|
296
|
+
this.pendingToolCall = state.pendingToolCall;
|
|
296
297
|
}
|
|
297
298
|
}
|
|
298
299
|
/**
|
|
@@ -7,6 +7,7 @@ import { samePath, encodeProjectDirName } from '../../platform/path.js';
|
|
|
7
7
|
import { extractContentBlocks } from '../common/content-blocks.js';
|
|
8
8
|
import { scanJsonlOnce, tailOffsetAfter, scanTailEvents } from '../common/two-pass.js';
|
|
9
9
|
import { getLogger } from '../../logger/index.js';
|
|
10
|
+
import { logNonEnoentDebug } from '../session-store.js';
|
|
10
11
|
import { UsageAccumulator, contextWindowOccupancy, cumulativeUsageFields, } from '../common/usage-accumulator.js';
|
|
11
12
|
import { SessionIndex, extractText, isTaskNotificationText, CLAUDE_MAPPING, } from './session-index.js';
|
|
12
13
|
/**
|
|
@@ -190,9 +191,7 @@ function hasRecentFileActivity(filePath, mtimeMs, now = Date.now()) {
|
|
|
190
191
|
* 否则「文件不可读」会被当成「会话不活跃」。
|
|
191
192
|
*/
|
|
192
193
|
function logStatFailure(filePath, err) {
|
|
193
|
-
|
|
194
|
-
return;
|
|
195
|
-
getLogger().debug(`[claude-session-reader] stat ${filePath} failed: ${err?.message ?? String(err)}`);
|
|
194
|
+
logNonEnoentDebug(err, (m) => `[claude-session-reader] stat ${filePath} failed: ${m}`);
|
|
196
195
|
}
|
|
197
196
|
/**
|
|
198
197
|
* Check if a Claude session (by sessionId + cwd) is still active.
|
|
@@ -353,110 +352,123 @@ export function buildClaudeUsage(records, opts) {
|
|
|
353
352
|
...cumulativeUsageFields(t),
|
|
354
353
|
};
|
|
355
354
|
}
|
|
356
|
-
|
|
357
|
-
* first-pass scan: stream the file once via `scanJsonlLines`
|
|
358
|
-
* (no `string[]` materialized), parse each line exactly once, and collect
|
|
359
|
-
* all metadata needed by readSessionContent (usage, tailOffset, aiTitle,
|
|
360
|
-
* recap, lastUserMessage). Does NOT retain parsed objects — the events tail
|
|
361
|
-
* is re-parsed separately by `extractEventsFromTail` so memory for both raw
|
|
362
|
-
* line strings and parsed objects is O(tail), not O(whole file).
|
|
363
|
-
*
|
|
364
|
-
* 主体是分发表:每类行只收集自己的标量(assistant → usage 并表、
|
|
365
|
-
* compact_boundary → 计数 + postTokens、user → tail 定位/标题、ai-title);
|
|
366
|
-
* usage 的逐字段 max 合并(maxUsageRecord)与装配(buildClaudeUsage)已
|
|
367
|
-
* 独立成纯函数。
|
|
368
|
-
*/
|
|
369
|
-
function scalarScan(filePath) {
|
|
355
|
+
function newScalarScanState() {
|
|
370
356
|
// Per-message-id best usage record (per-field max). A streaming assistant
|
|
371
357
|
// message spans multiple jsonl lines sharing the same message id; some
|
|
372
358
|
// backends (third-party gateways, e.g. DeepSeek) write all-zero usage on
|
|
373
359
|
// the early placeholder lines (empty thinking block) and the real usage
|
|
374
360
|
// only on the final line carrying stop_reason. First-occurrence-wins dedup
|
|
375
|
-
// would pin the zero record and discard the real one (auto-resume
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
361
|
+
// would pin the zero record and discard the real one (auto-resume card
|
|
362
|
+
// per-run stats all 0). Per-field max reconstructs the full usage
|
|
363
|
+
// regardless of line order — on the official API every line repeats
|
|
364
|
+
// identical usage, so max is a no-op there.
|
|
365
|
+
return {
|
|
366
|
+
usageByMessageId: new Map(),
|
|
367
|
+
lastPostTokens: 0,
|
|
368
|
+
compactCount: 0,
|
|
369
|
+
tailOffset: -1,
|
|
370
|
+
turnSeq: 0,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/** assistant 行 → 按 message id 逐字段 max 合并 usage(was aggregateSessionUsage)。 */
|
|
374
|
+
function collectAssistantUsage(state, obj) {
|
|
375
|
+
if (obj.type !== 'assistant')
|
|
376
|
+
return;
|
|
377
|
+
const msg = obj.message;
|
|
378
|
+
const usage = msg?.usage;
|
|
379
|
+
if (!usage)
|
|
380
|
+
return;
|
|
381
|
+
const msgId = msg?.id;
|
|
382
|
+
if (!msgId)
|
|
383
|
+
return;
|
|
384
|
+
state.usageByMessageId.set(msgId, maxUsageRecord(state.usageByMessageId.get(msgId), usage, state.turnSeq));
|
|
385
|
+
}
|
|
386
|
+
/** compact_boundary → 计数 + 压缩后 token 水位。 */
|
|
387
|
+
function collectCompactBoundary(state, obj) {
|
|
388
|
+
if (obj.type !== 'system' || obj.subtype !== 'compact_boundary')
|
|
389
|
+
return;
|
|
390
|
+
state.compactCount++;
|
|
391
|
+
const cm = obj.compactMetadata;
|
|
392
|
+
if (cm?.postTokens) {
|
|
393
|
+
state.lastPostTokens = cm.postTokens;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/** 记录 tail 起点:本 user 行之后的下一行字节偏移(见 tailOffsetAfter)。 */
|
|
397
|
+
function collectTailOffset(state, obj, line, offset) {
|
|
398
|
+
if (obj.type !== 'user')
|
|
399
|
+
return;
|
|
400
|
+
const msg = obj.message;
|
|
401
|
+
if (msg?.role === 'user') {
|
|
402
|
+
state.tailOffset = tailOffsetAfter(line, offset);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/** ai-title:最后一条生效。 */
|
|
406
|
+
function collectAiTitle(state, obj) {
|
|
407
|
+
if (obj.type === 'ai-title' && typeof obj.aiTitle === 'string') {
|
|
408
|
+
state.lastAiTitle = obj.aiTitle;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** recap:最后一条 isCompactSummary 生效。 */
|
|
412
|
+
function collectRecap(state, obj) {
|
|
413
|
+
if (obj.type !== 'user' || obj.isCompactSummary !== true)
|
|
414
|
+
return;
|
|
415
|
+
const msg = obj.message;
|
|
416
|
+
if (!msg?.content)
|
|
417
|
+
return;
|
|
418
|
+
const recapContent = extractText(msg.content);
|
|
419
|
+
if (recapContent) {
|
|
420
|
+
state.lastRecap = recapContent;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** displayTitle 用的最近真人输入:跳过 tool_result / recap / isMeta / 任务通知。 */
|
|
424
|
+
function collectLastUserMessage(state, obj) {
|
|
425
|
+
if (obj.type !== 'user' || obj.isCompactSummary || obj.isMeta)
|
|
426
|
+
return;
|
|
427
|
+
const msg = obj.message;
|
|
428
|
+
if (msg?.role !== 'user' || !msg.content)
|
|
429
|
+
return;
|
|
430
|
+
const content = msg.content;
|
|
431
|
+
const isToolResult = Array.isArray(content) &&
|
|
432
|
+
content.length > 0 &&
|
|
433
|
+
content[0]?.type === 'tool_result';
|
|
434
|
+
if (isToolResult)
|
|
435
|
+
return;
|
|
436
|
+
const userContent = extractText(content);
|
|
437
|
+
if (userContent && !isTaskNotificationText(userContent)) {
|
|
438
|
+
state.lastUserMessage = userContent;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** 真人 prompt 开启新 turn;注入/工具结果/sidechain 不递增。 */
|
|
442
|
+
function countRealUserPrompt(state, obj) {
|
|
443
|
+
if (isRealUserPrompt(obj))
|
|
444
|
+
state.turnSeq++;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 单行分派:收集器调用顺序与原回调逐字一致(尤其 turnSeq 在最后递增,
|
|
448
|
+
* 保证 assistant usage 记录归属的是「本行之前」的 turn)。
|
|
449
|
+
*/
|
|
450
|
+
function scanScalarLine(state, obj, line, offset) {
|
|
451
|
+
collectAssistantUsage(state, obj);
|
|
452
|
+
collectCompactBoundary(state, obj);
|
|
453
|
+
collectTailOffset(state, obj, line, offset);
|
|
454
|
+
collectAiTitle(state, obj);
|
|
455
|
+
collectRecap(state, obj);
|
|
456
|
+
collectLastUserMessage(state, obj);
|
|
457
|
+
countRealUserPrompt(state, obj);
|
|
458
|
+
}
|
|
459
|
+
function scalarScan(filePath) {
|
|
460
|
+
const state = newScalarScanState();
|
|
461
|
+
scanJsonlOnce(filePath, scanScalarLine.bind(null, state));
|
|
454
462
|
return {
|
|
455
|
-
tailOffset,
|
|
456
|
-
usage: buildClaudeUsage(usageByMessageId.values(), {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
463
|
+
tailOffset: state.tailOffset,
|
|
464
|
+
usage: buildClaudeUsage(state.usageByMessageId.values(), {
|
|
465
|
+
turnSeq: state.turnSeq,
|
|
466
|
+
lastPostTokens: state.lastPostTokens,
|
|
467
|
+
compactCount: state.compactCount,
|
|
468
|
+
}),
|
|
469
|
+
aiTitle: state.lastAiTitle,
|
|
470
|
+
recap: state.lastRecap,
|
|
471
|
+
lastUserMessage: state.lastUserMessage,
|
|
460
472
|
};
|
|
461
473
|
}
|
|
462
474
|
/**
|