@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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent/suspension.mjs — §17 挂起 / 唤醒驱动(核内形态——AGENT-LOOP.md §2.3 #184 定案)。
|
|
3
|
+
*
|
|
4
|
+
* 语义(两端同源,逐字承两端现行挂起驱动——CLI 侧 tui 面 / VSC 侧 extension 面,同语义移植):
|
|
5
|
+
* 回合尾后台池仍 live → 挂起会话——池项 settle → 入 pending;pending 非空 **或存在未 drain 的
|
|
6
|
+
* 上行 ask**(`upstreamWaiting`——F-UC7 / §6.27.12.4 ①)→ 合并消化轮 / 唤醒轮(auto-turn;旗标
|
|
7
|
+
* `upstreamTurn` 仅供域文本选择);挂起空闲用户输入优先于消化轮。池空 + pending 空 + 无待处理
|
|
8
|
+
* 输入 → 自然退出(idle)。退出清场:abort = 清池不注入(陈旧结果不注入);idle = 残余直注入(结果零丢失)。
|
|
9
|
+
*
|
|
10
|
+
* 载体注入(端差面 = 池 / pending / 标志挂谁):`ctx.carrier` = 字段集按核内异步面
|
|
11
|
+
* 现行口径的对象(`_asyncSubagents` · `_asyncAdvisors` · `_consultSessions` ·
|
|
12
|
+
* `_pendingAsyncResults` · `_suspended`)——CLI 形 = `agent` 对象;VSC 形 = depth-0
|
|
13
|
+
* `history` 数组(附加属性不污染会话文件)。机制对载体零预设(空字段一律 `?.` 读)。
|
|
14
|
+
*
|
|
15
|
+
* 核内零文案、零渲染、零端名分支(契约 5 / 10):状态行文本 / 提示行 / 消息族全在宿主侧,
|
|
16
|
+
* 本模块只发结构化计数(hooks.onCounts)与消化轮边界(hooks.onDigest)。
|
|
17
|
+
*
|
|
18
|
+
* ctx 接口(注入面):
|
|
19
|
+
* - carrier 池 / pending / 标志载体(见上)
|
|
20
|
+
* - runTurn(text, opts) 回合执行器(digest = `("", { autoTurn: true })`;上行唤醒轮 =
|
|
21
|
+
* `("", { autoTurn: true, upstreamTurn: true })`——旗标仅供域文本选择)
|
|
22
|
+
* - abortSignal 会话中止信号(兜底监听 + abort 清场判据)
|
|
23
|
+
* - hooks.onCounts(counts) 计数变化通知({ running, queued, pending, done })
|
|
24
|
+
* - hooks.onDigest(phase, counts) 消化轮边界(start / end)
|
|
25
|
+
* - hooks.reclaim(consumed) 消化完成条目逐条回收(不等池空)
|
|
26
|
+
* - hooks.freezeAll() 退出冻结(兜底残项)
|
|
27
|
+
* - injectResidual(entry) 残余注入面(idle 退出清场;宿主装配端注入器——CLI =
|
|
28
|
+
* 逐族注入分发 / VSC = pending 单容器注入器)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { parkAsyncPending } from "../agent-tools/async-settle.mjs"
|
|
32
|
+
import { upstreamWaiting } from "../agent-tools/parent-channel.mjs"
|
|
33
|
+
|
|
34
|
+
/** 运行中 consult children(非 stopped 会话的在飞子调用数——挂起活度/计数同源)。 */
|
|
35
|
+
function consultRunningChildren(carrier) {
|
|
36
|
+
let n = 0
|
|
37
|
+
for (const s of carrier?._consultSessions?.values() ?? []) {
|
|
38
|
+
if (!s.stopped) n += Math.max(0, s.pending ?? 0)
|
|
39
|
+
}
|
|
40
|
+
return n
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 后台池存活判据(D-S2/F5 口径——CLI 为准):running/queued 的池条目,或已 settle 未注入
|
|
44
|
+
* (pending 非空 = D-S3「未注入」),或运行中 consult children(会诊跨回合)。回合尾与每次
|
|
45
|
+
* 轮末都评估退出。 */
|
|
46
|
+
export function poolLive(carrier) {
|
|
47
|
+
const sub = carrier?._asyncSubagents
|
|
48
|
+
const adv = carrier?._asyncAdvisors
|
|
49
|
+
return (sub && sub.size > 0) || (adv && adv.size > 0)
|
|
50
|
+
|| (carrier?._pendingAsyncResults?.length ?? 0) > 0
|
|
51
|
+
|| consultRunningChildren(carrier) > 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** D-S3 ③ 记账清扫:回合边界竞态落下的已 settle 项(settle 回调未及移交——发生在回合刚结束、
|
|
55
|
+
* `_suspended` 尚未置位的窗口)补入 pending。幂等:`parkAsyncPending` 置 `_inPending` 防重复,
|
|
56
|
+
* 键按条目实际 key 删除(两端 key 形态不同——对载体零预设)。 */
|
|
57
|
+
export function sweepSettledToPending(carrier) {
|
|
58
|
+
for (const key of ["_asyncSubagents", "_asyncAdvisors"]) {
|
|
59
|
+
const map = carrier?.[key]
|
|
60
|
+
if (!(map instanceof Map) || map.size === 0) continue
|
|
61
|
+
for (const [k, e] of [...map.entries()]) {
|
|
62
|
+
if (e.done && !e._inPending) {
|
|
63
|
+
parkAsyncPending(carrier, e)
|
|
64
|
+
map.delete(k)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 后台计数(D-S8——宿主状态行 / 消息族的数据面):{ running, queued, pending, done }。
|
|
71
|
+
* `done` = 回合尾留池的 settled 未消费项(挂起首轮 sweep 前的可见窗口)。 */
|
|
72
|
+
export function backgroundCounts(carrier) {
|
|
73
|
+
const entries = []
|
|
74
|
+
for (const key of ["_asyncSubagents", "_asyncAdvisors"]) {
|
|
75
|
+
const map = carrier?.[key]
|
|
76
|
+
if (map instanceof Map) entries.push(...map.values())
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
running: entries.filter((e) => e.status === "running").length + consultLiveCount(carrier),
|
|
80
|
+
queued: entries.filter((e) => e.status === "queued").length,
|
|
81
|
+
pending: carrier?._pendingAsyncResults?.length ?? 0,
|
|
82
|
+
done: entries.filter((e) => e.done).length,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** running 会诊会话计数(会话级——计数面同 poolLive 口径)。 */
|
|
87
|
+
function consultLiveCount(carrier) {
|
|
88
|
+
let n = 0
|
|
89
|
+
for (const s of carrier?._consultSessions?.values() ?? []) {
|
|
90
|
+
if (!s.stopped) n += 1
|
|
91
|
+
}
|
|
92
|
+
return n
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 退出清场(单点——驱动 finally 与单测共用):
|
|
97
|
+
* - abort:清池不注入(用户显式停——陈旧结果不回灌)+ 会诊会话清理标记;
|
|
98
|
+
* - idle:残余直注入(极端竞态残项——结果零丢失;注入器由宿主注入,缺省 no-op 保残项)。
|
|
99
|
+
*/
|
|
100
|
+
export async function finishSuspension(carrier, { aborted = false, injectResidual = null } = {}) {
|
|
101
|
+
if (aborted) {
|
|
102
|
+
carrier._asyncSubagents?.clear()
|
|
103
|
+
carrier._asyncAdvisors?.clear()
|
|
104
|
+
carrier._pendingAsyncResults = []
|
|
105
|
+
if (carrier._consultSessions instanceof Map) {
|
|
106
|
+
const { cleanupConsultSessions } = await import("../agent-tools/consult.mjs")
|
|
107
|
+
cleanupConsultSessions(carrier)
|
|
108
|
+
}
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
const residual = carrier._pendingAsyncResults
|
|
112
|
+
if (residual?.length && typeof injectResidual === "function") {
|
|
113
|
+
for (const e of residual.splice(0)) await injectResidual(e)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 等待下一次 settle(池 waiter——核内异步面 settle 尾部唤醒)或宿主唤醒(wake——用户输入)。
|
|
118
|
+
* 双路单次兑现(settle / wake / aborted 三态,先到先得);cleanup 摘除全部注册。 */
|
|
119
|
+
function waitForSettleOrWake(carrier, abortSignal, latch) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
let finished = false
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
latch.wake = null
|
|
124
|
+
const i = (carrier._asyncWaiters ?? []).indexOf(onSettle)
|
|
125
|
+
if (i >= 0) carrier._asyncWaiters.splice(i, 1)
|
|
126
|
+
abortSignal?.removeEventListener("abort", onAbort)
|
|
127
|
+
}
|
|
128
|
+
const finish = (why) => {
|
|
129
|
+
if (finished) return
|
|
130
|
+
finished = true
|
|
131
|
+
cleanup()
|
|
132
|
+
resolve(why)
|
|
133
|
+
}
|
|
134
|
+
const onSettle = () => finish("settle")
|
|
135
|
+
const onAbort = () => finish("aborted")
|
|
136
|
+
;(carrier._asyncWaiters ??= []).push(onSettle)
|
|
137
|
+
latch.wake = () => finish("wake")
|
|
138
|
+
if (abortSignal?.aborted) { onAbort(); return }
|
|
139
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true })
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 启动挂起会话(同步返回句柄;宿主 `await handle.done` ⇒ `{ reason, residualInput }`)。
|
|
145
|
+
*
|
|
146
|
+
* 状态机行表(承 AGENT-LOOP.md §9/§7 行表):
|
|
147
|
+
* 1. 用户输入优先(D-S5):pendingInput 非空 → 以该消息开普通回合(`_suspended=false`);
|
|
148
|
+
* 2. pending 非空 或 存在未 drain 的 ask(`upstreamWaiting`)→ 合并消化轮 / 唤醒轮(auto-turn;
|
|
149
|
+
* 注入由宿主 runTurn 首行完成——单注入点);
|
|
150
|
+
* 3. 池空(无 running / queued / 未注入)→ 自然退出;
|
|
151
|
+
* 4. 等下一 settle / 宿主唤醒(handle.wake)。
|
|
152
|
+
* 每轮消化 / 用户回合后:hooks.reclaim(consumed) 逐条回收(不等池空)。
|
|
153
|
+
*/
|
|
154
|
+
export function startSuspension(ctx) {
|
|
155
|
+
const { carrier, runTurn, abortSignal = null, hooks = {}, injectResidual = null } = ctx
|
|
156
|
+
const pendingInput = []
|
|
157
|
+
const latch = { wake: null }
|
|
158
|
+
let finished = false
|
|
159
|
+
|
|
160
|
+
const consumedByRun = () => {
|
|
161
|
+
const before = [...(carrier._pendingAsyncResults ?? [])]
|
|
162
|
+
return () => {
|
|
163
|
+
const after = new Set(carrier._pendingAsyncResults ?? [])
|
|
164
|
+
return before.filter((e) => !after.has(e))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const done = (async () => {
|
|
169
|
+
carrier._suspended = true
|
|
170
|
+
hooks.onCounts?.(backgroundCounts(carrier))
|
|
171
|
+
let aborted = false
|
|
172
|
+
try {
|
|
173
|
+
while (true) {
|
|
174
|
+
if (abortSignal?.aborted) { aborted = true; break }
|
|
175
|
+
sweepSettledToPending(carrier)
|
|
176
|
+
// 1. 用户输入优先:单槽(至多一条待交接——宿主在 busy 期拒收提交)。
|
|
177
|
+
if (pendingInput.length > 0) {
|
|
178
|
+
const head = String(pendingInput.shift())
|
|
179
|
+
const afterRun = consumedByRun()
|
|
180
|
+
carrier._suspended = false // 用户回合 = 普通回合语义(settle 即冻结 + 回合尾直注入)
|
|
181
|
+
try {
|
|
182
|
+
await runTurn(head)
|
|
183
|
+
} finally {
|
|
184
|
+
carrier._suspended = true
|
|
185
|
+
}
|
|
186
|
+
hooks.reclaim?.(afterRun())
|
|
187
|
+
hooks.onCounts?.(backgroundCounts(carrier))
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
// 2. pending 非空 或 未 drain 的 ask → 合并消化轮 / 上行唤醒轮(单注入点由 runTurn
|
|
191
|
+
// 首行完成:pending 经 run 起始注入器,ask 经回合头 `drainChildUpstream`)。
|
|
192
|
+
const upstream = upstreamWaiting(carrier) // §6.27.12.4 ①:第二开轮源(谓词必须先于第 3 步退出判)
|
|
193
|
+
if ((carrier._pendingAsyncResults?.length ?? 0) > 0 || upstream) {
|
|
194
|
+
const afterRun = consumedByRun()
|
|
195
|
+
hooks.onDigest?.("start", backgroundCounts(carrier))
|
|
196
|
+
try {
|
|
197
|
+
await runTurn("", { autoTurn: true, upstreamTurn: upstream })
|
|
198
|
+
} catch (e) {
|
|
199
|
+
// 消化轮自身的回合级中止不是会话停止——记边界后重入循环(池空 / pending 空自然退出)。
|
|
200
|
+
if (e?.name === "AbortError" && !abortSignal?.aborted) {
|
|
201
|
+
hooks.onDigest?.("end", backgroundCounts(carrier))
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
throw e
|
|
205
|
+
}
|
|
206
|
+
hooks.onDigest?.("end", backgroundCounts(carrier))
|
|
207
|
+
hooks.reclaim?.(afterRun())
|
|
208
|
+
hooks.onCounts?.(backgroundCounts(carrier))
|
|
209
|
+
continue
|
|
210
|
+
}
|
|
211
|
+
// 3. 池空 → 自然退出。
|
|
212
|
+
if (!poolLive(carrier)) break
|
|
213
|
+
// 4. 等下一 settle / 宿主唤醒。
|
|
214
|
+
const why = await waitForSettleOrWake(carrier, abortSignal, latch)
|
|
215
|
+
if (why === "aborted") { aborted = true; break }
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
if (!finished) {
|
|
219
|
+
finished = true
|
|
220
|
+
carrier._suspended = false
|
|
221
|
+
latch.wake = null
|
|
222
|
+
// §2.3 退出清场:abort 判定与实时信号合并(两端同式——Stop 落在 runTurn 内时
|
|
223
|
+
// abort 以 AbortError 抛出、循环顶检查不会再执行——不合并会把清场误走 idle 注入)。
|
|
224
|
+
const abortedNow = aborted || Boolean(abortSignal?.aborted)
|
|
225
|
+
aborted = abortedNow
|
|
226
|
+
await finishSuspension(carrier, { aborted: abortedNow, injectResidual })
|
|
227
|
+
hooks.freezeAll?.()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { reason: aborted ? "aborted" : "idle", residualInput: pendingInput.splice(0) }
|
|
231
|
+
})()
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
/** 挂起空闲期入槽(单槽语义:宿主 busy 期拒收;至多一条待交接)。 */
|
|
235
|
+
pushInput(msg) { pendingInput.push(String(msg)) },
|
|
236
|
+
/** 宿主唤醒(用户输入落槽后 / 宿主 settle 回调路径——settle 由池 waiter 另一路唤醒)。 */
|
|
237
|
+
wake() { latch.wake?.() },
|
|
238
|
+
done,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent/write-gate.mjs — M4 写权门禁(评审目标解析 + D5 冻结窗口判据组装)。
|
|
3
|
+
*
|
|
4
|
+
* v2 唯一增量(模块设计 §2.1):评审对象 / 被审文件路径来源改读 manifest `docRoot`
|
|
5
|
+
* (去硬编码 `docs/`),token 门(`anyLiveDesignSlot`)与冻结窗口谓词本体继承 v1 零改
|
|
6
|
+
* (KD-M4-3)——本档只承载两个单点:
|
|
7
|
+
*
|
|
8
|
+
* 1. `resolveReviewTargetPaths(agent)` —— 评审目标解析单点:读 manifest `docRoot`
|
|
9
|
+
* 产出评审对象 / 被审文件的绝对路径集合(目录级;子键值形态 = 串 | 多根数组,
|
|
10
|
+
* 展开走 `docRootPaths`)。缺 `docRoot` 键 → `readManifest`
|
|
11
|
+
* 的 fillDefaults 已补默认(架构 §2.3 E2 便利 fallback 落点);整档缺失 / 非法 JSON →
|
|
12
|
+
* `{ok:false}` → 回退 DEFAULT_MANIFEST.docRoot(本函数是壳门之下的便利层——E2 的
|
|
13
|
+
* 「整档缺失 → 拒进正常循环」由壳面 requireManifest 在启动期拦(**工程模式会话**口径——
|
|
14
|
+
* 普通会话装配钩子零 manifest I/O,KD-M1-12):CLI make-agent.mjs / VSC
|
|
15
|
+
* setup.mjs 均 fail-closed);读错(权限等)→ 上抛不伪装成缺失(manifest.mjs 契约)。
|
|
16
|
+
* M6 的 `advisor.mjs` design-review 分类分支消费本导出
|
|
17
|
+
* (KD-M6-1——同源不重复实现;落 dispatch.mjs 会让 advisor 反向 import 门禁簇成环)。
|
|
18
|
+
* 2. `freezeWindowConflict(agent, absPaths)` —— D5 冻结窗口判据组装:被审文件集 =
|
|
19
|
+
* 声明文档集(`inflightDesignReviewConflict` 同源 docAbs 腿)+ 批次档(`run.batchDoc`
|
|
20
|
+
* 腿——评审绑定批档在途时,写批档同样致 stale)。dispatch.mjs / VSC tool-gates.mjs
|
|
21
|
+
* 只 import 消费,不再各自直连 `advisor-settle.mjs`。
|
|
22
|
+
*
|
|
23
|
+
* 继承零改面(本档不碰):token 门资格判据(anyLiveDesignSlot / validateDesignToken)、
|
|
24
|
+
* 代码路径判定(conventions.mjs 单一权威分类——KD-M4-1 保留为代码路径判定用)。
|
|
25
|
+
*/
|
|
26
|
+
import { DEFAULT_MANIFEST, docRootPaths, readManifest } from "../manifest.mjs"
|
|
27
|
+
// advisor-settle 无上游依赖本簇(dispatch → write-gate → advisor-settle 单向)。normAbs
|
|
28
|
+
// 权威本体 = review-facts.mjs(第 33 批迁入),advisor-settle re-export;本档再 re-export
|
|
29
|
+
// (指针链非副本),M6 经本档一行 import 取齐。
|
|
30
|
+
import { inflightDesignReviewConflict, normAbs } from "../agent-tools/advisor-settle.mjs"
|
|
31
|
+
export { normAbs }
|
|
32
|
+
|
|
33
|
+
/** 评审目标解析读的 docRoot 键集(评审对象/被审文件可住的所有文档层——M6 分类据此判定)。 */
|
|
34
|
+
const REVIEW_ROOT_KEYS = ["requirements", "specs", "design", "modules", "batches"]
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 评审目标解析单点(M4 §2.1#1):读 manifest `docRoot` → 评审对象 / 被审文件的绝对路径
|
|
38
|
+
* 集合(目录级,反斜杠归一;子键值形态 = 串 | 多根数组,逐键经 `docRootPaths` 展开)。
|
|
39
|
+
* 缺 `docRoot` 键 → readManifest 已补默认;整档缺失 / 非法 →
|
|
40
|
+
* `{ok:false}` → 回退 DEFAULT_MANIFEST.docRoot(便利层——E2 的「拒进正常循环」由壳面
|
|
41
|
+
* requireManifest 在启动期拦(**工程模式会话**口径——KD-M1-12),本函数不重复拦);
|
|
42
|
+
* 读错(权限等)→ 上抛 fail-closed。
|
|
43
|
+
* @param {Object} agent
|
|
44
|
+
* @returns {string[]} 评审目标目录的绝对路径(去重)
|
|
45
|
+
*/
|
|
46
|
+
export function resolveReviewTargetPaths(agent) {
|
|
47
|
+
const cwd = agent?.cwd ?? process.cwd()
|
|
48
|
+
// 读错上抛(manifest.mjs:readManifest 契约——不伪装成缺失);整档缺失/非法 → {ok:false}。
|
|
49
|
+
const man = readManifest(cwd)
|
|
50
|
+
const root = man.ok && man.manifest?.docRoot && typeof man.manifest.docRoot === "object"
|
|
51
|
+
? man.manifest.docRoot
|
|
52
|
+
: DEFAULT_MANIFEST.docRoot
|
|
53
|
+
const out = []
|
|
54
|
+
for (const key of REVIEW_ROOT_KEYS) out.push(...docRootPaths(root?.[key], cwd))
|
|
55
|
+
return [...new Set(out)]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* D5 冻结窗口判据组装(M4 §2.1#2):评审在途(点火 → 结算)期间父侧对被审文件集
|
|
60
|
+
* (声明文档集 + 批次档)的写入 → 返回冲突 { id, path };无冲突 → null。
|
|
61
|
+
* 声明文档集腿 = `inflightDesignReviewConflict`(同 docAbs / 同 normAbs,仅 running
|
|
62
|
+
* 未取消的设计条目);批次档腿 = 同池条目 `run.batchDoc`(评审绑定批档在途时写批档
|
|
63
|
+
* 同样致 stale——被审文件集合流点)。两腿同形返回,消费端拒绝文案零改。
|
|
64
|
+
* @param {Object} agent
|
|
65
|
+
* @param {string[]} absPaths — 本次写将触碰的路径(ABS——调用方 resolve(agent.cwd, p))
|
|
66
|
+
* @returns {{id: string, path: string}|null} 命中评审 id + 冲突路径(ABS),无冲突 null
|
|
67
|
+
*/
|
|
68
|
+
export function freezeWindowConflict(agent, absPaths) {
|
|
69
|
+
const declared = inflightDesignReviewConflict(agent, absPaths)
|
|
70
|
+
if (declared) return declared
|
|
71
|
+
const pool = agent?._asyncAdvisors
|
|
72
|
+
if (!(pool instanceof Map) || pool.size === 0) return null
|
|
73
|
+
const wanted = (absPaths ?? [])
|
|
74
|
+
.filter((p) => typeof p === "string" && p.length > 0)
|
|
75
|
+
.map((p) => normAbs(p, agent?.cwd))
|
|
76
|
+
if (wanted.length === 0) return null
|
|
77
|
+
const wantedSet = new Set(wanted)
|
|
78
|
+
for (const entry of pool.values()) {
|
|
79
|
+
if (!entry || entry.reviewType !== "design") continue
|
|
80
|
+
if (entry.status !== "running" || entry.cancelled || entry.done) continue
|
|
81
|
+
const batchDoc = entry.run?.batchDoc
|
|
82
|
+
if (typeof batchDoc !== "string" || batchDoc.length === 0) continue
|
|
83
|
+
const batchAbs = normAbs(batchDoc, agent?.cwd)
|
|
84
|
+
if (wantedSet.has(batchAbs)) return { id: String(entry.id), path: batchAbs }
|
|
85
|
+
}
|
|
86
|
+
return null
|
|
87
|
+
}
|