lark-remote 0.5.10 → 0.5.13
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 +51 -2
- package/README.en.md +1 -14
- package/README.md +1 -14
- package/dist/binder.js +7 -1
- package/dist/bridge/approval-coordinator.d.ts +1 -0
- package/dist/bridge/approval-coordinator.js +12 -24
- package/dist/bridge/index.d.ts +7 -0
- package/dist/bridge/index.js +89 -230
- package/dist/bridge/run-approval.d.ts +15 -0
- package/dist/bridge/run-approval.js +41 -0
- package/dist/bridge/run-usage.d.ts +46 -0
- package/dist/bridge/run-usage.js +96 -0
- package/dist/bridge/session-writeback.d.ts +18 -0
- package/dist/bridge/session-writeback.js +36 -0
- package/dist/card/approval-render.d.ts +14 -0
- package/dist/card/approval-render.js +155 -29
- package/dist/card/bash-card-session.js +10 -5
- package/dist/card/bash-renderer.js +11 -23
- package/dist/card/card-budget.js +24 -4
- package/dist/card/card-session.d.ts +2 -0
- package/dist/card/card-session.js +20 -22
- package/dist/card/card-shared.d.ts +4 -4
- package/dist/card/card-shared.js +3 -9
- package/dist/card/collapsible.js +3 -2
- package/dist/card/final-answer-overflow.d.ts +7 -0
- package/dist/card/final-answer-overflow.js +46 -0
- package/dist/card/run-card-session.js +2 -7
- package/dist/card/run-renderer.d.ts +14 -0
- package/dist/card/run-renderer.js +150 -10
- package/dist/card/run-state.d.ts +12 -1
- package/dist/card/run-state.js +73 -34
- package/dist/common/text-width.js +5 -0
- package/dist/common/tool-summary.js +4 -2
- package/dist/config/codex-config.js +5 -3
- package/dist/config/index.d.ts +4 -2
- package/dist/config/index.js +19 -5
- package/dist/inbound/turn-assembler.js +6 -0
- package/dist/index.js +15 -391
- package/dist/instance-lock.d.ts +9 -1
- package/dist/instance-lock.js +42 -7
- package/dist/message-handlers.d.ts +26 -0
- package/dist/message-handlers.js +275 -0
- package/dist/platform/identity.js +4 -3
- package/dist/platform/path.js +3 -1
- package/dist/platform/terminator-win32.d.ts +13 -1
- package/dist/platform/terminator-win32.js +4 -14
- package/dist/router/card-action-payload.d.ts +1 -1
- package/dist/router/card-action-payload.js +1 -1
- package/dist/router/commands/ls.d.ts +9 -0
- package/dist/router/commands/ls.js +243 -0
- package/dist/router/commands/order.d.ts +12 -0
- package/dist/router/commands/order.js +178 -0
- package/dist/router/commands/resume.d.ts +47 -0
- package/dist/router/commands/resume.js +246 -0
- package/dist/router/commands/ws.d.ts +15 -0
- package/dist/router/commands/ws.js +189 -0
- package/dist/router/config/claude.js +3 -3
- package/dist/router/config/codex.js +2 -6
- package/dist/router/config/common/turn-idle.d.ts +2 -0
- package/dist/router/config/common/turn-idle.js +9 -0
- package/dist/router/config/kimi.js +2 -6
- package/dist/router/config/opencode.js +2 -6
- package/dist/router/index.d.ts +14 -22
- package/dist/router/index.js +93 -1133
- package/dist/router/paging.d.ts +13 -0
- package/dist/router/paging.js +20 -0
- package/dist/router/path-validation.d.ts +27 -0
- package/dist/router/path-validation.js +45 -0
- package/dist/runner/claude/runner.d.ts +3 -4
- package/dist/runner/claude/runner.js +4 -5
- package/dist/runner/claude/session.js +1 -1
- package/dist/runner/codex/app-server/runner.js +12 -0
- package/dist/runner/codex/app-server/translator.d.ts +2 -0
- package/dist/runner/codex/app-server/translator.js +65 -9
- package/dist/runner/common/acp/base-translator.d.ts +15 -8
- package/dist/runner/common/acp/base-translator.js +38 -7
- package/dist/runner/common/jsonl-stream.js +12 -1
- package/dist/runner/common/jsonrpc/connection-manager.js +11 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +2 -1
- package/dist/runner/common/jsonrpc/transport.js +7 -1
- package/dist/runner/common/spawning-runner.js +10 -4
- package/dist/runner/common/turn-started.d.ts +2 -0
- package/dist/runner/common/turn-started.js +10 -0
- package/dist/runner/dsh/approval.js +16 -3
- package/dist/runner/dsh/auth.js +2 -3
- package/dist/runner/dsh/client.d.ts +32 -7
- package/dist/runner/dsh/client.js +115 -149
- package/dist/runner/dsh/daemon.d.ts +0 -2
- package/dist/runner/dsh/daemon.js +1 -6
- package/dist/runner/dsh/errors.d.ts +5 -0
- package/dist/runner/dsh/errors.js +11 -0
- package/dist/runner/dsh/exit-cancel.d.ts +0 -2
- package/dist/runner/dsh/exit-cancel.js +63 -24
- package/dist/runner/dsh/home.d.ts +2 -0
- package/dist/runner/dsh/home.js +8 -0
- package/dist/runner/dsh/inflight.d.ts +0 -2
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.d.ts +26 -0
- package/dist/runner/dsh/mux-stream.js +157 -0
- package/dist/runner/dsh/runner.d.ts +7 -0
- package/dist/runner/dsh/runner.js +42 -5
- package/dist/runner/dsh/translator.d.ts +0 -10
- package/dist/runner/dsh/translator.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.js +42 -5
- package/dist/runner/kimi/acp/translator.d.ts +8 -0
- package/dist/runner/kimi/acp/translator.js +11 -12
- package/dist/runner/opencode/acp/translator.js +1 -12
- package/dist/runner/pi/rpc/runner.js +33 -5
- package/dist/runner/pi/rpc/translator.d.ts +2 -8
- package/dist/runner/pi/rpc/translator.js +2 -7
- package/dist/runner/registry.d.ts +0 -19
- package/dist/runner/registry.js +0 -32
- package/dist/runner/resolve-agent-choices.js +5 -1
- package/dist/runner/sync-agent-choices.js +4 -1
- package/dist/runner/types.d.ts +17 -1
- package/dist/session/claude/session-index.js +13 -4
- package/dist/session/codex/rollout-reader.js +51 -26
- package/dist/session/common/usage-accumulator.d.ts +10 -1
- package/dist/session/common/usage-accumulator.js +10 -2
- package/dist/session/dsh/sessions.js +29 -9
- package/dist/session/kimi/sessions.js +11 -7
- package/dist/session/opencode/sessions.js +15 -7
- package/dist/session/pi/sessions.js +3 -2
- package/dist/session/session-store.js +4 -3
- package/package.json +1 -1
package/dist/runner/types.d.ts
CHANGED
|
@@ -263,6 +263,22 @@ export interface AssistantEvent {
|
|
|
263
263
|
content: AssistantContent[];
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Authoritative final answer from an agent protocol.
|
|
268
|
+
*
|
|
269
|
+
* This is deliberately separate from ordinary assistant text: progress
|
|
270
|
+
* narration and tool-loop messages have lower display priority than the answer
|
|
271
|
+
* the user actually asked for. Agents without an explicit protocol marker
|
|
272
|
+
* promote their last text block when the turn finishes.
|
|
273
|
+
*/
|
|
274
|
+
export interface FinalAnswerEvent {
|
|
275
|
+
type: 'final_answer';
|
|
276
|
+
text: string;
|
|
277
|
+
/** Source item id, used to remove the streamed progress copy of the answer. */
|
|
278
|
+
itemId?: string;
|
|
279
|
+
complete?: boolean;
|
|
280
|
+
timestamp?: string;
|
|
281
|
+
}
|
|
266
282
|
export interface UserEvent {
|
|
267
283
|
type: 'user';
|
|
268
284
|
timestamp?: string;
|
|
@@ -470,7 +486,7 @@ export interface TurnDiffEvent {
|
|
|
470
486
|
export type RunnerLifetime = 'turn' | 'workspace';
|
|
471
487
|
/** 工具渲染 hint:runner 自愿声明,渲染层只消费不推断。 */
|
|
472
488
|
export type ToolHint = 'shell' | 'file' | 'search' | 'web' | 'patch';
|
|
473
|
-
export type AgentEvent = SystemInitEvent | SystemCompactEvent | ApprovalRequestedEvent | ApprovalResolvedEvent | ApprovalViewUpdatedEvent | ApprovalExpiredEvent | AssistantEvent | UserEvent | ResultEvent | PlanEvent | FileChangeEvent | TurnStartedEvent | TurnDiffEvent | NoticeEvent | SessionInfoEvent | RunStatusEvent | BackgroundJobsEvent | GoalStateEvent;
|
|
489
|
+
export type AgentEvent = SystemInitEvent | SystemCompactEvent | ApprovalRequestedEvent | ApprovalResolvedEvent | ApprovalViewUpdatedEvent | ApprovalExpiredEvent | AssistantEvent | FinalAnswerEvent | UserEvent | ResultEvent | PlanEvent | FileChangeEvent | TurnStartedEvent | TurnDiffEvent | NoticeEvent | SessionInfoEvent | RunStatusEvent | BackgroundJobsEvent | GoalStateEvent;
|
|
474
490
|
export interface SpawnOptions {
|
|
475
491
|
cwd: string;
|
|
476
492
|
sessionId?: string;
|
|
@@ -62,8 +62,15 @@ export function parseSessionJsonl(filePath) {
|
|
|
62
62
|
if (typeof obj.cwd === 'string' && obj.cwd) {
|
|
63
63
|
cwdSet.add(obj.cwd);
|
|
64
64
|
}
|
|
65
|
-
// Find first real user message for summary (same logic as summarizeSession)
|
|
66
|
-
|
|
65
|
+
// Find first real user message for summary (same logic as summarizeSession).
|
|
66
|
+
// 过滤谓词与 scalarScan 的 lastUserMessage 同源:isMeta(/clear 等命令回显
|
|
67
|
+
// 与项目规则注入)和 isCompactSummary(压缩摘要)不是用户说的话,当列表
|
|
68
|
+
// 标题会误导(codex 修过同型缺陷:注入文本被当成「最近输入」)。
|
|
69
|
+
if (!foundSummary &&
|
|
70
|
+
obj.type === 'user' &&
|
|
71
|
+
!obj.isCompactSummary &&
|
|
72
|
+
!obj.isMeta &&
|
|
73
|
+
!!obj.message) {
|
|
67
74
|
const msg = obj.message;
|
|
68
75
|
if (msg.content) {
|
|
69
76
|
const text = extractText(msg.content);
|
|
@@ -270,14 +277,16 @@ export class SessionIndex {
|
|
|
270
277
|
if (!st.isDirectory())
|
|
271
278
|
continue;
|
|
272
279
|
}
|
|
273
|
-
catch {
|
|
280
|
+
catch (err) {
|
|
281
|
+
getLogger().debug(`[session-index] skipped unreadable project dir ${subdir}: ${err?.message ?? String(err)}`);
|
|
274
282
|
continue;
|
|
275
283
|
}
|
|
276
284
|
let files;
|
|
277
285
|
try {
|
|
278
286
|
files = fs.readdirSync(subdir).filter((f) => f.endsWith('.jsonl'));
|
|
279
287
|
}
|
|
280
|
-
catch {
|
|
288
|
+
catch (err) {
|
|
289
|
+
getLogger().debug(`[session-index] skipped unreadable project subdir ${subdir}: ${err?.message ?? String(err)}`);
|
|
281
290
|
continue;
|
|
282
291
|
}
|
|
283
292
|
for (const f of files) {
|
|
@@ -455,6 +455,14 @@ export function readCodexSessionContent(sessionId, opts = {}) {
|
|
|
455
455
|
if (!entry) {
|
|
456
456
|
return { events: [] };
|
|
457
457
|
}
|
|
458
|
+
// subagent rollout 不得当父会话历史返回:子线程 rollout 的
|
|
459
|
+
// session_meta.session_id 是父 id(§9.27),索引冲突解决在主文件缺失时会让
|
|
460
|
+
// 子文件占据该 id 的槽位,threadId 检查因等于父 id 而天然通过——list/active
|
|
461
|
+
// 都已过滤(isSubagent),按 id 读取必须同口径,否则「列表没有、active 报
|
|
462
|
+
// false、读内容却成功」三处语义互相矛盾。
|
|
463
|
+
if (entry.isSubagent) {
|
|
464
|
+
return { events: [] };
|
|
465
|
+
}
|
|
458
466
|
// Cwd guard: when a cwd is provided, the session's working directory must
|
|
459
467
|
// match. Without this, /resume <id> finds sessions by global ID lookup and
|
|
460
468
|
// allows resuming a session from cwd B while the user is in cwd A.
|
|
@@ -495,6 +503,10 @@ export function readCodexSessionSummary(sessionId, opts = {}) {
|
|
|
495
503
|
if (!entry) {
|
|
496
504
|
return {};
|
|
497
505
|
}
|
|
506
|
+
// subagent rollout 同样不得当父会话摘要(与 readCodexSessionContent 同口径)。
|
|
507
|
+
if (entry.isSubagent) {
|
|
508
|
+
return {};
|
|
509
|
+
}
|
|
498
510
|
// Cwd guard: same semantics as readCodexSessionContent — when a cwd is
|
|
499
511
|
// provided, the session's working directory must match.
|
|
500
512
|
if (opts.cwd && !samePath(entry.cwd, opts.cwd)) {
|
|
@@ -560,42 +572,55 @@ function walkRolloutFiles(sessionsDir) {
|
|
|
560
572
|
if (!fs.existsSync(sessionsDir))
|
|
561
573
|
return [];
|
|
562
574
|
const result = [];
|
|
563
|
-
try
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
575
|
+
// per-entry try/catch:目录在 readdir 与 stat 之间被删(并发归档/清理)时,
|
|
576
|
+
// 旧实现整个 walk 提前终止并返回部分结果——索引漏掉后续文件,会话从
|
|
577
|
+
// /resume 列表与 total 消失直到 5s TTL 重建。单条目失败只跳过自己。
|
|
578
|
+
const years = safeReaddir(sessionsDir);
|
|
579
|
+
for (const year of years) {
|
|
580
|
+
if (!/^\d{4}$/.test(year))
|
|
581
|
+
continue;
|
|
582
|
+
const yearPath = path.join(sessionsDir, year);
|
|
583
|
+
if (!safeIsDir(yearPath))
|
|
584
|
+
continue;
|
|
585
|
+
for (const month of safeReaddir(yearPath)) {
|
|
586
|
+
if (!/^\d{2}$/.test(month))
|
|
567
587
|
continue;
|
|
568
|
-
const
|
|
569
|
-
if (!
|
|
588
|
+
const monthPath = path.join(yearPath, month);
|
|
589
|
+
if (!safeIsDir(monthPath))
|
|
570
590
|
continue;
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
if (!/^\d{2}$/.test(month))
|
|
591
|
+
for (const day of safeReaddir(monthPath)) {
|
|
592
|
+
if (!/^\d{2}$/.test(day))
|
|
574
593
|
continue;
|
|
575
|
-
const
|
|
576
|
-
if (!
|
|
594
|
+
const dayPath = path.join(monthPath, day);
|
|
595
|
+
if (!safeIsDir(dayPath))
|
|
577
596
|
continue;
|
|
578
|
-
const
|
|
579
|
-
for (const
|
|
580
|
-
|
|
581
|
-
continue;
|
|
582
|
-
const dayPath = path.join(monthPath, day);
|
|
583
|
-
if (!fs.statSync(dayPath).isDirectory())
|
|
584
|
-
continue;
|
|
585
|
-
const files = fs
|
|
586
|
-
.readdirSync(dayPath)
|
|
587
|
-
.filter((f) => f.startsWith('rollout-') && f.endsWith('.jsonl'));
|
|
588
|
-
for (const file of files) {
|
|
589
|
-
result.push(path.join(dayPath, file));
|
|
590
|
-
}
|
|
597
|
+
const files = safeReaddir(dayPath).filter((f) => f.startsWith('rollout-') && f.endsWith('.jsonl'));
|
|
598
|
+
for (const file of files) {
|
|
599
|
+
result.push(path.join(dayPath, file));
|
|
591
600
|
}
|
|
592
601
|
}
|
|
593
602
|
}
|
|
594
603
|
}
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
606
|
+
/** readdir 的竞态安全版:目录消失/不可读返回空并记 debug(外层不再中断)。 */
|
|
607
|
+
function safeReaddir(dir) {
|
|
608
|
+
try {
|
|
609
|
+
return fs.readdirSync(dir);
|
|
610
|
+
}
|
|
595
611
|
catch (err) {
|
|
596
|
-
getLogger().
|
|
612
|
+
getLogger().debug(`[codex-rollout-reader] readdir failed (raced delete?): ${err}`);
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/** statSync 的竞态安全目录判定。 */
|
|
617
|
+
function safeIsDir(p) {
|
|
618
|
+
try {
|
|
619
|
+
return fs.statSync(p).isDirectory();
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
return false;
|
|
597
623
|
}
|
|
598
|
-
return result;
|
|
599
624
|
}
|
|
600
625
|
const INDEX_TTL_MS = 5_000; // 5 seconds — covers a single /resume flow
|
|
601
626
|
const sessionIndexCache = new Map();
|
|
@@ -55,9 +55,18 @@ export declare class UsageAccumulator {
|
|
|
55
55
|
*/
|
|
56
56
|
bumpCompact(): void;
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* 统一契约:上下文窗口占用 = 末轮 input + output + cacheRead + cacheCreation。
|
|
60
|
+
*
|
|
61
|
+
* 口径以 design.md §9.13/§9.21 为准(文档在 6 处钉死该公式):上下文占用衡量
|
|
62
|
+
* 「本轮结束后窗口里有多少 token」,末轮 output 已进入历史、下一个 prompt 会
|
|
63
|
+
* 全量携带;与 max() 另一侧的 compact postTokens(含全部内容的压缩后水位)
|
|
64
|
+
* 语义对齐。此前实现漏加 output(长收尾 turn 可低估数千~3 万 token),且
|
|
65
|
+
* compact 会话与非 compact 会话两口径不可比。
|
|
66
|
+
*/
|
|
59
67
|
export declare function contextWindowOccupancy(l: {
|
|
60
68
|
input: number;
|
|
69
|
+
output: number;
|
|
61
70
|
cacheRead: number;
|
|
62
71
|
cacheCreation: number;
|
|
63
72
|
}): number;
|
|
@@ -69,9 +69,17 @@ export class UsageAccumulator {
|
|
|
69
69
|
this._compactCount++;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* 统一契约:上下文窗口占用 = 末轮 input + output + cacheRead + cacheCreation。
|
|
74
|
+
*
|
|
75
|
+
* 口径以 design.md §9.13/§9.21 为准(文档在 6 处钉死该公式):上下文占用衡量
|
|
76
|
+
* 「本轮结束后窗口里有多少 token」,末轮 output 已进入历史、下一个 prompt 会
|
|
77
|
+
* 全量携带;与 max() 另一侧的 compact postTokens(含全部内容的压缩后水位)
|
|
78
|
+
* 语义对齐。此前实现漏加 output(长收尾 turn 可低估数千~3 万 token),且
|
|
79
|
+
* compact 会话与非 compact 会话两口径不可比。
|
|
80
|
+
*/
|
|
73
81
|
export function contextWindowOccupancy(l) {
|
|
74
|
-
return l.input + l.cacheRead + l.cacheCreation;
|
|
82
|
+
return l.input + l.output + l.cacheRead + l.cacheCreation;
|
|
75
83
|
}
|
|
76
84
|
/** 统一契约:session 全量累计字段组装(4 个 reader 共用:claude/dsh/kimi/pi;
|
|
77
85
|
* codex 走 last_token_usage 口径、opencode 保留自己的条件赋值,原为逐字段复制的 4 连拷贝)。 */
|
|
@@ -285,7 +285,11 @@ export class DshSessionReader {
|
|
|
285
285
|
const input = lastUsage.inputTokens ?? 0;
|
|
286
286
|
const cacheRead = lastUsage.cacheReadTokens ?? 0;
|
|
287
287
|
const cacheWrite = lastUsage.cacheWriteTokens ?? 0;
|
|
288
|
-
|
|
288
|
+
// 在途 turn(turn/start 无 turn/end,run 进行中读历史)的采样优先于
|
|
289
|
+
// 上一轮:旧实现只认已结算的 lastTurnUsage,mid-run 读取会把当前 turn
|
|
290
|
+
// 已消耗的量显示成上一个完整 turn 的数字。
|
|
291
|
+
const flow = lastTurnUsage ??
|
|
292
|
+
turnUsage ?? {
|
|
289
293
|
input,
|
|
290
294
|
output: lastUsage.outputTokens ?? 0,
|
|
291
295
|
cacheRead,
|
|
@@ -294,7 +298,8 @@ export class DshSessionReader {
|
|
|
294
298
|
sessionUsage = {
|
|
295
299
|
inputTokens: flow.input,
|
|
296
300
|
outputTokens: flow.output,
|
|
297
|
-
|
|
301
|
+
// §9.21 口径:含末轮 output(下一 prompt 全量携带,与 postTokens 水位同语义)。
|
|
302
|
+
contextLength: input + flow.output + cacheRead + cacheWrite,
|
|
298
303
|
cacheReadTokens: flow.cacheRead,
|
|
299
304
|
cacheCreationTokens: flow.cacheWrite,
|
|
300
305
|
totalTokens: flow.input + flow.output + flow.cacheRead + flow.cacheWrite,
|
|
@@ -432,11 +437,27 @@ const TEXT_MAX_BYTES = 2_000;
|
|
|
432
437
|
/** 工具原始参数上限(标题已有摘要,正文只用于看完整命令)。 */
|
|
433
438
|
const TOOL_INPUT_MAX_BYTES = 600;
|
|
434
439
|
/** Map one DSH SessionEvent to display content events (+ title/usage side effects). */
|
|
440
|
+
/**
|
|
441
|
+
* 单条畸形事件只跳过自己,不炸整张卡:DSH 服务端格式漂移时(time 非法、
|
|
442
|
+
* content 非数组),旧实现会让异常冒到外层 catch 把整个 resume 卡变成空卡
|
|
443
|
+
* (fail-whole)。时间戳缺失由卡片层兜底;content 非数组按无块处理。
|
|
444
|
+
*/
|
|
445
|
+
function safeEventTimestamp(time) {
|
|
446
|
+
if (typeof time !== 'string' && typeof time !== 'number')
|
|
447
|
+
return undefined;
|
|
448
|
+
const d = new Date(time);
|
|
449
|
+
return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
|
|
450
|
+
}
|
|
451
|
+
function asBlocks(value) {
|
|
452
|
+
if (!Array.isArray(value))
|
|
453
|
+
return [];
|
|
454
|
+
return value.filter((b) => !!b && typeof b === 'object');
|
|
455
|
+
}
|
|
435
456
|
function mapEvent(ev) {
|
|
436
457
|
const out = { events: [] };
|
|
437
458
|
switch (ev.type) {
|
|
438
459
|
case 'user/message': {
|
|
439
|
-
const content = ev.data.content
|
|
460
|
+
const content = asBlocks(ev.data.content);
|
|
440
461
|
const text = content
|
|
441
462
|
.filter((c) => c.type === 'text')
|
|
442
463
|
.map((c) => c.text ?? '')
|
|
@@ -456,14 +477,13 @@ function mapEvent(ev) {
|
|
|
456
477
|
type: isNotice ? 'notice' : 'user',
|
|
457
478
|
...(isNotice ? { title: '🔔 通知' } : {}),
|
|
458
479
|
content: isNotice ? truncateUtf8(text.trim(), NOTICE_MAX_BYTES) : text,
|
|
459
|
-
timestamp:
|
|
480
|
+
timestamp: safeEventTimestamp(ev.time),
|
|
460
481
|
});
|
|
461
482
|
break;
|
|
462
483
|
}
|
|
463
484
|
case 'assistant/message': {
|
|
464
|
-
const content = ev.data.message
|
|
465
|
-
|
|
466
|
-
const timestamp = new Date(ev.time).toISOString();
|
|
485
|
+
const content = asBlocks(ev.data.message?.content);
|
|
486
|
+
const timestamp = safeEventTimestamp(ev.time);
|
|
467
487
|
// DSH 的块类型是 text / reasoning / tool-call(不是 Anthropic 的
|
|
468
488
|
// thinking)。只认 text 会让 resume 卡"只有工具调用、没有思考与输出"
|
|
469
489
|
// (2026-10-19 反馈:一个 31 turn 的会话里 reasoning 块 371 个、text 12 个,
|
|
@@ -503,7 +523,7 @@ function mapEvent(ev) {
|
|
|
503
523
|
type: 'tool_use',
|
|
504
524
|
title: summary ? `🔧 ${name} — ${summary}` : `🔧 ${name}`,
|
|
505
525
|
content: truncateUtf8(args, TOOL_INPUT_MAX_BYTES),
|
|
506
|
-
timestamp:
|
|
526
|
+
timestamp: safeEventTimestamp(ev.time),
|
|
507
527
|
};
|
|
508
528
|
break;
|
|
509
529
|
}
|
|
@@ -515,7 +535,7 @@ function mapEvent(ev) {
|
|
|
515
535
|
type: 'tool_result',
|
|
516
536
|
title: isError ? '🔴 结果(失败)' : '🟢 结果',
|
|
517
537
|
content: truncateUtf8(raw, TOOL_RESULT_MAX_BYTES),
|
|
518
|
-
timestamp:
|
|
538
|
+
timestamp: safeEventTimestamp(ev.time),
|
|
519
539
|
});
|
|
520
540
|
break;
|
|
521
541
|
}
|
|
@@ -215,8 +215,8 @@ export class KimiSessionReader {
|
|
|
215
215
|
state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
216
216
|
stateParsed = true;
|
|
217
217
|
}
|
|
218
|
-
catch {
|
|
219
|
-
|
|
218
|
+
catch (err) {
|
|
219
|
+
getLogger().debug(`[kimi-session-reader] corrupt state.json skipped session=${entry.sessionId}: ${err?.message ?? String(err)}`);
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
// Index workDir is v1-only; v2 sessions may have empty/missing workDir.
|
|
@@ -227,7 +227,10 @@ export class KimiSessionReader {
|
|
|
227
227
|
entryWorkDir = extractWorkDir(state);
|
|
228
228
|
// Unreadable state — cannot determine cwd, entryWorkDir stays undefined
|
|
229
229
|
}
|
|
230
|
-
|
|
230
|
+
// cwd 判定与守卫(checkCwdGuard 的 samePath)同口径:严格字符串比较会
|
|
231
|
+
// 把大小写(darwin)/尾分隔符形态不同的会话从列表里滤掉,而 /resume <id>
|
|
232
|
+
// 的守卫却判等通过——「列表没有、按 id 却能读」。
|
|
233
|
+
if (entryWorkDir === undefined || !samePath(entryWorkDir, realCwd)) {
|
|
231
234
|
continue;
|
|
232
235
|
}
|
|
233
236
|
let summary = 'New Session';
|
|
@@ -332,9 +335,10 @@ export class KimiSessionReader {
|
|
|
332
335
|
cacheCreation: inputCacheCreation,
|
|
333
336
|
});
|
|
334
337
|
if (updateContextLength) {
|
|
335
|
-
// unified contract: input + cacheRead + cacheCreation
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
+
// unified contract (§9.21): input + output + cacheRead + cacheCreation.
|
|
339
|
+
// 末轮 output 已进入历史、下一 prompt 全量携带——与 compact postTokens
|
|
340
|
+
// 水位同语义(此前漏加 output 会系统性低估长收尾 turn)。
|
|
341
|
+
mainContextLength = inputOther + output + inputCacheRead + inputCacheCreation;
|
|
338
342
|
}
|
|
339
343
|
};
|
|
340
344
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
@@ -593,7 +597,7 @@ export class KimiSessionReader {
|
|
|
593
597
|
// different cwd when state.json is unavailable.
|
|
594
598
|
if (!cwdGuardPassed) {
|
|
595
599
|
const indexWorkDir = indexEntry?.workDir;
|
|
596
|
-
if (indexWorkDir && indexWorkDir
|
|
600
|
+
if (indexWorkDir && !samePath(indexWorkDir, realCwd)) {
|
|
597
601
|
return null;
|
|
598
602
|
}
|
|
599
603
|
// v2 sessions have empty index workDir (v1-only field) and may lack
|
|
@@ -243,14 +243,16 @@ export class OpencodeSessionReader {
|
|
|
243
243
|
// all render 0). Skip zero-total steps so per-turn usage reflects
|
|
244
244
|
// the last step that actually consumed tokens.
|
|
245
245
|
// ccusage-aligned: cache.write -> cacheCreationTokens.
|
|
246
|
-
// contextLength = input + cacheRead + cacheCreation
|
|
247
|
-
//
|
|
248
|
-
//
|
|
246
|
+
// contextLength = input + output + cacheRead + cacheCreation —
|
|
247
|
+
// the unified context-window occupancy contract across all readers
|
|
248
|
+
// (§9.21:末轮 output 已进入历史、下一 prompt 全量携带,与 compact
|
|
249
|
+
// postTokens 水位同语义).
|
|
249
250
|
if ((part.tokens.total ?? 0) !== 0) {
|
|
250
251
|
usage = {
|
|
251
252
|
inputTokens: part.tokens.input,
|
|
252
253
|
outputTokens: part.tokens.output,
|
|
253
|
-
contextLength: part.tokens.input +
|
|
254
|
+
contextLength: (part.tokens.input ?? 0) +
|
|
255
|
+
(part.tokens.output ?? 0) +
|
|
254
256
|
(part.tokens.cache?.read ?? 0) +
|
|
255
257
|
(part.tokens.cache?.write ?? 0),
|
|
256
258
|
cacheReadTokens: part.tokens.cache?.read,
|
|
@@ -460,7 +462,8 @@ export class OpencodeSessionReader {
|
|
|
460
462
|
latest = {
|
|
461
463
|
inputTokens: tokens.input,
|
|
462
464
|
outputTokens: tokens.output,
|
|
463
|
-
|
|
465
|
+
// §9.21 口径:含末轮 output(与 postTokens 水位同语义)。
|
|
466
|
+
contextLength: tokens.input + tokens.output + cacheRead + cacheWrite,
|
|
464
467
|
cacheReadTokens: cacheRead,
|
|
465
468
|
cacheCreationTokens: cacheWrite,
|
|
466
469
|
totalTokens: total,
|
|
@@ -507,7 +510,8 @@ export class OpencodeSessionReader {
|
|
|
507
510
|
// Check if updated within STALE_MS
|
|
508
511
|
return !isStale(entry.updated);
|
|
509
512
|
}
|
|
510
|
-
catch {
|
|
513
|
+
catch (err) {
|
|
514
|
+
getLogger().debug(`[opencode-session-reader] isSessionActive failed session=${sessionId}: ${err?.message ?? String(err)}`);
|
|
511
515
|
return false;
|
|
512
516
|
}
|
|
513
517
|
}
|
|
@@ -628,7 +632,11 @@ export class OpencodeSessionReader {
|
|
|
628
632
|
* stdout when it is redirected to a file fd, so we read the file instead.
|
|
629
633
|
*/
|
|
630
634
|
captureExportViaFileFd(sessionId) {
|
|
631
|
-
|
|
635
|
+
// sessionId 经 path.basename 净化:它来自用户 /resume <id> 输入或畸形 CLI
|
|
636
|
+
// 数据,含 / 或 .. 时 path.join 会归一化出 tmpdir 之外的路径(写入内容为
|
|
637
|
+
// CLI stdout,可能截断/覆盖目标文件)。
|
|
638
|
+
const safeId = path.basename(sessionId);
|
|
639
|
+
const tmp = path.join(os.tmpdir(), `opencode-export-${safeId}-${process.pid}-${Date.now()}.json`);
|
|
632
640
|
let fd;
|
|
633
641
|
try {
|
|
634
642
|
const exportOpts = {
|
|
@@ -10,6 +10,7 @@ import { encodeProjectDirName, samePath } from '../../platform/path.js';
|
|
|
10
10
|
import { paginate } from '../common/pagination.js';
|
|
11
11
|
import { sortByRecencyDesc } from '../common/recency.js';
|
|
12
12
|
import { TtlCache } from '../../common/ttl-cache.js';
|
|
13
|
+
import { getLogger } from '../../logger/index.js';
|
|
13
14
|
function defaultPiDir() {
|
|
14
15
|
return path.join(os.homedir(), '.pi', 'agent');
|
|
15
16
|
}
|
|
@@ -356,8 +357,8 @@ export class PiSessionReader {
|
|
|
356
357
|
mtime: st.mtimeMs,
|
|
357
358
|
});
|
|
358
359
|
}
|
|
359
|
-
catch {
|
|
360
|
-
|
|
360
|
+
catch (err) {
|
|
361
|
+
getLogger().debug(`[pi-session-reader] skipped ${full}: ${err?.message ?? String(err)}`);
|
|
361
362
|
}
|
|
362
363
|
}
|
|
363
364
|
// Same-mtime ties: secondary key keeps the full order deterministic.
|
|
@@ -230,9 +230,10 @@ export class SessionStore {
|
|
|
230
230
|
for (const [userId, data] of Object.entries(parsed)) {
|
|
231
231
|
if (typeof data === 'object' && data !== null) {
|
|
232
232
|
const entry = data;
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
233
|
+
// 四个 session 字段缺失 = 损坏记录,整条跳过。cwd 允许缺失:
|
|
234
|
+
// 全新用户(未 /cd)先切 agent 时 previous/arrival 停车位已落盘,
|
|
235
|
+
// cwd 尚为空——不能因 cwd 缺失把停车位整条丢掉
|
|
236
|
+
// (anchor_r7_parked_previous_survives_rebuild_without_cwd)。
|
|
236
237
|
if (!entry.sessions ||
|
|
237
238
|
!entry.previousSessions ||
|
|
238
239
|
!entry.arrivalSessions ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-remote",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
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",
|