@thincoder/core 0.10.3 → 0.10.5
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 +62 -0
- package/README.md +1 -1
- package/advisor/citations.mjs +29 -3
- package/advisor/compaction.mjs +31 -7
- package/advisor/history.mjs +8 -6
- package/advisor/loop.mjs +29 -27
- package/advisor/messages.mjs +2 -1
- package/advisor/run.mjs +9 -5
- package/advisor/timeline.mjs +26 -0
- package/advisor.mjs +4 -2
- package/agent/assemble.mjs +1 -1
- package/agent/chat-call.mjs +1 -1
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +3 -1
- package/agent/digest-account.mjs +189 -0
- package/agent/dispatch-gates.mjs +8 -0
- package/agent/dispatch.mjs +39 -18
- package/agent/family-tools.mjs +27 -16
- package/agent/helpers.mjs +12 -3
- package/agent/light-round.mjs +66 -0
- package/agent/post-turn.mjs +8 -4
- package/agent/record-results.mjs +4 -0
- package/agent/run-stages.mjs +32 -7
- package/agent/run-start.mjs +50 -3
- package/agent/setup-reminders.mjs +1 -1
- package/agent/setup.mjs +18 -3
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +37 -12
- package/agent/timers.mjs +56 -5
- package/agent/turn-loop.mjs +30 -5
- package/agent/write-gate.mjs +20 -32
- package/agent-tools/advisor-async.mjs +15 -7
- package/agent-tools/advisor-settle.mjs +2 -2
- package/agent-tools/advisor.mjs +34 -33
- package/agent-tools/async-discard.mjs +34 -0
- package/agent-tools/async-settle.mjs +32 -7
- package/agent-tools/audit-block.mjs +1 -1
- package/agent-tools/bash-async.mjs +1 -1
- package/agent-tools/batch-lifecycle.mjs +59 -17
- package/agent-tools/batch-paths.mjs +24 -4
- package/agent-tools/batch-skeleton.mjs +59 -43
- package/agent-tools/batch.mjs +39 -18
- package/agent-tools/browser-async.mjs +198 -0
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/design-slots.mjs +232 -0
- package/agent-tools/design-token.mjs +20 -0
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/escalate-async.mjs +4 -2
- package/agent-tools/manifest.mjs +193 -0
- package/agent-tools/parent-channel.mjs +7 -4
- package/agent-tools/review-facts.mjs +143 -16
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/skill.mjs +1 -1
- package/agent-tools/subagent-actions-query.mjs +13 -6
- package/agent-tools/subagent-async.mjs +31 -15
- package/agent-tools/subagent-panel.mjs +2 -1
- package/agent-tools/subagent-scheduler.mjs +8 -5
- package/agent-tools/subagent-spawn.mjs +30 -56
- package/agent-tools/subagent.mjs +21 -8
- package/agent-tools/timer.mjs +186 -12
- package/agent-tools/zero-write-watch.mjs +77 -0
- package/agent-tools.mjs +2 -1
- package/agent.mjs +2 -2
- package/attachments.mjs +1 -1
- package/config-io.mjs +18 -13
- package/config-migrate.mjs +13 -29
- package/config-presets.mjs +33 -29
- package/config.mjs +68 -25
- package/context-degrade.mjs +1 -1
- package/context-echo.mjs +1 -1
- package/context-push.mjs +2 -2
- package/context.mjs +1 -1
- package/conventions.mjs +28 -8
- package/declaration.mjs +18 -2
- package/escape.mjs +1 -1
- package/file-links.mjs +1 -1
- package/file-refs.mjs +1 -1
- package/history-window.mjs +1 -1
- package/i18n.mjs +7 -0
- package/ledger-cmd.mjs +62 -16
- package/ledger-db.mjs +6 -0
- package/ledger-surface.mjs +49 -5
- package/ledger-tools.mjs +158 -87
- package/ledger-variant-notice.mjs +47 -0
- package/ledger.mjs +11 -18
- package/log.mjs +1 -1
- package/manifest-discovery.mjs +1 -1
- package/manifest-schema.mjs +7 -5
- package/manifest.mjs +5 -3
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +1 -1
- package/memory/code-index.mjs +1 -1
- package/memory/code-search.mjs +1 -1
- package/memory/code-sync.mjs +1 -1
- package/memory/docs.mjs +6 -2
- package/memory/file-list.mjs +1 -1
- package/memory/file-walk.mjs +17 -5
- package/memory/maintain.mjs +410 -0
- package/memory/memory-tool.mjs +21 -6
- package/memory/schema.mjs +19 -7
- package/memory/sweep.mjs +8 -5
- package/memory/sync-tail.mjs +16 -10
- package/model-ref.mjs +16 -13
- package/model-specs.mjs +21 -9
- package/package.json +1 -1
- package/peer-instances.mjs +1 -1
- package/permission.mjs +33 -0
- package/process-probe.mjs +2 -2
- package/prompts/advisor-design.md +4 -2
- package/prompts/advisor-round1.md +1 -1
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +38 -4
- package/prompts/consult-base.md +1 -1
- package/prompts/discipline-engineering.md +32 -15
- package/prompts/discipline-normal.md +46 -2
- package/prompts/persona-eng-designer.md +6 -6
- package/prompts/persona-engineering.md +18 -9
- package/prompts/persona-normal.md +2 -0
- package/prompts/subagent-base.md +3 -0
- package/provider/anthropic.mjs +14 -2
- package/provider/core.mjs +6 -0
- package/provider/errors.mjs +28 -3
- package/provider/google.mjs +22 -10
- package/provider/list-models.mjs +3 -2
- package/provider/responses-request.mjs +3 -1
- package/provider/responses.mjs +38 -4
- package/provider/sse.mjs +3 -0
- package/provider-flows.mjs +14 -15
- package/proxy-chunked.mjs +102 -0
- package/proxy-target.mjs +54 -0
- package/proxy-transport.mjs +298 -0
- package/proxy.mjs +17 -248
- package/session-gc.mjs +2 -2
- package/session-lifecycle.mjs +18 -14
- package/session-slot-claims.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session-slots.mjs +3 -3
- package/shell-identity.mjs +107 -0
- package/team-ledger.mjs +282 -0
- package/team.mjs +223 -0
- package/tool-docs/advisor.md +1 -1
- package/tool-docs/bash.md +2 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +48 -0
- package/tool-docs/delete.md +7 -5
- package/tool-docs/ledger.md +6 -0
- package/tool-docs/manifest.md +16 -0
- package/tool-docs/memory.md +1 -0
- package/tool-docs/notify_parent.md +5 -0
- package/tool-docs/process.md +4 -4
- package/tool-docs/question.md +1 -0
- package/tool-docs/subagent.md +14 -3
- package/tool-docs/team_ledger.md +9 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/wait_for.md +1 -0
- package/tools/bash.mjs +9 -4
- package/tools/browser.mjs +161 -0
- package/tools/git.mjs +1 -1
- package/tools/index.mjs +4 -4
- package/tools/ops.mjs +26 -10
- package/tools/patch.mjs +8 -2
- package/tools/repomap.mjs +1 -1
- package/tools/write-path.mjs +8 -6
- package/traces/trace-cleanup.mjs +1 -1
- package/traces/trace-store.mjs +1 -1
- package/vision-reader.mjs +10 -16
- package/tool-docs/ledger_add.md +0 -1
- package/tool-docs/ledger_close.md +0 -1
- package/tool-docs/ledger_count.md +0 -1
- package/tool-docs/ledger_query.md +0 -1
- package/tool-docs/ledger_update.md +0 -1
package/agent/run-stages.mjs
CHANGED
|
@@ -13,20 +13,27 @@ import { pushManifestStateReminder } from "./setup-reminders.mjs"
|
|
|
13
13
|
import { cleanupConsultSessions } from "../agent-tools/consult.mjs"
|
|
14
14
|
import { logEvent, errText } from "../log.mjs"
|
|
15
15
|
// ASYNC-RESULT-CONTAINER.md D1:池 accessor(absorb 双池——advisor 独立池无队列)
|
|
16
|
-
import { getAsyncPool, releaseSettledEntry } from "../agent-tools/async-settle.mjs"
|
|
16
|
+
import { carrierField, getAsyncPool, releaseSettledEntry } from "../agent-tools/async-settle.mjs"
|
|
17
|
+
// §6.31 消化账务(批 digest-accounting · 2026-10-05 · 台账 #930):见账 pass ∥ 回合分类单点
|
|
18
|
+
import { accountDigestRound, isDigestRound } from "./digest-account.mjs"
|
|
17
19
|
// 批 4 CLI-ASYNC-DISCARD(AGENT-LOOP-ASYNC-POOL.md §6.20):中止分支「只清已死」收尾单点
|
|
18
20
|
// (#9:+ 后台 bash 任务族——逐条杀树 + 出池 + 墓碑)
|
|
19
|
-
import { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks } from "../agent-tools/async-discard.mjs"
|
|
21
|
+
import { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks, discardAbortedBrowserTasks } from "../agent-tools/async-discard.mjs"
|
|
20
22
|
// R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3a):回合末域登记 flush(写工具钩子累积 →
|
|
21
23
|
// 整写一次本实例 peers 文件——无写入跳过;失败容忍不抛)
|
|
22
24
|
import { flushPeerDomains } from "../peer-domains.mjs"
|
|
23
25
|
// 第 30 批 D1(Stop 钩子——AGENT-LOOP.md §6.13):主会话 run 终止事件(触发块见 finalizeAgentTurn)
|
|
24
26
|
import { runHooks } from "../hooks.mjs"
|
|
25
27
|
|
|
28
|
+
/** 结束信号缺席提醒(§6.31.7 F-DA5——逐字 = 父侧定稿;与「非 stop 异常」同面)。 */
|
|
29
|
+
const FINISH_MISSING_REMINDER =
|
|
30
|
+
"[System reminder: the previous turn ended without a finish signal — the provider did not report how generation ended. If the response appears incomplete, continue from where it left off.]"
|
|
31
|
+
|
|
26
32
|
/**
|
|
27
33
|
* 响应后置提醒注入(2026-09-05 实践轮——自 runAgent 响应处理链提取,verbatim):
|
|
28
34
|
* 流规则 warning(warn 动作——流已完成——去重注入,模型下轮可见)+ 异常 finish
|
|
29
|
-
* reason 警告(响应可能不完整/截断——reasonMap
|
|
35
|
+
* reason 警告(响应可能不完整/截断——reasonMap 人类化描述注入)+ 结束信号缺席提醒
|
|
36
|
+
* (§6.31.7 F-DA5——clean end 无 finishReason ⇒ 同面提醒 + 留痕)。
|
|
30
37
|
*/
|
|
31
38
|
export function injectResponseReminders(agent, response) {
|
|
32
39
|
// Stream rule warnings (action: "warn"): stream completed; inject de-duplicated
|
|
@@ -39,6 +46,14 @@ export function injectResponseReminders(agent, response) {
|
|
|
39
46
|
})
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
// §6.31.7 F-DA5(#929——批 digest-accounting):结束信号缺席(clean end 且无 finishReason)
|
|
50
|
+
// ⇒ 不得静默当正常——同面异常提醒注入 + `ev:finish-missing` 留痕;partial / interrupted /
|
|
51
|
+
// ruleTriggered 径零触(网络断流既有 `_warnings` 已覆盖;ruleTriggered 在调用点之前分流)。
|
|
52
|
+
if (response.finishReason == null && !response.partial && !response.interrupted) {
|
|
53
|
+
agent.history.push({ role: "user", content: FINISH_MISSING_REMINDER })
|
|
54
|
+
logEvent("ev:finish-missing", { turn: agent._currentTurn ?? 0, auto: agent._inAutoTurn === true, child: agent._logId })
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
// Warn on abnormal finish reasons — the response may be incomplete/truncated.
|
|
43
58
|
if (response.finishReason && response.finishReason !== "stop" && response.finishReason !== "tool_calls") {
|
|
44
59
|
const reasonMap = {
|
|
@@ -145,11 +160,12 @@ export async function injectTurnReminders(agent, ctx) {
|
|
|
145
160
|
* 回合收尾(2026-09-05 实践轮——自 runAgent finally 提取,verbatim + 签名化):写足迹
|
|
146
161
|
* flush → Stop 钩子(第 30 批 D1——ctx.depth === 0 的主会话 run 终止事件,
|
|
147
162
|
* fire-and-forget)→ consult 清理(无孤儿烧 token 的 consult children)→ async 池回合尾
|
|
148
|
-
* 分流(Ctrl+C 清池不注入 / ContinueError 留池续跑 / 其余 collectSettledAsync)→
|
|
163
|
+
* 分流(Ctrl+C 清池不注入 / ContinueError 留池续跑 / 其余 collectSettledAsync)→
|
|
164
|
+
* **见账 pass(§6.31.5——消化轮收尾逐条机检账目覆盖;消化账务批)** → auto-turn
|
|
149
165
|
* guard 标记继承(对象字段清单——正常结束才继承;中止丢弃;ContinueError 由续跑快照)。
|
|
150
166
|
*/
|
|
151
167
|
export async function finalizeAgentTurn(agent, ctx) {
|
|
152
|
-
const { signal, autoTurn, suspDriven, thrownError, depth } = ctx
|
|
168
|
+
const { signal, autoTurn, upstreamTurn, timerTurn, suspDriven, thrownError, depth } = ctx
|
|
153
169
|
// R10 L3(MULTI-INSTANCE-COLLAB §2a.5 D-L3a):回合末登记 flush——本回合写足迹整写
|
|
154
170
|
// 一次(首行执行:收尾链后续任何异常都不吞登记);无写入 → 跳过(hot 窗口自然衰减)。
|
|
155
171
|
flushPeerDomains(agent)
|
|
@@ -190,20 +206,23 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
190
206
|
const subPool = getAsyncPool(agent, "subagent")
|
|
191
207
|
const advPool = getAsyncPool(agent, "advisor")
|
|
192
208
|
const bgPool = getAsyncPool(agent, "bg") // #9:后台 bash 任务池(第三域)
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
const brPool = getAsyncPool(agent, "browser") // 批 browser-async-fix:后台浏览器动作池(第四域)
|
|
210
|
+
if ((subPool?.size ?? 0) > 0 || (advPool?.size ?? 0) > 0 || (bgPool?.size ?? 0) > 0 || (brPool?.size ?? 0) > 0) {
|
|
211
|
+
logEvent("ev:stopped", { poolN: (subPool?.size ?? 0) + (advPool?.size ?? 0) + (bgPool?.size ?? 0) + (brPool?.size ?? 0), where: "turn-end-abort" })
|
|
195
212
|
}
|
|
196
213
|
// §6.20:只清已死条目(存活/已 settle 留池——收尾见 async-discard.mjs;原无条件清池已弃);
|
|
197
214
|
// #9:后台 bash 任务同面(杀 ⟺ 控制器已中止——逐条杀树 + 出池 + 墓碑)。
|
|
198
215
|
discardAbortedPool(agent)
|
|
199
216
|
discardAbortedAdvisors(agent)
|
|
200
217
|
discardAbortedBgTasks(agent)
|
|
218
|
+
discardAbortedBrowserTasks(agent) // 批 browser-async-fix:后台浏览器动作同面(不杀浏览器)
|
|
201
219
|
// R17: the consult family dies with the user stop (marked stopped — no digest).
|
|
202
220
|
cleanupConsultSessions(agent)
|
|
203
221
|
// ASYNC-RESULT-CONTAINER.md D2:pending 单容器——中止丢弃 consult/escalate 族停靠
|
|
204
222
|
// (原 _pendingConsultResults/_pendingEscalateResults 同口径——VSC 同语义 filter);
|
|
205
223
|
// subagent/advisor 停靠保留(挂起期 settle 的已完成结果不随中止丢)。
|
|
206
224
|
agent._pendingAsyncResults = (agent._pendingAsyncResults ?? []).filter((e) => e.role !== "consult" && e.role !== "escalate")
|
|
225
|
+
agent._unsettledDigests = [] // §6.31.5 清账面:会话中止 ⇒ 升级账本随 pending 一并清(用户全停 = 旧账不续)
|
|
207
226
|
} else if (thrownError instanceof ContinueError) {
|
|
208
227
|
// keep _asyncSubagents/_asyncAdvisors/_consultSessions — the resumed run continues them
|
|
209
228
|
} else {
|
|
@@ -220,6 +239,12 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
220
239
|
closeOpenCodeAdvisorRuns(agent)
|
|
221
240
|
}
|
|
222
241
|
}
|
|
242
|
+
// §6.31.5 见账面(消化轮收尾——finalize 单点;消化账务批 · 2026-10-05 · 台账 #930):
|
|
243
|
+
// 恒达窗 = 正常 ∥ Abort-continue ∥ 异常抛三径同达;会话停止径不判(清场语义归 finishSuspension)。
|
|
244
|
+
if (isDigestRound({ autoTurn, upstreamTurn, timerTurn }) && carrierField(agent, "_daSession") === true
|
|
245
|
+
&& !(signal?.aborted && !signal?.reason?.interrupt)) {
|
|
246
|
+
accountDigestRound(agent)
|
|
247
|
+
}
|
|
223
248
|
agent._inAutoTurn = false
|
|
224
249
|
// AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6: auto-turn guard marks survive into the next USER run (restored at its
|
|
225
250
|
// !resume reset above). Normal ends only — abort discards; ContinueError lets the
|
package/agent/run-start.mjs
CHANGED
|
@@ -30,14 +30,56 @@ export async function beginRun(agent, input, callbacks, {
|
|
|
30
30
|
// +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
|
|
31
31
|
// (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 CONSULTATION.md §6.2 D-R17a/b)
|
|
32
32
|
// ——单容器一处清,不再逐族三段。
|
|
33
|
+
// §6.31 消化账务(批 digest-accounting · 2026-10-05 · 台账 #930):**投递 ≠ 销账**——
|
|
34
|
+
// 会话态(`_daSession`)投递留容器(覆盖才离容器 / 未见账经消化轮回路重投);fallback
|
|
35
|
+
// (headless / 直连 runAgent)缺省条逐字沿用取尽语义(先离容后注入 ∥ `releaseSettledEntry` 面;
|
|
36
|
+
// 零账目动作——不销账 / 不重投),已投递未销账条同判据跳过且留容器(§6.31 出口缝② · 父侧裁定 2026-10-05)。
|
|
33
37
|
const pendingAsync = agent._pendingAsyncResults
|
|
34
|
-
|
|
38
|
+
const { carrierField, releaseChildHold, releaseSettledEntry } = await import("../agent-tools/async-settle.mjs")
|
|
39
|
+
if (carrierField(agent, "_daSession") === true) {
|
|
40
|
+
const { armAccountRound, isDigestRound, shouldDeliverEntry, digestAccountRequirement } = await import("./digest-account.mjs")
|
|
41
|
+
armAccountRound(agent) // §6.31.5 落痕清位:会话态起跑即清(缺痕 ⇒ 全条未覆盖——安全方向)
|
|
42
|
+
if (pendingAsync?.length) {
|
|
43
|
+
const { injectAsyncResult } = await import("../agent-tools/subagent.mjs")
|
|
44
|
+
const { injectConsultResult } = await import("../agent-tools/consult.mjs")
|
|
45
|
+
const digestRound = isDigestRound({ autoTurn, upstreamTurn, timerTurn })
|
|
46
|
+
// 投递门控两态(§6.31.4 · §6.34 收窄):首投**仅系统轮**(`autoTurn`——用户回合零注入)∥ 重投仅消化轮;
|
|
47
|
+
// 注入后**留容器**(不 splice)。
|
|
48
|
+
// 注入抛错 ⇒ 未投条目(`_daDelivered` 未置)留容器待下轮——收窄既有「splice 先取尽」的丢失窗。
|
|
49
|
+
for (const e of [...pendingAsync]) {
|
|
50
|
+
if (!shouldDeliverEntry(e, digestRound, autoTurn)) continue
|
|
51
|
+
if (e.role === "consult") await injectConsultResult(agent, e)
|
|
52
|
+
else await injectAsyncResult(agent, e)
|
|
53
|
+
e._daDelivered = true
|
|
54
|
+
e._daRetry = false
|
|
55
|
+
releaseChildHold(e) // 仅释放 `childAgent`——`report` 保留至销账 / 升级(重投需原文)
|
|
56
|
+
}
|
|
57
|
+
// 账目要求行(§6.31.3):仅会话态消化轮且未销账清单非空时注入(先投递后拼清单——
|
|
58
|
+
// 含本回合首投 / 重投);两档同发(手动 / AUTO);transient(机器线独有)。
|
|
59
|
+
if (digestRound) {
|
|
60
|
+
const ids = pendingAsync.filter((e) => e._daDelivered === true).map((e) => e.id)
|
|
61
|
+
if (ids.length > 0) {
|
|
62
|
+
agent.history.push({ role: "user", content: digestAccountRequirement(ids), transient: true })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else if (pendingAsync?.length) {
|
|
35
67
|
const { injectAsyncResult } = await import("../agent-tools/subagent.mjs")
|
|
36
68
|
const { injectConsultResult } = await import("../agent-tools/consult.mjs")
|
|
37
69
|
// TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15 消费点②——run 起始 pending 注入):
|
|
38
70
|
// 注入完成后释放条目对子代理对象的持有(childAgent/report 置空——幂等 helper)。
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
// §6.31 出口缝②(#44 披露①扩散面 · 父侧裁定 2026-10-05):fallback 支同判据延伸——
|
|
72
|
+
// 注入前按投递账分区:已投递未销账条(`_daDelivered === true`——内容已在历史)不注入且
|
|
73
|
+
// 留容器(与 idle 清场同义——防重复 / 条目不离账务面);`_daDelivered` 缺省条逐字沿用
|
|
74
|
+
// (先离容后注入的取尽窗 ∥ 注入顺序 ∥ `releaseSettledEntry` 面)。
|
|
75
|
+
const taken = []
|
|
76
|
+
let kept = 0
|
|
77
|
+
for (const e of pendingAsync) {
|
|
78
|
+
if (e?._daDelivered === true) pendingAsync[kept++] = e
|
|
79
|
+
else taken.push(e)
|
|
80
|
+
}
|
|
81
|
+
pendingAsync.length = kept
|
|
82
|
+
for (const e of taken) {
|
|
41
83
|
if (e.role === "consult") await injectConsultResult(agent, e)
|
|
42
84
|
else await injectAsyncResult(agent, e)
|
|
43
85
|
releaseSettledEntry(e)
|
|
@@ -80,6 +122,11 @@ export async function beginRun(agent, input, callbacks, {
|
|
|
80
122
|
agent._verifyPassed = undefined
|
|
81
123
|
agent._calledAdvisorThisRun = false
|
|
82
124
|
agent._touchedFiles = []
|
|
125
|
+
// #934(零落笔看门狗——AGENT-LOOP-UPSTREAM.md §6.32.6 载体重置):streak / 闩与
|
|
126
|
+
// `_touchedFiles = []`(本块)同点复位——链起点;续跑(resume)不执行 ⇒ 「连续」
|
|
127
|
+
// 跨段存活(锚与分支差别注见 §6.32.2 ①)。
|
|
128
|
+
agent._zeroWriteStreak = 0
|
|
129
|
+
agent._zeroWriteAlerted = false
|
|
83
130
|
agent._verifyRetries = 0
|
|
84
131
|
agent._advisorRound = 0
|
|
85
132
|
agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
|
|
@@ -34,7 +34,7 @@ import { peerInstances } from "../peer-instances.mjs"
|
|
|
34
34
|
import { specForModel } from "../config.mjs"
|
|
35
35
|
import { manifestFilePath, readManifest, projectView } from "../manifest.mjs"
|
|
36
36
|
|
|
37
|
-
/** env-state line builder — 对入参 + 进程端名确定(§6.20 端名缝 sessionEnd();setSessionEnd 注入 ⇒
|
|
37
|
+
/** env-state line builder — 对入参 + 进程端名确定(§6.20 端名缝 sessionEnd();setSessionEnd 注入 ⇒ 单元测试可用)。
|
|
38
38
|
* §6.11(F1):slot 字段入行——位置在 model 后 resumed 前(N3:无绑定 → 显式 null——
|
|
39
39
|
* 不读 manifest active 共享指针——粘性 _slot 才是"本 agent 之槽")。 */
|
|
40
40
|
export function envStateLine({ mode, model, slot, resumed }) {
|
package/agent/setup.mjs
CHANGED
|
@@ -110,10 +110,25 @@ export async function prepareRun(agent, input, callbacks, {
|
|
|
110
110
|
// §6.10 修法 B(TUI 假死批):回合召回注入 = **depth-0 门**——子代理(depth > 0)不注入
|
|
111
111
|
// 相关文档 / 相关记忆两块(同函数其余自动注入全为 depth-0 门;需求锚 = F-M7 + 核 §4.11)。
|
|
112
112
|
if (agent.memory && depth === 0) {
|
|
113
|
-
|
|
113
|
+
// ── 文档检索注入:§6.15 origin 集 + #1100 声明源保底名额(两路分取——总上限 2×DOC_SEARCH_LIMIT)──
|
|
114
|
+
// 无声明源(origin 集 ≤1)⇒ 原单调用路径逐字保留;有声明源 ⇒ 两路 `Promise.all` 并行:
|
|
115
|
+
// 本仓路(origin = 项目)∥ 声明源路(集 − 本仓),各 DOC_SEARCH_LIMIT——合并序 = 本仓 ≤5 在前
|
|
116
|
+
// + 声明 ≤5 补位(两路 origin 集互斥 ⇒ 行不可能重复,无需去重)。配额机制 = `docs/core/design/AGENT-LOOP.md` §6.3。
|
|
117
|
+
const originSet = searchOrigins(agent.memory)
|
|
118
|
+
let docs
|
|
119
|
+
if (originSet.length > 1) {
|
|
120
|
+
const project = normalizeOrigin(agent.memory.codeOrigin)
|
|
121
|
+
const [localDocs, declaredDocs] = await Promise.all([
|
|
122
|
+
docSearch(agent.memory, input, { limit: DOC_SEARCH_LIMIT, origins: [project] }),
|
|
123
|
+
docSearch(agent.memory, input, { limit: DOC_SEARCH_LIMIT, origins: originSet.filter((o) => o !== project) }),
|
|
124
|
+
])
|
|
125
|
+
docs = [...localDocs, ...declaredDocs]
|
|
126
|
+
} else {
|
|
127
|
+
docs = await docSearch(agent.memory, input, { limit: DOC_SEARCH_LIMIT })
|
|
128
|
+
}
|
|
114
129
|
if (docs.length > 0) {
|
|
115
|
-
// §6.14 B3 + §6.15:origin 集限定 + 有界计数(子查询 LIMIT = WARN+1
|
|
116
|
-
const origins =
|
|
130
|
+
// §6.14 B3 + §6.15:origin 集限定 + 有界计数(子查询 LIMIT = WARN+1 形——越界回「+」形(WARN 值);跨 origin 全扫不再回升)
|
|
131
|
+
const origins = originSet
|
|
117
132
|
const bound = INDEX_ORIGIN_ROW_WARN + 1
|
|
118
133
|
const countFilter = origins.length === 1 ? " WHERE origin = ?" : origins.length > 1 ? ` WHERE origin IN (${origins.map(() => "?").join(", ")})` : ""
|
|
119
134
|
const countRow = agent.memory.db.prepare(
|
package/agent/subagent-event.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 文法:`⟦ev⟧<小写名>\x1e<字段…>`(字段以 RS 分隔)。终止符约束 load-bearing:正文含
|
|
13
13
|
* `⟦ev⟧` 而无 RS 终止符 ⇒ 非事件形态 ⇒ null ⇒ 消费方照常转发(不吞真实正文)。
|
|
14
14
|
* 逐事件字段序 = 发射侧单源(各发射点既有构造——本批零改发射侧);投影映射以样本逐字
|
|
15
|
-
*
|
|
15
|
+
* 钉住(单元测试档 T-862-1…6)——发射侧改字段序 = 契约面变更(设计档 §12.3)。
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/** 事件 token 哨兵串(D1)——LLM 正常内容混淆概率极低;字段分隔用 RS (\x1e)。 */
|
package/agent/suspension.mjs
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
* 回合尾后台池仍 live → 挂起会话——池项 settle → 入 pending;pending 非空 **或存在未 drain 的
|
|
6
6
|
* 上行 ask**(`upstreamWaiting`——F-UC7 / §6.27.12.4 ①)→ 合并消化轮 / 唤醒轮(auto-turn;旗标
|
|
7
7
|
* `upstreamTurn` 仅供域文本选择);挂起空闲用户输入优先于消化轮。池空 + pending 空 + 无待处理
|
|
8
|
-
* 输入 → 自然退出(idle)。退出清场:abort = 清池不注入(陈旧结果不注入);idle =
|
|
8
|
+
* 输入 → 自然退出(idle)。退出清场:abort = 清池不注入(陈旧结果不注入);idle = 残余直注入
|
|
9
|
+
* (结果零丢失;已投递未销账条按投递账过滤——留容器不注入,§6.31 出口缝)。
|
|
9
10
|
*
|
|
10
11
|
* 载体注入(端差面 = 池 / pending / 标志挂谁):`ctx.carrier` = 字段集按核内异步面
|
|
11
12
|
* 现行口径的对象(`_asyncSubagents` · `_asyncAdvisors` · `_consultSessions` ·
|
|
12
|
-
* `_pendingAsyncResults` · `_suspended`;#9 后台 bash 任务池 `_bgTasks`
|
|
13
|
+
* `_pendingAsyncResults` · `_suspended`;#9 后台 bash 任务池 `_bgTasks` ∥ 批 browser-async-fix 池 `_browserTasks` 同列;
|
|
14
|
+
* 消化账务批 · 2026-10-05 同列 `_daSession`(挂起会话标记——置位 / 复位单点)与
|
|
15
|
+
* `_unsettledDigests`(升级账本——中止清场))
|
|
13
16
|
* ——CLI 形 = `agent` 对象;VSC 形 = depth-0
|
|
14
17
|
* `history` 数组(附加属性不污染会话文件)。机制对载体零预设(空字段一律 `?.` 读)。
|
|
15
18
|
*
|
|
@@ -56,12 +59,14 @@ function consultRunningChildren(carrier) {
|
|
|
56
59
|
/** 后台池存活判据(D-S2/F5 口径——CLI 为准):running/queued 的池条目,或已 settle 未注入
|
|
57
60
|
* (pending 非空 = D-S3「未注入」),或运行中 consult children(会诊跨回合)。回合尾与每次
|
|
58
61
|
* 轮末都评估退出。**#9 三域并入**:`_bgTasks`(后台 bash 任务)在途亦为 live——settle 经
|
|
59
|
-
* pending 停靠驱动消化轮(`bash-async.mjs`
|
|
62
|
+
* pending 停靠驱动消化轮(`bash-async.mjs` 恒停靠);**批 browser-async-fix 第四域**:
|
|
63
|
+
* `_browserTasks` 同判(`browser-async.mjs` 恒停靠)。 */
|
|
60
64
|
export function poolLive(carrier) {
|
|
61
65
|
const sub = carrier?._asyncSubagents
|
|
62
66
|
const adv = carrier?._asyncAdvisors
|
|
63
67
|
const bg = carrier?._bgTasks
|
|
64
|
-
|
|
68
|
+
const br = carrier?._browserTasks
|
|
69
|
+
return (sub && sub.size > 0) || (adv && adv.size > 0) || (bg && bg.size > 0) || (br && br.size > 0)
|
|
65
70
|
|| (carrier?._pendingAsyncResults?.length ?? 0) > 0
|
|
66
71
|
|| consultRunningChildren(carrier) > 0
|
|
67
72
|
}
|
|
@@ -108,25 +113,30 @@ function consultLiveCount(carrier) {
|
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
/**
|
|
111
|
-
* 退出清场(单点——驱动 finally
|
|
116
|
+
* 退出清场(单点——驱动 finally 与单元测试共用):
|
|
112
117
|
* - abort:**只清已死**(§6.20 口径——增补 D:`discardAbortedPool` ∕ `discardAbortedAdvisors`
|
|
113
118
|
* 单点,与核 run-stages 回合尾中止同式;存活 ∕ 已 settle 条目留池——settled 报告沿后续
|
|
114
119
|
* 回合边界注入到达,不静默丢)+ pending 单容器清 + 会诊会话清理标记;
|
|
115
|
-
* - idle:残余直注入(极端竞态残项——结果零丢失;注入器由宿主注入,缺省 no-op
|
|
120
|
+
* - idle:残余直注入(极端竞态残项——结果零丢失;注入器由宿主注入,缺省 no-op 保残项);
|
|
121
|
+
* **已投递未销账条(`_daDelivered === true`)按投递账过滤**——内容已在历史(投递起跑窗):
|
|
122
|
+
* 不注入且留容器(消化账务批出口缝 · #41 披露① · 父侧裁定 2026-10-05——状态面可见 / 可续账)。
|
|
116
123
|
*
|
|
117
|
-
* 返回 `{ injected, left, error }`(子代理面板批 · 2026-10-04 —— `hooks.reclaim`
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
124
|
+
* 返回 `{ injected, left, error }`(子代理面板批 · 2026-10-04 —— `hooks.reclaim` 恒达窗的退出半;
|
|
125
|
+
* 消化账务批出口缝——`left` = **注入集离容全量**(离容全取——注入失败条不缩水;已投递未销账
|
|
126
|
+
* 跳过条不取、留容器),供调用点 `reclaim(left)` 补发(「消费 ⇒ 必发 done」不变量——C2 零发射口;
|
|
127
|
+
* 跳过条未消费、其 done 已在投递起跑窗落——不入补发面);注入循环 try/catch 收口 ⇒ `injected` =
|
|
128
|
+
* 成功前缀、`error` = 首错(调用点补发毕后重抛——fail-loud 保持);abort ∥ 无注入器 ∥ 无残差 ⇒ 三键空值。
|
|
121
129
|
* 既有 C3/C4 快照径(中止冻结快照)零改。
|
|
122
130
|
*/
|
|
123
131
|
export async function finishSuspension(carrier, { aborted = false, injectResidual = null } = {}) {
|
|
124
132
|
if (aborted) {
|
|
125
|
-
const { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks } = await import("../agent-tools/async-discard.mjs")
|
|
133
|
+
const { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks, discardAbortedBrowserTasks } = await import("../agent-tools/async-discard.mjs")
|
|
126
134
|
discardAbortedPool(carrier)
|
|
127
135
|
discardAbortedAdvisors(carrier)
|
|
128
136
|
discardAbortedBgTasks(carrier) // #9 收尾档②:后台 bash 任务逐条杀树 + 出池 + 墓碑(杀 ⟺ 控制器已中止)
|
|
137
|
+
discardAbortedBrowserTasks(carrier) // 批 browser-async-fix:后台浏览器动作同面(在飞中止 + 出池 + 墓碑——不杀浏览器)
|
|
129
138
|
carrier._pendingAsyncResults = []
|
|
139
|
+
carrier._unsettledDigests = [] // §6.31.5 清账面(消化账务批):会话中止 ⇒ 升级账本随 pending 一并清(旧账不续)
|
|
130
140
|
if (carrier._consultSessions instanceof Map) {
|
|
131
141
|
const { cleanupConsultSessions } = await import("../agent-tools/consult.mjs")
|
|
132
142
|
cleanupConsultSessions(carrier)
|
|
@@ -135,7 +145,18 @@ export async function finishSuspension(carrier, { aborted = false, injectResidua
|
|
|
135
145
|
}
|
|
136
146
|
const residual = carrier._pendingAsyncResults
|
|
137
147
|
if (!residual?.length || typeof injectResidual !== "function") return { injected: [], left: [], error: null }
|
|
138
|
-
|
|
148
|
+
// §6.31 出口缝(#41 披露① · 父侧裁定 2026-10-05):清场按投递账过滤——已投递未销账条
|
|
149
|
+
// (`_daDelivered === true`——内容已在历史)**不注入且不动容器**(状态面可见 / 可续账——防重复);
|
|
150
|
+
// 判据 = 条目既有字段(调用窗 `_daSession` 已复位——不以其为门);其余条逐字沿用。
|
|
151
|
+
// 单遍分区写法(#44 披露④ · 父侧裁定 2026-10-05):一次遍历同出 `left`(离容集)与留容器集——
|
|
152
|
+
// 消除 `indexOf` 未命中 ⇒ `splice(-1,1)` 静默删尾的失败类;离容顺序 ∥ `left` 语义不缩水。
|
|
153
|
+
const left = []
|
|
154
|
+
let kept = 0
|
|
155
|
+
for (const e of residual) {
|
|
156
|
+
if (e?._daDelivered === true) residual[kept++] = e
|
|
157
|
+
else left.push(e)
|
|
158
|
+
}
|
|
159
|
+
residual.length = kept
|
|
139
160
|
const injected = []
|
|
140
161
|
for (const e of left) {
|
|
141
162
|
try { await injectResidual(e) } catch (error) { return { injected, left, error } } // 首错收口(前缀 = injected)
|
|
@@ -217,6 +238,9 @@ export function startSuspension(ctx) {
|
|
|
217
238
|
|
|
218
239
|
const done = (async () => {
|
|
219
240
|
carrier._suspended = true
|
|
241
|
+
// §6.31.2 会话标记(消化账务批 · 2026-10-05 · 台账 #930):挂起会话期 true——投递留容器 /
|
|
242
|
+
// 见账 / 重投回路的唯一判据;退出(finally)置 false ⇒ 会话外回 fallback 语义。
|
|
243
|
+
carrier._daSession = true
|
|
220
244
|
hooks.onCounts?.(backgroundCounts(carrier))
|
|
221
245
|
let aborted = false
|
|
222
246
|
try {
|
|
@@ -298,6 +322,7 @@ export function startSuspension(ctx) {
|
|
|
298
322
|
if (!finished) {
|
|
299
323
|
finished = true
|
|
300
324
|
carrier._suspended = false
|
|
325
|
+
carrier._daSession = false // §6.31.2 会话标记复位(退出即回 fallback 语义)
|
|
301
326
|
latch.wake = null
|
|
302
327
|
// §2.3 退出清场:abort 判定与实时信号合并(两端同式——Stop 落在 runTurn 内时
|
|
303
328
|
// abort 以 AbortError 抛出、循环顶检查不会再执行——不合并会把清场误走 idle 注入)。
|
package/agent/timers.mjs
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* agent/timers.mjs — timer 到期件(载体①:在途 timer 的单一权威读面)+ 空闲唤醒机制(闩 ∕ 派发 ∕ 火策略)。
|
|
3
3
|
*
|
|
4
4
|
* 机制单源 = `docs/core/design/AGENT-LOOP-ASYNC-POOL.md` §6.30.2(载体定形 D-TW1):
|
|
5
|
-
*
|
|
5
|
+
* 读面纯函数住本档——最近到期时点 / 到期出列(幂等 · 含周期重排)/ 历史注入单点 / 下一到期时点
|
|
6
|
+
* (`nextFireAt`——§6.30.18)。消费面三处共用
|
|
6
7
|
* 同一读点(零第二份过滤语义):① 回合边界轮询(`agent/post-turn.mjs`——行为零变);
|
|
7
8
|
* ② 空闲闩 deadline;③ 挂起窗 deadline;显示面(状态行标记 / `/timers`)同读源。
|
|
8
9
|
*
|
|
9
|
-
* 载体仍在 agent 字段 `_pendingTimers`(条目形 `{ id, expiresAt, message }
|
|
10
|
-
* timer 工具 `thincoder-core/agent-tools/timer.mjs
|
|
10
|
+
* 载体仍在 agent 字段 `_pendingTimers`(条目形 `{ id, expiresAt, message }` + 可选 `everyMs` ∥ `daily`——§6.30.18;
|
|
11
|
+
* 绝对时刻条目另携 `at` 来源标记位(非载重——父侧裁定 2026-10-12);一次性条目形逐字零变);写点两处 = timer 工具 `thincoder-core/agent-tools/timer.mjs`
|
|
12
|
+
* (设 ∕ 取消)+ 本档 `takeExpiredTimers` 内**到点重排**(出列+重排原子——§6.30.18 D-TW10);
|
|
13
|
+
* 跨 run 存活 = 规范语义(D-TW3,无 per-run 复位)。
|
|
11
14
|
*
|
|
12
15
|
* 空闲唤醒族(2026-09-28 流程批 R4 **上提核件** · 纯搬 + 转口 · 零语义改;上提源 = VSC
|
|
13
16
|
* `thincoder-vscode/src/extension/timer-watch.mjs:46` ∕ `:73` ∕ `:94`——as-of 2026-09-28 R4 读数;两端现址(B3 收编后,实读 2026-09-29)=
|
|
@@ -21,6 +24,13 @@
|
|
|
21
24
|
* 形态先例 = 队满「拒 + 提示 + 保留」)。与 CLI 排队容量 `QUEUED_MAX_ITEMS`(8)同值同形。 */
|
|
22
25
|
export const TIMER_MAX_PENDING = 8
|
|
23
26
|
|
|
27
|
+
/** `every` 间隔下限(毫秒——§6.30.18 D-TW16):工具面**硬拒**低于此值(不静默钳制);
|
|
28
|
+
* 核重排面不设界(防御 = 非有限正数 ⇒ `nextFireAt` 返 `null` 照消费——下限校验在工具面)。 */
|
|
29
|
+
export const TIMER_MIN_EVERY_MS = 60_000
|
|
30
|
+
|
|
31
|
+
/** 日历式 `HH:MM` 形判据(防御面——工具面为唯一生产者,两处同形)。 */
|
|
32
|
+
const DAILY_RE = /^(?:[01]\d|2[0-3]):[0-5]\d$/
|
|
33
|
+
|
|
24
34
|
/** 最近到期时点(毫秒)或 `null`(空在途)——端闩 / 挂起窗 deadline / 显示面共用同源(每帧活读)。 */
|
|
25
35
|
export function pendingTimerDeadline(agent) {
|
|
26
36
|
const timers = agent?._pendingTimers ?? []
|
|
@@ -33,13 +43,54 @@ export function pendingTimerDeadline(agent) {
|
|
|
33
43
|
return min
|
|
34
44
|
}
|
|
35
45
|
|
|
46
|
+
/** 下一到期时点(毫秒)或 `null`(无重排)——§6.30.18「载体扩展 ∥ 重排」单源:
|
|
47
|
+
* ① 固定间隔(`everyMs`):基准 = `entry.expiresAt`(首设刻);`k = max(1, floor((now − base) / everyMs) + 1)`;
|
|
48
|
+
* 返 `base + k × everyMs`——**零漂移** ∥ 错过任意拍直落下一格(**跳过不补投**——一次出列对一条至多一投);
|
|
49
|
+
* ② 日历式(`daily` = `HH:MM`):宿主本地**严格 `> now`** 的下一 occurrence(今日该刻未过 ⇒ 今日,已过 ⇒
|
|
50
|
+
* 明日;秒位归零——DST 按宿主日历规则走,不作特例);
|
|
51
|
+
* ③ 一次性 ∥ 绝对时刻(无两字段)⇒ `null`;守卫:`everyMs` 非有限正数 ∥ 基准非有限 ∥ `daily` 形不合
|
|
52
|
+
* ⇒ `null`(照消费——防御面;下限校验在工具面)。
|
|
53
|
+
* 首设换算同函数:`every` ⇒ `nextFireAt({ everyMs, expiresAt: now }, now)`(= 设时 + N)∥ `daily` ⇒
|
|
54
|
+
* `nextFireAt({ daily }, now)`;`at` ⇒ 直解算(不经本函数)。 */
|
|
55
|
+
export function nextFireAt(entry, now) {
|
|
56
|
+
const everyMs = entry?.everyMs
|
|
57
|
+
if (everyMs !== undefined && everyMs !== null) {
|
|
58
|
+
const base = entry.expiresAt
|
|
59
|
+
if (typeof everyMs !== "number" || !Number.isFinite(everyMs) || everyMs <= 0) return null
|
|
60
|
+
if (typeof base !== "number" || !Number.isFinite(base)) return null
|
|
61
|
+
const k = Math.max(1, Math.floor((now - base) / everyMs) + 1)
|
|
62
|
+
return base + k * everyMs
|
|
63
|
+
}
|
|
64
|
+
const daily = entry?.daily
|
|
65
|
+
if (daily !== undefined && daily !== null) {
|
|
66
|
+
if (typeof daily !== "string" || !DAILY_RE.test(daily)) return null
|
|
67
|
+
const [hh, mm] = daily.split(":").map(Number)
|
|
68
|
+
const at = new Date(now)
|
|
69
|
+
at.setHours(hh, mm, 0, 0) // 秒位归零(该刻整分投递)
|
|
70
|
+
if (at.getTime() <= now) {
|
|
71
|
+
at.setDate(at.getDate() + 1) // 已过 ⇒ 明日(跨日 = `Date` 日历运算承载——零特例代码)
|
|
72
|
+
at.setHours(hh, mm, 0, 0) // 跨日归一后再归零(DST 走宿主日历规则)
|
|
73
|
+
}
|
|
74
|
+
return at.getTime()
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
/** 到期出列(**幂等**):取走 `expiresAt <= now` 的条目并返回(在途表内移除)——二次调用零返
|
|
37
|
-
* (§6.30.3 成本闸②「到期即消费」——绝不重复投递)。未到期项原序保留。
|
|
80
|
+
* (§6.30.3 成本闸②「到期即消费」——绝不重复投递)。未到期项原序保留。
|
|
81
|
+
* 携 `everyMs`/`daily` 的到期项**同 id 克隆回插**(新 `expiresAt = nextFireAt(t, now)`,严格 `> now`——
|
|
82
|
+
* §6.30.18 重排:出列+重排原子 ∥ 净零不触帽;重排先于交付 ⇒「到点即重排」与「交付幂等」互不依赖);
|
|
83
|
+
* 一次性 ∥ 绝对时刻照旧被消费(无重排)。 */
|
|
38
84
|
export function takeExpiredTimers(agent, now = Date.now()) {
|
|
39
85
|
const timers = agent?._pendingTimers ?? []
|
|
40
86
|
const expired = timers.filter((t) => t.expiresAt <= now)
|
|
41
87
|
if (expired.length === 0) return []
|
|
42
|
-
|
|
88
|
+
const rest = timers.filter((t) => t.expiresAt > now)
|
|
89
|
+
for (const t of expired) {
|
|
90
|
+
const next = nextFireAt(t, now)
|
|
91
|
+
if (next !== null) rest.push({ ...t, expiresAt: next })
|
|
92
|
+
}
|
|
93
|
+
agent._pendingTimers = rest
|
|
43
94
|
return expired
|
|
44
95
|
}
|
|
45
96
|
|
package/agent/turn-loop.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
|
|
23
23
|
export async function runTurnLoop(agent, {
|
|
24
24
|
maxTurns, threshold, toolSchemas, toolByName, systemPrompt,
|
|
25
|
-
depth, signal, autoTurn, streamOutput,
|
|
25
|
+
depth, signal, autoTurn, suspDriven, streamOutput,
|
|
26
26
|
consumeInjected, consumeQueuedInput, callbacks, distillSignal,
|
|
27
27
|
}) {
|
|
28
28
|
let guardPushbacks = 0
|
|
@@ -49,8 +49,13 @@ export async function runTurnLoop(agent, {
|
|
|
49
49
|
|
|
50
50
|
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 消费点):子 → 父在飞消息的
|
|
51
51
|
// 回合边界注入单点取用一次(模块缓存 ⇒ 每 run 一次代价);动态 import = 零新增静态边
|
|
52
|
-
//
|
|
52
|
+
// (同款先例 = run-start.mjs:43 ∥ :66——injectAsyncResult 动态 import)。
|
|
53
53
|
const { drainChildUpstream } = await import("../agent-tools/parent-channel.mjs")
|
|
54
|
+
// #934(零落笔看门狗——AGENT-LOOP-UPSTREAM.md §6.32):连续零写越阈 ⇒ 自动上行提醒(只推——
|
|
55
|
+
// 不杀 / 不转向);单源叶 = zero-write-watch.mjs,动态 import 沿上条同款(零新增静态边)。
|
|
56
|
+
const { maybeZeroWriteAlert } = await import("../agent-tools/zero-write-watch.mjs")
|
|
57
|
+
// §6.31.5 落痕写点门(#939):载体吸收经 `carrierField` 单点(§6.31.4 同式);动态 import = 零新增静态边(沿上方两条同款)。
|
|
58
|
+
const { carrierField } = await import("../agent-tools/async-settle.mjs")
|
|
54
59
|
|
|
55
60
|
// #417(撞帽载荷「本段零落盘轮数」——只报数:零阈值 / 零自动动作):计数基线——回合环逐轮
|
|
56
61
|
// 比对 `_touchedFiles` 增量(轮顶对上一轮结账;撞帽收尾轮在环外单结)。
|
|
@@ -70,7 +75,18 @@ export async function runTurnLoop(agent, {
|
|
|
70
75
|
agent._maxTurns = frame.maxTurns
|
|
71
76
|
// #417(撞帽载荷「本段零落盘轮数」——采集点与 `_turnSeq` 同源面):上一轮至今 `_touchedFiles`
|
|
72
77
|
// 零增 ⇒ 上一轮零落盘,计数 +1;mark 前移(收尾轮在环外结账——见下方 ContinueError 前)。
|
|
73
|
-
|
|
78
|
+
// #934(零落笔看门狗——§6.32.2 ① 单点采集):streak 与越阈判定同点——上轮零写 ⇒ +1 并判越阈;
|
|
79
|
+
// 有写 ⇒ streak 归零 + 清闩(写后重臂——每连续零写段恰一条)。#417 计数语义逐字保持。
|
|
80
|
+
if (turn > 0) {
|
|
81
|
+
if (agent._touchedFiles.length === agent._turnFilesMark) {
|
|
82
|
+
agent._zeroWriteTurns += 1
|
|
83
|
+
agent._zeroWriteStreak += 1
|
|
84
|
+
maybeZeroWriteAlert(agent) // #934:连续零写越阈 ⇒ 自动上行提醒(只推——§6.32)
|
|
85
|
+
} else {
|
|
86
|
+
agent._zeroWriteStreak = 0
|
|
87
|
+
agent._zeroWriteAlerted = false // 写后重臂
|
|
88
|
+
}
|
|
89
|
+
}
|
|
74
90
|
agent._turnFilesMark = agent._touchedFiles.length
|
|
75
91
|
// D2 (AGENT-LOOP-SUBAGENT.md §6.7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
|
|
76
92
|
// single emit point covering all three spawn tools; phase=llm (tool/done progress rides
|
|
@@ -89,7 +105,9 @@ export async function runTurnLoop(agent, {
|
|
|
89
105
|
// consumeInjected 回调承载(异步子代理专属——缺省 null:主会话/阻塞子代理零开销)。
|
|
90
106
|
consumeInjected?.(agent)
|
|
91
107
|
// 子代理在飞消息(子 → 父;§6.27):空队列 no-op;非空 ⇒ 恰一条合并 user 消息注入。
|
|
92
|
-
|
|
108
|
+
// 批 async-delivery-formalize(§6.34.2 行 2):驱动面收窄「交付轮轮首」(`turn === 0` ∧ `autoTurn`——
|
|
109
|
+
// 用户回合零 drain);回退面(`!suspDriven`)逐字保持(每轮每步)。
|
|
110
|
+
if (!suspDriven || (turn === 0 && autoTurn === true)) drainChildUpstream(agent)
|
|
93
111
|
// ③ 用户 → 主会话投递(步边界 pickup——queue-visible 批 fix 轮 2026-09-24 · F16):循环头
|
|
94
112
|
// 回合边界缝(核只给缝、不给策略——取批 / pushReal / 呈现归端侧闭包);缺省 null,系统轮不传
|
|
95
113
|
// (`AGENT-LOOP-ASYNC-POOL.md` §6.8「步边界 pickup」· 适用面 = 用户回合)。
|
|
@@ -109,6 +127,13 @@ export async function runTurnLoop(agent, {
|
|
|
109
127
|
systemPrompt, toolSchemas, streamOutput, callbacks, signal, streamRuleFired, autoTurn, depth, turn,
|
|
110
128
|
})
|
|
111
129
|
|
|
130
|
+
// §6.31.5 落痕(消化账务批 · 2026-10-05 · 台账 #930 ∥ #939 收正):本 run 内**逐轮追加**——
|
|
131
|
+
// 每模型轮返回即写(会话态);首轮签收不为收口轮覆盖(见账取件读累积痕)。
|
|
132
|
+
// 落父字段(抗 `history` 整体替换——`context.mjs` `applyCompression`);非会话零动作。
|
|
133
|
+
if (carrierField(agent, "_daSession") === true && response.content) {
|
|
134
|
+
agent._lastRunOutput = `${agent._lastRunOutput ?? ""}${response.content}\n`
|
|
135
|
+
}
|
|
136
|
+
|
|
112
137
|
// 内置工具(Responses web_search)结果本地化:服务端已执行——入历史为 tool 消息;
|
|
113
138
|
// 服务端 item id 是 msg_xxx 非 web_search_call_ 前缀——必须合成前缀(toItems 识别锚点),
|
|
114
139
|
// 原始 id 存入 content(真机冒烟 2026-08-31 验证)。
|
|
@@ -237,7 +262,7 @@ export async function runTurnLoop(agent, {
|
|
|
237
262
|
// Commit tool results (pairing, multimodal deferral, mutation accounting, reindex)
|
|
238
263
|
await recordToolResults(agent, toolByName, results)
|
|
239
264
|
|
|
240
|
-
injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
|
|
265
|
+
injectPostTurn(agent, results, recentCallSigs, callbacks, turn, { suspDriven })
|
|
241
266
|
}
|
|
242
267
|
|
|
243
268
|
// #417(撞帽载荷「本段零落盘轮数」):收尾轮(撞帽轮)若零落盘同样计入——轮顶计数只
|
package/agent/write-gate.mjs
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* (KD-M4-3)——本档承载三个单点:
|
|
7
7
|
*
|
|
8
8
|
* 1. `resolveReviewTargetPaths(agent)` —— 评审目标解析单点:读 manifest `docRoot`
|
|
9
|
-
* 产出评审对象 / 被审文件的绝对路径集合(目录级;子键值形态 = 串 |
|
|
10
|
-
*
|
|
9
|
+
* 产出评审对象 / 被审文件的绝对路径集合(目录级;子键值形态 = 串 | 多根数组)。
|
|
10
|
+
* 判定本体(`REVIEW_ROOT_KEYS` 键集 + `resolveReviewRootsFor(dir)`——2026-10-02 收口 ·
|
|
11
|
+
* 台账 #828 按用点形)自本档迁 `review-facts.mjs`(2026-10-04 · #921 · KD-2——本档同名
|
|
12
|
+
* 再出口保既有 import 面);缺 `docRoot` 键 → `readManifest`
|
|
11
13
|
* 的 fillDefaults 已补默认(架构 §2.3 E2 便利 fallback 落点);整档缺失 / 非法 JSON →
|
|
12
14
|
* `{ok:false}` → 回退 DEFAULT_MANIFEST.docRoot(本函数是壳门之下的便利层——E2 的
|
|
13
15
|
* 「整档缺失」由壳面 requireManifest 按用点处置(判 + 报明 / 建档,**不抛**——
|
|
@@ -15,11 +17,9 @@
|
|
|
15
17
|
* KD-M1-12):CLI make-agent.mjs / VSC setup.mjs 同判);读错(权限等)→ 上抛不伪装成缺失(manifest.mjs 契约)。
|
|
16
18
|
* M6 的 `advisor.mjs` design-review 分类分支消费本导出
|
|
17
19
|
* (KD-M6-1——同源不重复实现;落 dispatch.mjs 会让 advisor 反向 import 门禁簇成环)。
|
|
18
|
-
* **按用点形(2026-10-02 收口 · 台账 #828)**:`resolveReviewRootsFor(dir)`——同单点
|
|
19
|
-
* 非第二实现(按目标目录 ∥ 所属项目根解析;多档锚下各文档按各自项目判定)。
|
|
20
20
|
* 2. `freezeWindowConflict(agent, absPaths)` —— D5 冻结窗口判据组装:被审文件集 =
|
|
21
|
-
* 声明文档集(`inflightDesignReviewConflict` 同源 docAbs
|
|
22
|
-
*
|
|
21
|
+
* 声明文档集(`inflightDesignReviewConflict` 同源 docAbs 分支)+ 批次档(`run.batchDoc`
|
|
22
|
+
* 分支——评审绑定批档在途时,写批档同样致 stale)。dispatch.mjs
|
|
23
23
|
* 只 import 消费,不再各自直连 `advisor-settle.mjs`。
|
|
24
24
|
* 3. `batchRecordWriteConflict(agent, depth, absPaths)` —— 批次档写门判据组装(#309 ·
|
|
25
25
|
* `AGENT-LOOP-SUBAGENT.md` §6.29.1):判据与拒绝文案同址(文案字面只出本档)。
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
* 代码路径判定(conventions.mjs 单一权威分类——KD-M4-1 保留为代码路径判定用)。
|
|
31
31
|
*/
|
|
32
32
|
import { basename, dirname, resolve } from "node:path"
|
|
33
|
-
import { DEFAULT_MANIFEST, docRootPaths, readManifest } from "../manifest.mjs"
|
|
34
33
|
// advisor-settle 无上游依赖本簇(dispatch → write-gate → advisor-settle 单向)。normAbs
|
|
35
34
|
// 权威本体 = review-facts.mjs(第 33 批迁入),advisor-settle re-export;本档再 re-export
|
|
36
35
|
// (指针链非副本),M6 经本档一行 import 取齐。
|
|
@@ -38,13 +37,17 @@ import { inflightDesignReviewConflict, normAbs } from "../agent-tools/advisor-se
|
|
|
38
37
|
// 批次档路径单源(叶档——`BATCH-RECORD.md` §4.15):批次档写门基底集取 `batchDocBases`。
|
|
39
38
|
import { batchDocBases } from "../agent-tools/batch-paths.mjs"
|
|
40
39
|
export { normAbs }
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// 2026-10-04(工具路径基面根治批 · #921 · KD-2 同名再出口):REVIEW_ROOT_KEYS +
|
|
41
|
+
// resolveReviewRootsFor 判定本体迁 `review-facts.mjs`(评审事实面——advisor-async 既有
|
|
42
|
+
// import 面零新模块边);本档同名再出口(指针非副本——normAbs 同款先例)——既有 import 面
|
|
43
|
+
// (advisor.mjs / 测试)零改;本档消费点 `resolveReviewTargetPaths` 薄委托同名函数。
|
|
44
|
+
export { REVIEW_ROOT_KEYS, resolveReviewRootsFor } from "../agent-tools/review-facts.mjs"
|
|
45
|
+
import { resolveReviewRootsFor } from "../agent-tools/review-facts.mjs"
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
|
-
* 评审目标解析单点(M4 §2.1#1
|
|
47
|
-
*
|
|
48
|
+
* 评审目标解析单点(M4 §2.1#1):评审对象 / 被审文件的绝对路径集合(目录级)——
|
|
49
|
+
* 判定本体(manifest `docRoot` 读面 / 缺档回退 / 读错上抛语义)2026-10-04 迁
|
|
50
|
+
* `review-facts.mjs` 单源(KD-2——本档薄委托;详述见该档 resolveReviewRootsFor JSDoc)。
|
|
48
51
|
* 缺 `docRoot` 键 → readManifest 已补默认;整档缺失 / 非法 →
|
|
49
52
|
* `{ok:false}` → 回退 DEFAULT_MANIFEST.docRoot(便利层——E2 的「整档缺失」由壳面
|
|
50
53
|
* requireManifest 按用点处置(判 + 报明 / 建档,**不抛**——启动侧恒不拒,KD-M1-25;**工程模式会话**口径——KD-M1-12),本函数不重复拦);
|
|
@@ -56,27 +59,12 @@ export function resolveReviewTargetPaths(agent) {
|
|
|
56
59
|
return resolveReviewRootsFor(agent?.cwd ?? process.cwd())
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
/** 同单点之**按用点解析**增量(2026-09-21「参数按用点解析」之裁收口 · 台账 #828):按**目标目录**
|
|
60
|
-
* (其所属项目根——最近带档祖先)解析评审目标根集——多档锚下各文档按各自项目判定。
|
|
61
|
-
* @param {string} dir 目标目录(项目根——最近带档祖先)
|
|
62
|
-
* @returns {string[]} 评审目标目录绝对路径(去重)
|
|
63
|
-
*/
|
|
64
|
-
export function resolveReviewRootsFor(dir) {
|
|
65
|
-
const man = readManifest(dir)
|
|
66
|
-
const root = man.ok && man.manifest?.docRoot && typeof man.manifest.docRoot === "object"
|
|
67
|
-
? man.manifest.docRoot
|
|
68
|
-
: DEFAULT_MANIFEST.docRoot
|
|
69
|
-
const out = []
|
|
70
|
-
for (const key of REVIEW_ROOT_KEYS) out.push(...docRootPaths(root?.[key], dir))
|
|
71
|
-
return [...new Set(out)]
|
|
72
|
-
}
|
|
73
|
-
|
|
74
62
|
/**
|
|
75
63
|
* D5 冻结窗口判据组装(M4 §2.1#2):评审在途(点火 → 结算)期间父侧对被审文件集
|
|
76
64
|
* (声明文档集 + 批次档)的写入 → 返回冲突 { id, path };无冲突 → null。
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* 同样致 stale
|
|
65
|
+
* 声明文档集分支 = `inflightDesignReviewConflict`(同 docAbs / 同 normAbs,仅 running
|
|
66
|
+
* 未取消的设计条目);批次档分支 = 同池条目 `run.batchDoc`(评审绑定批档在途时写批档
|
|
67
|
+
* 同样致 stale——被审文件集合流点)。两分支同形返回,消费端拒绝文案零改。
|
|
80
68
|
* @param {Object} agent
|
|
81
69
|
* @param {string[]} absPaths — 本次写将触碰的路径(ABS——调用方 resolve(agent.cwd, p))
|
|
82
70
|
* @returns {{id: string, path: string}|null} 命中评审 id + 冲突路径(ABS),无冲突 null
|
|
@@ -144,10 +132,10 @@ function isOwnBatchCompanion(targetKey, boundKey) {
|
|
|
144
132
|
/**
|
|
145
133
|
* 批次档写门判据(#309 · `docs/core/design/AGENT-LOOP-SUBAGENT.md` §6.29.1——containment;
|
|
146
134
|
* 2026-09-29 收窄 · 台账 #545):
|
|
147
|
-
*
|
|
135
|
+
* 携绑定的子代理(`depth > 0` ∧ `agent._batchDoc` 在场——工程角色 ∥ 普通面 coder,`BATCH-RECORD.md` §4.16)对**批次档文件**的写 ⇒ 目标 ≠ 绑定档
|
|
148
136
|
* **且非绑定批次的伴随件**(见 `isOwnBatchCompanion`)⇒ 冲突(fail-closed;写自己绑定的档 ∥
|
|
149
137
|
* 自身批次伴随件 = 正常面放行)。比较键 = 绝对路径 + win32 大小写归一。
|
|
150
|
-
* 界面:depth 0(主 agent——跨档处置是父侧职责)· 无 `_batchDoc
|
|
138
|
+
* 界面:depth 0(主 agent——跨档处置是父侧职责)· 无 `_batchDoc`(未携绑定族——普通面 coder 亦入绑定面)· 主基底外的
|
|
151
139
|
* 普通文件写 —— 三种形态均不进本门(返回 null,行为零变)。判据集由调用方限定 = `FILE_MUTATORS`
|
|
152
140
|
* (`file_ops` / 读类不在门内——§6.29.1 边界)。
|
|
153
141
|
* 拒绝文案**单源 = 本档**(两端直取 `message`——零字面副本)。
|