@thincoder/core 0.9.1
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/abort-provenance.mjs +116 -0
- package/advisor/citations.mjs +139 -0
- package/advisor/compaction.mjs +174 -0
- package/advisor/convergence.mjs +80 -0
- package/advisor/history.mjs +77 -0
- package/advisor/loop.mjs +288 -0
- package/advisor/messages.mjs +299 -0
- package/advisor/notice.mjs +141 -0
- package/advisor/project-context.mjs +197 -0
- package/advisor/repos.mjs +150 -0
- package/advisor/run.mjs +190 -0
- package/advisor/truncate.mjs +57 -0
- package/advisor.mjs +281 -0
- package/agent/child-marks.mjs +24 -0
- package/agent/completion.mjs +145 -0
- package/agent/dispatch.mjs +493 -0
- package/agent/family-tools.mjs +174 -0
- package/agent/helpers.mjs +412 -0
- package/agent/post-turn.mjs +70 -0
- package/agent/record-results.mjs +174 -0
- package/agent/relay-prefix.mjs +39 -0
- package/agent/run-stages.mjs +244 -0
- package/agent/setup-reminders.mjs +199 -0
- package/agent/setup.mjs +234 -0
- package/agent/spawn-child.mjs +258 -0
- package/agent/suspension.mjs +240 -0
- package/agent/write-gate.mjs +87 -0
- package/agent-tools/advisor-async.mjs +481 -0
- package/agent-tools/advisor-settle.mjs +240 -0
- package/agent-tools/advisor.mjs +280 -0
- package/agent-tools/async-discard.mjs +143 -0
- package/agent-tools/async-settle.mjs +299 -0
- package/agent-tools/batch-segment.mjs +265 -0
- package/agent-tools/child-permission.mjs +45 -0
- package/agent-tools/consult.mjs +471 -0
- package/agent-tools/design-token.mjs +117 -0
- package/agent-tools/digest-budget.mjs +76 -0
- package/agent-tools/eng.mjs +102 -0
- package/agent-tools/escalate-async.mjs +302 -0
- package/agent-tools/goal.mjs +119 -0
- package/agent-tools/panel-blocks.mjs +24 -0
- package/agent-tools/parent-channel.mjs +231 -0
- package/agent-tools/plan.mjs +86 -0
- package/agent-tools/read-history.mjs +309 -0
- package/agent-tools/recent-changes.mjs +24 -0
- package/agent-tools/review-facts.mjs +31 -0
- package/agent-tools/settings.mjs +268 -0
- package/agent-tools/skill.mjs +63 -0
- package/agent-tools/spawn-gates.mjs +109 -0
- package/agent-tools/subagent-actions.mjs +496 -0
- package/agent-tools/subagent-async.mjs +456 -0
- package/agent-tools/subagent-panel.mjs +160 -0
- package/agent-tools/subagent-run.mjs +208 -0
- package/agent-tools/subagent-scheduler.mjs +446 -0
- package/agent-tools/subagent-spawn.mjs +478 -0
- package/agent-tools/subagent.mjs +419 -0
- package/agent-tools/task.mjs +87 -0
- package/agent-tools/timer.mjs +46 -0
- package/agent-tools/verify.mjs +295 -0
- package/agent-tools.mjs +23 -0
- package/agent.mjs +430 -0
- package/auto-think.mjs +115 -0
- package/compress-form.mjs +24 -0
- package/config-io.mjs +277 -0
- package/config-migrate.mjs +178 -0
- package/config-presets.mjs +49 -0
- package/config.mjs +419 -0
- package/context.mjs +495 -0
- package/conventions.mjs +223 -0
- package/embedding.mjs +120 -0
- package/escape.mjs +152 -0
- package/expand-home.mjs +16 -0
- package/explore-distill.mjs +152 -0
- package/generate-title.mjs +123 -0
- package/git/checkpoint.mjs +448 -0
- package/git/gitmem.mjs +100 -0
- package/history-window.mjs +179 -0
- package/hooks.mjs +108 -0
- package/i18n.mjs +106 -0
- package/index-bin.mjs +48 -0
- package/index-discover.mjs +176 -0
- package/ledger-cmd.mjs +209 -0
- package/ledger-db.mjs +86 -0
- package/ledger-surface.mjs +76 -0
- package/ledger.mjs +202 -0
- package/log.mjs +195 -0
- package/manifest.mjs +338 -0
- package/markdown.mjs +106 -0
- package/mcp/helpers.mjs +51 -0
- package/mcp/transport-http.mjs +248 -0
- package/mcp/transport-stdio.mjs +140 -0
- package/mcp/transport-ws.mjs +122 -0
- package/mcp.mjs +295 -0
- package/memory/code-index.mjs +219 -0
- package/memory/code-sync.mjs +427 -0
- package/memory/core.mjs +318 -0
- package/memory/delete.mjs +242 -0
- package/memory/docs.mjs +431 -0
- package/memory/file-walk.mjs +109 -0
- package/memory/origin.mjs +24 -0
- package/memory/scan.mjs +177 -0
- package/memory/schema.mjs +460 -0
- package/memory.mjs +21 -0
- package/model-ref.mjs +66 -0
- package/model-specs.mjs +277 -0
- package/package.json +32 -0
- package/peer-domains.mjs +265 -0
- package/peer-instances.mjs +178 -0
- package/permission.mjs +79 -0
- package/process-probe.mjs +315 -0
- package/prompt-files.mjs +113 -0
- package/prompt-overlays.mjs +78 -0
- package/prompts/advisor-design.md +43 -0
- package/prompts/advisor-round1.md +41 -0
- package/prompts/advisor-round2.md +46 -0
- package/prompts/advisor-round3.md +42 -0
- package/prompts/common.md +158 -0
- package/prompts/consult-base.md +19 -0
- package/prompts/discipline-engineering.md +123 -0
- package/prompts/discipline-normal.md +206 -0
- package/prompts/persona-coder.md +21 -0
- package/prompts/persona-eng-coder.md +41 -0
- package/prompts/persona-eng-designer.md +80 -0
- package/prompts/persona-engineering.md +160 -0
- package/prompts/persona-explore.md +15 -0
- package/prompts/persona-normal.md +35 -0
- package/prompts/persona-plan.md +27 -0
- package/provider/anthropic.mjs +225 -0
- package/provider/core.mjs +491 -0
- package/provider/errors.mjs +101 -0
- package/provider/google.mjs +257 -0
- package/provider/index.mjs +7 -0
- package/provider/list-models.mjs +163 -0
- package/provider/normalize.mjs +81 -0
- package/provider/rate.mjs +168 -0
- package/provider/responses.mjs +495 -0
- package/provider/retry.mjs +88 -0
- package/provider/sse.mjs +264 -0
- package/provider/wait-status.mjs +59 -0
- package/proxy.mjs +274 -0
- package/rules.mjs +53 -0
- package/session-gc.mjs +248 -0
- package/session-guard.mjs +59 -0
- package/session-lifecycle.mjs +305 -0
- package/session-migrate.mjs +48 -0
- package/session-rename.mjs +38 -0
- package/session-segments.mjs +99 -0
- package/session-slot-write.mjs +168 -0
- package/session-slots-manifest.mjs +264 -0
- package/session-slots.mjs +298 -0
- package/session-store.mjs +441 -0
- package/session.mjs +244 -0
- package/skills.mjs +234 -0
- package/text-budget.mjs +79 -0
- package/token-ttl.mjs +285 -0
- package/tool-docs/apply_patch.md +15 -0
- package/tool-docs/bash.md +38 -0
- package/tool-docs/delete.md +13 -0
- package/tool-docs/edit.md +30 -0
- package/tool-docs/execute.md +21 -0
- package/tool-docs/fetch.md +12 -0
- package/tool-docs/file_ops.md +17 -0
- package/tool-docs/get_current_time.md +8 -0
- package/tool-docs/git.md +54 -0
- package/tool-docs/glob.md +11 -0
- package/tool-docs/grep.md +19 -0
- package/tool-docs/hashline_edit.md +14 -0
- package/tool-docs/insert_after.md +15 -0
- package/tool-docs/lint.md +10 -0
- package/tool-docs/ls.md +12 -0
- package/tool-docs/lsp.md +10 -0
- package/tool-docs/process.md +10 -0
- package/tool-docs/question.md +16 -0
- package/tool-docs/read.md +20 -0
- package/tool-docs/read_image.md +8 -0
- package/tool-docs/tree.md +14 -0
- package/tool-docs/wait_for.md +22 -0
- package/tool-docs/websearch.md +16 -0
- package/tool-docs/write.md +11 -0
- package/tools/bash.mjs +276 -0
- package/tools/edit-batch.mjs +204 -0
- package/tools/edit-diff.mjs +388 -0
- package/tools/exec-run.mjs +43 -0
- package/tools/execute.mjs +243 -0
- package/tools/file.mjs +464 -0
- package/tools/git-checkpoint.mjs +143 -0
- package/tools/git-ext.mjs +173 -0
- package/tools/git.mjs +415 -0
- package/tools/glob-dialect.mjs +130 -0
- package/tools/index.mjs +76 -0
- package/tools/linter.mjs +120 -0
- package/tools/lsp.mjs +335 -0
- package/tools/ops.mjs +293 -0
- package/tools/patch.mjs +290 -0
- package/tools/question.mjs +26 -0
- package/tools/repomap.mjs +314 -0
- package/tools/search.mjs +248 -0
- package/tools/shared.mjs +467 -0
- package/tools/tree.mjs +81 -0
- package/tools/web.mjs +224 -0
- package/tools/write-path.mjs +191 -0
- package/traces/trace-store.mjs +303 -0
- package/undo-stack.mjs +47 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-slots.mjs — slot / manifest 管理(2026-08-31 advisor round1 🔴 拆分:
|
|
3
|
+
* session.mjs 曾超 500 行硬限;slot 所有权、认领、清单与核心读写分离,session.mjs
|
|
4
|
+
* re-export 全部导出以保持既有 import 兼容)。
|
|
5
|
+
*
|
|
6
|
+
* init-block 批 · F-MI7 二次拆分:探测束接线后本档实读 517 行(破 490 判定线并越 500 硬限)
|
|
7
|
+
* ⇒ 按 `CORE-UNIFICATION.md` §2.8.1 表第 3 行**随批**外提清单 / 认领 / 属主面至
|
|
8
|
+
* `session-slots-manifest.mjs`(语义原样迁移;本档 re-export 该档导出保既有 import 面——
|
|
9
|
+
* 消费档 import 路径与名面不动)。本档保留 = 存储原语(cwd 哈希 / 路径 / 原子写 / 进程
|
|
10
|
+
* sessionId)· 端记录(end marker)· 列表面(listSlots / loadSlotMeta)· 删槽 · 恢复决策面
|
|
11
|
+
* (usableSlot / loadLegacyFile / resumeSlot)。
|
|
12
|
+
*
|
|
13
|
+
* 模型:每个项目(cwd hash)拥有无限编号 slot;manifest 记录 active 指针 + 每个
|
|
14
|
+
* slot 的属主进程(slotSessions: slot → "pid-timestamp-random",CLI ↔ VS Code
|
|
15
|
+
* 共享 manifest 以互斥认领)。
|
|
16
|
+
*
|
|
17
|
+
* 属主判定(init-block 批 · F-MI7 · SESSION.md §6.2):**入口一次探测束**
|
|
18
|
+
* (`probeOwnersSync` / `probeOwnersAsync`——≤1 判活 + ≤1 命令行拿全量属主 pid),
|
|
19
|
+
* 清理 / 占用 / 空闲一律**查表**(`ownerState` 三态:dead / alive / unknown)——
|
|
20
|
+
* 本档零自有 exec、零逐 pid 探测。unknown(探测失败 / 缺行)⇒ 不认领 / 不判死 / 不删,
|
|
21
|
+
* 方向不对称见 process-probe.mjs 头注(D-MI10)。
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createHash } from "node:crypto"
|
|
25
|
+
import { mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, existsSync } from "node:fs"
|
|
26
|
+
import { join, dirname, basename } from "node:path"
|
|
27
|
+
import { configDir } from "./config.mjs"
|
|
28
|
+
import { migrateHashLength } from "./session-migrate.mjs"
|
|
29
|
+
// §6.10 D-2(2026-09-05):resumeSlot 决策归本文件(slot/claim 层);data 层读(loadSlotFile/
|
|
30
|
+
// legacy 过滤)属 session.mjs(500 行内不迁移)——静态环(本档 ↔ session.mjs;init-block 批起
|
|
31
|
+
// 另加本档 ↔ session-slots-manifest.mjs,见头注与 L38-47):函数声明实例化期已初始化、只函数
|
|
32
|
+
// 体内运行时使用(环安全);VS Code 端 session-slots ↔ session-io 同构镜像。
|
|
33
|
+
import { loadSlotFile, isLegacyTransient } from "./session.mjs"
|
|
34
|
+
// TUI-OOM-ROOTCAUSE 批(SESSION.md §6.14 生命周期联动):删槽联动记录存储(store 零项目内依赖——无环)。
|
|
35
|
+
import { unlinkRecordStore } from "./session-store.mjs"
|
|
36
|
+
// 探测束(init-block 批 · F-MI7):实现 / 判据单源住 process-probe.mjs——本档零自有 exec、
|
|
37
|
+
// 零逐 pid 探测(判据面与探测面分离);单 pid 判活 re-export 见下方「判活兼容面」。
|
|
38
|
+
import { probeOwnersAsync, isProcessAlive } from "./process-probe.mjs"
|
|
39
|
+
// 清单 / 认领 / 属主面(init-block 批外提——双向静态环见头注):本档 import 取用 + re-export
|
|
40
|
+
// 保既有 import 面(消费档路径与名面不动)。
|
|
41
|
+
import {
|
|
42
|
+
extractSlotMeta, loadManifest, saveManifest, ownerPids, ownerStateOf, cleanDeadOwners,
|
|
43
|
+
allocateFresh, claimSlot, activeSlot,
|
|
44
|
+
} from "./session-slots-manifest.mjs"
|
|
45
|
+
export {
|
|
46
|
+
slotDigest, loadManifest, saveManifest, ownerPid, ownerPids,
|
|
47
|
+
allocateFresh, claimSlot, activeSlot,
|
|
48
|
+
} from "./session-slots-manifest.mjs"
|
|
49
|
+
|
|
50
|
+
let currentSessionId = null
|
|
51
|
+
|
|
52
|
+
/** Generate unique session ID for this process */
|
|
53
|
+
export function getSessionId() {
|
|
54
|
+
if (!currentSessionId) {
|
|
55
|
+
currentSessionId = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
56
|
+
}
|
|
57
|
+
return currentSessionId
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Normalize cwd for hashing: uppercase Windows drive letter so both ends
|
|
61
|
+
* (CLI's process.cwd() vs VS Code's uri.fsPath, which lowercases it) agree. */
|
|
62
|
+
export function normalizeCwd(cwd) {
|
|
63
|
+
return cwd.replace(/^([a-z]):/, (_, d) => d.toUpperCase() + ":")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Full sha1 hex (40 chars), not truncated. Shared contract with the VS Code extension. */
|
|
67
|
+
function cwdHash(cwd) {
|
|
68
|
+
return createHash("sha1").update(normalizeCwd(cwd)).digest("hex")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** sessions 根目录隔离缝(2026-09-08 DESIGN-TOKEN-SETTLEMENT 测试——VSC 端
|
|
72
|
+
* _setSessionsDirForTest 同构):默认 null 走真实 configDir;测试指向临时目录
|
|
73
|
+
* (beforeEach 设、afterEach 复位)。migrateHashLength 的 legacy 迁移仍查真实
|
|
74
|
+
* 目录(temp 哈希无 legacy 文件——no-op 无害)。 */
|
|
75
|
+
let sessionsDirOverride = null
|
|
76
|
+
export function _setSessionsDirForTest(dir) { sessionsDirOverride = dir }
|
|
77
|
+
export function _resetSessionsDirForTest() { sessionsDirOverride = null }
|
|
78
|
+
function sessionsBase() { return sessionsDirOverride ?? join(configDir, "sessions") }
|
|
79
|
+
|
|
80
|
+
/** Derive base session path from cwd hash. Migrates legacy short-hash files on first access. */
|
|
81
|
+
export function sessionPath(cwd) {
|
|
82
|
+
const hash = cwdHash(cwd)
|
|
83
|
+
migrateHashLength(cwd, hash)
|
|
84
|
+
return join(sessionsBase(), `${hash}.json`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function slotPath(cwd, n) { return sessionPath(cwd) + "." + n }
|
|
88
|
+
export function manifestPath(cwd) { return sessionPath(cwd) + ".manifest" }
|
|
89
|
+
|
|
90
|
+
// ========== end marker(SESSION.md §6.10 端分离恢复——本端"最后使用槽位"记录)==========
|
|
91
|
+
|
|
92
|
+
/** 端常量:本端记录文件后缀——CLI 写 .cli、VS Code 写 .vscode,互不触碰
|
|
93
|
+
* (NF1:本端记录是本端单写者文件——不新增跨端共享可变字段)。 */
|
|
94
|
+
export const END = "cli"
|
|
95
|
+
|
|
96
|
+
/** 本端记录路径:{manifest}.{END}(manifest 旁的独立小文件,非内嵌字段——NF1)。 */
|
|
97
|
+
export function endMarkerPath(cwd) { return `${manifestPath(cwd)}.${END}` }
|
|
98
|
+
|
|
99
|
+
/** 读本端记录:返回 { slot: <number|null>, updatedAt } 或 null。三态语义(D-1):
|
|
100
|
+
* 文件缺失 = 从未记录(升级/首用迁移窗口——可能触发一次性继承);
|
|
101
|
+
* JSON 解析失败/结构非法(损坏)= 按"缺失"降级——不 rename 不 unlink(幂等、不误伤,
|
|
102
|
+
* T-M13);`slot: null` = 显式置空(删过本端记录槽——绝不触发继承,T-M4)。 */
|
|
103
|
+
export function readEndMarker(cwd) {
|
|
104
|
+
try {
|
|
105
|
+
const p = endMarkerPath(cwd)
|
|
106
|
+
if (!existsSync(p)) return null
|
|
107
|
+
const m = JSON.parse(readFileSync(p, "utf8"))
|
|
108
|
+
if (m && m.slot === null) return { slot: null, updatedAt: m.updatedAt ?? null }
|
|
109
|
+
if (m && Number.isInteger(m.slot) && m.slot >= 1) return { slot: m.slot, updatedAt: m.updatedAt ?? null }
|
|
110
|
+
return null
|
|
111
|
+
} catch { return null }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 写本端记录(原子 .tmp+rename,复用 writeSessionFile):slot = 目标槽号或 null(显式置空)。
|
|
115
|
+
* 失败容忍(NF2):写失败按无记录路径降级——不抛错,不影响会话数据(T-M14)。 */
|
|
116
|
+
export function writeEndMarker(cwd, slot) {
|
|
117
|
+
try {
|
|
118
|
+
writeSessionFile(endMarkerPath(cwd), { slot, updatedAt: Date.now() })
|
|
119
|
+
} catch { /* NF2:失败容忍 */ }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Path to the active slot's file */
|
|
123
|
+
export function activePath(cwd) {
|
|
124
|
+
return slotPath(cwd, activeSlot(cwd))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Atomic write: write to temp file then rename to replace, preventing truncated JSON from mid-write crash. */
|
|
128
|
+
export function writeSessionFile(p, data) {
|
|
129
|
+
mkdirSync(dirname(p), { recursive: true })
|
|
130
|
+
const tmp = `${p}.tmp`
|
|
131
|
+
writeFileSync(tmp, JSON.stringify(data), "utf8")
|
|
132
|
+
try {
|
|
133
|
+
renameSync(tmp, p)
|
|
134
|
+
} catch {
|
|
135
|
+
try { unlinkSync(p) } catch {}
|
|
136
|
+
try {
|
|
137
|
+
renameSync(tmp, p)
|
|
138
|
+
try { unlinkSync(tmp) } catch {}
|
|
139
|
+
} catch {
|
|
140
|
+
writeFileSync(p, readFileSync(tmp, "utf8"), "utf8")
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ========== 清单 / 认领 / 属主面(init-block 批外提——见头注)==========
|
|
146
|
+
// 摘要面(slotDigest)· 清单读写(loadManifest / saveManifest)· 属主面(ownerPid / ownerPids /
|
|
147
|
+
// ownerStateOf / cleanDeadOwners)· 认领面(ensureActive / allocateFresh / claimSlot / activeSlot)
|
|
148
|
+
// 语义原样迁至 `session-slots-manifest.mjs`;本档上方 import 取用、re-export 保既有 import 面。
|
|
149
|
+
|
|
150
|
+
/** 单 pid 判活(F-MI7:实现移居 `process-probe.mjs`——判据单源 + 同步有界 2 s + 三态):
|
|
151
|
+
* 此处 re-export 保既有 import 面(`session.mjs` 再 re-export 同保)。
|
|
152
|
+
* **三态**:`true` 活 / `false` 死 / `undefined` 未知——未知不作死判据(D-MI10)。 */
|
|
153
|
+
export { isProcessAlive }
|
|
154
|
+
|
|
155
|
+
/** Lazy-load slot metadata from slot file (for old-format manifest entries that lack metadata) */
|
|
156
|
+
function loadSlotMeta(cwd, slot, v) {
|
|
157
|
+
if (typeof v === "object" && v !== null && "ts" in v) return v
|
|
158
|
+
const ts = typeof v === "number" ? v : 0
|
|
159
|
+
try {
|
|
160
|
+
const p = slotPath(cwd, slot)
|
|
161
|
+
if (!existsSync(p)) return { ts }
|
|
162
|
+
const data = JSON.parse(readFileSync(p, "utf8"))
|
|
163
|
+
const history = data.history ?? []
|
|
164
|
+
const meta = extractSlotMeta(history, data.activeProvider, data.updatedAt ?? ts, data.title ?? "")
|
|
165
|
+
if (data.activeModel) meta.activeModel = data.activeModel
|
|
166
|
+
return { ts, ...meta }
|
|
167
|
+
} catch {
|
|
168
|
+
return { ts }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** List all slots, newest first. Includes isActive flag.
|
|
173
|
+
* 2026-09-01 会诊 🟢:只读操作不认领——原实现 active 缺失时调 activeSlot(写 manifest
|
|
174
|
+
* 副作用,ACP session/list 可触发)。m.active 缺失时全部 isActive=false,由下一次
|
|
175
|
+
* activeSlot 正常认领。 */
|
|
176
|
+
export function listSlots(cwd) {
|
|
177
|
+
const m = loadManifest(cwd)
|
|
178
|
+
const active = m.active ?? null
|
|
179
|
+
return Object.entries(m.slots)
|
|
180
|
+
.filter(([n]) => /^\d+$/.test(n))
|
|
181
|
+
.map(([n, v]) => {
|
|
182
|
+
const meta = loadSlotMeta(cwd, Number(n), v)
|
|
183
|
+
return {
|
|
184
|
+
slot: Number(n),
|
|
185
|
+
isActive: Number(n) === active,
|
|
186
|
+
timestamp: meta.ts,
|
|
187
|
+
date: new Date(meta.ts).toLocaleString(),
|
|
188
|
+
messageCount: meta.messageCount ?? 0,
|
|
189
|
+
turnCount: meta.turnCount ?? 0,
|
|
190
|
+
firstMessage: meta.firstMessage ?? "",
|
|
191
|
+
// MODEL-MERGE-SESSION 摘要 "p:m":activeProvider 保持裸渠道名——列表消费面显复合
|
|
192
|
+
// (旧摘要无 activeModel → 回退裸渠道名——新老兼容;cmd-session/VSC sessions 行免改)
|
|
193
|
+
activeProvider: meta.activeProvider ? (meta.activeModel ? `${meta.activeProvider}:${meta.activeModel}` : meta.activeProvider) : "",
|
|
194
|
+
updatedAt: meta.updatedAt ?? meta.ts,
|
|
195
|
+
updatedDate: new Date(meta.updatedAt ?? meta.ts).toLocaleString(),
|
|
196
|
+
title: meta.title ?? "",
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Delete a slot: remove its file and manifest entry. Deleting the active slot
|
|
203
|
+
* resets the manifest active pointer (the next claim re-creates one). */
|
|
204
|
+
export function deleteSlot(cwd, slot) {
|
|
205
|
+
const n = Number(slot)
|
|
206
|
+
if (!Number.isInteger(n) || n < 1) return false
|
|
207
|
+
const m = loadManifest(cwd)
|
|
208
|
+
if (!m.slots[n]) return false
|
|
209
|
+
delete m.slots[n]
|
|
210
|
+
delete m.slotSessions?.[n] // orphan session-id entries bloat the manifest forever
|
|
211
|
+
try { unlinkSync(slotPath(cwd, n)) } catch { /* missing file is fine */ }
|
|
212
|
+
unlinkRecordStore(slotPath(cwd, n)) // §6.14 生命周期联动:删槽连带删除记录存储(sidecar)
|
|
213
|
+
if (m.active === n) delete m.active
|
|
214
|
+
// setActive: true —— 显式表达"删到 active 时 active 置空"的意图(saveManifest 默认
|
|
215
|
+
// 保留 fresh.active,2026-09-01 会诊三家 🟡)
|
|
216
|
+
saveManifest(cwd, m, { slots: [n], slotSessions: [n] }, { setActive: true })
|
|
217
|
+
// 2026-09-05 §6.10 D-4/F4:删到本端记录槽 → 记录显式置空(文件保留 + slot:null——下次全新起步,不复活——T-M4/T-M8)
|
|
218
|
+
if (readEndMarker(cwd)?.slot === n) writeEndMarker(cwd, null)
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// renameSlot 已拆至 session-rename.mjs(2026-09-06 §6.12 标题写契约使本文件超 500 行硬限,
|
|
223
|
+
// 按 §6.12 模块与实现约束拆分);session.mjs re-export 保持调用点不变。
|
|
224
|
+
|
|
225
|
+
// ========== resumeSlot(SESSION.md §6.10 D-2——端分离恢复决策)==========
|
|
226
|
+
|
|
227
|
+
/** 本端记录槽可用判据(D-2):slot ∈ m.slots + 槽文件在盘 + 属主 空、死、本进程。
|
|
228
|
+
* F-MI7:占用判据查调用面束(零自有探测);属主 unknown(探测失败 / 缺行)⇒ **不可用**
|
|
229
|
+
* (不认领——保守:回落到 allocateFresh 取全新号,绝不与"可能活着"的属主同槽)。 */
|
|
230
|
+
function usableSlot(cwd, m, slot, bundle = null) {
|
|
231
|
+
if (!m.slots[slot] || !existsSync(slotPath(cwd, slot))) return false
|
|
232
|
+
const owner = m.slotSessions?.[slot]
|
|
233
|
+
if (!owner || owner === getSessionId()) return true
|
|
234
|
+
return ownerStateOf(owner, bundle) === "dead"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** legacy 单文件兜底(v1/v2 单会话 {hash}.json——迁移前残留;仅 data 层——2026-09-05 §6.10
|
|
238
|
+
* D-2:恢复数据兜底不改变 claim 落点,T-M10)。纪律与 loadSlotFile 一致:cwd 不匹配(别人的文件)
|
|
239
|
+
* 直接 null 不改名;坏结构改名 .unreadable 保留(version>2 不动);解析失败 .corrupted 保留。 */
|
|
240
|
+
function loadLegacyFile(cwd) {
|
|
241
|
+
const legacy = sessionPath(cwd)
|
|
242
|
+
try {
|
|
243
|
+
if (existsSync(legacy)) {
|
|
244
|
+
const data = JSON.parse(readFileSync(legacy, "utf8"))
|
|
245
|
+
// cwd 不匹配是别人的文件——与 loadSlotFile 一致直接 return null 不改名
|
|
246
|
+
// ("别人的文件不动"原则,2026-08-31 advisor round2 🟡)
|
|
247
|
+
if (data.cwd && data.cwd.toLowerCase() !== cwd.toLowerCase()) return null
|
|
248
|
+
if ((data?.version === 1 || data?.version === 2) && Array.isArray(data.history)) {
|
|
249
|
+
data.history = data.history.filter((m) => !isLegacyTransient(m))
|
|
250
|
+
return data
|
|
251
|
+
}
|
|
252
|
+
// 结构不匹配:保留现场(version>2 的新版文件不动)
|
|
253
|
+
if (!(typeof data?.version === "number" && data.version > 2)) {
|
|
254
|
+
try { renameSync(legacy, `${legacy}.unreadable`) } catch {}
|
|
255
|
+
console.error(`[session] legacy file ${legacy}: invalid structure — preserved as ${basename(legacy)}.unreadable`)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.error(`[session] failed to load legacy ${legacy}: ${e.message}`)
|
|
260
|
+
try { renameSync(legacy, `${legacy}.corrupted`) } catch {}
|
|
261
|
+
}
|
|
262
|
+
return null
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 恢复决策(SESSION.md §6.10 D-2)——TUI 启动的本端恢复入口(VS Code 面板同构镜像)。
|
|
267
|
+
* **async**(F-MI7:入口一次异步束——探测不阻塞事件循环;调用面必须 await)。
|
|
268
|
+
* 返回 { slot, data }(data 可为 null——全新起步或读槽失败)。判据:
|
|
269
|
+
* ① 本端记录可用(slot ≠ null 且 ∈ m.slots 且槽文件在盘 且属主 空/死/本进程)→ claimSlot;
|
|
270
|
+
* ② 记录缺失(从未记录 = 升级/首用迁移窗口):②a active 属主 = 本进程(同进程重入——
|
|
271
|
+
* ensureActive 早退语义镜像:认领后尚未写 slots 条目/文件的窗口)→ 直接沿用;
|
|
272
|
+
* ②b 否则一次性继承 manifest.active(同判据;活属主绝不继承——全新槽起步,T-M3);
|
|
273
|
+
* ③ 其余一切(slot:null 显式置空 / 槽被删 / 属主为活外人 / 继承失败)→ allocateFresh。
|
|
274
|
+
* 每次落点都写本端记录;claim 后读槽失败(.corrupted/.unreadable——loadSlotFile 既有改名
|
|
275
|
+
* 保全语义)→ 保持已 claim 槽 + data:null——不改 marker——下次保存原地重建(T-M15)。
|
|
276
|
+
*/
|
|
277
|
+
export async function resumeSlot(cwd) {
|
|
278
|
+
const m = loadManifest(cwd)
|
|
279
|
+
m.slotSessions ??= {}
|
|
280
|
+
// 入口一次**异步束**(F-MI7——整链 async):清理 / 可用 / 空闲全部查表,零逐 pid 探测。
|
|
281
|
+
const bundle = await probeOwnersAsync(ownerPids(m))
|
|
282
|
+
// 与 ensureActive 同型的死主清理(认领路径持久化——F5a 纪律:仅传 m 等于没删)
|
|
283
|
+
const deadParam = cleanDeadOwners(m, bundle)
|
|
284
|
+
const rec = readEndMarker(cwd) // null = 缺失/损坏;{slot:null|N} = 文件在(D-1)
|
|
285
|
+
let slot = null
|
|
286
|
+
if (rec?.slot != null && usableSlot(cwd, m, rec.slot, bundle)) slot = rec.slot // ① 本端记录可用
|
|
287
|
+
else if (rec === null) {
|
|
288
|
+
if (m.active && m.slotSessions?.[m.active] === getSessionId()) slot = m.active // ②a 同进程重入
|
|
289
|
+
else if (m.active && usableSlot(cwd, m, m.active, bundle)) slot = m.active // ②b 一次性继承
|
|
290
|
+
}
|
|
291
|
+
if (slot !== null) claimSlot(cwd, slot, m, deadParam)
|
|
292
|
+
else slot = allocateFresh(cwd, m, deadParam, bundle) // ③ 全新分配(slot:null 绝不继承——T-M4)
|
|
293
|
+
// data 层:读已认领槽(loadSlotFile 自 session.mjs——环 import 见文件头);读失败/槽文件不在 → legacy 单文件兜底(仅 data)
|
|
294
|
+
let data = loadSlotFile(cwd, slot)
|
|
295
|
+
if (!data) data = loadLegacyFile(cwd)
|
|
296
|
+
writeEndMarker(cwd, slot)
|
|
297
|
+
return { slot, data }
|
|
298
|
+
}
|