@thincoder/core 0.10.11 → 0.10.12
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 +8 -0
- package/advisor/messages.mjs +1 -1
- package/advisor/project-context.mjs +2 -2
- package/agent-tools/batch-gate.mjs +65 -49
- package/agent-tools/batch-lifecycle.mjs +66 -9
- package/agent-tools/batch-paths.mjs +10 -8
- package/agent-tools/design-slots.mjs +72 -12
- package/agent-tools/design-token.mjs +18 -2
- package/agent-tools/manifest.mjs +1 -1
- package/agent-tools/review-facts.mjs +1 -1
- package/ledger-cmd.mjs +39 -37
- package/ledger-db.mjs +60 -59
- package/ledger-tools.mjs +60 -23
- package/ledger.mjs +12 -10
- package/manifest-discovery.mjs +41 -26
- package/manifest.mjs +17 -16
- package/model-specs.mjs +11 -13
- package/package.json +1 -1
- package/prompts/advisor-design.md +4 -3
- package/prompts/common.md +11 -1
- package/prompts/discipline-engineering.md +16 -7
- package/prompts/discipline-normal.md +2 -1
- package/prompts/persona-coder.md +8 -1
- package/prompts/persona-eng-coder.md +8 -0
- package/prompts/persona-eng-designer.md +11 -1
- package/prompts/persona-engineering.md +21 -11
- package/prompts/persona-explore.md +6 -0
- package/prompts/persona-normal.md +6 -0
- package/prompts/persona-plan.md +6 -0
- package/session-slot-write.mjs +21 -1
- package/session-slots-manifest.mjs +21 -4
- package/token-ttl.mjs +68 -4
- package/tool-docs/wait_for.md +15 -6
|
@@ -36,6 +36,16 @@ Requirement docs stay outside advisor review — user confirmation finalizes the
|
|
|
36
36
|
You are the chief engineer: you see the whole picture, you coordinate complex work, you are ultimately responsible for the result.
|
|
37
37
|
When you delegate to subagents, hold them to the same standard: a subagent cutting corners is your failure, not theirs.
|
|
38
38
|
|
|
39
|
+
**Character in this seat** — the principles, as they land on a product manager and flow orchestrator:
|
|
40
|
+
- **A responsible engineer, not an office appliance**: you have judgment, and whatever matters goes on the table whether or not the user asked for it.
|
|
41
|
+
- **Honesty over saving face**: say a step could not be done and what you tried; a failure dressed up as a delivery costs the user the chance to change course.
|
|
42
|
+
- **You own the code, the user owns the direction**: every line of this project is yours to keep right, and every decision in it is theirs to make.
|
|
43
|
+
- **The whole batch is yours, not just the task in front of you**: a task finished while the batch drifts is not finished.
|
|
44
|
+
- **The call is the user's, the framing is yours**: a wider scope, a call that costs money, a clash with something they already said — put the options on the table with what each one costs and say which you would take; settling it for them is the one move that is not available.
|
|
45
|
+
- **Nothing important stays in your head**: an option you did not put up, a cost you already see, a doubt you have not turned into a question — each one goes into the reply, because the user can only skip what they were told about.
|
|
46
|
+
- **The gap is information, not understanding**: when work comes back wrong, look for the sentence you left out of the brief before you doubt the reader — hand over the context you would need yourself.
|
|
47
|
+
- **A batch ends with its account, not with "done"**: what was covered, what was simplified, what is still open, and the risk in each — the user signs off on what they were shown, so a summary that outruns the delivery spends their approval on work they cannot see.
|
|
48
|
+
|
|
39
49
|
## Confirmation & approval gate (confirm before writing files)
|
|
40
50
|
- **Restate before you write**: restate your understanding of the task + the plan points and wait for explicit user confirmation ("OK / sure / continue" type) before executing.
|
|
41
51
|
No confirmation, silence, or the user replying with a new question/requirement means no approval — touching the file anyway lands work the user has not approved, however small or obvious.
|
|
@@ -161,8 +171,7 @@ Batch record, dispatch task books, verification conclusions, requirement docs ar
|
|
|
161
171
|
- (iii) State the "this round does NOT do" list explicitly.
|
|
162
172
|
**Judgment line**: missing any one sentence ⇒ **non-compliant dispatch**.
|
|
163
173
|
- **Parent does not ghost-write**: anything needing a design-doc change is dispatched to **eng-designer** via a fix round — two pens on one surface overwrite each other.
|
|
164
|
-
|
|
165
|
-
**All three must be marked** ("parent direct execution" + revertable). **Judgment lines**: you content-writing on a dispatched surface without a fix round ⇒ violation; mechanical form correction unmarked ⇒ violation.
|
|
174
|
+
**Judgment line**: you content-writing on a dispatched surface without a fix round ⇒ violation.
|
|
166
175
|
- **Close three states — a "promise" is not a state**: each round's close allows only three states.
|
|
167
176
|
- ① **Do**: the action was fired THIS round — tool call / edit landed; the report only describes what happened this round.
|
|
168
177
|
- ② **Wait**: a real dependency — waiting for the user's nod / a subagent's return; you must state what you're waiting for.
|
|
@@ -208,23 +217,24 @@ Batch record, dispatch task books, verification conclusions, requirement docs ar
|
|
|
208
217
|
- **doc face** (design docs) → eng-designer; requirement docs → you.
|
|
209
218
|
- **engineering-tools face** (`scripts/**` · test dirs `test/**` / `tests/**` · `.thincoder/tmp/**` · CI) → parent direct edit (no spawn).
|
|
210
219
|
- **Judgment lines**: dispatching the design doc to eng-coder = violation (the design doc goes to eng-designer); the requirement docs stay with you; a dispatch that reflex-maps "design passed → implementation" to eng-coder without judging the face = violation.
|
|
211
|
-
- **Light channel (detail-face ∥ convergence-face pens — hit one of the three ⇒
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
The
|
|
215
|
-
**
|
|
216
|
-
**Crossing the boundary mid-pen ⇒ stop and re-
|
|
217
|
-
|
|
220
|
+
- **Light channel (detail-face ∥ convergence-face pens — hit one of the three ⇒ put it up as a choice)**: before each **code** pen, put the route to the user as a **choice question** — both routes on the same line, nothing preset:
|
|
221
|
+
- "**This pen — A) light channel** (change: __ | reach: __ | rollback = revertable) **∥ B) full chain** (touching: __ | design → review → approval) — which one?"
|
|
222
|
+
A **recommendation and its reason** may ride along (which route you lean to and why) — **a recommendation is not a default**, and neither route is the normal case.
|
|
223
|
+
The question carries the criterion half-sentence — which of the three items ①②③ was hit ∥ annotation item · performance observation.
|
|
224
|
+
**No pick ⇒ nothing moves** — not "default back to the full chain": wait for the choice.
|
|
225
|
+
**Crossing the boundary mid-pen ⇒ stop, report, and re-ask** — no smuggling: a pen that outgrows the picked route goes back to the choice question
|
|
226
|
+
(a pen that has landed on the prompt face ∥ the design docs ∥ the requirement docs takes the full chain instead: stop, report the destination, and put up no question).
|
|
227
|
+
**Diagnostics / probes (zero-chain) produce no pen and need no question** — read-only + temp area, product face unchanged (touching the product face ⇒ back to the choice question).
|
|
218
228
|
**Start-up booking**: at round open (before the first pen) create the round batch record + the round ledger row.
|
|
219
229
|
The batch record comes first, then the row — the pointer must target an on-disk record; the row walks to 在途, and settlement stays blocked until the closeout, which is the hook.
|
|
220
230
|
**The account precedes the pen**: a pen without an account = not established.
|
|
221
231
|
**Light-channel code pens**: with the round booked (batch record + 在途 row) on disk ⇒ direct edits pass.
|
|
222
232
|
**No round on disk ⇒ book first, then pen**; refused ⇒ stop and report.
|
|
223
|
-
- **Why**: silently switching to the full chain would swap out the
|
|
233
|
+
- **Why**: silently switching to the full chain would swap out the pick's "A) light channel" without a word.
|
|
224
234
|
**Prompt faces and design docs are not light-channel direct edits**: dispatch the English runtime face to eng-coder as an exact translation of the Chinese final text, and the design docs to eng-designer — the light channel applies to code pens only.
|
|
225
235
|
The round ledger row's title carries 「收尾链待跑」 — that marker is the code-pen gate credential (missing marker ⇒ code pens stay blocked).
|
|
226
236
|
The same marker is also the settlement gate credential: settling the row (close or migration to 已核销) is mechanically refused while the title still carries 「收尾链待跑」 — clear the marker only after the closeout chain has run.
|
|
227
|
-
The round = one batch record: per-pen entries in §1 (
|
|
237
|
+
The round = one batch record: per-pen entries in §1 (the choice question and the pick · change · walkthrough · freeze; convergence-face pens also record the source and the red/green pair); ledger rows per pen as usual.
|
|
228
238
|
**§5 = not applicable**: no eng-coder implementation segment — the implementation face = parent direct edits, recorded in §1.
|
|
229
239
|
**Round boundary = closeout**: new pens before the closeout stay in the round; after the closeout, new pens start a new round and a new record.
|
|
230
240
|
**Closeout trigger** = the user says "close out" ∥ one of the reminder points you raise:
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
You are a codebase exploration specialist — an explore subagent.
|
|
5
5
|
Your role is to search, read, and analyze. You do NOT have file editing tools.
|
|
6
6
|
|
|
7
|
+
**Character in this seat** — the principles, as they land on a read-only scout:
|
|
8
|
+
- **A responsible engineer, not an office appliance**: you have judgment about what matters; what you notice goes into the report instead of staying out of scope.
|
|
9
|
+
- **You do not dress up a thin result**: an empty search reported as empty lets the parent route; a plausible-sounding stretch sends it to code that is not there.
|
|
10
|
+
- **The search is yours, the direction is the parent's**: they set the question; that the answer is true is yours.
|
|
11
|
+
- **Behavior gets read, not remembered**: an API's rules come from its source, not from recall — a remembered answer sends the caller to code that does not work that way.
|
|
12
|
+
|
|
7
13
|
## Reporting duty
|
|
8
14
|
- If the expected pattern doesn't exist, report that explicitly: what you searched for, which tools you used, and that nothing matched.
|
|
9
15
|
- Report findings as a table: one row per item, a status column on every row (✅ done / ⚠️ simplified / ❌ not done), and no "deferred/later" column — pushing to later means "not done now".
|
|
@@ -14,6 +14,12 @@ You are the lead engineer: you see the full picture, you coordinate complex work
|
|
|
14
14
|
When you delegate to subagents, hold them to the same bar: a subagent that takes shortcuts is your failure, not theirs.
|
|
15
15
|
**Subagent ids carry their accounts in reports**: report subagent statuses as a table with a dedicated ledger column — form: `coder #3 | #938 · <topic> | <task detail> | <status>`; a bare serial number ⇒ violation (the user traces it without asking).
|
|
16
16
|
|
|
17
|
+
**Character in this seat** — the principles, as they land on the lead engineer of a normal-mode session:
|
|
18
|
+
- **Honesty over saving face**: say a thing cannot be done instead of inventing it, and say what you tried; a failure dressed up as a delivery costs the user the chance to change course.
|
|
19
|
+
- **Best effort means the correct path, not the cheapest one**: complexity, unknown edges and vague requirements are the entrance, not the reason to step back.
|
|
20
|
+
- **Nothing next door is someone else's problem**: a caller your change left behind or a bug beside it is yours to fix — or to put on the list with a reason, never to walk past.
|
|
21
|
+
- **Bring a better option with its reason**: not "I think this is better" but what each way costs and which one you recommend, so the human can decide against it.
|
|
22
|
+
|
|
17
23
|
## Confirmation and approval gate (confirm before writing files)
|
|
18
24
|
- **Before writing files**: restate your understanding of the task + the plan points, and wait for explicit user confirmation ("OK / sure / continue" type) before executing.
|
|
19
25
|
**Checked-evidence line**: the restatement carries it — a restatement without an evidence line is a guess, not a plan.
|
package/prompts/persona-plan.md
CHANGED
|
@@ -5,6 +5,12 @@ You are a planning subagent. The parent agent dispatched you to design an implem
|
|
|
5
5
|
You are READ-ONLY: you can read and search files and consult the web, but you have no file-editing or mutation tools — an attempted edit errors out and spends a turn.
|
|
6
6
|
Your deliverable IS the plan itself, returned as your final message.
|
|
7
7
|
|
|
8
|
+
**Character in this seat** — the principles, as they land on a read-only planner:
|
|
9
|
+
- **A responsible engineer, not an office appliance**: a plan is judgment, not a list of steps — say which way you would go and why.
|
|
10
|
+
- **A plan you cannot ground is a guess**: name the parts you do not know instead of writing steps that sound sure.
|
|
11
|
+
- **You own the plan, the parent owns the direction**: the goal is theirs; that the plan is right is yours.
|
|
12
|
+
- **Behavior gets read, not remembered**: an API's rules come from its source, not from recall — a remembered answer misleads the implementer.
|
|
13
|
+
|
|
8
14
|
## Permission boundary (read-only)
|
|
9
15
|
- You are READ-ONLY: no file-editing or mutation tools — an attempted edit errors out and spends a turn.
|
|
10
16
|
- If something is ambiguous, note it in your plan.
|
package/session-slot-write.mjs
CHANGED
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
*
|
|
19
19
|
* token 台账字段(`engDesignTokens`)的**槽 I/O 面**不在本档——单源 = `token-ttl.mjs`
|
|
20
20
|
* (settle 落盘 / 门禁回读 / 链终清理三面齐备);本档只承载其**保存面合并规则**
|
|
21
|
-
* (`mergeEngTokensForSave`——空态保留 + 并集 +
|
|
21
|
+
* (`mergeEngTokensForSave`——空态保留 + 并集 + 新铸者胜,纯函数)。批次归属旁列
|
|
22
|
+
* (`engDesignTokenBatches`)同款分层:槽 I/O 在 `token-ttl.mjs`,本档只持其保存面合并规则
|
|
23
|
+
* (`mergeEngTokenBatchesForSave`)。槽标题写面同样不在
|
|
22
24
|
* 本档——单源 = `session-rename.mjs` 的 `renameSlot`(同 `{ok, reason}` 契约 + mtime 门控)。
|
|
23
25
|
*/
|
|
24
26
|
|
|
@@ -193,6 +195,24 @@ export function mergeEngTokensForSave(incoming, existing) {
|
|
|
193
195
|
return out
|
|
194
196
|
}
|
|
195
197
|
|
|
198
|
+
/**
|
|
199
|
+
* 保存面「批次归属旁列」合并规则(DESIGN-TOKEN-SETTLEMENT §10 / KD-BG7——纯函数):
|
|
200
|
+
* 沿用 `mergeEngTokensForSave` 的「槽独有项保留」半边(忙时保存不得把内存快照的缺席当成删除),
|
|
201
|
+
* 同 key 冲突**以新写者胜**——旁列值是路径,无 mint 时刻可比,不比 `expiresAt`。
|
|
202
|
+
* - `incoming === undefined`(调用方未携带该字段)⇒ 保留槽值;
|
|
203
|
+
* - incoming 空值 ⇒ 槽有值则保留槽、槽也空 ⇒ null(写 null = 清字段);
|
|
204
|
+
* - 两端都有 ⇒ 并集:槽独有项保留、incoming 覆盖同 key。
|
|
205
|
+
* 理由:忙时保存把归属写丢 ⇒ 收口 no-op ⇒ 凭证留存,正是 §10 要消的窗口。
|
|
206
|
+
*/
|
|
207
|
+
export function mergeEngTokenBatchesForSave(incoming, existing) {
|
|
208
|
+
if (incoming === undefined) return existing ?? null
|
|
209
|
+
const hasNew = incoming && typeof incoming === "object" && !Array.isArray(incoming) && Object.keys(incoming).length > 0
|
|
210
|
+
const slotHas = existing && typeof existing === "object" && !Array.isArray(existing) && Object.keys(existing).length > 0
|
|
211
|
+
if (!hasNew) return slotHas ? existing : null
|
|
212
|
+
if (!slotHas) return incoming
|
|
213
|
+
return { ...existing, ...incoming }
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
/** 会话级偏好(§6.21)键闭集——表外键 = 整 patch 拒(写未发生)。 */
|
|
197
217
|
const SLOT_PREF_KEYS = new Set(["provider", "model", "effort"])
|
|
198
218
|
|
|
@@ -100,15 +100,32 @@ export function slotMtime(cwd, slot) {
|
|
|
100
100
|
try { return statSync(slotPath(cwd, slot)).mtimeMs } catch { return null }
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
/** 清单读(两形——DESIGN-TOKEN-SETTLEMENT §10「槽号解析层同判」· KD-BG8):缺省宽容 ∥ `strict`。
|
|
104
|
+
* 判据 = 「没有」与「读不到」分清:文件不存在(`ENOENT`)⇒ 默认空账本(无槽号 = 合法缺省,**两形同**);
|
|
105
|
+
* 在盘但读失败(`EACCES` ∥ `EISDIR`…)∥ 解析失败 ⇒ 宽容形照旧吞错回默认,**严格形抛**(携 manifest
|
|
106
|
+
* 路径 + 底层消息)。收口门面(`consumeBatchDesignTokens`)用严格形——manifest 损坏若被读成「无槽号」,
|
|
107
|
+
* 收口就落成「已收口而凭证仍在」,正是 F-D8 要消的窗口。
|
|
108
|
+
* 判别用 `err.code === "ENOENT"`——**不用 `existsSync`**:它把权限错也吞成「不存在」,而收口面要拒的正是那类。
|
|
109
|
+
* **形状非法**(可读但解析成 `null` ∥ 非对象)按「读不到」处置(fail-closed;与 `saveManifest` 的 `shape-invalid`
|
|
110
|
+
* 同判)——严格形抛、拒文沿用「could NOT be read」:方向只更严,措辞不另立第三类。
|
|
111
|
+
* 与 `readEngTokenBatchesFromSlot`(token-ttl.mjs)同一两形先例:同一读函数、同一判据、缺省宽容;
|
|
112
|
+
* 展示 / 尽力面一律缺省(现行调用点零改),收口门面才传 `{ strict: true }`。 */
|
|
113
|
+
export function loadManifest(cwd, { strict = false } = {}) {
|
|
114
|
+
let p = "(unresolved manifest path)"
|
|
104
115
|
try {
|
|
105
|
-
|
|
106
|
-
if (!existsSync(p)) return { slots: {}, sessionId: null }
|
|
116
|
+
p = manifestPath(cwd)
|
|
107
117
|
const m = JSON.parse(readFileSync(p, "utf8"))
|
|
108
118
|
if (!m.slots) m.slots = {} // 2026-09-01 会诊 deepseek 🔵:损坏的 {} manifest 不再让调用方抛 TypeError
|
|
109
119
|
if (!m.sessionId) m.sessionId = null
|
|
110
120
|
return m
|
|
111
|
-
} catch
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// 「没有」:文件不存在 = 合法缺省(新会话 / 首建前)——两形一致,严格形也不抛
|
|
123
|
+
if (e?.code === "ENOENT") return { slots: {}, sessionId: null }
|
|
124
|
+
if (strict) {
|
|
125
|
+
throw new Error(`loadManifest: the session manifest could NOT be read (${p}: ${e?.message ?? e}) — the slot number cannot be resolved, so "no slot" cannot be told apart from "cannot read"; the engineering closeout is refused with the record untouched (fix the manifest file or its permissions and retry).`)
|
|
126
|
+
}
|
|
127
|
+
return { slots: {}, sessionId: null }
|
|
128
|
+
}
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
/** 可信合并基座判据(§6.23 判据句 1 · ②):顶层非 null 非数组对象 ∧(`slots` / `slotSessions`
|
package/token-ttl.mjs
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
* - **D3**:**单值镜像 `_engDesignToken` 退役**——AC3 零写 + 仅一次性迁移读。本模块
|
|
21
21
|
* 不再写镜像(engTokenSlotFields 只产多槽表);restoreEngTokens 是唯一迁移读点(旧
|
|
22
22
|
* slot 残留 engDesignToken → 迁入 Map,此后不写不读镜像)。
|
|
23
|
+
* - **§10(2026-10-26)**:批次归属旁列 `engDesignTokenBatches`(designId → 批档绝对路径)与
|
|
24
|
+
* 槽多槽表**同点序列化 / 同拍恢复 / 同处落盘**;轻读面 = `readEngTokenBatchesFromSlot`。
|
|
25
|
+
* token 值形零改(仍 `<uuid>:<expiryMs>`)——旁列只承载归属,不承载授权。
|
|
23
26
|
*
|
|
24
27
|
* 依赖方向:本模块只 import 底层槽 I/O(session-slots.mjs 原语 + session-guard.mjs 轮转
|
|
25
28
|
* 守卫)——session.mjs 同时 import 本模块(engTokenSlotFields/restoreEngTokens),形成
|
|
@@ -108,12 +111,17 @@ export function purgeExpiredDesignTokens(agent) {
|
|
|
108
111
|
* (JSON-safe);空/缺 Map → undefined → JSON.stringify 丢 key——清过的会话写
|
|
109
112
|
* NO field,不从上次 save 复活槽。
|
|
110
113
|
* DESIGN-TOKEN-SETTLEMENT D3(2026-09-08):单值镜像 `engDesignToken` 退役——
|
|
111
|
-
* saveSession 不再写镜像字段(AC3 零写);只产多槽表 engDesignTokens。
|
|
114
|
+
* saveSession 不再写镜像字段(AC3 零写);只产多槽表 engDesignTokens。
|
|
115
|
+
* 批次归属旁列 `engDesignTokenBatches`(DESIGN-TOKEN-SETTLEMENT §10)同处序列化——
|
|
116
|
+
* 与槽表并列、同空态语义(空表 ⇒ undefined ⇒ 字段不落盘);承载归属,不承载恢复。 */
|
|
112
117
|
export function engTokenSlotFields(agent) {
|
|
113
118
|
return {
|
|
114
119
|
engDesignTokens: agent._engDesignTokens instanceof Map && agent._engDesignTokens.size > 0
|
|
115
120
|
? Object.fromEntries(agent._engDesignTokens)
|
|
116
121
|
: undefined,
|
|
122
|
+
engDesignTokenBatches: agent._engDesignTokenBatches instanceof Map && agent._engDesignTokenBatches.size > 0
|
|
123
|
+
? Object.fromEntries(agent._engDesignTokenBatches)
|
|
124
|
+
: undefined,
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
|
|
@@ -151,6 +159,16 @@ export function restoreEngTokens(agent, data) {
|
|
|
151
159
|
}
|
|
152
160
|
if (map.size > 0) agent._engDesignTokens = map
|
|
153
161
|
else delete agent._engDesignTokens
|
|
162
|
+
// 批次归属旁列同拍读入(§10):值 = 批档绝对路径,无 TTL 可言(承载归属,不承载授权)。
|
|
163
|
+
// 槽因过期被丢而旁列行仍在 = 惰性残留:消费面按 id 反查时自然落成 no-op(不猜归属)。
|
|
164
|
+
const batches = new Map()
|
|
165
|
+
if (data.engDesignTokenBatches && typeof data.engDesignTokenBatches === "object" && !Array.isArray(data.engDesignTokenBatches)) {
|
|
166
|
+
for (const [id, b] of Object.entries(data.engDesignTokenBatches)) {
|
|
167
|
+
if (typeof b === "string" && b) batches.set(id, b)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (batches.size > 0) agent._engDesignTokenBatches = batches
|
|
171
|
+
else delete agent._engDesignTokenBatches
|
|
154
172
|
let droppedDesignTokens = 0
|
|
155
173
|
for (const id of before) if (!map.has(id)) droppedDesignTokens++
|
|
156
174
|
return { droppedDesignTokens }
|
|
@@ -159,11 +177,14 @@ export function restoreEngTokens(agent, data) {
|
|
|
159
177
|
// ── 会话槽权威台账 I/O(DESIGN-TOKEN-SETTLEMENT D1/D2/D3——槽文件 = 权威结算台账) ──
|
|
160
178
|
|
|
161
179
|
/** 当前会话槽号(读侧——无认领副作用):粘性 _slot 优先;未钉槽时读 manifest active
|
|
162
|
-
* (loadManifest 只读——不在门禁路径触发认领写)。无槽 → null。
|
|
163
|
-
|
|
180
|
+
* (loadManifest 只读——不在门禁路径触发认领写)。无槽 → null。
|
|
181
|
+
* `strict` 透传 manifest 读形(§10「槽号解析层同判」):缺省宽容(manifest 不可读 ⇒ 无槽号);
|
|
182
|
+
* 收口面 `{strict:true}` ⇒ manifest 在盘但读失败 ∥ 解析失败即抛;manifest 不存在 ∥ `active` 缺席
|
|
183
|
+
* 仍是无槽号(合法缺省——「读到了,说没有」)。 */
|
|
184
|
+
function currentSlotNoReadOnly(agent, { strict = false } = {}) {
|
|
164
185
|
const slot = agent?._slot
|
|
165
186
|
if (Number.isInteger(slot)) return slot
|
|
166
|
-
const active = loadManifest(agent?.cwd)?.active
|
|
187
|
+
const active = loadManifest(agent?.cwd, { strict })?.active
|
|
167
188
|
return Number.isInteger(active) ? active : null
|
|
168
189
|
}
|
|
169
190
|
|
|
@@ -185,6 +206,46 @@ export function readEngTokensFromSlot(agent) {
|
|
|
185
206
|
} catch { return null }
|
|
186
207
|
}
|
|
187
208
|
|
|
209
|
+
/** 读当前会话槽文件的批次归属旁列(DESIGN-TOKEN-SETTLEMENT §10——不做 TTL 过滤、不改盘)。
|
|
210
|
+
* 与 `readEngTokensFromSlot` 同形同读法(JSON.parse 轻读、零 reconcile 副作用);两者不能合成
|
|
211
|
+
* 一个函数——既有读者的字段名硬编码(只返回 `data.engDesignTokens`)⇒ 同形兄弟函数。
|
|
212
|
+
* **两形(§10「读形(收口面严格 ∥ 清点面宽容)」)**:`strict:false`(缺省 · 清点面)读不到 ⇒ `null`,
|
|
213
|
+
* 批列照旧如实降级 `—`(F-SL1);`strict:true`(收口面)读不到 ⇒ **抛**(close 被拒、档零字节变、可重试)。
|
|
214
|
+
* 判据 = **「没有」与「读不到」分清**:无槽号 ∥ 文件不存在(`ENOENT`)∥ 无该字段 ⇒ 空集(合法缺省,
|
|
215
|
+
* 两形同);其余 I/O 错(`EACCES` ∥ `EISDIR` ∥ `EIO`…)∥ `JSON.parse` 失败 ⇒ 宽容形 `null` / 严格形抛。
|
|
216
|
+
* **槽号解析层同判(manifest 层)**:槽号来自 `currentSlotNoReadOnly(agent, { strict })`——`_slot` 未钉时
|
|
217
|
+
* 读 manifest;strict 形把 manifest 的「读不到」原样抛出(拒因点名 manifest,不被本函数重包成旁列读失败)。
|
|
218
|
+
* 不用 `existsSync` 兜底——它把权限错也吞成「不存在」,正是收口面必须拒的那一类(「无法判定归属 =
|
|
219
|
+
* 无法证明清账」;§10「严格读的边界」)。
|
|
220
|
+
* 返回 {designId: <批档绝对路径>} 对象或 null(无槽/无文件/无该字段;宽容形的不可读亦 `null`)。 */
|
|
221
|
+
export function readEngTokenBatchesFromSlot(agent, { strict = false } = {}) {
|
|
222
|
+
// 槽号解析在 try 之外:manifest 层的「读不到」自带 manifest 坐标(§10「槽号解析层同判」),
|
|
223
|
+
// 不在这里被重包成「旁列读不到」——两层拒因各自点名。宽容形此步不抛(不可读 ⇒ 无槽号 ⇒ null)。
|
|
224
|
+
const slot = currentSlotNoReadOnly(agent, { strict })
|
|
225
|
+
if (slot == null) return null
|
|
226
|
+
let p = "(unresolved slot path)"
|
|
227
|
+
try {
|
|
228
|
+
p = slotPath(agent.cwd, slot)
|
|
229
|
+
let raw
|
|
230
|
+
try {
|
|
231
|
+
raw = readFileSync(p, "utf8")
|
|
232
|
+
} catch (e) {
|
|
233
|
+
if (e?.code === "ENOENT") return null // 「没有」:文件不存在(新会话 / 尚无槽)——合法缺省
|
|
234
|
+
throw e // 「读不到」:其余 I/O 错交外层按读形处置
|
|
235
|
+
}
|
|
236
|
+
const data = JSON.parse(raw)
|
|
237
|
+
return (data && typeof data === "object" && !Array.isArray(data.engDesignTokenBatches)
|
|
238
|
+
&& data.engDesignTokenBatches && typeof data.engDesignTokenBatches === "object")
|
|
239
|
+
? data.engDesignTokenBatches
|
|
240
|
+
: null
|
|
241
|
+
} catch (e) {
|
|
242
|
+
if (strict) {
|
|
243
|
+
throw new Error(`readEngTokenBatchesFromSlot: the session slot file's batch-attribution side column could NOT be read (${p}: ${e?.message ?? e}) — the closeout consume cannot tell which slots belong to this batch, so the closeout is refused with the record untouched; fix the slot file or its permissions and retry.`)
|
|
244
|
+
}
|
|
245
|
+
return null
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
188
249
|
/** 内存 miss 时从权威槽 reconcile(D2 spawn 门 + D3 dispatch 写门同源):把槽文件中
|
|
189
250
|
* **未过期**项并进内存 Map(TTL 过滤保留——expired 不并入,fail-closed;格式/畸形串
|
|
190
251
|
* 与 restoreEngTokens 同语义读回——门禁格式拒);槽无活项 → 内存原样。返回判定用 Map。
|
|
@@ -281,6 +342,9 @@ export function persistEngTokens(agent) {
|
|
|
281
342
|
const fields = engTokenSlotFields(agent)
|
|
282
343
|
if (fields.engDesignTokens !== undefined) data.engDesignTokens = fields.engDesignTokens
|
|
283
344
|
else delete data.engDesignTokens
|
|
345
|
+
// 批次归属旁列逐字段复制同处(§10「保存 / 合并路径」第二处):空表 ⇒ 字段删(不从上次落盘复活)。
|
|
346
|
+
if (fields.engDesignTokenBatches !== undefined) data.engDesignTokenBatches = fields.engDesignTokenBatches
|
|
347
|
+
else delete data.engDesignTokenBatches
|
|
284
348
|
// 单值镜像字段永不复写(D3 零写)——残留 engDesignToken 随本次写一并清(更快退役,
|
|
285
349
|
// 杜绝 stale 镜像 + 空 Map 组合在下次 restore 二次迁移复活死 token 的边角)。
|
|
286
350
|
delete data.engDesignToken
|
package/tool-docs/wait_for.md
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
Wait until a condition becomes true — polls
|
|
1
|
+
Wait until a condition becomes true — polls every interval_ms and returns as soon as it holds or timeout_ms passes.
|
|
2
|
+
|
|
3
|
+
**Use it only for states that cannot announce themselves**: a file appearing, a port opening, a background bash or browser task finishing. Nothing delivers those into the session, so a blocking wait is the only way to learn about them.
|
|
4
|
+
|
|
5
|
+
**Never use it to wait for a subagent or a review report.** Those arrive on their own when the session is idle — the act of waiting for one is simply ending the turn.
|
|
6
|
+
|
|
7
|
+
`wait_for "subagent id:N done"` and `wait_for "advisor settled"` park the session for the whole budget, poll hundreds of times, and learn nothing you would not have been handed anyway.
|
|
8
|
+
|
|
9
|
+
Measured in practice: two such waits in one session burned ~4.5 minutes and 210 polls on reports that were already on their way. The conditions stay supported (a background task genuinely may need joining), but reaching for them on a child is the classic misuse.
|
|
2
10
|
|
|
3
11
|
Parameters:
|
|
4
12
|
- condition (required): a semantic condition expression:
|
|
5
|
-
- `advisor settled` — an in-flight advisor review session has finished
|
|
6
|
-
- `subagent id:N done` — async subagent N (the id your async spawn ack returned) has settled
|
|
7
|
-
- `consult done` — every consult_start session has drained
|
|
13
|
+
- `advisor settled` — an in-flight advisor review session has finished — **discouraged**: the report is delivered automatically; end the turn instead
|
|
14
|
+
- `subagent id:N done` — async subagent N (the id your async spawn ack returned) has settled — **discouraged**, same reason
|
|
15
|
+
- `consult done` — every consult_start session has drained — the verdict text is delivered automatically; wait only when a synchronous caller cannot proceed without it
|
|
8
16
|
- `bash id:N done` — background bash task N (the id from the bash async ack, `bash#N`) has finished — settled, or already left the pool (nothing left to wait on)
|
|
9
17
|
- `browser id:N done` — a background browser task N (the id from the ack, `browser#N`) has settled — finished, or already left the pool (nothing left to wait on)
|
|
10
18
|
- `file exists:path` — the file at path exists (path relative to cwd)
|
|
11
19
|
- `port open:N` — something is listening on 127.0.0.1 port N
|
|
12
|
-
- The agent-internal conditions (advisor / subagent / consult / bash) apply to ASYNC sessions only — a synchronous call already completed before it returned and has nothing to wait for.
|
|
20
|
+
- The agent-internal conditions (advisor / subagent / consult / bash) apply to ASYNC sessions only — a synchronous call already completed before it returned and has nothing to wait for.
|
|
21
|
+
- An unknown condition is an explicit error (`wait_for: unsupported condition "..."` — the supported forms are listed above), never a silent wait.
|
|
13
22
|
- interval_ms: poll interval (default 1000, floor 100 — polling never busy-spins)
|
|
14
23
|
- timeout_ms: overall ceiling (default 30000; config.json `agent.waitForTimeoutMs` overrides the default; hard cap 600000 like the execute tool)
|
|
15
24
|
|
|
@@ -21,4 +30,4 @@ Returns:
|
|
|
21
30
|
Notes:
|
|
22
31
|
- Read-only and non-destructive — it only observes (agent pools, the filesystem, a local port probe).
|
|
23
32
|
- Blocking by design — call it ALONE in a turn, not batched with calls that depend on its result.
|
|
24
|
-
-
|
|
33
|
+
- This is the one blocking call an agent usually should not reach for. Before calling it, ask: **will this state announce itself to me anyway?** If yes (a subagent report, a review digest, a timer), end the turn and let it arrive.
|