@thincoder/core 0.9.5 → 0.10.2
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 +49 -0
- package/README.md +1 -1
- package/abort-provenance.mjs +10 -10
- package/advisor/citations.mjs +5 -5
- package/advisor/compaction.mjs +16 -15
- package/advisor/loop.mjs +12 -12
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +22 -12
- package/advisor.mjs +4 -4
- package/agent/assemble.mjs +111 -0
- package/agent/chat-call.mjs +68 -0
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +9 -2
- package/agent/continue-decision.mjs +25 -0
- package/agent/dispatch-gates.mjs +140 -0
- package/agent/dispatch-run.mjs +167 -0
- package/agent/dispatch.mjs +54 -296
- package/agent/family-tools.mjs +5 -5
- package/agent/helpers.mjs +53 -3
- package/agent/live-beat.mjs +49 -0
- package/agent/post-turn.mjs +4 -8
- package/agent/record-results.mjs +23 -20
- package/agent/relay-prefix.mjs +2 -2
- package/agent/run-stages.mjs +15 -11
- package/agent/run-start.mjs +111 -0
- package/agent/setup-reminders.mjs +9 -9
- package/agent/setup.mjs +32 -4
- package/agent/spawn-child.mjs +9 -9
- package/agent/suspension.mjs +89 -29
- package/agent/timers.mjs +113 -0
- package/agent/turn-loop.mjs +244 -0
- package/agent/write-gate.mjs +103 -6
- package/agent-tools/advisor-async.mjs +9 -9
- package/agent-tools/advisor-settle.mjs +12 -12
- package/agent-tools/advisor.mjs +36 -41
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +21 -18
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +57 -18
- package/agent-tools/batch-paths.mjs +52 -8
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +12 -65
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +56 -56
- package/agent-tools/context.mjs +4 -20
- package/agent-tools/design-token.mjs +3 -3
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/eng.mjs +5 -5
- package/agent-tools/escalate-async.mjs +16 -11
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +2 -2
- package/agent-tools/parent-channel.mjs +3 -13
- package/agent-tools/plan.mjs +3 -4
- package/agent-tools/read-history.mjs +12 -28
- package/agent-tools/recent-changes.mjs +4 -5
- package/agent-tools/review-facts.mjs +1 -1
- package/agent-tools/settings.mjs +47 -20
- package/agent-tools/skill.mjs +3 -5
- package/agent-tools/spawn-gates.mjs +3 -3
- package/agent-tools/subagent-actions-query.mjs +270 -0
- package/agent-tools/subagent-actions.mjs +21 -265
- package/agent-tools/subagent-async.mjs +25 -20
- package/agent-tools/subagent-panel.mjs +33 -4
- package/agent-tools/subagent-run.mjs +36 -21
- package/agent-tools/subagent-scheduler.mjs +28 -27
- package/agent-tools/subagent-spawn.mjs +45 -14
- package/agent-tools/subagent.mjs +33 -53
- package/agent-tools/task.mjs +3 -9
- package/agent-tools/timer.mjs +12 -12
- package/agent-tools/verify.mjs +14 -13
- package/agent-tools.mjs +0 -1
- package/agent.mjs +18 -355
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +11 -6
- package/config-watch.mjs +83 -0
- package/config.mjs +29 -12
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/history-window.mjs +17 -2
- package/i18n.mjs +2 -0
- package/index-discover.mjs +7 -7
- package/ledger-cmd.mjs +34 -126
- package/ledger-db.mjs +10 -2
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +9 -4
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-ref.mjs +96 -3
- package/model-specs.mjs +16 -7
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +2 -2
- package/peer-domains.mjs +7 -11
- package/peer-instances.mjs +9 -10
- package/process-probe-exec.mjs +187 -0
- package/process-probe.mjs +27 -175
- package/prompt-overlays.mjs +11 -10
- package/prompts/advisor-design.md +2 -2
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +16 -8
- package/prompts/discipline-engineering.md +57 -16
- package/prompts/discipline-normal.md +6 -3
- package/prompts/persona-coder.md +0 -1
- package/prompts/persona-eng-coder.md +1 -5
- package/prompts/persona-eng-designer.md +2 -3
- package/prompts/persona-engineering.md +32 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +1 -1
- package/prompts/persona-plan.md +3 -4
- package/prompts/subagent-base.md +14 -0
- package/provider/core.mjs +11 -48
- package/provider/list-models.mjs +3 -0
- package/provider/responses-request.mjs +237 -0
- package/provider/responses.mjs +6 -228
- package/provider-flows.mjs +248 -0
- package/queued.mjs +90 -0
- package/rules.mjs +209 -2
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +108 -27
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -241
- package/session-slots.mjs +99 -71
- package/session.mjs +30 -10
- package/shell-candidates.mjs +78 -0
- package/text-budget.mjs +2 -2
- package/think-off.mjs +49 -0
- package/token-ttl.mjs +15 -4
- package/tool-args.mjs +73 -0
- package/tool-docs/advisor.md +9 -0
- package/tool-docs/batch.md +1 -0
- package/tool-docs/code_search.md +1 -0
- package/tool-docs/consult_start.md +5 -0
- package/tool-docs/consult_stop.md +3 -0
- package/tool-docs/context.md +7 -0
- package/tool-docs/doc_search.md +1 -0
- package/tool-docs/edit.md +2 -13
- package/tool-docs/eng.md +1 -0
- package/tool-docs/execute.md +1 -9
- package/tool-docs/git.md +25 -51
- package/tool-docs/goal.md +6 -0
- package/tool-docs/ledger_add.md +1 -0
- package/tool-docs/ledger_close.md +1 -0
- package/tool-docs/ledger_count.md +1 -0
- package/tool-docs/ledger_query.md +1 -0
- package/tool-docs/ledger_update.md +1 -0
- package/tool-docs/main_history.md +1 -0
- package/tool-docs/memory.md +10 -0
- package/tool-docs/notify_parent.md +9 -0
- package/tool-docs/peer_instances.md +1 -0
- package/tool-docs/plan.md +5 -0
- package/tool-docs/process.md +7 -3
- package/tool-docs/read_history.md +7 -0
- package/tool-docs/recent_changes.md +1 -0
- package/tool-docs/repo_outline.md +1 -0
- package/tool-docs/settings.md +6 -0
- package/tool-docs/skill.md +5 -0
- package/tool-docs/subagent.md +31 -0
- package/tool-docs/task.md +7 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/verify.md +1 -0
- package/tool-docs/wait_for.md +2 -1
- package/tools/bash.mjs +28 -4
- package/tools/edit-batch.mjs +4 -4
- package/tools/edit-diff.mjs +4 -23
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +4 -9
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/index.mjs +2 -1
- package/tools/linter.mjs +2 -1
- package/tools/node-child.mjs +21 -0
- package/tools/ops.mjs +50 -13
- package/tools/patch.mjs +4 -2
- package/tools/repomap.mjs +20 -9
- package/tools/shared.mjs +8 -6
- package/tools/web.mjs +5 -2
- package/tools/write-path.mjs +1 -1
- package/traces/trace-store.mjs +17 -14
- package/vision-reader.mjs +76 -0
- package/agent-tools/batch-segment.mjs +0 -18
package/session-lifecycle.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* 硬限;各函数语义原样外提,SESSION.md §6.10 D-2 / §6.12 / §6.11 注记随迁):
|
|
4
4
|
* 本端恢复入口包装(resumeSlot——残留 GC 钩子)/ 槽数据应用(applySession)/ 新建槽
|
|
5
5
|
* (newSession)/ 运行态清空(resetSessionState)/ 槽切换(switchToSlot)/ 占用查询
|
|
6
|
-
* (slotOccupancy)/
|
|
6
|
+
* (slotOccupancy)/ 打开态读数(sessionReading——§6.24 只读纯投影,桌面残余批落)/
|
|
7
|
+
* v1 老文件清扫(stripTruncatedToolArgs)。
|
|
7
8
|
*
|
|
8
9
|
* 静态环(与 session-slots.mjs ↔ session.mjs 同形):本档引 `session.mjs` 的
|
|
9
10
|
* `loadSlotFile`(data 层读留 session.mjs——2026-09-05 §6.10 D-2 拆分点),session.mjs re-export
|
|
@@ -18,26 +19,41 @@
|
|
|
18
19
|
|
|
19
20
|
import { existsSync } from "node:fs"
|
|
20
21
|
import { resolveCompactThreshold } from "./config.mjs"
|
|
22
|
+
// 槽面判据单源(#841 KD-841-2:槽渠道 key 门 + 模型面序——PROVIDER.md §6.22)。
|
|
23
|
+
import { resolveProviderPlan } from "./model-ref.mjs"
|
|
21
24
|
import {
|
|
22
25
|
slotPath, loadManifest, saveManifest, slotDigest, writeSessionFile, getSessionId,
|
|
23
|
-
writeEndMarker, ownerPid, ownerPids,
|
|
26
|
+
writeEndMarker, sessionEnd, ownerPid, ownerPids,
|
|
24
27
|
resumeSlot as slotsResumeSlot,
|
|
25
28
|
} from "./session-slots.mjs"
|
|
26
29
|
import { probeOwnersSync, probeOwnersAsync, ownerState } from "./process-probe.mjs"
|
|
30
|
+
// 落点 A 顺手补写(§6.25 判据句 3 · LEDGER-RELIABILITY 批)——判据单源住清单档(与 resumeSlot 同函数)。
|
|
31
|
+
import { healDigest, slotMtime } from "./session-slots-manifest.mjs"
|
|
27
32
|
// 环 import:见头注(loadSlotFile 属 session.mjs 的 data 层读面)。
|
|
28
33
|
import { loadSlotFile } from "./session.mjs"
|
|
34
|
+
// 全新槽首物化构造器单源(SLOT-END-PARAM 批 §6.20 判据句 4 写面:「`newSession` 并入」——
|
|
35
|
+
// 与 VSC `newSlot` / `loadSlotForWrite` 全新分支同一构造器;环 import 同上,函数体内运行时使用)。
|
|
36
|
+
import { newSlotData } from "./session-slot-write.mjs"
|
|
29
37
|
import { mergeAdjacentAssistantEchoes } from "./context.mjs"
|
|
38
|
+
// 打开态读数公式(§6.24 判据句 2——与 CLI 状态行 / VSC 同式;零新公式,只组合)。
|
|
39
|
+
import { historyPercent } from "./token-window.mjs"
|
|
30
40
|
import { scheduleSessionGC } from "./session-gc.mjs"
|
|
31
41
|
import { restoreEngTokens } from "./token-ttl.mjs"
|
|
32
42
|
import { clearPlanMode } from "./agent-tools/plan.mjs"
|
|
33
43
|
import { bindRecordStore, unbindRecordStore, RECORD_WINDOW_MESSAGES } from "./session-store.mjs"
|
|
44
|
+
// 会话级档位(§6.21 判据句 4)判据单源:off 形族(`think-off.mjs`)+ 逐模型枚举
|
|
45
|
+
// (`model-specs.mjs`)——两叶档(零 import)⇒ 不新增环,核内零第二份族别表。
|
|
46
|
+
import { specForModel } from "./model-specs.mjs"
|
|
47
|
+
import { thinkOffShape, thinkOffPath } from "./think-off.mjs"
|
|
34
48
|
|
|
35
49
|
/** 恢复决策包装(SESSION.md §6.12 启动钩子,2026-09-06):本端恢复入口触发一次残留 GC——
|
|
36
50
|
* scheduleSessionGC 内部**启动窗外延迟拍**(`GC_PASS_DELAY_MS` = 3s)+ 每进程每前缀去重,
|
|
37
|
-
* **异步非阻塞**启动路径(N4;§6.17 D-SE39);**async**(F-MI7——调用面必须 await)。
|
|
38
|
-
|
|
51
|
+
* **异步非阻塞**启动路径(N4;§6.17 D-SE39);**async**(F-MI7——调用面必须 await)。
|
|
52
|
+
* `opts.end`(§6.20 判据句 2——显式端参 > 进程端名):本端记录端名透传(端壳经绑定转口
|
|
53
|
+
* 传本端常量;缺省 = 进程端名 ⇒ CLI 全调用点零改)。 */
|
|
54
|
+
export async function resumeSlot(cwd, { end = sessionEnd() } = {}) {
|
|
39
55
|
scheduleSessionGC(cwd)
|
|
40
|
-
return slotsResumeSlot(cwd)
|
|
56
|
+
return slotsResumeSlot(cwd, { end })
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
/** slimForDisplay 截断的 arguments 以 U+2026(…)结尾——不是合法 JSON 的完整值。
|
|
@@ -61,9 +77,9 @@ function stripTruncatedToolArgs(m) {
|
|
|
61
77
|
*
|
|
62
78
|
* MODEL-MERGE-SESSION(F-4)applySession 重写为两支 + 删旧支(评审 #7/#9 措辞——防
|
|
63
79
|
* "三支"误读):
|
|
64
|
-
* ① 槽 provider 存在 → provider/model 按槽值设(双字段恒非空——模型取 data.activeModel
|
|
65
|
-
*
|
|
66
|
-
* v2;老 models[]
|
|
80
|
+
* ① 槽 provider 存在 → provider/model 按槽值设(双字段恒非空——模型取 data.activeModel(**串值**;
|
|
81
|
+
* 非串 ∕ null ∕ 空串 = 未登记 ⇒ 回该渠道默认模型 `providers[].model` 单值——2026-09-10 MODEL-SELECTION
|
|
82
|
+
* v2;老 models[] 首候选形态已退场)+ 重算
|
|
67
83
|
* compactThreshold(auto 时——按恢复后模型)——不看 config(defaultModel 只是新会话起点);
|
|
68
84
|
* ② 槽 provider 没了 → D-S3 保留——静默保持现状(config 有效则有效——两方都无效由调用侧
|
|
69
85
|
* 复验 validateProvider 弹重选)。
|
|
@@ -126,6 +142,10 @@ export function applySession(agent, data, opts = {}) {
|
|
|
126
142
|
agent._verifyPassed = false
|
|
127
143
|
agent._slot = null // 粘性缓存清空——切换后重新认领(F1b)
|
|
128
144
|
agent._slotMtime = null
|
|
145
|
+
// 会话级档位携带(§6.21 判据句 5——保存面的一半):值 = 本槽 `effort` **原样**携带(表外 /
|
|
146
|
+
// 不可 off 的原值不静默改写——写面归一只在 `setSlotPrefs`);缺键(老槽)⇒ `undefined` ⇒
|
|
147
|
+
// `saveSession` 不落该键(禁回填——同 `createdBy` 先例;键在而值为 `null` ⇒ 原样携 `null`)。
|
|
148
|
+
agent._slotEffort = Object.hasOwn(data, "effort") ? (data.effort ?? null) : undefined
|
|
129
149
|
// ── 记录存储绑定(TUI-OOM-ROOTCAUSE · SESSION.md §6.14 绑定与对账 / 追加时点)──
|
|
130
150
|
// opts.slot 在场(启动恢复 / 非占用的 /session 切换 / ACP 钉槽)→ 绑定(身份核验 + 对账 +
|
|
131
151
|
// 窗口)——人读线 = store.tail(200);未传(模式 F:被他人活进程占用的槽 / 测试 / ACP fork)
|
|
@@ -140,29 +160,57 @@ export function applySession(agent, data, opts = {}) {
|
|
|
140
160
|
} else {
|
|
141
161
|
unbindRecordStore(agent)
|
|
142
162
|
}
|
|
143
|
-
// ── MODEL-MERGE-SESSION 恢复(F-4
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
// ── MODEL-MERGE-SESSION 恢复(F-4——两支;#841:槽面判据单源 = 统一解析第 1 步)──
|
|
164
|
+
// 槽渠道(`activeProvider` 在册 ∧ 持 key)⇒ 槽值设;槽无 key ∥ 不在册 ⇒ 跳过(落 config 链——
|
|
165
|
+
// 不把不可运行渠道钉进运行态;**跳过不写槽**——原槽值保留(D-S3 / KD-841-2))。判定 + 模型面
|
|
166
|
+
// 同源 = `resolveProviderPlan`(PROVIDER.md §6.22——零第二判据)。
|
|
167
|
+
const slotPlan = resolveProviderPlan({
|
|
168
|
+
providers: agent.providers,
|
|
169
|
+
defaultModel: agent.config?.defaultModel,
|
|
170
|
+
slot: { provider: data.activeProvider ?? null, model: data.activeModel ?? null },
|
|
171
|
+
})
|
|
172
|
+
if (slotPlan.source === "slot") {
|
|
173
|
+
const slotProvider = agent.providers?.find((pr) => pr.name === slotPlan.channel)
|
|
174
|
+
// ① 槽 provider 存在 → 按槽值设(不看 config):双字段恒非空。
|
|
175
|
+
// 模型面序(#841):槽模型 → `defaultModel` 属本渠道 ⇒ 其模型段 → 渠道默认单值
|
|
176
|
+
// (`providers[].model`);三档皆无 ⇒ `null`(不落 model 键——下游沿既有「model 缺失」处置)。
|
|
177
|
+
// F-2d(MODEL-400-FIX)径保留:空串 / 非串槽模型同样兜(#638 非串 = 未登记)——slotPlan.model
|
|
178
|
+
// 已按同归一出值(单源,无第二判据)。
|
|
150
179
|
const prevName = agent.activeProvider
|
|
151
180
|
const prevModel = agent.activeModel ?? null
|
|
152
|
-
const slotModel =
|
|
181
|
+
const slotModel = slotPlan.model
|
|
153
182
|
const switched = prevName !== slotProvider.name || prevModel !== slotModel
|
|
154
183
|
agent.activeProvider = slotProvider.name
|
|
155
184
|
agent.activeModel = slotModel
|
|
156
185
|
agent.provider = { ...slotProvider }
|
|
157
186
|
if (slotModel) agent.provider.model = slotModel
|
|
158
|
-
// 重算 compactThreshold(auto 时——阈值跟模型走;原在 bin 的 switched
|
|
159
|
-
|
|
187
|
+
// 重算 compactThreshold(auto 时——阈值跟模型走;原在 bin 的 switched 分支——收拢本处)。
|
|
188
|
+
// 非串 model(外端脏载 / 手工档)⇒ 不重算(#638 后查表族恒 total,本门保留为同形防御——与
|
|
189
|
+
// 下方档位块同口径);类型脏载不中断恢复。
|
|
190
|
+
if (switched && agent.config?.agent?.compactThresholdAuto && typeof agent.provider.model === "string" && agent.provider.model) {
|
|
160
191
|
agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
|
|
161
192
|
}
|
|
193
|
+
// 会话级档位施加(§6.21 判据句 4——在模型合并支之后,以**合并后**的 `provider.model` 判 spec;
|
|
194
|
+
// 三端共用本施加面):`"off"` 记号 ⇒ 该模型有 off 路径才设关思考形(effort 族该形为 `null`
|
|
195
|
+
// ⇒ 另置 `reasoningEffort: "none"`——§16.4 载荷门,单源 = `think-off.mjs`);枚举字面 ⇒ 直置;
|
|
196
|
+
// `null` / 缺键 / 表外串 ⇒ 不动(边界表第 1–3 行:老槽零行为变更,表外不静默改写)。
|
|
197
|
+
if (data.effort != null) {
|
|
198
|
+
// 非串 model(外端脏载 / 手工档)按未登记归一——本块不因类型脏载抛(同 `resolveEffortPatch` 口径)
|
|
199
|
+
const spec = specForModel(typeof agent.provider.model === "string" ? agent.provider.model : "")
|
|
200
|
+
if (data.effort === "off") {
|
|
201
|
+
if (thinkOffPath(spec)) {
|
|
202
|
+
const shape = thinkOffShape(spec)
|
|
203
|
+
agent.provider.thinking = shape
|
|
204
|
+
if (shape === null) agent.provider.reasoningEffort = "none"
|
|
205
|
+
}
|
|
206
|
+
} else if (spec?.reasoningEffortEnum?.includes(data.effort)) {
|
|
207
|
+
agent.provider.reasoningEffort = data.effort
|
|
208
|
+
}
|
|
209
|
+
}
|
|
162
210
|
return switched
|
|
163
211
|
}
|
|
164
|
-
// ②
|
|
165
|
-
//
|
|
212
|
+
// ② 槽无 key ∥ 不在册 → D-S3 保留——静默保持现状(落 config 链:config 统一解析产物已是
|
|
213
|
+
// `agent.provider`;不报错不纠正——两方都无效由调用侧复验 validateProvider 弹重选)
|
|
166
214
|
return false
|
|
167
215
|
}
|
|
168
216
|
|
|
@@ -211,10 +259,13 @@ export async function newSession(cwd, opts = {}) {
|
|
|
211
259
|
while (m.slots[slot] || existsSync(slotPath(cwd, slot)) || liveClaimed(slot)) slot++
|
|
212
260
|
|
|
213
261
|
// Write empty session — 2026-09-01 advisor 🔵:补 contextHistory/planMode 字段与
|
|
214
|
-
// VS Code newSlot 对齐(SESSION.md §6.3 v2
|
|
215
|
-
|
|
262
|
+
// VS Code newSlot 对齐(SESSION.md §6.3 v2 格式双端一致;两端读侧均有兜底,功能等价)。
|
|
263
|
+
// SLOT-END-PARAM 批(§6.20 判据句 4 写面):改用首物化构造器单源 `newSlotData(cwd)`
|
|
264
|
+
// (原字面逐字同形——并入后随其带创建端 = 本端名)。
|
|
265
|
+
const data = newSlotData(cwd)
|
|
216
266
|
writeSessionFile(slotPath(cwd, slot), data)
|
|
217
|
-
|
|
267
|
+
// 摘要 `ts` 地板入参(#503 同族随动——保存面写后取 mtime;`slotMtime` 读失败 ⇒ null ⇒ 裸墙钟)
|
|
268
|
+
m.slots[slot] = slotDigest(data, slotMtime(cwd, slot))
|
|
218
269
|
m.active = slot
|
|
219
270
|
// 2026-08-31 advisor round1 🟡:与 ensureActive 认领模型一致——立即记录新 slot 所有权,
|
|
220
271
|
// 否则 /new 后到首次保存之间并发方(VS Code/另一 CLI)会把新 active 槽当"空闲可恢复"
|
|
@@ -262,6 +313,7 @@ export function resetSessionState(agent) {
|
|
|
262
313
|
agent._pendingReminders = []
|
|
263
314
|
agent._slot = null
|
|
264
315
|
agent._slotMtime = null
|
|
316
|
+
agent._slotEffort = null // 新会话无档位(§6.21 判据句 5:全新槽规范形 = `effort: null`)
|
|
265
317
|
agent._osReminderInjected = false
|
|
266
318
|
// §6.11(2026-09-08):一次性注入标志换名——_restartReminderInjected 推断退役(F3——
|
|
267
319
|
// _sessionStart != null 伪触发);/new 清零新标记——恢复武装/启动句标志不跨会话复活
|
|
@@ -279,11 +331,16 @@ export function resetSessionState(agent) {
|
|
|
279
331
|
* (无认领副作用);目标槽空闲则一并认领、被另一活进程占用则不认领(slotOccupancy——
|
|
280
332
|
* 下次保存经 activeSlot 自然 fork 到新槽);只改 manifest 指针(setActive 意图)。
|
|
281
333
|
* 2026-09-01 会诊三家 🟡:saveManifest 条目级合并 + setActive(不把并发方刚翻的指针回滚)。 */
|
|
282
|
-
export function switchToSlot(cwd, slot) {
|
|
334
|
+
export function switchToSlot(cwd, slot, { end = sessionEnd() } = {}) {
|
|
283
335
|
const m = loadManifest(cwd)
|
|
284
|
-
|
|
336
|
+
// §6.22 判据句 4(SESSION-LIST-DISK 批):准入 = 盘面文件存在 ∨ manifest 有条目——盘上独有槽
|
|
337
|
+
// 可切;旧分支逐字保留(有条目无文件 ⇒ 读槽返 null 同今日)。
|
|
338
|
+
if (!m.slots[slot] && !existsSync(slotPath(cwd, slot))) return null
|
|
285
339
|
const data = loadSlotFile(cwd, slot)
|
|
286
340
|
if (!data) return null
|
|
341
|
+
// 落点 A(§6.25 判据句 3):数据已在手 ⇒ 摘要顺手补写——随本次 saveManifest 落(**零额外读 /
|
|
342
|
+
// 零额外写**;新鲜条目不写——幂等,§6.25 谓词单源 `healNeeded`)。
|
|
343
|
+
healDigest(m, slot, data, slotMtime(cwd, slot))
|
|
287
344
|
m.active = slot
|
|
288
345
|
const occ = slotOccupancy(cwd, slot)
|
|
289
346
|
if (!occ.occupied) {
|
|
@@ -294,8 +351,9 @@ export function switchToSlot(cwd, slot) {
|
|
|
294
351
|
// (不认领目标 + 旧认领一并释放——下次保存经 `allocateFresh` fork,既有语义)。核受占 =
|
|
295
352
|
// 切换成立(D-SE33:指针 / 记录按 D-6 / D-4 落点——fork 面依赖指针翻至目标槽)。
|
|
296
353
|
saveManifest(cwd, m, null, { setActive: true, release: occ.occupied ? [] : [slot] })
|
|
297
|
-
// 2026-09-05 §6.10 D-4:/session N 跟随"最后查看的槽"
|
|
298
|
-
|
|
354
|
+
// 2026-09-05 §6.10 D-4:/session N 跟随"最后查看的槽"(成功切换才写);§6.20 判据句 2:
|
|
355
|
+
// 写点端名随动(显式端参 > 进程端名——端壳经绑定转口传本端常量)
|
|
356
|
+
writeEndMarker(cwd, slot, end)
|
|
299
357
|
return data
|
|
300
358
|
}
|
|
301
359
|
|
|
@@ -316,3 +374,26 @@ export function slotOccupancy(cwd, slot) {
|
|
|
316
374
|
if (st === "unknown") return { occupied: true, unknown: true, owner }
|
|
317
375
|
return { occupied: false }
|
|
318
376
|
}
|
|
377
|
+
|
|
378
|
+
/** 打开态读数(SESSION.md §6.24 · 桌面残余批——恢复态播种的读数**单源**):槽数据 ⇒ 状态行上下文
|
|
379
|
+
* 读数百分数(整数)。与 `applySession` 后读数**同源同式**(四事同判——组合既有件,零新公式):
|
|
380
|
+
* ① 线选 = `contextHistory` 非空取之,否则 `history` 经 `stripTruncatedToolArgs` 回退(同 applySession);
|
|
381
|
+
* ② 回声归并 = `mergeAdjacentAssistantEchoes`(同 applySession 装线前一步);
|
|
382
|
+
* ③ 渠道合并 = `providers` 命中 `data.activeProvider` ⇒ 串值 `activeModel` 覆盖 `entry.model`(非串 ∕
|
|
383
|
+
* 空串 = 未登记 ⇒ 回退 `entry.model`——模型合并支 ① 同归一);未命中 ⇒ `fallback`(支 ②「静默保持现状」的装配口径 = `loadConfig().provider`);
|
|
384
|
+
* ④ 公式 = `historyPercent`(`thincoder-core/token-window.mjs`——与 CLI 状态行 / VSC 同式)。
|
|
385
|
+
* **零副作用**:`data` / `providers` / `fallback` 皆入参(不读盘、不写盘、不改任何进程态);
|
|
386
|
+
* `data` 非对象 ⇒ `0`(与空历史同值——「非正 ⇒ 零节点」显示门归端侧,沿 `historyPercent` 空历史口径)。 */
|
|
387
|
+
export function sessionReading(data, { providers, fallback } = {}) {
|
|
388
|
+
if (data === null || typeof data !== "object") return 0
|
|
389
|
+
const full = Array.isArray(data.history) ? data.history : []
|
|
390
|
+
const ch = data.contextHistory
|
|
391
|
+
const machine = (Array.isArray(ch) && ch.length > 0) ? ch : full.map(stripTruncatedToolArgs)
|
|
392
|
+
const line = mergeAdjacentAssistantEchoes(machine)
|
|
393
|
+
const entry = data.activeProvider ? providers?.find((pr) => pr.name === data.activeProvider) : null
|
|
394
|
+
// #638:非串 `activeModel` 归一(与 applySession 模型合并支 ① 同判)——脏值不达 `historyPercent`。
|
|
395
|
+
const provider = entry
|
|
396
|
+
? { ...entry, model: (typeof data.activeModel === "string" && data.activeModel) ? data.activeModel : entry.model }
|
|
397
|
+
: fallback
|
|
398
|
+
return historyPercent(line, provider)
|
|
399
|
+
}
|
package/session-rename.mjs
CHANGED
|
@@ -29,9 +29,13 @@ export function renameSlot(cwd, slot, title, _stat = statSync) {
|
|
|
29
29
|
// 读与写之间文件被并发方改过 → 放弃(保留并发内容,重命名下次重试)
|
|
30
30
|
if (_stat(p).mtimeMs !== t0) return { ok: false, reason: "mtime-conflict" }
|
|
31
31
|
writeSessionFile(p, data)
|
|
32
|
+
// 写后取 mtime(标题写也是一次落盘)——摘要 `ts` 地板入参(#503 同族随动;读失败 ⇒ 缺省回溯墙钟。
|
|
33
|
+
// `statSync` 是真实 stat——`_stat` 只为 mtime-conflict 判据注入,地板不共用该缝)。
|
|
34
|
+
let wroteMtimeMs
|
|
35
|
+
try { wroteMtimeMs = statSync(p).mtimeMs } catch { /* 不可得 ⇒ 裸墙钟 */ }
|
|
32
36
|
const m = loadManifest(cwd)
|
|
33
37
|
if (m.slots[n]) {
|
|
34
|
-
m.slots[n] = slotDigest(data)
|
|
38
|
+
m.slots[n] = slotDigest(data, wroteMtimeMs)
|
|
35
39
|
saveManifest(cwd, m)
|
|
36
40
|
}
|
|
37
41
|
return { ok: true }
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-slot-claims.mjs — 认领面族(2026-09-28 拆分批 · R3——自 `session-slots-manifest.mjs`
|
|
3
|
+
* (421 > 300)外提认领面:`staleClaims` ∕ owner 三件(`ownerPid` / `ownerPids` /
|
|
4
|
+
* `ownerStateOf`)∕ `cleanDeadOwners` ∕ `ensureActive` ∕ `allocateFresh` ∕ `claimSlot` ∕
|
|
5
|
+
* `_releaseStats` ∕ `releaseClaimsAll` ∕ `activeSlot`;**逐字迁移**、零行为改——台账 #484
|
|
6
|
+
* 「认领面族拆分」顺延项落形)。
|
|
7
|
+
* 原档(`session-slots-manifest.mjs`)保留摘要 / 清单读写 / 属主健康面,并 re-export 本档
|
|
8
|
+
* 导出保既有 import 面(消费档路径与名面不动)。
|
|
9
|
+
*
|
|
10
|
+
* 静态环(与既有的 session.mjs ↔ session-slots.mjs 同形):本档引 `session-slots.mjs` 的存储
|
|
11
|
+
* 原语(`getSessionId` / `manifestPath` / `slotPath`)与 `session-slots-manifest.mjs` 的清单
|
|
12
|
+
* 读写(`loadManifest` / `saveManifest`),后者反向 re-export 本档导出——函数声明在实例化期
|
|
13
|
+
* 已初始化、双方只在函数体内运行时使用(模块求值期零顶层调用 ⇒ 环安全)。
|
|
14
|
+
* 探测纪律(init-block 批 · F-MI7 / SESSION.md §6.2):本档**零自有 exec、零逐 pid 探测**——
|
|
15
|
+
* 只消费调用面传入的**探测束**(`bundle`);属主三态判据单源 = `process-probe.mjs`,
|
|
16
|
+
* `unknown`(探测失败 / 缺行)⇒ 不认领 / 不判死 / 不删(D-MI10)。
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync } from "node:fs"
|
|
19
|
+
// 探测束(判活)+ 属主三态判据 / 死主判定(本档零自有 exec——判据面与探测面分离)。
|
|
20
|
+
import { probeOwnersSync, ownerState, filterDeadOwners } from "./process-probe.mjs"
|
|
21
|
+
// 存储原语(路径 / 原子写 / 进程 sessionId)——静态环见头注。
|
|
22
|
+
import { getSessionId, manifestPath, slotPath } from "./session-slots.mjs"
|
|
23
|
+
// 清单读写(落盘单源住原档——静态环见头注;反向 re-export 保 import 面)。
|
|
24
|
+
import { loadManifest, saveManifest } from "./session-slots-manifest.mjs"
|
|
25
|
+
|
|
26
|
+
/** 本进程**残留认领集**(F-CR1 释放谓词 —— **判据单源** · SESSION.md §6.2 / §6.16):
|
|
27
|
+
* 「释放 A ⟺ `slotSessions[A]` 为本进程 ∧ `A ∉ 保留集`」。`keep` = 保留集 = 落点槽 ∪ 本进程
|
|
28
|
+
* 其余活绑定槽(单绑定落点 ⇒ `{落点槽}`;被占落点 ⇒ 空集)。返回槽号(**字符串键**形态)。 */
|
|
29
|
+
export function staleClaims(src, keep = []) {
|
|
30
|
+
const keepSet = new Set((keep ?? []).filter((s) => s != null).map(Number))
|
|
31
|
+
const mySessionId = getSessionId()
|
|
32
|
+
return Object.entries(src?.slotSessions ?? {})
|
|
33
|
+
.filter(([slot, owner]) => owner === mySessionId && !keepSet.has(Number(slot)))
|
|
34
|
+
.map(([slot]) => slot)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 属主串 → pid(`"pid-ts-rand"`——`owner.split("-")[0]` 格式判据单源:session-slots ↔
|
|
38
|
+
* session-lifecycle 共用,防两处漂移)。不可解析 ⇒ NaN——判据层按 dead 处理
|
|
39
|
+
* (见 process-probe `ownerState`)。 */
|
|
40
|
+
export function ownerPid(owner) { return parseInt(owner.split("-")[0]) }
|
|
41
|
+
|
|
42
|
+
/** manifest 属主 pid 全量清单(**非本进程**——束入参:一次探测覆盖所有待判 pid;
|
|
43
|
+
* 空清单 ⇒ 探测面零 exec 早退)。`exclude` 缺省 = 本进程 sessionId。 */
|
|
44
|
+
export function ownerPids(m, exclude = getSessionId()) {
|
|
45
|
+
const pids = []
|
|
46
|
+
for (const owner of Object.values(m.slotSessions ?? {})) {
|
|
47
|
+
if (!owner || owner === exclude) continue
|
|
48
|
+
const pid = ownerPid(owner)
|
|
49
|
+
if (pid) pids.push(pid)
|
|
50
|
+
}
|
|
51
|
+
return pids
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 束 → 属主三态(判据单源 = process-probe `ownerState`;束缺失 / 探测失败 ⇒ "unknown"——
|
|
55
|
+
* 保守保留,**不得**当全死)。 */
|
|
56
|
+
export function ownerStateOf(owner, bundle) {
|
|
57
|
+
const pid = ownerPid(owner)
|
|
58
|
+
return ownerState(pid, { aliveSet: bundle?.aliveSet ?? null, cmds: bundle?.cmds ?? null })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 束 → `filterDeadOwners` 入参(清理面 API 保面——三态 `alive`:无束 / 失败 ⇒ undefined = 未知)。 */
|
|
62
|
+
function ownerArgs(owner, bundle) {
|
|
63
|
+
const pid = ownerPid(owner)
|
|
64
|
+
const aliveSet = bundle?.aliveSet ?? null
|
|
65
|
+
return [pid, { alive: aliveSet ? aliveSet.has(pid) : undefined, cmdline: bundle?.cmds?.get?.(pid) }]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 死主条目清理(2026-08-31 会诊 F4 + 2026-09-01 会诊 deepseek/kimi 🔴 语义原样抽取——
|
|
69
|
+
* ensureActive/resumeSlot 共用):删除 owner 进程已死的 slotSessions 条目。死主判定必须跑
|
|
70
|
+
* 判据(不能以"文件缺失"短路——活进程在"认领 → 首次保存"窗口文件暂缺,误删会
|
|
71
|
+
* 致双进程同槽),但**不自行探测**:判活 / 命令行来自调用面同一探测束(F-MI7——
|
|
72
|
+
* 零逐 pid exec)。返回 deletions 计算函数(调用时按 m 当前状态过滤刚重新认领的槽——防删
|
|
73
|
+
* 掉自己的新属主),无清理返回 null。删除须经 saveManifest 的 deletions 显式落盘——条目级
|
|
74
|
+
* 合并会把磁盘死条目从 fresh 复活回写(N1 + 会诊 🔴)。
|
|
75
|
+
*
|
|
76
|
+
* 身份复核(批 1 · F-MI6 / D-MI11——判据单源 `filterDeadOwners`):pid 活 + 命令行**明确
|
|
77
|
+
* 可得且非本产品** ⇒ 同判可删(pid 复用即此场景);探测失败 / 缺行 ⇒ 保守保留(D-MI10)。 */
|
|
78
|
+
export function cleanDeadOwners(m, bundle) {
|
|
79
|
+
const mySessionId = getSessionId()
|
|
80
|
+
const entries = Object.entries(m.slotSessions).filter(([, o]) => o && o !== mySessionId)
|
|
81
|
+
const deadSlots = []
|
|
82
|
+
for (const [slot, owner] of entries) {
|
|
83
|
+
const [pid, args] = ownerArgs(owner, bundle)
|
|
84
|
+
if (filterDeadOwners(pid, args)) {
|
|
85
|
+
delete m.slotSessions[slot]
|
|
86
|
+
deadSlots.push(slot)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return deadSlots.length === 0 ? null : () => ({ slotSessions: deadSlots.filter((s) => m.slotSessions[s] !== mySessionId) })
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Claim a slot for this process and set it as active. Idempotent.
|
|
94
|
+
* Preference order:
|
|
95
|
+
* 1. The current active slot, if it is unowned / ours / its owner is dead — reuse it.
|
|
96
|
+
* 2. Any slot that is unowned or owned by a dead process (reclaim). → allocateFresh
|
|
97
|
+
* 3. A brand-new slot when all are owned by live processes. → allocateFresh
|
|
98
|
+
* The owner is recorded in m.slotSessions so other processes (CLI ↔ VS Code) can
|
|
99
|
+
* see which slots are taken and avoid them.
|
|
100
|
+
*
|
|
101
|
+
* F-MI7(同步例外收窄):粘性早退(已拥有 active)**零探测**(无束、无 exec);
|
|
102
|
+
* 冷路径 = 一次有界同步束(`probeOwnersSync`)——清理 / 占用 / 空闲全部查表。
|
|
103
|
+
*/
|
|
104
|
+
function ensureActive(cwd, m) {
|
|
105
|
+
const mySessionId = getSessionId()
|
|
106
|
+
if (!m.slotSessions) m.slotSessions = {}
|
|
107
|
+
|
|
108
|
+
// Already own the active slot — nothing to do. 粘性早退:**零探测**(死项清理交给
|
|
109
|
+
// 非粘性路径——resumeSlot 每次进程入口的异步束 / 冷路径同步束都会清并落盘)。
|
|
110
|
+
if (m.active && m.slotSessions[m.active] === mySessionId) return
|
|
111
|
+
|
|
112
|
+
// 冷路径:入口一次有界同步束(每 exec ≤ SYNC_PROBE_MS)覆盖全量属主 pid。
|
|
113
|
+
const bundle = probeOwnersSync(ownerPids(m))
|
|
114
|
+
const deadParam = cleanDeadOwners(m, bundle)
|
|
115
|
+
|
|
116
|
+
const isFree = (slot) => {
|
|
117
|
+
const owner = m.slotSessions[slot]
|
|
118
|
+
if (!owner || owner === mySessionId) return true
|
|
119
|
+
// 未知(探测失败 / 缺行)⇒ 不认为是空闲(不认领——D-MI10)
|
|
120
|
+
return ownerStateOf(owner, bundle) === "dead"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 1. Prefer the current active slot if we can take it (preserves "resume where you left off").
|
|
124
|
+
if (m.active && m.slots[m.active] && isFree(m.active)) {
|
|
125
|
+
m.slotSessions[m.active] = mySessionId
|
|
126
|
+
saveManifest(cwd, m, deadParam?.() ?? null, { setActive: true })
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 2/3. 分支 2/3 已抽取为 allocateFresh(2026-09-05 §6.10 D-2——resumeSlot 全新分配复用)。
|
|
131
|
+
allocateFresh(cwd, m, deadParam, bundle)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Allocate a slot with ensureActive 分支 2/3 语义(2026-09-05 §6.10 D-2 抽取——resumeSlot
|
|
136
|
+
* 步骤③"其余一切 → 全新分配"复用;与 newSession 选号语义对齐):
|
|
137
|
+
* 2. Reclaim a manifest slot whose FILE does not exist (never held a session);
|
|
138
|
+
* 3. A brand-new slot when none is free / all numbers are taken.
|
|
139
|
+
* 记录所有权 + active 指针(setActive)并落盘。返回分配的槽号。
|
|
140
|
+
* F-MI7:占用 / 空闲判据全部查**调用面束**(`bundle`——零自有探测);缺省 null ⇒
|
|
141
|
+
* 全部属主按 unknown 论(只可能取全新号——保守,不认领陌生人)。
|
|
142
|
+
*/
|
|
143
|
+
export function allocateFresh(cwd, m, deadParam = null, bundle = null) {
|
|
144
|
+
const mySessionId = getSessionId()
|
|
145
|
+
if (!m.slotSessions) m.slotSessions = {}
|
|
146
|
+
const isFree = (slot) => {
|
|
147
|
+
const owner = m.slotSessions[slot]
|
|
148
|
+
if (!owner || owner === mySessionId) return true
|
|
149
|
+
return ownerStateOf(owner, bundle) === "dead" // 未知 ⇒ 非空闲(不认领——D-MI10)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2. Reclaim a slot whose FILE does not exist (never held a session). 2026-08-31 会诊 F4:
|
|
153
|
+
// 原实现认领"编号最小的空闲 slot"——死主的旧 slot 文件仍在,新进程会 resume 进
|
|
154
|
+
// 陌生会话("会话乱了"实锤)且退出时覆盖它。只有文件缺失的空 slot 才允许回收。
|
|
155
|
+
const allSlots = Object.keys(m.slots).filter((n) => /^\d+$/.test(n)).map(Number).sort((a, b) => a - b)
|
|
156
|
+
for (const slot of allSlots) {
|
|
157
|
+
if (isFree(slot) && !existsSync(slotPath(cwd, slot))) {
|
|
158
|
+
m.active = slot
|
|
159
|
+
m.slotSessions[slot] = mySessionId
|
|
160
|
+
saveManifest(cwd, m, deadParam?.() ?? null, { setActive: true })
|
|
161
|
+
return slot
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 3. All slots owned by live processes — allocate a new one (no limit).
|
|
166
|
+
// 2026-08-31 advisor round2 🟡:新号从 max+1 起逐号跳过"已被活进程认领但尚未落盘"
|
|
167
|
+
// 的号(认领→首次保存窗口:slotSessions 有条目、m.slots 无条目、文件不存在——
|
|
168
|
+
// 仅凭 m.slots/existsSync 查不到 → 双进程认领同一号 → 同槽双写/互旋)。
|
|
169
|
+
// 2026-09-01 会诊 kimi 🟡:同时跳过文件仍存在的号(与 newSession 对齐)——manifest
|
|
170
|
+
// 条目丢失/损坏时 max+1 会撞上孤儿槽文件 → F2 把真会话轮转成不可见的 .bak。
|
|
171
|
+
// 另:allSlots 已升序,取 max 用 allSlots[allSlots.length-1](数万槽位时 Math.max
|
|
172
|
+
// spread 有 RangeError 风险)。
|
|
173
|
+
const liveClaimed = (n) => {
|
|
174
|
+
const owner = m.slotSessions?.[n]
|
|
175
|
+
if (!owner || owner === mySessionId) return false
|
|
176
|
+
return ownerStateOf(owner, bundle) !== "dead" // 活 / 未知 ⇒ 视为已占(跳号——保守)
|
|
177
|
+
}
|
|
178
|
+
let newSlot = allSlots.length > 0 ? allSlots[allSlots.length - 1] + 1 : 1
|
|
179
|
+
while (liveClaimed(newSlot) || existsSync(slotPath(cwd, newSlot))) newSlot++
|
|
180
|
+
m.active = newSlot
|
|
181
|
+
m.slotSessions[newSlot] = mySessionId
|
|
182
|
+
saveManifest(cwd, m, deadParam?.() ?? null, { setActive: true })
|
|
183
|
+
return newSlot
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** 认领指定槽为本进程所有并置为 active(2026-09-05 §6.10 D-2 resumeSlot 认领路径——调用方
|
|
187
|
+
* 已按"属主 空/死/本进程"判据校验可用性;幂等)。manifest active 保留为共享指针(D-6:
|
|
188
|
+
* 旧版端/ACP 恢复依据 + 无记录端一次性继承源 + 列表回退高亮——不再作本端恢复第一依据)。
|
|
189
|
+
* deadParam 在写入所有权之后求值(防 deletions 删掉本调用刚认领的槽——ensureActive
|
|
190
|
+
* deadParam 同型过滤)。
|
|
191
|
+
* F-CR1(2026-09-21 · SESSION-CLAIM 批):认领落点释放本进程残留认领(保留集 = {认领槽}——
|
|
192
|
+
* §6.2 公式代入);本函数 = 启动恢复 / 本端恢复落点(核 `resumeSlot` 与 VSC 端壳
|
|
193
|
+
* `resumeSlot` 共用),落盘判据(fresh 快照 / 值条件删除 / 内存移除)见 `saveManifest`。 */
|
|
194
|
+
export function claimSlot(cwd, slot, m = loadManifest(cwd), deadParam = null) {
|
|
195
|
+
m.slotSessions ??= {}
|
|
196
|
+
m.slotSessions[slot] = getSessionId()
|
|
197
|
+
m.active = slot // 认领即翻共享指针(setActive 写 m.active——不更新则落快照旧值)
|
|
198
|
+
saveManifest(cwd, m, deadParam?.() ?? null, { setActive: true, release: [slot] })
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 观测计数(测试缝——`_storeStats` / `_staleHooks` 同惯例):`releaseClaimsAll` 调用计数。
|
|
202
|
+
* 端壳「工作区转空 ⇒ 旧 cwd 释放恰一次」类断言以此为桩计数(生产零消费)。 */
|
|
203
|
+
export const _releaseStats = { calls: 0 }
|
|
204
|
+
|
|
205
|
+
/** 退出全释放(EXIT-CLAIM-RELEASE 批 · F-XR1 · SESSION.md §6.18 D-SE41):优雅退出前释放
|
|
206
|
+
* 本进程**全部**槽认领(保留集空——`staleClaims` 释放谓词单源);落盘判据 = `saveManifest`
|
|
207
|
+
* `opts.release` 三条自动继承(fresh 同次快照取释放集 / 值条件删除——他人窗口内新认领不误删 /
|
|
208
|
+
* 内存认领表同步移除)。**零触碰 marker 面**(F-XR2 路标保留——下次启动经 `usableSlot`
|
|
209
|
+
* 无属主短路零探测直达恢复);崩溃路径不经此(信号无 JS 钩子——F-XR3)。
|
|
210
|
+
* 早退面:磁盘无 manifest ⇒ `existsSync` 单条目早退**零写**(不造盘面);本进程无认领 ⇒
|
|
211
|
+
* 零写返回 false。失败容忍(F-XR1「退出恒达」):整体 try/catch **永不抛出**,返回 boolean
|
|
212
|
+
* (true = 有释放且落盘成功;false = 早退 / 失败 / **写面拒写**——`saveManifest` 返回值透传,
|
|
213
|
+
* §6.23 判据句 3——断言面);写失败盘面 = 认领残留 →
|
|
214
|
+
* 恢复走既有探测面(现状形态,数据零险)。 */
|
|
215
|
+
export function releaseClaimsAll(cwd) {
|
|
216
|
+
_releaseStats.calls += 1
|
|
217
|
+
try {
|
|
218
|
+
if (!existsSync(manifestPath(cwd))) return false // 无 manifest ⇒ 零写(不造盘面)
|
|
219
|
+
const m = loadManifest(cwd)
|
|
220
|
+
if (staleClaims(m, []).length === 0) return false // 无本进程认领 ⇒ 零写(免白刷共享 sessionId)
|
|
221
|
+
return saveManifest(cwd, m, null, { release: [] }) !== false // 保留集空 = 全释放;不 setActive;返回值透传(§6.23 判据句 3——拒写 ⇒ false)
|
|
222
|
+
} catch { return false } // F-XR1:退出恒达(写失败 = 认领残留 → 恢复走探测面)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Return the active slot number for this process, claiming one if necessary */
|
|
226
|
+
export function activeSlot(cwd) {
|
|
227
|
+
const m = loadManifest(cwd)
|
|
228
|
+
ensureActive(cwd, m)
|
|
229
|
+
return m.active
|
|
230
|
+
}
|