@thincoder/core 0.10.9 → 0.10.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -2
- package/advisor/messages.mjs +4 -5
- package/advisor/project-context.mjs +69 -44
- package/agent/completion.mjs +4 -1
- package/agent/family-tools.mjs +12 -10
- package/agent/helpers.mjs +19 -11
- package/agent/light-round.mjs +1 -4
- package/agent/record-results.mjs +6 -1
- package/agent/run-start.mjs +1 -0
- package/agent/setup-reminders.mjs +1 -1
- package/agent/turn-loop.mjs +17 -13
- package/agent/unbooked-pen.mjs +62 -0
- package/agent-tools/advisor.mjs +15 -31
- package/agent-tools/audit-block.mjs +16 -62
- package/agent-tools/batch-gate.mjs +12 -21
- package/agent-tools/checkpoint.mjs +11 -6
- package/agent-tools/context.mjs +1 -1
- package/agent-tools/design-token.mjs +9 -2
- package/agent-tools/digest-budget.mjs +2 -0
- package/agent-tools/doc-check.mjs +17 -23
- package/agent-tools/manifest.mjs +45 -14
- package/agent-tools/review-facts.mjs +35 -81
- package/agent-tools/settings.mjs +65 -5
- package/agent-tools/spawn-gates.mjs +68 -31
- package/agent-tools/subagent-async.mjs +6 -3
- package/agent-tools/subagent-spawn.mjs +76 -80
- package/agent-tools/subagent.mjs +16 -16
- package/agent-tools.mjs +1 -1
- package/browser/launch.mjs +14 -3
- package/browser/queue.mjs +31 -3
- package/browser/session.mjs +4 -0
- package/config-defaults.mjs +8 -0
- package/config-presets.mjs +1 -1
- package/config.mjs +60 -11
- package/declaration.mjs +5 -12
- package/doc-check/anchors.mjs +53 -60
- package/doc-check/index.mjs +106 -140
- package/doc-check/paragraph.mjs +119 -0
- package/fts-text.mjs +4 -1
- package/i18n.mjs +5 -0
- package/ledger-cmd.mjs +17 -4
- package/ledger-db.mjs +14 -8
- package/ledger.mjs +1 -1
- package/manifest-discovery.mjs +19 -10
- package/manifest-schema.mjs +7 -35
- package/manifest.mjs +21 -9
- package/memory/code-search.mjs +5 -14
- package/memory/docs.mjs +5 -14
- package/memory/read-gate.mjs +26 -0
- package/model-context.mjs +98 -0
- package/model-specs.mjs +110 -37
- package/package.json +2 -2
- package/permission.mjs +3 -1
- package/prompts/advisor-design.md +104 -38
- package/prompts/advisor-round1.md +72 -24
- package/prompts/advisor-round2.md +62 -25
- package/prompts/advisor-round3.md +68 -24
- package/prompts/common.md +214 -58
- package/prompts/consult-base.md +10 -3
- package/prompts/discipline-engineering.md +202 -54
- package/prompts/discipline-normal.md +95 -44
- package/prompts/persona-coder.md +3 -1
- package/prompts/persona-eng-coder.md +29 -9
- package/prompts/persona-eng-designer.md +101 -35
- package/prompts/persona-engineering.md +320 -98
- package/prompts/persona-explore.md +1 -1
- package/prompts/persona-normal.md +16 -8
- package/provider/anthropic.mjs +127 -4
- package/provider/core.mjs +16 -6
- package/provider/google.mjs +8 -8
- package/provider/list-models.mjs +37 -5
- package/provider/normalize.mjs +2 -2
- package/provider/responses.mjs +7 -7
- package/provider/sse.mjs +6 -5
- package/proxy-transport.mjs +5 -3
- package/proxy.mjs +2 -1
- package/session-guard.mjs +7 -1
- package/session-lifecycle.mjs +23 -8
- package/session-segments.mjs +1 -0
- package/session-slot-claims.mjs +4 -0
- package/session-slot-write.mjs +6 -0
- package/session-slots-manifest.mjs +4 -0
- package/token-ttl.mjs +10 -0
- package/tool-docs/advisor.md +24 -5
- package/tool-docs/doc_check.md +44 -25
- package/tool-docs/ledger.md +2 -2
- package/tool-docs/manifest-read.md +8 -0
- package/tool-docs/manifest.md +9 -8
- package/tool-docs/subagent.md +41 -16
- package/tools/web-deepseek.mjs +26 -11
- package/doc-check/version.mjs +0 -84
- package/doc-check/width.mjs +0 -132
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,31 @@
|
|
|
3
3
|
All notable changes to the core package are documented here.
|
|
4
4
|
Format: Keep a Changelog · 中文 · 号在发布时定(CalVer——见 `docs/RELEASE.md` §4)。
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.10.11] — 2026-10-25
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- **`providers[].modelContext`(按模型的人工上下文声明,单位 K)**:键 = 模型 ID(末段归一等价——`qwen/ZHIPU/GLM-5.3` ≡ `glm-5.3`),值 = 正整数 K(`128` = 128K)。
|
|
11
|
+
- **模型上下文五级解析链**:`modelContext` > 旧键 `providers[].context` > 渠道 `/models` 自报(`context_window` / `context_length` / `max_model_len`,取第一个合法值)> `MODEL_SPECS` 静态表 > 默认 128K;列表自报值是 token(直接采用),人工声明是 K(核内唯一乘点 ×1024)。
|
|
12
|
+
- **核导出 `providerSpecResolution(provider)` / `formatContextK(tokens)`**:来源五值(`modelContext` / `provider-override` / `list` / `spec` / `default`)与三端同一展示格式器。
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **旧键 `providers[].context` 退役为只读过渡层(A7 兼容)**:仍可读(L2,K 单位,仅未被 `modelContext` 点名的模型),但不再写入新配置——K 单位旧编辑命令退役,三端写面改用 `modelContext`(三端同批,随各端包发布);实际用到 L2 时告警一次并给出迁移形 `modelContext: { "<模型>": <K> }`(单位同为 K)。
|
|
17
|
+
- **`requestBudget().windowSource` 扩为五值**:读 `providerSpecResolution().source`(原二值 `provider-override` / `spec`),五个枚举值原样入 `Request budget:` 行(不并类)。
|
|
18
|
+
- **模型名匹配改「末段唯一」**:按 `/` 拆分后只取最后一段查表,不再先扫完整名——`glm-5.3/zzz` 由误命中 `glm-5.3`(1M 上下文)改为未命中(`DEFAULT_SPEC` + 告警);`kimi/kimi-k3` 表行退役(末段 `kimi-k3` 已有同值行,含 `/` 的键不可达)。
|
|
19
|
+
|
|
20
|
+
## [0.10.10] — 2026-10-22
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **子代理 `manifest` 只读面**:同名工具、`action` 枚举只剩 `read`(schema 级不可达 + execute 入口再拒一次),挂全部 depth>0 角色段;写面仍归主 agent——`init` / `write` 不在其装配,只读位也不落在全量变体上,审批门不被绕过。`readAction` 增 `canInit`:子代理侧缺档 / 无项目两态的引导行不再指向它调不到的 `init`。
|
|
25
|
+
- **`thincoder-core/prompts/common.md` 新增「动手前先查文档」一节**:代码只记形状,理由与后果住在文档里;一次会话只完成大任务的一步、上下文只是项目已知内容的一小部分,不先查就是拿局部当整体。
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **提示词面 16 档重写(一条规则一行)**:`>300` 字符行 290 → **0**(最长 1,486 → 300,为未改动原行);整句加粗 / 回查指代 / 出处括注 / 写死配置值 / 本仓路径 / 中文评审面六类残留清零。判据 = 「换个仓还成立吗」——规则只写行为,不写归属、不写配置值、不写本仓路径(配置值改教模型用 `settings` 读,路径改教用 `manifest` 取 `docRoot`)。
|
|
30
|
+
- **`tool-docs` 计数随动**:53 → 54 档(枚举工具 52 → 53)。
|
|
7
31
|
|
|
8
32
|
## [0.10.9] — 2026-10-19
|
|
9
33
|
|
|
@@ -191,7 +215,9 @@ Format: Keep a Changelog · 中文 · 号在发布时定(CalVer——见 `docs
|
|
|
191
215
|
|
|
192
216
|
### Added
|
|
193
217
|
|
|
194
|
-
- **规格表覆盖扩面(新名专行)**:`qwen-flash` / `qwen-vl-max`(实测输出上限 32_768——服务端直报 `[1, 32768]
|
|
218
|
+
- **规格表覆盖扩面(新名专行)**:`qwen-flash` / `qwen-vl-max`(实测输出上限 32_768——服务端直报 `[1, 32768]`;前者思考默认可开、后者不支持)、
|
|
219
|
+
`claude-fable-5.1`(1_000_000 / 128_000)、`gemini-3.1-pro`(1_048_576 / 65_536)、`stepfun/step-3.7-flash`(262_144);
|
|
220
|
+
kimi-code 平台三行(`kimi-for-coding` / `kimi-for-coding-highspeed` / `k3-256k`——含 effort 枚举与默认档)——掉兜底名不再退默认。
|
|
195
221
|
- **bench 受测 10 档规格行**:此前经泛前缀/兜底解析的受测模型补独立行 + 视觉声明分态登记。
|
|
196
222
|
- **跨实例意图认领层(P1)**:`peers/{sessionId}.json` 增 claims 意图域(TTL 30 分钟租约 · 进程死亡即失效);写前命中他实例活认领 ⇒ 软提示(不阻写;与写后足迹登记分开存)。
|
|
197
223
|
|
package/advisor/messages.mjs
CHANGED
|
@@ -155,15 +155,14 @@ function buildAdvisorUserMessageInner(agent, prior, reviewType, designToken = nu
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
// Engineering mode: the project
|
|
159
|
-
//
|
|
158
|
+
// Engineering mode: the project standards section — the declaration face is gone with the
|
|
159
|
+
// manifest `advisor` key, so this is always an explicit degradation (PO-2).
|
|
160
160
|
if (agent.config?.agent?.engineering) {
|
|
161
161
|
injectProjectStandards(agent, parts, guideRoot)
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
// Document map:
|
|
165
|
-
//
|
|
166
|
-
// (never the old silent skip).
|
|
164
|
+
// Document map: derived from the manifest `docRoot` (declared roots' README, then their common
|
|
165
|
+
// ancestor); neither → explicit degradation sentence (never a silent skip).
|
|
167
166
|
injectDocumentMap(agent, parts, guideRoot ?? agent.cwd)
|
|
168
167
|
|
|
169
168
|
parts.push("## Instructions")
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
* absent — in a project with a different layout the reviewer still scored the
|
|
13
13
|
* dimension it could no longer check (silent failure, the worst class: invisible
|
|
14
14
|
* and unfixable by the user). Missing pieces now say so, and the reviewer is told
|
|
15
|
-
* to state the limitation in its findings.
|
|
16
|
-
*
|
|
15
|
+
* to state the limitation in its findings. The document map is derived from the
|
|
16
|
+
* manifest's `docRoot` (the one place that says where the docs live); the standards
|
|
17
|
+
* face has no derivable convention name and is therefore an explicit degradation.
|
|
17
18
|
*/
|
|
18
19
|
import { readFileSync, existsSync } from "node:fs"
|
|
19
20
|
import { resolve, join, relative, dirname, sep } from "node:path"
|
|
20
21
|
import { providerSpec } from "../config.mjs"
|
|
21
|
-
import {
|
|
22
|
+
import { DEFAULT_MANIFEST, docRootPaths, readManifest } from "../manifest.mjs"
|
|
22
23
|
|
|
23
24
|
/** Project guide (AGENTS.md) injection budget — decision 2026-08-08:
|
|
24
25
|
* NO fixed truncation; long-context models (1M+) get up to 5% of their context
|
|
@@ -32,8 +33,8 @@ const PROJECT_GUIDE_FRACTION = 0.05 // 5% of the reviewer model's context window
|
|
|
32
33
|
export const NO_GUIDE_NOTICE = "(No AGENTS.md found — neither at the working directory root nor in any review-scope subdirectory. Judge the user's requirements from the conversation background, and say so explicitly if the requirements are unclear.)"
|
|
33
34
|
|
|
34
35
|
/** Explicit degradation sentences (PORTABILITY design §4.4 — verbatim). */
|
|
35
|
-
export const NO_DOC_MAP_NOTICE = "(No document map found under the
|
|
36
|
-
export const NO_STANDARDS_NOTICE = "(No project standards document
|
|
36
|
+
export const NO_DOC_MAP_NOTICE = "(No document map found — no README.md under the declared document roots and none at their common ancestor — the Document ownership criterion is degraded: check placement against the Project Guide where present, and state the limitation in your findings.)"
|
|
37
|
+
export const NO_STANDARDS_NOTICE = "(No project standards document — nothing in the project declares one — judge methodology compliance from the Project Guide (when present) and the review criteria above; state the limitation in your findings.)"
|
|
37
38
|
export const NO_GIT_NOTICE = "(No git repository detected — change-set context is unavailable; read the review-scope files directly.)"
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -126,25 +127,66 @@ export function injectProjectGuide(agent, parts, scopeFiles = []) {
|
|
|
126
127
|
return root // guide injected — requirement-fit criteria apply (truthy root)
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/** `docRoot` probe order: the face a reviewer needs first (design) → down to the batch records. */
|
|
131
|
+
const DOC_ROOT_ORDER = ["design", "requirements", "specs", "modules", "batches"]
|
|
132
|
+
|
|
133
|
+
/** Declared document roots as ABSOLUTE paths, in declaration order (`null` / missing face ⇒ no root). */
|
|
134
|
+
function declaredDocRoots(base) {
|
|
135
|
+
const read = readManifest(base)
|
|
136
|
+
const docRoot = (read.ok && read.manifest ? read.manifest : DEFAULT_MANIFEST).docRoot ?? {}
|
|
137
|
+
const out = []
|
|
138
|
+
for (const key of DOC_ROOT_ORDER) {
|
|
139
|
+
for (const p of docRootPaths(docRoot[key], base)) if (!out.includes(p)) out.push(p)
|
|
140
|
+
}
|
|
141
|
+
return out
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Common ancestor of every root (segment-wise — `docs/design` and `docs/designX` are not siblings).
|
|
145
|
+
* A lone drive root is no map home ⇒ null. */
|
|
146
|
+
function commonAncestor(paths) {
|
|
147
|
+
if (paths.length === 0) return null
|
|
148
|
+
const split = (p) => p.split(/[\\/]+/)
|
|
149
|
+
let acc = split(paths[0])
|
|
150
|
+
for (const p of paths.slice(1)) {
|
|
151
|
+
const segs = split(p)
|
|
152
|
+
let i = 0
|
|
153
|
+
while (i < acc.length && i < segs.length && acc[i] === segs[i]) i++
|
|
154
|
+
acc = acc.slice(0, i)
|
|
155
|
+
}
|
|
156
|
+
if (acc.length === 0) return null
|
|
157
|
+
const joined = acc.join(sep)
|
|
158
|
+
return /^[A-Za-z]:$/.test(joined) ? null : joined
|
|
159
|
+
}
|
|
160
|
+
|
|
129
161
|
/**
|
|
130
|
-
*
|
|
131
|
-
* (
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
162
|
+
* Discover the document map from the manifest's `docRoot` — a pure fs probe, no index generation
|
|
163
|
+
* (the map is a hand-maintained register; a generated one would be a second index, and a second
|
|
164
|
+
* index is exactly the duplicate pointer the retired declaration face used to create).
|
|
165
|
+
* Ladder: ① `README.md` under each declared doc root (declaration order) ② `README.md` at the common
|
|
166
|
+
* ancestor of every declared root. Neither ⇒ null (caller injects `NO_DOC_MAP_NOTICE` — no silent skip).
|
|
167
|
+
* @param {string} base project root (or any dir inside it — `docRoot` resolves against the project root)
|
|
168
|
+
* @returns {string|null} absolute path of the map file, or null when degraded
|
|
169
|
+
*/
|
|
170
|
+
export function findDocumentMap(base) {
|
|
171
|
+
const roots = declaredDocRoots(base)
|
|
172
|
+
for (const r of roots) {
|
|
173
|
+
const p = join(r, "README.md")
|
|
174
|
+
if (existsSync(p)) return p
|
|
175
|
+
}
|
|
176
|
+
const ancestor = commonAncestor(roots)
|
|
177
|
+
if (!ancestor) return null
|
|
178
|
+
const p = join(ancestor, "README.md")
|
|
179
|
+
return existsSync(p) ? p : null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Inject the project document map (PO-1), derived from `docRoot`. Degrades visibly: the reviewer
|
|
184
|
+
* must know the Document-ownership criterion is unchecked — never a silent skip.
|
|
135
185
|
* @returns {string|null} the injected map path, or null when degraded
|
|
136
186
|
*/
|
|
137
187
|
export function injectDocumentMap(agent, parts, root) {
|
|
138
188
|
const base = root ?? agent.cwd
|
|
139
|
-
const
|
|
140
|
-
let mapPath = null
|
|
141
|
-
if (conv.advisor.docMap) {
|
|
142
|
-
const declared = resolve(base, conv.advisor.docMap)
|
|
143
|
-
if (existsSync(declared)) mapPath = declared
|
|
144
|
-
} else {
|
|
145
|
-
mapPath = [resolve(base, "docs", "README.md"), resolve(base, "docs", "design", "README.md")]
|
|
146
|
-
.find((p) => existsSync(p)) ?? null
|
|
147
|
-
}
|
|
189
|
+
const mapPath = findDocumentMap(base)
|
|
148
190
|
parts.push("## Document Map")
|
|
149
191
|
let text = null
|
|
150
192
|
if (mapPath) {
|
|
@@ -166,32 +208,15 @@ export function injectDocumentMap(agent, parts, root) {
|
|
|
166
208
|
}
|
|
167
209
|
|
|
168
210
|
/**
|
|
169
|
-
* Inject the project standards
|
|
170
|
-
*
|
|
171
|
-
* `METHODOLOGY.md` probe
|
|
172
|
-
*
|
|
173
|
-
* @returns {
|
|
211
|
+
* Inject the project standards section (PO-2). The declaration face is gone with the manifest
|
|
212
|
+
* `advisor` key, and no standards file name is derivable from what the project does declare (the old
|
|
213
|
+
* `METHODOLOGY.md` probe was deleted exactly because most projects have none). The section stays so the
|
|
214
|
+
* reviewer is TOLD the dimension is unchecked — visible degradation, not a silent skip.
|
|
215
|
+
* @returns {null} always degraded
|
|
174
216
|
*/
|
|
175
|
-
export function injectProjectStandards(
|
|
176
|
-
const base = root ?? agent.cwd
|
|
177
|
-
const conv = loadProjectDeclaration(agent.cwd)
|
|
178
|
-
const declared = conv.advisor.standardsDoc ? resolve(base, conv.advisor.standardsDoc) : null
|
|
217
|
+
export function injectProjectStandards(_agent, parts, _root) {
|
|
179
218
|
parts.push("## Project Standards")
|
|
180
|
-
|
|
181
|
-
if (declared && existsSync(declared)) {
|
|
182
|
-
try {
|
|
183
|
-
text = readFileSync(declared, "utf8")
|
|
184
|
-
} catch (e) {
|
|
185
|
-
console.warn(`[advisor] project standards unreadable at ${declared}: ${e.message}`)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (text === null) {
|
|
189
|
-
parts.push(NO_STANDARDS_NOTICE) // degraded — visible by contract
|
|
190
|
-
parts.push("")
|
|
191
|
-
return null
|
|
192
|
-
}
|
|
193
|
-
parts.push("The project declares these standards for this review. Judge the design/changes against them:")
|
|
194
|
-
parts.push(text)
|
|
219
|
+
parts.push(NO_STANDARDS_NOTICE) // degraded — visible by contract
|
|
195
220
|
parts.push("")
|
|
196
|
-
return
|
|
221
|
+
return null
|
|
197
222
|
}
|
package/agent/completion.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { hasCodeMutations } from "../advisor/repos.mjs"
|
|
9
9
|
import { pushReal } from "../context.mjs"
|
|
10
10
|
import { advisorReviewPending, effectiveAdvisorRound } from "../agent-tools/advisor-async.mjs"
|
|
11
|
+
import { poolLive } from "./suspension.mjs"
|
|
11
12
|
|
|
12
13
|
const MAX_VERIFY_PUSHBACKS = 2
|
|
13
14
|
const MAX_VERIFY_RETRIES = 3
|
|
@@ -59,7 +60,9 @@ export function handleCompletion(agent, response, depth, turn, guardPushbacks, h
|
|
|
59
60
|
// list state earns one fresh reminder.
|
|
60
61
|
// F10(催更门连带):工程模式下 task 工具机械停用(装配摘除 + execute 拒)⇒ 不再发指向被拒
|
|
61
62
|
// 工具的提醒(死胡同提醒防线);列表本体与 `_taskPushbacks` 预算语义零改。
|
|
62
|
-
|
|
63
|
+
// TIMEOUT-DIGEST.md §2.3(#29):后台池 live 时不推回——池在飞时回合本就还会被 settle 唤醒,
|
|
64
|
+
// 此时催 task 只会拖回合;判据复用 `poolLive`(与挂起尾巴同一口径,不立第二处)。
|
|
65
|
+
if (depth === 0 && !agent.config?.agent?.engineering && !poolLive(agent) && agent.tasks.some((t) => t.status === "pending") && (agent._taskPushbacks ?? 0) < 1) {
|
|
63
66
|
agent._taskPushbacks = (agent._taskPushbacks ?? 0) + 1
|
|
64
67
|
const pending = agent.tasks.filter((t) => t.status === "pending").map((t) => t.title).join(", ")
|
|
65
68
|
pushReal(agent, { role: "assistant", content: response.content })
|
package/agent/family-tools.mjs
CHANGED
|
@@ -26,7 +26,7 @@ export async function assembleFamilyTools({
|
|
|
26
26
|
decorate = null, // object 端差面:{ subagent?, consultStart?, consultStop?, settings? }
|
|
27
27
|
} = {}) {
|
|
28
28
|
// CORE-UNIFICATION TOOLS #83:consult 家族随统一登记册自 `../agent-tools.mjs` 取用(单一来源)
|
|
29
|
-
const { planTool, subagentTool, taskTool, skillTool, goalTool, verifyTool, recentChangesTool, timerTool, advisorTool, engTool, readHistoryTool, contextTool, batchTool, manifestTool, consultStartTool, consultStopTool, parentChannelTool, docCheckTool } = await import("../agent-tools.mjs")
|
|
29
|
+
const { planTool, subagentTool, taskTool, skillTool, goalTool, verifyTool, recentChangesTool, timerTool, advisorTool, engTool, readHistoryTool, contextTool, batchTool, manifestTool, manifestReadTool, consultStartTool, consultStopTool, parentChannelTool, docCheckTool } = await import("../agent-tools.mjs")
|
|
30
30
|
// 台账统一入口(2026-10-05 统一入口批——台账 #923):全量变体 = depth-0 段 ∥ 只读变体 = 全部
|
|
31
31
|
// depth>0 段。动态 import——ledger 链静态达 node:sqlite(W8 契约②);两变体同名单对象、schema 窄化。
|
|
32
32
|
const { ledgerTool, ledgerReadTool } = await import("../ledger.mjs")
|
|
@@ -95,8 +95,8 @@ export async function assembleFamilyTools({
|
|
|
95
95
|
// async, the check/status params (id/n), the eng-coder token params
|
|
96
96
|
// (designToken/designId are meaningless for a read-only spawn; the parent
|
|
97
97
|
// spawn already carried the token) and batchDoc (an audit child derives no
|
|
98
|
-
// batch parameter — its task book rides the
|
|
99
|
-
//
|
|
98
|
+
// batch parameter — its task book rides the parent's batch record §2 instead
|
|
99
|
+
// (the audit block carries the batchDoc path)). Schema noise would invite the
|
|
100
100
|
// model to pass irrelevant args.
|
|
101
101
|
delete props.async // sync only — the parent blocks on the child's report
|
|
102
102
|
delete props.id
|
|
@@ -126,7 +126,7 @@ export async function assembleFamilyTools({
|
|
|
126
126
|
name: "subagent",
|
|
127
127
|
description: designer
|
|
128
128
|
? "Spawn a read-only `explore` sub-agent to SURVEY the current state for the design: it reads code / docs / existing designs and reports evidence with file:line. BLOCKING ONLY (no async) and action:'spawn' ONLY — the survey channel is read-only; escalate/status/cancel/panel/consume-design/observe/send are not available. Survey budget: ≤6 explore spawns per batch — the main agent's survey result is reference only; do your own. The spawn carries the five task-book parameters (four core: goal / knownFacts / acceptance / reportFormat + `scope`); a task-only spawn is refused."
|
|
129
|
-
: "Spawn a read-only `explore` sub-agent to AUDIT your delivery against the design: it compares the delivered code with the design for divergence — partially implemented acceptance criteria, silent simplifications, doc drift, changes outside the approved file list. BLOCKING ONLY (no async — the audit report decides your next protocol step). action:'spawn' ONLY — the audit channel is a read-only spawn; escalate/status/cancel/panel/consume-design/observe/send are not available. The audit
|
|
129
|
+
: "Spawn a read-only `explore` sub-agent to AUDIT your delivery against the design: it compares the delivered code with the design for divergence — partially implemented acceptance criteria, silent simplifications, doc drift, changes outside the approved file list. BLOCKING ONLY (no async — the audit report decides your next protocol step). action:'spawn' ONLY — the audit channel is a read-only spawn; escalate/status/cancel/panel/consume-design/observe/send are not available. The audit context is appended MECHANICALLY — the batch record path (its §2 is the delivery's task book, read it before judging) plus the files you actually touched; never hand the audit a self-written file list (a self-report could omit exactly the out-of-scope file it must catch). The spawn carries the five task-book parameters (four core: goal / knownFacts / acceptance / reportFormat + `scope`); a task-only spawn is refused.",
|
|
130
130
|
parameters: { ...subagentTool.parameters, properties: props },
|
|
131
131
|
}
|
|
132
132
|
})()
|
|
@@ -149,7 +149,7 @@ export async function assembleFamilyTools({
|
|
|
149
149
|
// 团队台账全量变体(五 action——同律:子代理段只挂读变体;登录态门在工具内)
|
|
150
150
|
teamLedgerTool,
|
|
151
151
|
// manifest 工具(`MANIFEST.md` §2.10 · 2026-10-08 · 台账 #1098):主 agent 读写出口——
|
|
152
|
-
//
|
|
152
|
+
// **两模式同挂**(需求未设模式门);全量变体仅 depth-0——子代理各段挂只读变体(见下 `:187-193`)。
|
|
153
153
|
manifestTool,
|
|
154
154
|
// 文档纪律机检(`DOC-DISCIPLINE.md` §3.20 条目 S):只读报告态——depth-0 主 agent 段;
|
|
155
155
|
// 写文档 / 设计档的角色段(eng-coder / eng-designer)另挂(见下两分支)。
|
|
@@ -182,13 +182,15 @@ export async function assembleFamilyTools({
|
|
|
182
182
|
// consult 段不入(其角色语义 = 父发起的一次性会诊,父在其 settle 前不期望中途对话)。
|
|
183
183
|
// 台账只读变体(query / count)——全部 depth>0 角色段(含 consult ∕ 兜底;写三 schema 级不可达);
|
|
184
184
|
// 团队台账只读变体(team-ledger 批)同律。
|
|
185
|
-
|
|
185
|
+
// manifest 只读变体(MANIFEST.md §2.10.1 · 2026-10-22 · 台账 #1738):同名 `manifest`、action 枚举只剩 `read`;
|
|
186
|
+
// 与上两条只读先例同律挂在全部 depth>0 角色段(全量变体仅 depth-0——两变体互斥装配,同名不塌缩)。
|
|
187
|
+
: engChildRole === "eng-coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, manifestReadTool, advisorTool, verifyTool, batchTool(batchDoc), docCheckTool,
|
|
186
188
|
...(engChildSubagent ? [engChildSubagent] : [])]
|
|
187
|
-
: engChildRole === "eng-designer" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, batchTool(batchDoc), docCheckTool,
|
|
189
|
+
: engChildRole === "eng-designer" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, manifestReadTool, batchTool(batchDoc), docCheckTool,
|
|
188
190
|
...(engChildSubagent ? [engChildSubagent] : [])]
|
|
189
|
-
: role === "coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, verifyTool, advisorTool, ...(batchDoc ? [batchTool(batchDoc)] : [])]
|
|
190
|
-
: role === "consult" ? [recentChangesTool, ledgerReadTool, teamLedgerReadTool]
|
|
191
|
-
: [parentChannelTool, ledgerReadTool, teamLedgerReadTool]
|
|
191
|
+
: role === "coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, manifestReadTool, verifyTool, advisorTool, ...(batchDoc ? [batchTool(batchDoc)] : [])]
|
|
192
|
+
: role === "consult" ? [recentChangesTool, ledgerReadTool, teamLedgerReadTool, manifestReadTool]
|
|
193
|
+
: [parentChannelTool, ledgerReadTool, teamLedgerReadTool, manifestReadTool]
|
|
192
194
|
|
|
193
195
|
// 固定段(装配序 = agent.tools → 固定段 → 家族段 → extraTools):timer 全模式全深度;
|
|
194
196
|
// plan 随模式位——ENG-PLAN-EXCLUSION(FR31 ① · KD8 卸载而非「注册 + 报错」· KD11 全深度两端);
|
package/agent/helpers.mjs
CHANGED
|
@@ -150,24 +150,26 @@ export async function cleanupOldToolResults(dir) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
/** Offload
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
/** Offload a tool result / report to disk (§2.2): `force` = write regardless of size (async
|
|
154
|
+
* subagent reports must survive the parent turn). Rendering splits on the SAME constant as the old
|
|
155
|
+
* gate: ≤ TOOL_RESULT_OFFLOAD_LIMIT ⇒ body once + path (never dual-end — a short body would come
|
|
156
|
+
* out twice with `middle omitted: 0 chars`); > ⇒ dual-end preview + path. Writes self-clean first. */
|
|
157
|
+
export async function offloadToolResult(text, callId, dir = join(configDir, "tool-results"), { force = false } = {}) {
|
|
158
|
+
const overLimit = text.length > TOOL_RESULT_OFFLOAD_LIMIT
|
|
159
|
+
if (!force && !overLimit) return text
|
|
158
160
|
try {
|
|
159
161
|
await cleanupOldToolResults(dir)
|
|
160
162
|
await mkdir(dir, { recursive: true })
|
|
161
163
|
const file = join(dir, `${Date.now()}-${String(callId).replace(/[^a-zA-Z0-9_-]/g, "_")}.log`)
|
|
162
164
|
await writeFile(file, text, "utf8")
|
|
163
|
-
return (
|
|
164
|
-
|
|
165
|
-
`\n\n[... output too large (${text.length} chars total), full content saved to: ${file}\n` +
|
|
165
|
+
if (!overLimit) return `${text}\n\n[... full content saved to: ${file}\nPage through it with the read tool (offset/limit) if you need the full text later.]`
|
|
166
|
+
return buildDualEndPreview(text) + `\n\n[... output too large (${text.length} chars total), full content saved to: ${file}\n` +
|
|
166
167
|
`Page through it with the read tool (offset/limit) or sed -n 'START,ENDp' — do NOT re-run the tool blindly.]`
|
|
167
|
-
)
|
|
168
168
|
} catch {
|
|
169
|
-
//
|
|
170
|
-
|
|
169
|
+
// 写盘失败不吞报告:>LIMIT 走双端预览 + 原文案;≤LIMIT 正文整段在前(不谎称 truncated)
|
|
170
|
+
const body = overLimit ? buildDualEndPreview(text) : text
|
|
171
|
+
return body + (overLimit ? `\n\n[... truncated: ${text.length} chars total, offload to disk failed]`
|
|
172
|
+
: `\n\n[... full text kept inline above (${text.length} chars), offload to disk failed]`)
|
|
171
173
|
}
|
|
172
174
|
}
|
|
173
175
|
|
|
@@ -275,6 +277,12 @@ export function turnFrame(seq, turn, maxTurns) {
|
|
|
275
277
|
return { turn: seq, maxTurns: seq - turn - 1 + maxTurns }
|
|
276
278
|
}
|
|
277
279
|
|
|
280
|
+
/** #934(零落笔看门狗——AGENT-LOOP-UPSTREAM.md §6.32.2 ① 判据式):上轮结账至今写序是否推进;非整数(未跟踪面 / NaN)⇒ true 记「有落笔」不判(不用 `?? 0` 兜底——未跟踪面当零落笔正是本收正要消除的误报方向)。 */
|
|
281
|
+
export function wroteSince(agent, mark) {
|
|
282
|
+
const seq = agent._mutationSeq
|
|
283
|
+
return Number.isInteger(seq) ? seq !== mark : true
|
|
284
|
+
}
|
|
285
|
+
|
|
278
286
|
/** Repair malformed conversation history: remove orphan tool messages and fill missing tool results */
|
|
279
287
|
export function repairHistory(history) {
|
|
280
288
|
const out = []
|
package/agent/light-round.mjs
CHANGED
|
@@ -21,9 +21,6 @@ import { projectRootView, resolveProjectRoot } from "../manifest.mjs"
|
|
|
21
21
|
import { resolveDeclaredRef } from "../declaration.mjs"
|
|
22
22
|
import { readBatchStatusLine } from "../agent-tools/batch-skeleton.mjs"
|
|
23
23
|
|
|
24
|
-
/** 轮型标记(§2.3 开轮形升格为闸凭据;全链批行无此标记 ⇒ 不构成信号)。 */
|
|
25
|
-
const ROUND_MARKER = "收尾链待跑"
|
|
26
|
-
|
|
27
24
|
/** 轻通道轮在盘谓词(异步 · 只读 · fail-closed)。契约见头注。
|
|
28
25
|
* @param {object} agent — 会话 agent(读 `cwd`)
|
|
29
26
|
* @returns {Promise<boolean>} true = 放行(在盘轮次);读错 ∥ 不可判 ⇒ false(照拦)
|
|
@@ -33,7 +30,7 @@ export async function lightRoundOpen(agent) {
|
|
|
33
30
|
const cwd = agent?.cwd
|
|
34
31
|
if (typeof cwd !== "string" || cwd.trim() === "") return false
|
|
35
32
|
// W8 契约②:node:sqlite 链只经动态 import 进入(勿升静态)——候选枚举与逐候选查行同取本档公共面。
|
|
36
|
-
const { ledgerChildren, ledgerQuery } = await import("../ledger.mjs")
|
|
33
|
+
const { ledgerChildren, ledgerQuery, ROUND_MARKER } = await import("../ledger.mjs")
|
|
37
34
|
const anchor = resolve(cwd)
|
|
38
35
|
// 候选面 = 锚自身 + 直接子目录一层(纯向下;子项枚举单源 = `ledgerChildren`)。
|
|
39
36
|
const candidates = [anchor, ...ledgerChildren(anchor).map((p) => p.root)]
|
package/agent/record-results.mjs
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
import { pushReal } from "../context.mjs"
|
|
27
27
|
import { specForModel } from "../config.mjs"
|
|
28
28
|
import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
|
|
29
|
+
// F-LC6(LIGHT-CHANNEL §2.9 · 台账 #1753):无账之笔提醒叶(谓词 + 文案 + 闩单源)。
|
|
30
|
+
import { unbookedPenNotice } from "./unbooked-pen.mjs"
|
|
29
31
|
import { capHistoryImages } from "../provider/normalize.mjs"
|
|
30
32
|
import { resolve } from "node:path"
|
|
31
33
|
import { advisorRuns, stripApprovedSuffix } from "../agent-tools/advisor-async.mjs"
|
|
@@ -82,7 +84,10 @@ export async function recordToolResults(agent, toolByName, results) {
|
|
|
82
84
|
}
|
|
83
85
|
} catch { /* Parse failure doesn't affect normal tool messages */ }
|
|
84
86
|
}
|
|
85
|
-
|
|
87
|
+
// F-LC6(LIGHT-CHANNEL §2.9):主 agent 会话首笔产品源文件笔 ⇒ 回执末尾追加一行通道提醒
|
|
88
|
+
// (零拦截——只拼回执文本、入史时机不变;命中后本会话不再提示)。
|
|
89
|
+
const penNotice = unbookedPenNotice(agent, toolCall, ok, tool)
|
|
90
|
+
pushReal(agent, { role: "tool", tool_call_id: toolCall.id, name: toolCall.name, content: penNotice ? result + penNotice : result })
|
|
86
91
|
if (tool && ok) {
|
|
87
92
|
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
88
93
|
// Direct file edit — code was changed. The prior advisor review and
|
package/agent/run-start.mjs
CHANGED
|
@@ -105,6 +105,7 @@ export async function beginRun(agent, input, callbacks, {
|
|
|
105
105
|
// #417(撞帽载荷「本段零落盘轮数」——只报数:零阈值常量 / 零自动动作):段起点复位(首段 /
|
|
106
106
|
// 续跑新段——与 `_continueSegments` 同点);段内累加,采集点在回合环(与 `_turnSeq` 同源面)。
|
|
107
107
|
agent._zeroWriteTurns = 0
|
|
108
|
+
agent._segmentTurns = 0 // #934 B 面读数分母(§6.32.4):同点复位——同段同源(分子 ≤ 分母)
|
|
108
109
|
if (!resume) {
|
|
109
110
|
// 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §4):链内累计编号
|
|
110
111
|
// 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
|
|
@@ -88,7 +88,7 @@ export function manifestStateLine({ phase }) {
|
|
|
88
88
|
*/
|
|
89
89
|
export function manifestReportLine({ state, cwd, root, path, candidates, errors, matched } = {}) {
|
|
90
90
|
if (state === "no-project") {
|
|
91
|
-
return `${MANIFEST_LINE_PREFIX}none — no project at ${cwd} (no manifest
|
|
91
|
+
return `${MANIFEST_LINE_PREFIX}none — no project at ${cwd} (no manifest at this directory, none in the direct children). ` +
|
|
92
92
|
`Parameters fall back to defaults. Create PROJECT-MANIFEST.json here to land a project (git optional).]`
|
|
93
93
|
}
|
|
94
94
|
if (state === "workspace") {
|
package/agent/turn-loop.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
turnFrame, // 第 19 批(TURN-ACROSS-SEGMENTS):跨段累计编号帧(设计 TURN-CAP-CONTINUE.md §4)
|
|
19
19
|
ContinueError,
|
|
20
20
|
FILE_MUTATORS, toolTouchPaths,
|
|
21
|
+
wroteSince, // #934(§6.32.2 ① 判据式):写序是否推进——轮顶 ∥ 撞帽收尾两处判据点共用
|
|
21
22
|
} from "./helpers.mjs"
|
|
22
23
|
|
|
23
24
|
export async function runTurnLoop(agent, {
|
|
@@ -57,9 +58,10 @@ export async function runTurnLoop(agent, {
|
|
|
57
58
|
// §6.31.5 落痕写点门(#939):载体吸收经 `carrierField` 单点(§6.31.4 同式);动态 import = 零新增静态边(沿上方两条同款)。
|
|
58
59
|
const { carrierField } = await import("../agent-tools/async-settle.mjs")
|
|
59
60
|
|
|
60
|
-
// #417(撞帽载荷「本段零落盘轮数」——只报数:零阈值 /
|
|
61
|
-
//
|
|
62
|
-
|
|
61
|
+
// #417(撞帽载荷「本段零落盘轮数」——只报数:零阈值 / 零自动动作)∥ #934(零落笔看门狗):计数基线——
|
|
62
|
+
// 判据物 = 写序(每次写事件 +1,不去重);`_touchedFiles` 是去重路径集,同档重写长度不变 ⇒ 旧判据在
|
|
63
|
+
// 此读成零落笔(本收正要消除的误报方向)。环内逐轮结账、撞帽收尾轮在环外单结(同一谓词)。
|
|
64
|
+
agent._mutSeqMark = agent._mutationSeq
|
|
63
65
|
|
|
64
66
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
65
67
|
// #793(核面小修批——AGENT-LOOP.md §6.2 环边界中止前置 ∥ 需求 F5):环头检查点——中止恒以
|
|
@@ -73,21 +75,23 @@ export async function runTurnLoop(agent, {
|
|
|
73
75
|
const frame = turnFrame(++agent._turnSeq, turn, maxTurns)
|
|
74
76
|
agent._currentTurn = frame.turn
|
|
75
77
|
agent._maxTurns = frame.maxTurns
|
|
76
|
-
// #
|
|
77
|
-
|
|
78
|
-
// #
|
|
79
|
-
//
|
|
78
|
+
// #934 B 面读数分母(§6.32.4):本段轮数逐轮 +1——段起点复位见 run-start.mjs(与零落笔计数同点)。
|
|
79
|
+
agent._segmentTurns = (agent._segmentTurns ?? 0) + 1
|
|
80
|
+
// #417(撞帽载荷「本段零落盘轮数」——采集点与 `_turnSeq` 同源面):上一轮至今写序未推进
|
|
81
|
+
// ⇒ 上一轮零落笔,计数 +1;写序标每轮前移(撞帽收尾轮在环外结账——见下方 ContinueError 前)。
|
|
82
|
+
// #934(零落笔看门狗——§6.32.2 ① 单点采集):streak 与越阈判定同点——上轮零落笔 ⇒ +1 并判越阈;
|
|
83
|
+
// 有落笔 ⇒ streak 归零 + 清闩(写后重臂——每连续零落笔段恰一条)。#417 计数与 streak 同源同谓词。
|
|
80
84
|
if (turn > 0) {
|
|
81
|
-
if (agent
|
|
85
|
+
if (!wroteSince(agent, agent._mutSeqMark)) {
|
|
82
86
|
agent._zeroWriteTurns += 1
|
|
83
87
|
agent._zeroWriteStreak += 1
|
|
84
|
-
maybeZeroWriteAlert(agent) // #934
|
|
88
|
+
maybeZeroWriteAlert(agent) // #934:连续零落笔越阈 ⇒ 自动上行提醒(只推——§6.32)
|
|
85
89
|
} else {
|
|
86
90
|
agent._zeroWriteStreak = 0
|
|
87
91
|
agent._zeroWriteAlerted = false // 写后重臂
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
|
-
agent.
|
|
94
|
+
agent._mutSeqMark = agent._mutationSeq
|
|
91
95
|
// D2 (AGENT-LOOP-SUBAGENT.md §6.7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
|
|
92
96
|
// single emit point covering all three spawn tools; phase=llm (tool/done progress rides
|
|
93
97
|
// the onToolCall/onToolResult relay — no token for those). 第 19 批:载荷取上方帧值
|
|
@@ -271,9 +275,9 @@ export async function runTurnLoop(agent, {
|
|
|
271
275
|
injectPostTurn(agent, results, recentCallSigs, callbacks, turn, { suspDriven })
|
|
272
276
|
}
|
|
273
277
|
|
|
274
|
-
// #417
|
|
275
|
-
//
|
|
276
|
-
if (maxTurns > 0 && agent
|
|
278
|
+
// #417(撞帽载荷「本段零落盘轮数」):收尾轮(撞帽轮)单结——与环内同一谓词(不另作 alert ∥ streak
|
|
279
|
+
// 判定,链尾无下一轮可推报);轮顶计数只覆盖到倒数第二轮(maxTurns ≤ 0 时环未跑,不虚计)。
|
|
280
|
+
if (maxTurns > 0 && !wroteSince(agent, agent._mutSeqMark)) agent._zeroWriteTurns += 1
|
|
277
281
|
// #793(核面小修批——AGENT-LOOP.md §6.2 环边界中止前置 ∥ 需求 F5):环尾检查点——中止恒以
|
|
278
282
|
// AbortError 收束(不落 ContinueError——用户 Stop 优先于继续提示;消费契约 = 外层按 reason 判定)。
|
|
279
283
|
if (signal?.aborted) throw abortError(signal, "agent", "turn-tail")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* unbooked-pen.mjs — 无账之笔提醒叶(批 unbooked-pen · 台账 #1753)。
|
|
3
|
+
* 设计权威 = `docs/core/design/LIGHT-CHANNEL.md` §2.9(F-LC6);需求 = `docs/core/requirements/LIGHT-CHANNEL.md` §2.6。
|
|
4
|
+
*
|
|
5
|
+
* 机制:每会话**首次**由主 agent 落下**产品源文件**笔时,在工具回执末尾追加一行通道确认提醒。
|
|
6
|
+
* 只提示——不拦、不放行、不改任何判据;文案为条件句(轻通道笔须先挂轮,全链笔交 eng-coder)。
|
|
7
|
+
* 承载形对齐先例 `agent-tools/zero-write-watch.mjs`(谓词叶 + 文案函数 + 闩 + 非阻塞)。
|
|
8
|
+
* 零环:只 import 既有单源(`FILE_MUTATORS` ∥ `toolTouchPaths` ∥ `isCodePath` ∥ `declarationForTarget`),
|
|
9
|
+
* 只被 `agent/record-results.mjs` import(不反向引)。
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from "node:path"
|
|
12
|
+
import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
|
|
13
|
+
import { declarationForTarget } from "../declaration.mjs"
|
|
14
|
+
import { isCodePath } from "../conventions.mjs"
|
|
15
|
+
|
|
16
|
+
/** 笔集(§2.9 ②):`FILE_MUTATORS` **减** `delete`——减式派生,不另抄第二份工具名清单
|
|
17
|
+
* (`FILE_MUTATORS` 增删成员时本集自动跟上)。`delete` 不入集:删产品源文件须 `force`、
|
|
18
|
+
* 是显式动作,本批无场景(K20)。 */
|
|
19
|
+
export const PEN_TOOLS = new Set([...FILE_MUTATORS].filter((name) => name !== "delete"))
|
|
20
|
+
|
|
21
|
+
/** 文案单源(§2.9 文案块逐字——条件句 + 后果句;改文案只改这一处)。 */
|
|
22
|
+
export function unbookedPenText() {
|
|
23
|
+
return "[pen check] first product-source write this session — confirm the channel before it lands: a light-channel pen needs its round booked first (batch record + an in-flight ledger row pointing at the record); a full-chain pen goes to eng-coder. A pen with no account is invisible at closeout, so the ledger settlement misses it."
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 四合取 + 一闩判定(§2.9 触发面)——命中:置闩并返回回执后缀(`\n` + 文案行);未中:空串。
|
|
28
|
+
* ① 身份 = 主 agent(`_role == null`——主会话装配不传 role,子代理 spawn 面统一传角色)
|
|
29
|
+
* ② 工具 ∈ `PEN_TOOLS` ③ `ok === true` ④ 任一目标分类为产品源文件
|
|
30
|
+
* (逐目标 `resolve(cwd, p)` → `declarationForTarget(abs)` → `isCodePath(abs, conv)`——与父侧门
|
|
31
|
+
* `agent/dispatch.mjs:111-118` 同式;出辖目标不判,任一命中即触发)
|
|
32
|
+
* 闩 = `agent._unbookedPenNotified`(每会话一次——不按轮、不按文件)。
|
|
33
|
+
* @param {object} agent 主 agent(读 `cwd` ∥ `_role` ∥ 闩位)
|
|
34
|
+
* @param {{name: string, arguments?: string}} toolCall
|
|
35
|
+
* @param {boolean} ok 工具结果成功位(被拒的笔没有落盘,提醒无对象)
|
|
36
|
+
* @param {object} [tool] 工具对象——只用于 `toolTouchPaths` 的可选钩子 `touchedPaths`
|
|
37
|
+
* (`apply_patch` ∥ 多目标 `edit` 的目标解析靠它;缺省按单参 `path`)
|
|
38
|
+
* @returns {string} 命中 = 回执后缀;未中 = ""
|
|
39
|
+
*/
|
|
40
|
+
export function unbookedPenNotice(agent, toolCall, ok, tool) {
|
|
41
|
+
if (!agent || agent._role != null) return ""
|
|
42
|
+
if (!tool) return ""
|
|
43
|
+
if (ok !== true) return ""
|
|
44
|
+
if (agent._unbookedPenNotified) return ""
|
|
45
|
+
if (!PEN_TOOLS.has(toolCall?.name)) return ""
|
|
46
|
+
try {
|
|
47
|
+
const args = JSON.parse(toolCall.arguments || "{}")
|
|
48
|
+
const hit = toolTouchPaths(tool, args).some((p) => {
|
|
49
|
+
if (typeof p !== "string" || !p.trim()) return false
|
|
50
|
+
const abs = resolve(agent.cwd, p)
|
|
51
|
+
const conv = declarationForTarget(abs)
|
|
52
|
+
return !!conv && isCodePath(abs, conv)
|
|
53
|
+
})
|
|
54
|
+
if (!hit) return ""
|
|
55
|
+
} catch {
|
|
56
|
+
// 恒零抛(先例 zero-write-watch.mjs 同款):提醒是兜底面——判定失败不得中断回执提交
|
|
57
|
+
// (畸形 args ∥ 非标准装配的 cwd)。退回空串 = 本笔不提示,判据面零变。
|
|
58
|
+
return ""
|
|
59
|
+
}
|
|
60
|
+
agent._unbookedPenNotified = true
|
|
61
|
+
return `\n${unbookedPenText()}`
|
|
62
|
+
}
|