lark-remote 0.4.4 → 0.5.0
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 +28 -0
- package/dist/binder.d.ts +11 -10
- package/dist/binder.js +17 -16
- package/dist/bridge/approval-coordinator.d.ts +1 -1
- package/dist/bridge/approval-coordinator.js +3 -3
- package/dist/bridge/inbound-media.d.ts +1 -27
- package/dist/bridge/inbound-media.js +23 -21
- package/dist/bridge/index.d.ts +57 -57
- package/dist/bridge/index.js +136 -129
- package/dist/bridge/queue-manager.d.ts +45 -38
- package/dist/bridge/queue-manager.js +136 -136
- package/dist/card/approval-render.d.ts +1 -1
- package/dist/card/approval-render.js +6 -6
- package/dist/card/bash-card-session.d.ts +3 -3
- package/dist/card/bash-card-session.js +3 -3
- package/dist/card/bash-renderer.js +5 -5
- package/dist/card/card-session.d.ts +4 -4
- package/dist/card/card-session.js +6 -6
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +2 -2
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.d.ts +1 -1
- package/dist/card/run-renderer.js +22 -22
- package/dist/card/run-state.d.ts +2 -2
- package/dist/card/run-state.js +3 -3
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +6 -6
- package/dist/cli.js +5 -6
- package/dist/clone.d.ts +6 -9
- package/dist/clone.js +12 -8
- package/dist/common/clamp.d.ts +2 -0
- package/dist/common/clamp.js +5 -0
- package/dist/common/fs.d.ts +4 -3
- package/dist/common/fs.js +5 -4
- package/dist/common/truncate.d.ts +1 -1
- package/dist/common/truncate.js +2 -2
- package/dist/common/ttl-cache.d.ts +2 -2
- package/dist/common/ttl-cache.js +2 -2
- package/dist/config/codex-config.d.ts +9 -9
- package/dist/config/codex-config.js +18 -18
- package/dist/config/dir.d.ts +4 -5
- package/dist/config/dir.js +12 -10
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +6 -6
- package/dist/config/opencode-config.js +3 -3
- package/dist/config/wizard.js +2 -2
- package/dist/connector/index.d.ts +5 -7
- package/dist/connector/index.js +29 -16
- package/dist/inbound/event.d.ts +1 -1
- package/dist/inbound/event.js +1 -1
- package/dist/inbound/placeholder.d.ts +2 -2
- package/dist/inbound/placeholder.js +2 -2
- package/dist/inbound/receipt.d.ts +1 -3
- package/dist/inbound/receipt.js +2 -2
- package/dist/inbound/turn-assembler.d.ts +1 -3
- package/dist/inbound/turn-assembler.js +2 -2
- package/dist/index.js +38 -30
- package/dist/instance-lock.js +8 -7
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/index.js +4 -4
- package/dist/persistence/atomic-write.d.ts +1 -1
- package/dist/persistence/atomic-write.js +9 -5
- package/dist/platform/agent-stopper.d.ts +2 -2
- package/dist/platform/identity.d.ts +7 -1
- package/dist/platform/identity.js +2 -2
- package/dist/platform/path.d.ts +6 -0
- package/dist/platform/path.js +14 -8
- package/dist/platform/select.d.ts +1 -1
- package/dist/platform/select.js +1 -1
- package/dist/platform/shell.d.ts +2 -3
- package/dist/platform/spawn.d.ts +2 -2
- package/dist/platform/spawn.js +2 -2
- package/dist/platform/terminator-posix.js +6 -6
- package/dist/platform/terminator-win32.js +3 -3
- package/dist/platform/terminator.d.ts +3 -3
- package/dist/platform/terminator.js +2 -2
- package/dist/platform/types.d.ts +1 -1
- package/dist/restart.d.ts +1 -1
- package/dist/restart.js +5 -3
- package/dist/router/card-helpers.d.ts +1 -3
- package/dist/router/card-helpers.js +2 -2
- package/dist/router/config/codex.d.ts +1 -1
- package/dist/router/config/codex.js +3 -3
- package/dist/router/config/common/model-patch.d.ts +1 -2
- package/dist/router/config/common/model-patch.js +1 -2
- package/dist/router/config/opencode.js +2 -2
- package/dist/router/config/pi.js +1 -1
- package/dist/router/index.d.ts +30 -30
- package/dist/router/index.js +103 -84
- package/dist/router/order-exec-dispatch.d.ts +3 -10
- package/dist/router/order-exec-dispatch.js +5 -5
- package/dist/router/{utils.d.ts → usage-format.d.ts} +2 -4
- package/dist/router/{utils.js → usage-format.js} +3 -8
- package/dist/runner/bash/runner.d.ts +2 -2
- package/dist/runner/bash/runner.js +6 -6
- package/dist/runner/claude/runner.d.ts +1 -1
- package/dist/runner/claude/runner.js +2 -3
- package/dist/runner/claude/session.d.ts +4 -6
- package/dist/runner/claude/session.js +21 -23
- package/dist/runner/codex/app-server/runner.js +3 -3
- package/dist/runner/codex/app-server/translator.d.ts +2 -2
- package/dist/runner/codex/app-server/translator.js +5 -5
- package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +4 -10
- package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +7 -6
- package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
- package/dist/runner/common/acp/base-acp-runner.js +2 -2
- package/dist/runner/common/acp/base-translator.d.ts +3 -3
- package/dist/runner/common/acp/base-translator.js +4 -4
- package/dist/runner/common/acp/protocol-types.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +5 -5
- package/dist/runner/common/connection-based-runner.js +5 -5
- package/dist/runner/common/jsonl-stream.d.ts +2 -2
- package/dist/runner/common/jsonl-stream.js +12 -12
- package/dist/runner/common/jsonrpc/client.d.ts +1 -1
- package/dist/runner/common/jsonrpc/client.js +1 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +13 -13
- package/dist/runner/common/jsonrpc/connection-manager.js +37 -37
- package/dist/runner/common/jsonrpc/transport.d.ts +1 -1
- package/dist/runner/common/jsonrpc/transport.js +2 -2
- package/dist/runner/common/{usage.d.ts → result-usage.d.ts} +1 -1
- package/dist/runner/common/{usage.js → result-usage.js} +2 -2
- package/dist/runner/common/spawning-runner.d.ts +23 -10
- package/dist/runner/common/spawning-runner.js +48 -22
- package/dist/runner/common/{runner-utils.d.ts → synthetic-events.d.ts} +4 -4
- package/dist/runner/common/{runner-utils.js → synthetic-events.js} +5 -5
- package/dist/runner/dsh/client.d.ts +1 -1
- package/dist/runner/dsh/client.js +1 -1
- package/dist/runner/dsh/runner.js +3 -3
- package/dist/runner/kimi/acp/runner.d.ts +4 -4
- package/dist/runner/kimi/acp/runner.js +10 -10
- package/dist/runner/kimi/acp/translator.d.ts +2 -2
- package/dist/runner/kimi/acp/translator.js +5 -5
- package/dist/runner/opencode/acp/runner.d.ts +4 -4
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/opencode/acp/translator.js +2 -2
- package/dist/runner/pi/rpc/runner.d.ts +1 -1
- package/dist/runner/pi/rpc/runner.js +2 -2
- package/dist/runner/pi/rpc/translator.d.ts +1 -2
- package/dist/runner/pi/rpc/translator.js +4 -6
- package/dist/runner/registry.d.ts +3 -3
- package/dist/runner/registry.js +3 -3
- package/dist/runner/types.d.ts +1 -1
- package/dist/session/claude/session-index.d.ts +13 -1
- package/dist/session/claude/session-index.js +21 -6
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/session-reader.js +1 -1
- package/dist/session/claude/sessions.js +10 -10
- package/dist/session/codex/rollout-reader.d.ts +4 -4
- package/dist/session/codex/rollout-reader.js +14 -14
- package/dist/session/common/two-pass.js +2 -2
- package/dist/session/common/usage-accumulator.d.ts +2 -2
- package/dist/session/common/usage-accumulator.js +2 -2
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +3 -3
- package/dist/session/kimi/sessions.d.ts +2 -6
- package/dist/session/kimi/sessions.js +13 -19
- package/dist/session/opencode/sessions.d.ts +3 -3
- package/dist/session/opencode/sessions.js +15 -13
- package/dist/session/pi/sessions.js +13 -12
- package/dist/session/registry.d.ts +1 -1
- package/dist/session/registry.js +1 -1
- package/dist/session/session-store.d.ts +3 -4
- package/dist/session/session-store.js +3 -5
- package/dist/update/index.d.ts +1 -1
- package/dist/update/index.js +1 -1
- package/dist/update/version-check.d.ts +5 -3
- package/dist/update/version-check.js +10 -8
- package/dist/workspace/index.d.ts +4 -4
- package/dist/workspace/index.js +5 -5
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@ import { samePath } from '../../platform/path.js';
|
|
|
21
21
|
import { capEvents, paginate } from '../common/pagination.js';
|
|
22
22
|
import { truncateUtf8, TOOL_RESULT_MAX_BYTES } from '../../common/truncate.js';
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* history 拉取上限。DSH session.history 的 maxMessages 同时限制返回条数,
|
|
25
25
|
* 若用它同时充当展示截断,>50 事件的会话累计 usage 会少算。这里拉取一个足够大的
|
|
26
26
|
* 上限保证 usage 累加覆盖整个会话;展示仍由 capEvents(opts?.maxEvents ?? 50) 截断。
|
|
27
27
|
* (若 DSH 支持分页 cursor 可改成分页,当前用有界大拉取)
|
|
@@ -102,7 +102,7 @@ export class DshSessionReader {
|
|
|
102
102
|
getNewestSession(cwd) {
|
|
103
103
|
return this.listSessions(cwd, { limit: 1 }).sessions[0] ?? null;
|
|
104
104
|
}
|
|
105
|
-
readSessionContent(sessionId,
|
|
105
|
+
readSessionContent(sessionId, _cwd, opts) {
|
|
106
106
|
try {
|
|
107
107
|
const value = this.unary('session.history', {
|
|
108
108
|
sessionId,
|
|
@@ -159,7 +159,7 @@ export class DshSessionReader {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
return {
|
|
162
|
-
//
|
|
162
|
+
// 展示仍按 maxEvents 尾截断(默认 50,保持原行为),usage 已在上面基于
|
|
163
163
|
// 完整 history 累加。
|
|
164
164
|
events: capEvents(events, opts?.maxEvents ?? 50),
|
|
165
165
|
...(sessionUsage ? { usage: sessionUsage } : {}),
|
|
@@ -66,10 +66,6 @@ export declare class KimiSessionReader implements AgentSessionReader {
|
|
|
66
66
|
* Find a single index entry by sessionId, or undefined if not found.
|
|
67
67
|
*/
|
|
68
68
|
private findSessionIndexEntry;
|
|
69
|
-
/**
|
|
70
|
-
* Convenience wrapper: find sessionDir from index, or null if not found.
|
|
71
|
-
*/
|
|
72
|
-
private findSessionDirFromIndex;
|
|
73
69
|
/**
|
|
74
70
|
* Extract the last turn.prompt text from a wire.jsonl file.
|
|
75
71
|
* Used as a fallback summary source when state.json has no title/lastPrompt
|
|
@@ -108,7 +104,7 @@ export declare class KimiSessionReader implements AgentSessionReader {
|
|
|
108
104
|
readCompactionRecords(sessionId: string, cwd: string): KimiCompactionRecord[];
|
|
109
105
|
/**
|
|
110
106
|
* 由 begin/terminal 记录重建压缩状态机:最后一次 begin 无更新的 terminal
|
|
111
|
-
* 记录 =
|
|
107
|
+
* 记录 = 在途。崩溃墓碑由 kimi resume
|
|
112
108
|
* 时补写 full_compaction.cancel,天然闭合。
|
|
113
109
|
*/
|
|
114
110
|
readCompactionState(sessionId: string, cwd: string): {
|
|
@@ -118,7 +114,7 @@ export declare class KimiSessionReader implements AgentSessionReader {
|
|
|
118
114
|
records: KimiCompactionRecord[];
|
|
119
115
|
};
|
|
120
116
|
/**
|
|
121
|
-
*
|
|
117
|
+
* 单源:resolveSessionWirePath 与 isSessionActive 共用的 cwd 守卫
|
|
122
118
|
* (realpathSync → state.json workDir/cwd 三态 → index workDir 兜底 →
|
|
123
119
|
* fail-closed)。返回 sessionDir;会话未知或守卫不过返回 null。
|
|
124
120
|
*/
|
|
@@ -78,7 +78,7 @@ function readJsonlLines(filePath) {
|
|
|
78
78
|
try {
|
|
79
79
|
const stat = fs.statSync(filePath);
|
|
80
80
|
const mtime = stat.mtimeMs;
|
|
81
|
-
//
|
|
81
|
+
// TTL 由 TtlCache 按缓存写入时间判定(不是文件 mtime),命中即 LRU 刷新;
|
|
82
82
|
// mtime 变化视为失效(文件被改写后不得返回旧行)。
|
|
83
83
|
const cached = jsonlCache.get(filePath);
|
|
84
84
|
if (cached && mtime === cached.mtime) {
|
|
@@ -163,12 +163,6 @@ export class KimiSessionReader {
|
|
|
163
163
|
findSessionIndexEntry(sessionId) {
|
|
164
164
|
return this.scanSessionIndex().find((e) => e.sessionId === sessionId);
|
|
165
165
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Convenience wrapper: find sessionDir from index, or null if not found.
|
|
168
|
-
*/
|
|
169
|
-
findSessionDirFromIndex(sessionId) {
|
|
170
|
-
return this.findSessionIndexEntry(sessionId)?.sessionDir ?? null;
|
|
171
|
-
}
|
|
172
166
|
/**
|
|
173
167
|
* Extract the last turn.prompt text from a wire.jsonl file.
|
|
174
168
|
* Used as a fallback summary source when state.json has no title/lastPrompt
|
|
@@ -210,7 +204,7 @@ export class KimiSessionReader {
|
|
|
210
204
|
const realCwd = fs.realpathSync(cwd);
|
|
211
205
|
const sessions = [];
|
|
212
206
|
for (const entry of this.scanSessionIndex()) {
|
|
213
|
-
//
|
|
207
|
+
// state.json 一次读取共用(cwd 兜底 + summary),I/O 减半。
|
|
214
208
|
// 两次 catch 语义保留:解析失败 = 无法判 cwd → 跳过(continue);
|
|
215
209
|
// 解析成功但字段缺失 = summary 落 wire.jsonl 提取。
|
|
216
210
|
const statePath = path.join(entry.sessionDir, 'state.json');
|
|
@@ -248,7 +242,7 @@ export class KimiSessionReader {
|
|
|
248
242
|
const wirePath = path.join(entry.sessionDir, 'agents', 'main', 'wire.jsonl');
|
|
249
243
|
summary = this.extractLastPromptFromWire(wirePath) || 'New Session';
|
|
250
244
|
}
|
|
251
|
-
// Get mtime.
|
|
245
|
+
// Get mtime. the session DIRECTORY mtime only changes when
|
|
252
246
|
// entries are created/deleted/renamed inside it — kimi appending to
|
|
253
247
|
// agents/main/wire.jsonl during a run does NOT touch it, so dir-mtime
|
|
254
248
|
// sorting lags real activity by minutes. Use the wire.jsonl mtime
|
|
@@ -338,7 +332,7 @@ export class KimiSessionReader {
|
|
|
338
332
|
cacheCreation: inputCacheCreation,
|
|
339
333
|
});
|
|
340
334
|
if (updateContextLength) {
|
|
341
|
-
//
|
|
335
|
+
// unified contract: input + cacheRead + cacheCreation
|
|
342
336
|
// (excludes output — generated, not part of the input window).
|
|
343
337
|
mainContextLength = inputOther + inputCacheRead + inputCacheCreation;
|
|
344
338
|
}
|
|
@@ -356,7 +350,7 @@ export class KimiSessionReader {
|
|
|
356
350
|
// 展示计数只计 context.apply_compaction:一次手动 /compact 会同时写
|
|
357
351
|
// full_compaction.complete + context.apply_compaction 两条(2026-08-16
|
|
358
352
|
// 实弹 wire.jsonl),双计会把一次压缩虚报成 2 次。full_compaction.complete
|
|
359
|
-
// 仅作
|
|
353
|
+
// 仅作 runCompact 轮询的完成信号(readCompactionRecords 仍返回两种)。
|
|
360
354
|
if (isCompactionComplete(entry) && entry.type === 'context.apply_compaction') {
|
|
361
355
|
compactCount++;
|
|
362
356
|
// Capture context length before this compaction as the pre-compact
|
|
@@ -366,7 +360,7 @@ export class KimiSessionReader {
|
|
|
366
360
|
}
|
|
367
361
|
continue;
|
|
368
362
|
}
|
|
369
|
-
//
|
|
363
|
+
// displayTitle + display events now scan ALL lines (not a
|
|
370
364
|
// line-slice), then `events.slice(-maxEvents)` keeps the LAST N
|
|
371
365
|
// EVENTS. Previously `lines.slice(-maxEvents)` cut raw lines, but
|
|
372
366
|
// usage.record / step.begin / turn.prompt lines produce no display
|
|
@@ -408,7 +402,7 @@ export class KimiSessionReader {
|
|
|
408
402
|
}
|
|
409
403
|
else if (evt.type === 'tool.result') {
|
|
410
404
|
// result is { output }, { output, note }, or { isError, output }
|
|
411
|
-
// 对齐 opencode reader
|
|
405
|
+
// 对齐 opencode reader 的预折叠:单条 tool_result 上限
|
|
412
406
|
// TOOL_RESULT_MAX_BYTES,病理级输出(如 51KB 文件读取)不会
|
|
413
407
|
// 撑爆 resume 卡,也约束 events[] 内存占用。
|
|
414
408
|
const rawOut = evt.result?.output ?? `[tool result for ${evt.toolCallId}]`;
|
|
@@ -529,7 +523,7 @@ export class KimiSessionReader {
|
|
|
529
523
|
}
|
|
530
524
|
/**
|
|
531
525
|
* 由 begin/terminal 记录重建压缩状态机:最后一次 begin 无更新的 terminal
|
|
532
|
-
* 记录 =
|
|
526
|
+
* 记录 = 在途。崩溃墓碑由 kimi resume
|
|
533
527
|
* 时补写 full_compaction.cancel,天然闭合。
|
|
534
528
|
*/
|
|
535
529
|
readCompactionState(sessionId, cwd) {
|
|
@@ -551,7 +545,7 @@ export class KimiSessionReader {
|
|
|
551
545
|
};
|
|
552
546
|
}
|
|
553
547
|
/**
|
|
554
|
-
*
|
|
548
|
+
* 单源:resolveSessionWirePath 与 isSessionActive 共用的 cwd 守卫
|
|
555
549
|
* (realpathSync → state.json workDir/cwd 三态 → index workDir 兜底 →
|
|
556
550
|
* fail-closed)。返回 sessionDir;会话未知或守卫不过返回 null。
|
|
557
551
|
*/
|
|
@@ -596,7 +590,7 @@ export class KimiSessionReader {
|
|
|
596
590
|
// Fallback: when state.json is missing, unparseable, or has no workDir/cwd,
|
|
597
591
|
// use the workDir from the session index entry as a secondary guard.
|
|
598
592
|
// This prevents /resume <id> from accessing a session belonging to a
|
|
599
|
-
// different
|
|
593
|
+
// different cwd when state.json is unavailable.
|
|
600
594
|
if (!cwdGuardPassed) {
|
|
601
595
|
const indexWorkDir = indexEntry?.workDir;
|
|
602
596
|
if (indexWorkDir && indexWorkDir !== realCwd) {
|
|
@@ -604,7 +598,7 @@ export class KimiSessionReader {
|
|
|
604
598
|
}
|
|
605
599
|
// v2 sessions have empty index workDir (v1-only field) and may lack
|
|
606
600
|
// state.json cwd. When no cwd source can verify the session belongs to
|
|
607
|
-
// the requested
|
|
601
|
+
// the requested cwd, fail-closed to prevent cross-workspace access
|
|
608
602
|
// (aligned with claude's fail-closed cwd guard).
|
|
609
603
|
if (!indexWorkDir) {
|
|
610
604
|
getLogger().warn(`[kimi-session-reader] no cwd source (state.json + index) for session ${sessionId}, rejecting (fail-closed)`);
|
|
@@ -632,7 +626,7 @@ export class KimiSessionReader {
|
|
|
632
626
|
* Check if a session is currently active
|
|
633
627
|
*/
|
|
634
628
|
isSessionActive(sessionId, cwd) {
|
|
635
|
-
//
|
|
629
|
+
// cwd 守卫与 resolveSessionWirePath 共用 verifySessionCwd(单源)。
|
|
636
630
|
const verified = this.verifySessionCwd(sessionId, cwd);
|
|
637
631
|
if (!verified)
|
|
638
632
|
return false;
|
|
@@ -650,7 +644,7 @@ export class KimiSessionReader {
|
|
|
650
644
|
// Read the tail to check for active indicators. kimi appends
|
|
651
645
|
// usage.record after step.end at turn completion, so the last loop
|
|
652
646
|
// event (not the last line) decides whether the session is complete.
|
|
653
|
-
//
|
|
647
|
+
// `lastLoopEventIsStepEnd` only scans backwards up to
|
|
654
648
|
// LOOP_EVENT_SCAN_LIMIT lines, so we read only that many trailing lines
|
|
655
649
|
// via the tail-only helper instead of full-slurping the whole wire log
|
|
656
650
|
// (which can be multi-MB) into a string[].
|
|
@@ -20,7 +20,7 @@ interface OpencodeSessionReaderOptions {
|
|
|
20
20
|
/**
|
|
21
21
|
* Override the raw `opencode export <id>` capture (returns the JSON string).
|
|
22
22
|
* Default routes stdout to a temp file fd to bypass opencode's ~128KB pipe
|
|
23
|
-
* truncation on large sessions
|
|
23
|
+
* truncation on large sessions. Injectable for testing.
|
|
24
24
|
*/
|
|
25
25
|
captureExport?: (sessionId: string) => string;
|
|
26
26
|
}
|
|
@@ -65,12 +65,12 @@ export declare class OpencodeSessionReader implements AgentSessionReader {
|
|
|
65
65
|
/**
|
|
66
66
|
* Capture raw `opencode export <sessionId>` output (JSON string). Injectable
|
|
67
67
|
* via the constructor; the default routes stdout to a temp file fd to bypass
|
|
68
|
-
* opencode's ~128KB pipe truncation on large sessions
|
|
68
|
+
* opencode's ~128KB pipe truncation on large sessions.
|
|
69
69
|
*/
|
|
70
70
|
/** 导出会话内容(public:测试直接调用,替代 as unknown as)。 */
|
|
71
71
|
captureExport(sessionId: string): string;
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* default transport: run `opencode export` with stdout pointed at a
|
|
74
74
|
* regular-file fd (NOT a pipe). opencode truncates piped stdout at ~128KB for
|
|
75
75
|
* large sessions (huge tool_results), breaking JSON.parse; a file fd receives
|
|
76
76
|
* the full output (verified: 1.3MB parses OK). execFileSync returns null when
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { spawnProcessSync } from '../../platform/spawn.js';
|
|
17
17
|
import fs from 'node:fs';
|
|
18
|
-
import {
|
|
18
|
+
import { bestEffortUnlink } from '../../common/fs.js';
|
|
19
19
|
import os from 'node:os';
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { truncateUtf8, TOOL_RESULT_MAX_BYTES } from '../../common/truncate.js';
|
|
22
|
-
import { getLogger } from '../../logger/index.js';
|
|
23
22
|
import { isStale } from '../common/constants.js';
|
|
24
23
|
import { samePath } from '../../platform/path.js';
|
|
25
24
|
import { paginate, capEvents } from '../common/pagination.js';
|
|
26
25
|
import { sortByRecencyDesc } from '../common/recency.js';
|
|
27
26
|
import { TtlCache } from '../../common/ttl-cache.js';
|
|
27
|
+
import { getLogger } from '../../logger/index.js';
|
|
28
28
|
export class OpencodeSessionReader {
|
|
29
29
|
binary;
|
|
30
30
|
cacheTtlMs;
|
|
@@ -57,7 +57,7 @@ export class OpencodeSessionReader {
|
|
|
57
57
|
sessions: items.map((e) => ({
|
|
58
58
|
sessionId: e.id,
|
|
59
59
|
// CLI title 字段形状不保证恒为 string(null/缺字段时 router 的
|
|
60
|
-
// s.summary.trim() 会 TypeError)——非 string 给空串(Review
|
|
60
|
+
// s.summary.trim() 会 TypeError)——非 string 给空串(Review)。
|
|
61
61
|
summary: typeof e.title === 'string' ? e.title : '',
|
|
62
62
|
mtime: e.updated,
|
|
63
63
|
})),
|
|
@@ -75,7 +75,7 @@ export class OpencodeSessionReader {
|
|
|
75
75
|
*/
|
|
76
76
|
readSessionContent(sessionId, cwd, opts) {
|
|
77
77
|
const realCwd = this.realpath(cwd);
|
|
78
|
-
//
|
|
78
|
+
// capture raw export via the injectable seam, then parse. Distinguish
|
|
79
79
|
// a truly-missing session (empty capture) from a truncated/corrupt one
|
|
80
80
|
// (non-empty but unparseable) so we never show the misleading "未找到" for
|
|
81
81
|
// a session whose content is merely too large to pipe through stdout.
|
|
@@ -100,7 +100,9 @@ export class OpencodeSessionReader {
|
|
|
100
100
|
return { events: [] };
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
|
-
|
|
103
|
+
// 与 listSessions 的过滤同口径(PathKit.samePath):export 里的 directory
|
|
104
|
+
// 形态由 opencode 自行记录,裸 !== 会让会话列得出却读不出(win32 常态)
|
|
105
|
+
if (!samePath(data.info.directory, realCwd)) {
|
|
104
106
|
return { events: [] };
|
|
105
107
|
}
|
|
106
108
|
const events = [];
|
|
@@ -160,7 +162,7 @@ export class OpencodeSessionReader {
|
|
|
160
162
|
const rawOut = isError
|
|
161
163
|
? (part.state.error ?? part.state.output)
|
|
162
164
|
: (part.state.output ?? '');
|
|
163
|
-
//
|
|
165
|
+
// pre-fold pathological tool_results (e.g. a 500KB file
|
|
164
166
|
// listing) via the shared truncateUtf8 primitive, reusing the
|
|
165
167
|
// same …(已截断) suffix as the live renderer / card budget.
|
|
166
168
|
events.push({
|
|
@@ -183,7 +185,7 @@ export class OpencodeSessionReader {
|
|
|
183
185
|
// ccusage-aligned: cache.write -> cacheCreationTokens.
|
|
184
186
|
// contextLength = input + cacheRead + cacheCreation (excludes
|
|
185
187
|
// output/reasoning) — the unified context-window occupancy
|
|
186
|
-
// contract across all five readers
|
|
188
|
+
// contract across all five readers.
|
|
187
189
|
if ((part.tokens.total ?? 0) !== 0) {
|
|
188
190
|
usage = {
|
|
189
191
|
inputTokens: part.tokens.input,
|
|
@@ -317,7 +319,7 @@ export class OpencodeSessionReader {
|
|
|
317
319
|
const res = spawnProcessSync(this.binary, ['session', 'list', '--format', 'json'], {
|
|
318
320
|
encoding: 'utf-8',
|
|
319
321
|
timeout: 10000,
|
|
320
|
-
//
|
|
322
|
+
// default maxBuffer is 1MiB — a large session list (>1MiB JSON)
|
|
321
323
|
// throws ENOBUFS, which the catch below silently turns into [] and the
|
|
322
324
|
// user sees "no sessions" even though the CLI returned healthy data.
|
|
323
325
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -345,7 +347,7 @@ export class OpencodeSessionReader {
|
|
|
345
347
|
return data;
|
|
346
348
|
}
|
|
347
349
|
catch (err) {
|
|
348
|
-
//
|
|
350
|
+
// 区分「读取失败」与「真空/陈旧 cwd」。
|
|
349
351
|
// - ENOENT(cwd 已删除或 binary 缺失):沿用既有契约返回 []——/resume 对
|
|
350
352
|
// 陈旧 cwd 保持优雅(sessions.test.ts「Stale-cwd coverage gap」+ round10
|
|
351
353
|
// 探针的跨 reader 空目录不变量都钉死此行为)。
|
|
@@ -362,7 +364,7 @@ export class OpencodeSessionReader {
|
|
|
362
364
|
/**
|
|
363
365
|
* Capture raw `opencode export <sessionId>` output (JSON string). Injectable
|
|
364
366
|
* via the constructor; the default routes stdout to a temp file fd to bypass
|
|
365
|
-
* opencode's ~128KB pipe truncation on large sessions
|
|
367
|
+
* opencode's ~128KB pipe truncation on large sessions.
|
|
366
368
|
*/
|
|
367
369
|
/** 导出会话内容(public:测试直接调用,替代 as unknown as)。 */
|
|
368
370
|
captureExport(sessionId) {
|
|
@@ -371,7 +373,7 @@ export class OpencodeSessionReader {
|
|
|
371
373
|
return this.captureExportViaFileFd(sessionId);
|
|
372
374
|
}
|
|
373
375
|
/**
|
|
374
|
-
*
|
|
376
|
+
* default transport: run `opencode export` with stdout pointed at a
|
|
375
377
|
* regular-file fd (NOT a pipe). opencode truncates piped stdout at ~128KB for
|
|
376
378
|
* large sessions (huge tool_results), breaking JSON.parse; a file fd receives
|
|
377
379
|
* the full output (verified: 1.3MB parses OK). execFileSync returns null when
|
|
@@ -386,7 +388,7 @@ export class OpencodeSessionReader {
|
|
|
386
388
|
encoding: 'utf-8',
|
|
387
389
|
timeout: 30000,
|
|
388
390
|
stdio: ['ignore', fd, 'pipe'],
|
|
389
|
-
//
|
|
391
|
+
// stderr is piped here; without maxBuffer a chatty CLI (>1MiB
|
|
390
392
|
// stderr) would throw ENOBUFS and lose the export. Keep the default
|
|
391
393
|
// stdout file-fd transport unaffected by raising the pipe cap.
|
|
392
394
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -401,7 +403,7 @@ export class OpencodeSessionReader {
|
|
|
401
403
|
}
|
|
402
404
|
catch { }
|
|
403
405
|
}
|
|
404
|
-
|
|
406
|
+
bestEffortUnlink(tmp);
|
|
405
407
|
}
|
|
406
408
|
}
|
|
407
409
|
}
|
|
@@ -6,7 +6,7 @@ import { scanJsonlOnce, tailOffsetAfter, scanTailEvents } from '../common/two-pa
|
|
|
6
6
|
import { extractContentBlocks } from '../common/content-blocks.js';
|
|
7
7
|
import { UsageAccumulator, contextWindowOccupancy, cumulativeUsageFields, } from '../common/usage-accumulator.js';
|
|
8
8
|
import { isStale } from '../common/constants.js';
|
|
9
|
-
import { encodeProjectDirName } from '../../platform/path.js';
|
|
9
|
+
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';
|
|
@@ -26,7 +26,7 @@ function projectDirForCwd(cwd, sessionsDir) {
|
|
|
26
26
|
return path.join(sessionsDir, encoded);
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* TTL cache for pi directory scans (mirror codex getSessionIndex's
|
|
30
30
|
* 5s TTL). Repeated /resume pages re-scan the whole sessions dir per call.
|
|
31
31
|
*/
|
|
32
32
|
const piListCache = new TtlCache(5_000, 32);
|
|
@@ -71,7 +71,7 @@ function compressSkillInjection(text) {
|
|
|
71
71
|
}
|
|
72
72
|
/** Get a one-line summary from a pi session JSONL (first user message text).
|
|
73
73
|
*
|
|
74
|
-
*
|
|
74
|
+
* optimization: previously used `readJsonlLines` (full-file slurp)
|
|
75
75
|
* just to get the first user message. Now uses `findJsonlLine` which
|
|
76
76
|
* stops reading as soon as the predicate matches — typically only the
|
|
77
77
|
* first few lines of the file, saving ~99% I/O on large sessions.
|
|
@@ -136,14 +136,14 @@ function findSessionFile(sessionId, cwd, sessionsDir) {
|
|
|
136
136
|
return null;
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
|
-
*
|
|
139
|
+
* first-pass scan: stream the file once via `scanJsonlLines`
|
|
140
140
|
* (no `string[]` materialized), parse each line exactly once, and collect
|
|
141
141
|
* all metadata needed by readSessionContent (usage, tailOffset, displayTitle).
|
|
142
142
|
* Does NOT retain parsed objects — the events tail is re-parsed separately
|
|
143
143
|
* by `extractPiEventsFromTail` so memory for both raw line strings and
|
|
144
144
|
* parsed objects is O(tail), not O(whole file).
|
|
145
145
|
*
|
|
146
|
-
* Mirrors claude's scalarScan
|
|
146
|
+
* Mirrors claude's scalarScan. Preserves the skill-signature
|
|
147
147
|
* compression (`<skill>…</skill>` → `skill:x`) for displayTitle and the
|
|
148
148
|
* "last turn" semantics for usage tokens.
|
|
149
149
|
*/
|
|
@@ -198,7 +198,7 @@ function piScalarScan(filePath) {
|
|
|
198
198
|
* Build the AgentSessionUsage from an accumulator, preserving pi's
|
|
199
199
|
* "last turn" semantics: inputTokens/outputTokens use the LAST turn's
|
|
200
200
|
* values, contextLength = last turn's context-window occupancy
|
|
201
|
-
* (input + cacheRead + cacheCreation, excludes output —
|
|
201
|
+
* (input + cacheRead + cacheCreation, excludes output —unified
|
|
202
202
|
* contract across all five readers), cumulative = session-wide sum.
|
|
203
203
|
* Returns undefined when no usage data was observed.
|
|
204
204
|
*/
|
|
@@ -221,10 +221,10 @@ function buildPiUsage(acc) {
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
/**
|
|
224
|
-
*
|
|
224
|
+
* second-pass tail mapper: expand one parsed tail line into
|
|
225
225
|
* content-block events. Re-parses only tail lines (scanTailEvents does the
|
|
226
226
|
* offset re-read + line filtering), so both raw line-string memory and
|
|
227
|
-
* parsed-object memory are O(tail), not O(whole file)
|
|
227
|
+
* parsed-object memory are O(tail), not O(whole file) —.
|
|
228
228
|
* `maxEvents` capping is applied by scanTailEvents.
|
|
229
229
|
*/
|
|
230
230
|
function mapPiTailLine(rawObj) {
|
|
@@ -285,12 +285,13 @@ export class PiSessionReader {
|
|
|
285
285
|
if (!filePath) {
|
|
286
286
|
return { events: [] };
|
|
287
287
|
}
|
|
288
|
-
// Verify cwd matches
|
|
288
|
+
// Verify cwd matches(PathKit.samePath:win32/darwin 大小写与分隔符不敏感,
|
|
289
|
+
// JSONL 里的 cwd 形态由写方决定,与请求 cwd 不保证逐字符一致)
|
|
289
290
|
const fileCwd = readCwdFromPiJsonl(filePath);
|
|
290
|
-
if (fileCwd && fileCwd
|
|
291
|
+
if (fileCwd && !samePath(fileCwd, cwd)) {
|
|
291
292
|
return { events: [] };
|
|
292
293
|
}
|
|
293
|
-
//
|
|
294
|
+
// Two-pass scan (see `scanJsonlOnce` / `scanTailEvents` in
|
|
294
295
|
// session/common/two-pass.ts): pass 1 streams the file once collecting
|
|
295
296
|
// scalars (usage, tailOffset, displayTitle) with O(1) parsed-object
|
|
296
297
|
// retention; pass 2 re-reads ONLY the tail and re-parses it for events,
|
|
@@ -338,7 +339,7 @@ export class PiSessionReader {
|
|
|
338
339
|
try {
|
|
339
340
|
const st = fs.statSync(full);
|
|
340
341
|
const fileCwd = readCwdFromPiJsonl(full);
|
|
341
|
-
if (fileCwd && fileCwd
|
|
342
|
+
if (fileCwd && !samePath(fileCwd, cwd))
|
|
342
343
|
continue;
|
|
343
344
|
// Extract sessionId (UUID) from filename: <timestamp>_<uuid>.jsonl
|
|
344
345
|
// The timestamp and uuid are separated by '_', and neither contains '_'
|
|
@@ -3,7 +3,7 @@ import type { AgentKind, AgentSessionReader } from '../runner/index.js';
|
|
|
3
3
|
* Registry of `AgentSessionReader` instances, keyed by `AgentKind`. Mirrors
|
|
4
4
|
* `AgentRegistry` but for session-history reads. `index.ts` registers one
|
|
5
5
|
* reader per available agent at startup; router/bridge look up readers here
|
|
6
|
-
* instead of importing `
|
|
6
|
+
* instead of importing `sessions.ts` directly.
|
|
7
7
|
*/
|
|
8
8
|
export declare class SessionReaderRegistry {
|
|
9
9
|
private readonly readers;
|
package/dist/session/registry.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Registry of `AgentSessionReader` instances, keyed by `AgentKind`. Mirrors
|
|
3
3
|
* `AgentRegistry` but for session-history reads. `index.ts` registers one
|
|
4
4
|
* reader per available agent at startup; router/bridge look up readers here
|
|
5
|
-
* instead of importing `
|
|
5
|
+
* instead of importing `sessions.ts` directly.
|
|
6
6
|
*/
|
|
7
7
|
export class SessionReaderRegistry {
|
|
8
8
|
readers = new Map();
|
|
@@ -13,7 +13,7 @@ interface SessionEntry {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Per-user session store. Persists cwd + per-agent sessions to disk so that
|
|
16
|
-
* the last
|
|
16
|
+
* the last cwd AND the last-used sessionId per agent are restored after
|
|
17
17
|
* a lark-remote restart.
|
|
18
18
|
*
|
|
19
19
|
* Persistence strategy:
|
|
@@ -23,13 +23,12 @@ interface SessionEntry {
|
|
|
23
23
|
* - Atomic write (temp + rename) to avoid truncated files on crash
|
|
24
24
|
* - Corrupt files are treated as empty (with a warning)
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* 3: sessions is Map<AgentKind, sessionId> (one session per agent), allowing
|
|
27
27
|
* each agent to maintain its own session context when switching between agents.
|
|
28
28
|
*/
|
|
29
29
|
export declare class SessionStore {
|
|
30
30
|
private sessions;
|
|
31
31
|
private readonly persistPath?;
|
|
32
|
-
private readonly defaultAgent?;
|
|
33
32
|
/**
|
|
34
33
|
* 进程内会话代际(2026-08-09 /new 被在途 run init 写回撤销事故):
|
|
35
34
|
* 每次「用户意图移动 session 指针」+1。system.init 写回通道不 bump。
|
|
@@ -47,7 +46,7 @@ export declare class SessionStore {
|
|
|
47
46
|
bumpSessionEpoch(userId: string, agent: AgentKind): void;
|
|
48
47
|
/** 用户意图移动了全部 agent 的 session 指针(/cd、/ws use 的 setCwd)。 */
|
|
49
48
|
private bumpUserEpoch;
|
|
50
|
-
constructor(persistPath?: string
|
|
49
|
+
constructor(persistPath?: string);
|
|
51
50
|
/** A fresh, empty session entry (default factory). */
|
|
52
51
|
private static emptyEntry;
|
|
53
52
|
/**
|
|
@@ -3,7 +3,7 @@ import { getLogger } from '../logger/index.js';
|
|
|
3
3
|
import { atomicWriteJson } from '../persistence/atomic-write.js';
|
|
4
4
|
/**
|
|
5
5
|
* Per-user session store. Persists cwd + per-agent sessions to disk so that
|
|
6
|
-
* the last
|
|
6
|
+
* the last cwd AND the last-used sessionId per agent are restored after
|
|
7
7
|
* a lark-remote restart.
|
|
8
8
|
*
|
|
9
9
|
* Persistence strategy:
|
|
@@ -13,13 +13,12 @@ import { atomicWriteJson } from '../persistence/atomic-write.js';
|
|
|
13
13
|
* - Atomic write (temp + rename) to avoid truncated files on crash
|
|
14
14
|
* - Corrupt files are treated as empty (with a warning)
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* 3: sessions is Map<AgentKind, sessionId> (one session per agent), allowing
|
|
17
17
|
* each agent to maintain its own session context when switching between agents.
|
|
18
18
|
*/
|
|
19
19
|
export class SessionStore {
|
|
20
20
|
sessions = new Map();
|
|
21
21
|
persistPath;
|
|
22
|
-
defaultAgent;
|
|
23
22
|
/**
|
|
24
23
|
* 进程内会话代际(2026-08-09 /new 被在途 run init 写回撤销事故):
|
|
25
24
|
* 每次「用户意图移动 session 指针」+1。system.init 写回通道不 bump。
|
|
@@ -47,9 +46,8 @@ export class SessionStore {
|
|
|
47
46
|
bumpUserEpoch(userId) {
|
|
48
47
|
this.userEpochs.set(userId, (this.userEpochs.get(userId) ?? 0) + 1);
|
|
49
48
|
}
|
|
50
|
-
constructor(persistPath
|
|
49
|
+
constructor(persistPath) {
|
|
51
50
|
this.persistPath = persistPath;
|
|
52
|
-
this.defaultAgent = defaultAgent;
|
|
53
51
|
if (persistPath)
|
|
54
52
|
this.load();
|
|
55
53
|
}
|
package/dist/update/index.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* package installation (detect package manager + global install),
|
|
6
6
|
* and the combined workflow for the /update command and --update CLI flag.
|
|
7
7
|
*/
|
|
8
|
-
export {
|
|
8
|
+
export { compareVersions, checkLatestVersion, formatUpdateHint, type VersionCheckResult, } from './version-check.js';
|
|
9
9
|
export { runInstallLatest, type InstallResult } from './install.js';
|
package/dist/update/index.js
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* package installation (detect package manager + global install),
|
|
6
6
|
* and the combined workflow for the /update command and --update CLI flag.
|
|
7
7
|
*/
|
|
8
|
-
export {
|
|
8
|
+
export { compareVersions, checkLatestVersion, formatUpdateHint, } from './version-check.js';
|
|
9
9
|
export { runInstallLatest } from './install.js';
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
|
@@ -12,10 +12,12 @@ export interface VersionCheckResult {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Compare two semver strings (major.minor.patch).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
*
|
|
16
|
+
* Returns the sign of `latest - current`: `1` if latest is newer, `-1` if it
|
|
17
|
+
* is older, `0` if equal. Returns `null` if either version is not a valid
|
|
18
|
+
* semver string (callers should treat `null` as "not newer").
|
|
17
19
|
*/
|
|
18
|
-
export declare function
|
|
20
|
+
export declare function compareVersions(current: string, latest: string): -1 | 0 | 1 | null;
|
|
19
21
|
/**
|
|
20
22
|
* Check the latest available version of lark-remote.
|
|
21
23
|
* Uses a local cache file with 1-hour TTL to avoid frequent network requests.
|
|
@@ -10,10 +10,12 @@ export const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
|
10
10
|
const DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org/lark-remote/latest';
|
|
11
11
|
/**
|
|
12
12
|
* Compare two semver strings (major.minor.patch).
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
*
|
|
14
|
+
* Returns the sign of `latest - current`: `1` if latest is newer, `-1` if it
|
|
15
|
+
* is older, `0` if equal. Returns `null` if either version is not a valid
|
|
16
|
+
* semver string (callers should treat `null` as "not newer").
|
|
15
17
|
*/
|
|
16
|
-
export function
|
|
18
|
+
export function compareVersions(current, latest) {
|
|
17
19
|
const parse = (v) => {
|
|
18
20
|
// Split core and pre-release suffix; compare the core numerically and treat
|
|
19
21
|
// a release as newer than any pre-release of the same core version.
|
|
@@ -32,14 +34,14 @@ export function isNewer(current, latest) {
|
|
|
32
34
|
return null;
|
|
33
35
|
for (let i = 0; i < 3; i++) {
|
|
34
36
|
if (l.nums[i] > c.nums[i])
|
|
35
|
-
return
|
|
37
|
+
return 1;
|
|
36
38
|
if (l.nums[i] < c.nums[i])
|
|
37
|
-
return
|
|
39
|
+
return -1;
|
|
38
40
|
}
|
|
39
41
|
// Equal core version: a release beats a pre-release of the same version.
|
|
40
42
|
if (c.pre !== l.pre)
|
|
41
|
-
return l.pre === '';
|
|
42
|
-
return
|
|
43
|
+
return l.pre === '' ? 1 : -1;
|
|
44
|
+
return 0;
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
47
|
* Fetch the latest version from npm registry.
|
|
@@ -151,7 +153,7 @@ export async function checkLatestVersion(opts) {
|
|
|
151
153
|
* Returns null if no newer version is available.
|
|
152
154
|
*/
|
|
153
155
|
export function formatUpdateHint(current, latest) {
|
|
154
|
-
if (
|
|
156
|
+
if (compareVersions(current, latest) !== 1)
|
|
155
157
|
return null;
|
|
156
158
|
return `📦 有新版本 ${latest} 可用(当前 ${current}),发送 /update 升级`;
|
|
157
159
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** Public view of a
|
|
1
|
+
/** Public view of a cwd entry returned by list(). */
|
|
2
2
|
export interface WorkspaceEntry {
|
|
3
3
|
name: string;
|
|
4
4
|
path: string;
|
|
5
5
|
lastUsedAt: number;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* Persistent named-alias store for
|
|
8
|
+
* Persistent named-alias store for cwd directories.
|
|
9
9
|
*
|
|
10
10
|
* Atomic write (§9.10): writes a temp file then `fs.rename` so a crash
|
|
11
11
|
* mid-write cannot leave a truncated JSON file. On load, a corrupt file
|
|
@@ -13,7 +13,7 @@ export interface WorkspaceEntry {
|
|
|
13
13
|
*
|
|
14
14
|
* lastUsedAt 语义(2026-08-19 语义对齐):保存(save)与使用(use/touch)
|
|
15
15
|
* 都会把时间戳更新为当前时间。save 内部调用 touch,保证「最近使用」排序
|
|
16
|
-
* 对新保存的
|
|
16
|
+
* 对新保存的 cwd 同样生效(新条目应排在列表最前,而非沉底)。
|
|
17
17
|
* 同一毫秒内连续操作时 touch 会用 max+1 保证严格递增,避免 Date.now 相同
|
|
18
18
|
* 导致最近排序退化为字母序。
|
|
19
19
|
*
|
|
@@ -42,7 +42,7 @@ export declare class WorkspaceStore {
|
|
|
42
42
|
*/
|
|
43
43
|
touch(name: string): void;
|
|
44
44
|
/**
|
|
45
|
-
* List all
|
|
45
|
+
* List all cwd entries in insertion order.
|
|
46
46
|
* Sorting is the caller's responsibility (view concern).
|
|
47
47
|
*/
|
|
48
48
|
list(): WorkspaceEntry[];
|