@thincoder/core 0.10.4 → 0.10.6

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.
Files changed (153) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +1 -1
  3. package/advisor/compaction.mjs +11 -6
  4. package/advisor/run.mjs +4 -4
  5. package/agent/assemble.mjs +1 -1
  6. package/agent/chat-call.mjs +1 -1
  7. package/agent/child-marks.mjs +2 -2
  8. package/agent/digest-account.mjs +5 -4
  9. package/agent/dispatch-gates.mjs +8 -0
  10. package/agent/dispatch.mjs +39 -18
  11. package/agent/family-tools.mjs +22 -11
  12. package/agent/helpers.mjs +4 -3
  13. package/agent/light-round.mjs +66 -0
  14. package/agent/post-turn.mjs +8 -4
  15. package/agent/run-stages.mjs +5 -3
  16. package/agent/run-start.mjs +3 -2
  17. package/agent/setup-reminders.mjs +1 -1
  18. package/agent/setup.mjs +18 -3
  19. package/agent/subagent-event.mjs +1 -1
  20. package/agent/suspension.mjs +8 -5
  21. package/agent/timers.mjs +56 -5
  22. package/agent/turn-loop.mjs +5 -3
  23. package/agent/write-gate.mjs +7 -7
  24. package/agent-tools/advisor-async.mjs +2 -2
  25. package/agent-tools/advisor-settle.mjs +2 -2
  26. package/agent-tools/advisor.mjs +1 -1
  27. package/agent-tools/async-discard.mjs +34 -0
  28. package/agent-tools/async-settle.mjs +3 -2
  29. package/agent-tools/audit-block.mjs +1 -1
  30. package/agent-tools/bash-async.mjs +1 -1
  31. package/agent-tools/batch-lifecycle.mjs +35 -14
  32. package/agent-tools/batch-paths.mjs +4 -4
  33. package/agent-tools/batch-skeleton.mjs +29 -11
  34. package/agent-tools/batch.mjs +28 -15
  35. package/agent-tools/browser-async.mjs +198 -0
  36. package/agent-tools/checkpoint.mjs +1 -1
  37. package/agent-tools/design-slots.mjs +232 -0
  38. package/agent-tools/digest-budget.mjs +1 -1
  39. package/agent-tools/manifest.mjs +193 -0
  40. package/agent-tools/parent-channel.mjs +7 -4
  41. package/agent-tools/review-facts.mjs +13 -13
  42. package/agent-tools/settings.mjs +1 -1
  43. package/agent-tools/skill.mjs +1 -1
  44. package/agent-tools/subagent-async.mjs +21 -12
  45. package/agent-tools/subagent-scheduler.mjs +8 -5
  46. package/agent-tools/subagent-spawn.mjs +29 -55
  47. package/agent-tools/subagent.mjs +21 -8
  48. package/agent-tools/timer.mjs +186 -12
  49. package/agent-tools/zero-write-watch.mjs +2 -2
  50. package/agent-tools.mjs +2 -1
  51. package/agent.mjs +1 -1
  52. package/attachments.mjs +1 -1
  53. package/browser/actions.mjs +163 -0
  54. package/browser/cdp.mjs +161 -0
  55. package/browser/clipboard.mjs +142 -0
  56. package/browser/input-actions.mjs +197 -0
  57. package/browser/input.mjs +256 -0
  58. package/browser/launch.mjs +143 -0
  59. package/browser/queue.mjs +171 -0
  60. package/browser/session.mjs +494 -0
  61. package/browser/snapshot.mjs +270 -0
  62. package/config-io.mjs +18 -13
  63. package/config-migrate.mjs +13 -29
  64. package/config-presets.mjs +33 -29
  65. package/config.mjs +68 -25
  66. package/context-degrade.mjs +1 -1
  67. package/context-echo.mjs +1 -1
  68. package/context-push.mjs +2 -2
  69. package/context.mjs +1 -1
  70. package/conventions.mjs +2 -2
  71. package/declaration.mjs +18 -2
  72. package/file-links.mjs +1 -1
  73. package/file-refs.mjs +1 -1
  74. package/history-window.mjs +1 -1
  75. package/i18n.mjs +2 -0
  76. package/ledger-cmd.mjs +62 -16
  77. package/ledger-db.mjs +6 -0
  78. package/ledger-surface.mjs +49 -5
  79. package/ledger-tools.mjs +16 -8
  80. package/ledger.mjs +9 -16
  81. package/log.mjs +1 -1
  82. package/manifest-discovery.mjs +1 -1
  83. package/manifest-schema.mjs +1 -1
  84. package/mcp/transport-stdio.mjs +1 -1
  85. package/mcp.mjs +1 -1
  86. package/memory/code-index.mjs +1 -1
  87. package/memory/code-search.mjs +1 -1
  88. package/memory/code-sync.mjs +1 -1
  89. package/memory/docs.mjs +6 -2
  90. package/memory/file-list.mjs +1 -1
  91. package/memory/file-walk.mjs +17 -5
  92. package/memory/maintain.mjs +410 -0
  93. package/memory/memory-tool.mjs +21 -6
  94. package/memory/schema.mjs +19 -7
  95. package/memory/sweep.mjs +8 -5
  96. package/memory/sync-tail.mjs +16 -10
  97. package/model-ref.mjs +16 -13
  98. package/model-specs.mjs +21 -9
  99. package/package.json +2 -1
  100. package/peer-instances.mjs +1 -1
  101. package/permission.mjs +33 -0
  102. package/process-probe.mjs +2 -2
  103. package/prompts/advisor-design.md +4 -2
  104. package/prompts/advisor-round1.md +1 -1
  105. package/prompts/advisor-round2.md +1 -1
  106. package/prompts/advisor-round3.md +1 -1
  107. package/prompts/common.md +36 -2
  108. package/prompts/consult-base.md +1 -1
  109. package/prompts/discipline-engineering.md +32 -15
  110. package/prompts/discipline-normal.md +46 -2
  111. package/prompts/persona-eng-designer.md +6 -6
  112. package/prompts/persona-engineering.md +14 -6
  113. package/prompts/persona-normal.md +2 -0
  114. package/prompts/subagent-base.md +3 -0
  115. package/provider/core.mjs +6 -0
  116. package/provider/errors.mjs +5 -3
  117. package/provider/google.mjs +9 -8
  118. package/provider/list-models.mjs +3 -2
  119. package/provider/sse.mjs +3 -0
  120. package/provider-flows.mjs +14 -15
  121. package/proxy-chunked.mjs +102 -0
  122. package/proxy-target.mjs +54 -0
  123. package/proxy-transport.mjs +298 -0
  124. package/proxy.mjs +17 -248
  125. package/session-gc.mjs +2 -2
  126. package/session-lifecycle.mjs +18 -14
  127. package/session-slot-claims.mjs +2 -2
  128. package/session-slots-manifest.mjs +1 -1
  129. package/session-slots.mjs +3 -3
  130. package/team-ledger.mjs +282 -0
  131. package/team.mjs +223 -0
  132. package/tool-docs/bash.md +1 -0
  133. package/tool-docs/batch.md +1 -1
  134. package/tool-docs/browser.md +48 -0
  135. package/tool-docs/ledger.md +3 -3
  136. package/tool-docs/manifest.md +16 -0
  137. package/tool-docs/memory.md +1 -0
  138. package/tool-docs/notify_parent.md +5 -0
  139. package/tool-docs/process.md +4 -4
  140. package/tool-docs/question.md +1 -0
  141. package/tool-docs/subagent.md +4 -3
  142. package/tool-docs/team_ledger.md +9 -0
  143. package/tool-docs/timer.md +6 -0
  144. package/tool-docs/wait_for.md +1 -0
  145. package/tools/browser.mjs +161 -0
  146. package/tools/git.mjs +1 -1
  147. package/tools/index.mjs +3 -0
  148. package/tools/ops.mjs +26 -10
  149. package/tools/repomap.mjs +1 -1
  150. package/tools/write-path.mjs +2 -2
  151. package/traces/trace-cleanup.mjs +1 -1
  152. package/traces/trace-store.mjs +1 -1
  153. package/vision-reader.mjs +10 -16
package/CHANGELOG.md CHANGED
@@ -3,7 +3,45 @@
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
- ## [Unreleased]
6
+ ## [0.10.6] — 2026-10-13
7
+
8
+ > 0.10.5 → 0.10.6(月内 +1——发布时定号;P0 热修)
9
+
10
+ ### Fixed
11
+
12
+ - **核包 `files` 白名单补 `browser/`**(P0——0.10.5 装机缺该目录 ⇒ CLI 装后启动崩(`ERR_MODULE_NOT_FOUND`):`tools/browser.mjs:13-15` ∥ `permission.mjs:16` 引 `../browser/*` 而白名单未列;台账 #1362)。发布守则教训:白名单须覆盖运行时引用闭包——发布预检现只对根 `.mjs` ∥ `prompts/` ∥ `tool-docs/` 计数(引闭包断言待另轮补,已登记)。
13
+
14
+ ## [0.10.5] — 2026-10-13
15
+
16
+ > 0.10.4 → 0.10.5(月内 +1——发布时定号)
17
+
18
+ ### Added
19
+
20
+ - **浏览器工具输入域(#1045)**:`press` ∥ `hover` ∥ `wheel` ∥ `mouse` ∥ `drag` ∥ `touch` ∥ `insert` ∥ `clipboard` 八动作面(回执带来源标记)∥ 异步任务 + kill 缝 ∥ 动作预算与会话自愈。
21
+ - **`manifest` 工具(#1098)**:读 ∥ 初始化 ∥ 白名单点写(`docRoot`/`checkConfig`/`index` 子键;整对象写与表外键拒);设计门辖区 = manifest 树(#1104)。
22
+ - **索引自维护(#1096)**:幽灵行 GC ∥ 压实 ∥ 备份轮换 + `memory maintain` action;记忆索引行预算面向面收正(#1107)。
23
+ - **disk-root 启动门(#1080/#1081)**:磁盘根启动拦截 ∥ 索引排除名。
24
+ - **团队核件(B1)**:`teamVerify` 登录校验缝(三端共用);**团队台账核工具 `team_ledger`**(五 action——写三仅主会话 ∥ 子代理只读变体 ∥ 仓派生原文上送 ∥ 登录态门零 fetch)。
25
+ - **台账工具面深化**:证据语义(append 缺省 ∥ `replace` 旗)∥ 家族锚解析(`discoverFamily` anchor-local)。
26
+ - **提示词纪律线族**:每个配置项须有配置 UI(用户裁定)∥ 用户视角门(U1/U2 注册)∥ 平实写作 ∥ 禁以用户名义裁定 ∥ 工程判断 ∥ 任务书再派生(#969)∥ 上抛标记(#970)∥ 板-域-文件三层结构(#956)∥ 子代理报告四列表格 ∥ **上抛前先读档**(#1328——上抛须携已查证据行)。
27
+ - provider 配置一致性(核 + CLI 双舱);gemini-3.8/3.7 flash 规格行(#1198)。
28
+
29
+ ### Changed
30
+
31
+ - **provider 默认模型清退**(通道模型退役——五舱随动)。
32
+ - gemini 思考签名捕获/传递(#1205——工具环内保真);代理分块/传输拆分;会话槽面。
33
+ - 普通模式批档作者缝 + 工作管理说课(#1111)。
34
+ - 测试术语统一(unit / integration——单元测试件为开发期工具,永不入任何闸)。
35
+
36
+ ### Fixed
37
+
38
+ - 主会话异步交付两点(投递 ∥ 消费时点收正)。
39
+ - provider 探测随 per-provider 代理 ∥ loopback 恒直连(#1026)。
40
+ - 上下文预算 = 可用窗口推导(advisor ∥ agent 压实不再越界)。
41
+ - 陈旧注释 ∥ 字符串 ∥ 死导入清扫(#1131)。
42
+ - 台账状态行写后即时刷新(库变更 `fs.watch` 去抖 ⇒ 写后 ≤~0.5s 重扫)。
43
+
44
+ ## [0.10.4] — 2026-10-05
7
45
 
8
46
  > 0.10.3 → 0.10.4(月内 +1——发布时定号)
9
47
 
package/README.md CHANGED
@@ -82,7 +82,7 @@ Per-release notes and artifacts: `CHANGELOG.md` in this package and the [GitHub
82
82
  The core lives in `thincoder-core/` of the ThinCoder repository; bug reports and questions go to the
83
83
  repository issue tracker. Two conventions shape every change: pure ESM `.mjs` with no build step, and
84
84
  zero third-party dependencies — if the Node.js standard library can do it, no third-party package is
85
- allowed. `npm test` runs the repo suite entry (`node test/run.mjs`); after the 2026-09-28 full reset the suite manifest is empty (zero tests = green — collection self-checks still run). Unit tests live as batch-local files under `docs/batches/` in the repository — current state: `docs/core/design/TESTING.md` §2.3. It runs again on release through `prepublishOnly`.
85
+ allowed. `npm test` runs the test entry (`node test/run.mjs`); after the 2026-09-28 full reset the test manifest is empty (zero tests = green — collection self-checks still run). Unit tests live as batch-local files under `docs/batches/` in the repository — current state: `docs/core/design/TESTING.md` §2.3. It runs again on release through `prepublishOnly`.
86
86
  Mechanism design documents live in the repository under `docs/core/design/`.
87
87
 
88
88
  ## License
@@ -10,7 +10,7 @@
10
10
  * advisor-loop-split 批:loop 的压缩检查点归位本档(`compactContextIfNeeded`——`context_limit` 尾串生成点同迁;时间线面 ⇒ `advisor/timeline.mjs`)。
11
11
  */
12
12
 
13
- import { providerSpec } from "../config.mjs" // 第 25 批:预算派生(与 loop.mjs:12 同源导入)
13
+ import { providerSpec, outputReserve, usableWindow } from "../config.mjs" // 第 25 批:预算派生(与 loop.mjs:12 同源导入);`outputReserve`/`usableWindow` = 2026-10-06 预留裁定(#975)
14
14
  // B4(群 B 批,ADVISOR-GUARDS.md §9——F32):CJK 加权单源(provider/rate.mjs 叶子向无环)
15
15
  import { estimateText } from "../provider/rate.mjs"
16
16
 
@@ -22,14 +22,19 @@ export const MAX_ADVISOR_TURNS = 100
22
22
 
23
23
  // Context window limits
24
24
  // 上下文预算(第 25 批——120K 硬编码退场):预算跟随评审模型窗口(providerSpec:
25
- // 模型规格表 × provider 级 context 覆盖)。头寸用途 = chars/4 估算误差 + 响应/协议开销
26
- // (内存不构成约束——`ADVISOR-GUARDS.md §8`);判死线仍是宿主机自限线,服务端窗口约束不变。
27
- export const CONTEXT_LIMIT_RATIO = 0.8 // 判死线 = 窗口 × 0.8
25
+ // 模型规格表 × provider 级 context 覆盖)**扣完成预留**(2026-10-06 裁定——预留坐在窗口里 ⇒
26
+ // 压缩先行、不判死;基数 = 可用窗口,见 advisorContextBudget)。头寸用途 = chars/4 估算误差 +
27
+ // 响应/协议开销(内存不构成约束——`ADVISOR-GUARDS.md §8`);判死线仍是宿主机自限线,服务端窗口约束不变。
28
+ export const CONTEXT_LIMIT_RATIO = 0.8 // 判死线 = 可用窗口 × 0.8
28
29
  const COMPACT_TRIGGER_RATIO = 0.8 // 压缩触发 = 判死线 × 0.8(既有关系零改)
29
30
 
30
- /** 评审上下文预算(纯函数——两档阈值可机测;provider 为 null 时退化默认规格)。 */
31
+ /** 评审上下文预算(纯函数——两档阈值可机测;provider 为 null 时退化默认规格)。
32
+ * 基数 = **可用窗口 = 窗口 − 完成预留**(#975/用户 2026-10-06 裁定——预留坐在窗口里 ⇒
33
+ * 先压缩、不许 provider 先拒:provider 口径 = 输入 + max_tokens ≤ 窗口;预留 = `outputReserve`)。 */
31
34
  export function advisorContextBudget(provider) {
32
- const limit = Math.floor(providerSpec(provider).context * CONTEXT_LIMIT_RATIO)
35
+ const spec = providerSpec(provider)
36
+ const usable = usableWindow(spec.context, outputReserve(provider, spec))
37
+ const limit = Math.floor(usable * CONTEXT_LIMIT_RATIO)
33
38
  return { limit, compactAt: Math.floor(limit * COMPACT_TRIGGER_RATIO) }
34
39
  }
35
40
 
package/advisor/run.mjs CHANGED
@@ -32,10 +32,10 @@ export function resolveAdvisorProvider(agent) {
32
32
  const provider = findProvider(agent.providers?.length ? agent.providers : agent.config?.providersList ?? [agent.provider], cfg.provider)
33
33
  // F-2a (MODEL-400-FIX):无 cfg.model 时渠道克隆须重派生 model——渠道裸克隆会丢 model 键
34
34
  // → 无 model 请求 → serde 400。
35
- // MODEL-SELECTION v2(M3④):无 cfg.model → 命中渠道自己的默认模型(`provider.model`
36
- // 单值);渠道无默认模型 → 父 provider 兜底(与 subagent F-2c 同构)——绝不产出静默
37
- // undefined-model 请求(最极端两者皆无 → chat 前 assertProviderModel fail-fast)。
38
- const result = cfg.model ? { ...provider, model: cfg.model } : { ...provider, model: provider.model ?? agent.provider?.model }
35
+ // 2026-10-09 清除批(与 CLI `cmd-advisor` 同族收正):模型面 = **父解析单档**——渠条目不携
36
+ // 模型(渠道单值模型退场)⇒ 无 cfg.model 取父运行模型 `agent.provider?.model`;两者皆无 →
37
+ // chat 前 `assertProviderModel` fail-fast(不静默)。
38
+ const result = cfg.model ? { ...provider, model: cfg.model } : { ...provider, model: agent.provider?.model }
39
39
  // 台账 #329 / `MODEL-SPECS.md §15.4-3`:off 形须**活着到达载荷层**——载荷层 off 门首款 = `provider.thinking === null`
40
40
  // (`thincoder-core/provider/core.mjs:213-219`);归一为 `undefined` 会让该门永不开(effort 族 advisor
41
41
  // 关思考静默失效)。显式 off 同清**继承档**(渠条目自带 / 主 provider 的 `reasoningEffort` 不得随行——
@@ -65,7 +65,7 @@ export async function assembleAgent({ cwd, deps = {}, toolsFinalize = null }) {
65
65
  const config = D.loadConfig()
66
66
  const provider = config.provider
67
67
  const providers = config.providersList
68
- // 代理注入(double opt-in: provider.proxy + config.proxy.model);config.provider 是 loadConfig 里的
68
+ // 代理注入(逐渠独立:provider.proxy ∧ `proxy.uri` 在案 ⇒ 该渠模型请求经代理——无全局闸,2026-10-08);config.provider 是 loadConfig 里的
69
69
  // 独立拷贝 —— 同步注入结果(proxyUri)。
70
70
  const injectProxy = D.injectProxy ?? (await import("../proxy.mjs")).injectProxy
71
71
  injectProxy(providers, config)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * agent/chat-call.mjs — 单回合模型调用(2026-09-29 P2 三拆:agent.mjs runAgent 段 4 逐字迁入 +
3
3
  * `streamOutputAllowed` 定义随迁——agent.mjs re-export 保 import 面)。
4
- * 接口 = callModelTurn(agent, ctx) → response;错误走原样:AbortError-中断臂处理完照旧 `throw e`,
4
+ * 接口 = callModelTurn(agent, ctx) → response;错误走原样:AbortError-中断分支处理完照旧 `throw e`,
5
5
  * 由 runAgent 的 catch/finally 收束。
6
6
  */
7
7
  import { chat } from "../provider/index.mjs"
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * 收编自 `spawn-child.mjs`(2026-09-20 端差·显示面消差批 §2.2 X6 实现轮):端壳侧需**静态**
5
5
  * 导入锚点做块头注记判定,而 `spawn-child.mjs` 静态链经核 agent 栈可达 `node:sqlite`
6
- * ⇒ 端壳静态闭包禁达(W8 契约②判据(现载体 = 批件 `docs/batches/2026-09-29-residuals-round2.test.mjs`;
7
- * 单测树重建时回迁端侧单测档):fail-closed 红)⇒ 锚点下沉**零依赖叶**(先例 = `relay-prefix.mjs` 同因下沉)。`spawn-child.mjs`
6
+ * ⇒ 端壳静态闭包禁达(W8 契约②判据(现载体 = 单元测试档 `docs/batches/2026-09-29-residuals-round2.test.mjs`;
7
+ * 单元测试树重建时回迁端侧单元测试档):fail-closed 红)⇒ 锚点下沉**零依赖叶**(先例 = `relay-prefix.mjs` 同因下沉)。`spawn-child.mjs`
8
8
  * 原样再导出 ⇒ 既有 import 面(核 agent-tools 族 / CLI `tool-events.mjs`)零改。
9
9
  *
10
10
  * 语义:消费端用 `includes()` 检出锚点 ⇒ 合成块头注记("work may be partial")——两端各一处:
@@ -35,13 +35,14 @@ export function isDigestRound(opts) {
35
35
  }
36
36
 
37
37
  /**
38
- * 投递门控(§6.31.4 单点谓词):
39
- * - `!_daDelivered` ⇒ 投(首次投递——任意回合,沿既有「user + auto」面);
38
+ * 投递门控(§6.31.4 单点谓词;批 async-delivery-formalize 2026-10-12 · §6.34 收窄):
39
+ * - `!_daDelivered` ∧ 本回合 = 系统轮(`autoTurn`)⇒ 投(首次投递——**用户回合零投递**);
40
40
  * - `_daRetry` ∧ 本回合 = 消化轮 ⇒ 投(重投经消化轮回路);
41
41
  * - 其余 ⇒ 跳过(防重复注入)。
42
+ * 注:`autoTurn` 判据由调用方(`run-start.mjs` 会话态支)传入;回退面(非会话态支)不经本谓词。
42
43
  */
43
- export function shouldDeliverEntry(entry, digestRound) {
44
- if (entry?._daDelivered !== true) return true
44
+ export function shouldDeliverEntry(entry, digestRound, autoTurn) {
45
+ if (entry?._daDelivered !== true) return autoTurn === true
45
46
  return entry._daRetry === true && digestRound === true
46
47
  }
47
48
 
@@ -76,6 +76,11 @@ export function isSubagentReadonlyAction(toolName, args) {
76
76
  const action = args?.action
77
77
  return action === "list" || action === "get"
78
78
  }
79
+ // manifest read 只读格(`MANIFEST.md` §2.10 · 台账 #1098——settings list/get 同分类;init / write 保持侧效门)
80
+ if (toolName === "manifest") {
81
+ const action = args?.action
82
+ return action === "read"
83
+ }
79
84
  // #9(TOOLS.md §6.19):process 动作分级——list 保持只读分类(planMode 放行/免审批/可批并行);
80
85
  // kill 是破坏性动作(审批门 + planMode 拒)。
81
86
  if (toolName === "process") return args?.action !== "kill"
@@ -87,6 +92,9 @@ export function isSubagentReadonlyAction(toolName, args) {
87
92
  if (action === "observe") return true
88
93
  // AGENT-LOOP-SUBAGENT.md §6.7.2 panel view 面(freeze 缺省/空 = 视图请求——readonly;非空 freeze 归控制类)
89
94
  if (action === "panel" && (args.freeze === undefined || args.freeze === null || String(args.freeze) === "")) return true
95
+ // DESIGN-TOKEN-SETTLEMENT.md §10 F-SL1(2026-10-07 批 ledger-tool):design-slots = 只读清点
96
+ // (同 status / observe——planMode 放行 / 免审批 / digest 放行;工程模式父侧门在执行器内)。
97
+ if (action === "design-slots") return true
90
98
  return false
91
99
  }
92
100
  export function isSubagentControlAction(toolName, args) {
@@ -7,9 +7,12 @@
7
7
  */
8
8
  import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
9
9
  import { runHooks } from "../hooks.mjs"
10
- import { isCodePath, loadProjectDeclaration } from "../conventions.mjs"
10
+ import { isCodePath, declarationForTarget } from "../conventions.mjs"
11
+ import { manifestFilePath } from "../manifest.mjs"
11
12
  import { resolve, relative } from "node:path"
12
13
  import { anyLiveDesignSlot } from "../token-ttl.mjs"
14
+ // F-LC5(LIGHT-CHANNEL §2.8 · 2026-10-09):轻通道轮在盘谓词(代码笔通路——两件双在盘 ⇒ 父侧代码笔放行)。
15
+ import { lightRoundOpen } from "./light-round.mjs"
13
16
  // M4 写权门禁(模块设计 §2.1#2):冻结窗口判据组装(被审文件集 = 声明文档集 + 批次档
14
17
  // 的合流点)落 write-gate.mjs 单一权威源——本档只 import 消费(KD-M4-4 拆分点)。
15
18
  import { freezeWindowConflict, batchRecordWriteConflict } from "./write-gate.mjs"
@@ -83,28 +86,46 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
83
86
  // design token. The project can declare its own code paths (the manifest's
84
87
  // `codePaths`) so a non-src layout is not silently exempted. Mechanically
85
88
  // blocks "talk then code".
89
+ // F-LC5(LIGHT-CHANNEL §2.8 · 2026-10-09):轻通道轮在盘 = 机械开路——轮次台账行(在途 · title 携「收尾链
90
+ // 待跑」· 携指针)与所指轮档(§1 状态行「进行中」)双在盘期间父侧代码笔放行(谓词 fail-closed)。
91
+ // 查找面 = 会话锚 + 其直接子目录一层(纯向下——工作区锚会话同判)。
92
+ // F11(#1104 · PORTABILITY §3.9 / D20):辖域 = 目标所属项目——逐目标沿祖先链取最近带档
93
+ // 目录(`declarationForTarget`——nearest wins ∥ 纯向上 ∥ 发现梯不参与);出辖(祖先链无档)
94
+ // ⇒ 放行(门只管 manifest 树以内的内容)。相对形先按会话 cwd 解析(cwd 只是相对基,非声明源);
95
+ // 在辖目标按所属项目装载声明,项目内判定(code 段 ∥ 兜底 fail-closed ∥ doc·temp·aux·state 豁免)零改。
86
96
  // DESIGN-TOKEN-SETTLEMENT D3(2026-09-08):资格判据 = 权威槽"任一活槽存在"
87
97
  // (anyLiveDesignSlot——查内存 Map,miss 回读槽文件——单值镜像 `_engDesignToken`
88
98
  // 已退役,门禁不再读镜像——AC4)。
89
99
  if (agent.config?.agent?.engineering && depth === 0
90
100
  && !anyLiveDesignSlot(agent)
91
- && FILE_MUTATORS.has(toolCall.name)) {
101
+ && FILE_MUTATORS.has(toolCall.name)
102
+ && !(await lightRoundOpen(agent))) {
92
103
  const paths = toolTouchPaths(tool, args)
93
- const conv = loadProjectDeclaration(agent.cwd)
94
- // Unknown/missing paths (non-string, e.g. no path argument) are treated
95
- // as code — cannot tell what they touch, so block conservatively. Known
96
- // paths go through the single shared classifier: a declared code segment
97
- // (default: "src") at ANY depth, else anything that is not documentation.
98
- const touchesCode = paths.some((p) => typeof p !== "string" || isCodePath(p, conv))
99
- if (touchesCode) {
100
- // Undeclared project → point at the declaration file (§4.3 降级可见契约).
101
- const convNote = conv.declared
102
- ? ""
103
- : ` — this path was classified as product code by the default conventions (code paths: ${conv.codePaths.join(", ")}); declare project conventions in PROJECT-MANIFEST.json to adjust.`
104
+ // Unknown/missing paths (non-string / empty — nothing to judge) block conservatively.
105
+ // Known paths are judged per target: resolve against the session cwd first, then
106
+ // classify on the ABSOLUTE form against the target's OWNING PROJECT declaration
107
+ // (the segment face anchors at that project's root — D17). Out of jurisdiction ⇒ the
108
+ // target passes; non-string / empty targets keep blocking above (nothing to judge).
109
+ let blocked = false
110
+ let noteConv = null // first undeclared-and-blocked target's project (hint pointer — #1102)
111
+ for (const p of paths) {
112
+ if (typeof p !== "string" || p.trim() === "") { blocked = true; continue }
113
+ const abs = resolve(agent.cwd, p)
114
+ const conv = declarationForTarget(abs)
115
+ if (!conv || !isCodePath(abs, conv)) continue
116
+ blocked = true
117
+ if (!noteConv && !conv.declared) noteConv = conv
118
+ }
119
+ if (blocked) {
120
+ // Undeclared project → point at ITS declaration file (§4.3 降级可见契约; #1102).
121
+ const convNote = noteConv
122
+ ? ` — this path was classified as product code by the default conventions (code paths: ${noteConv.codePaths.join(", ")}); declare project conventions in ${manifestFilePath(noteConv.root)} to adjust.`
123
+ : ""
104
124
  prepared.push({
105
125
  toolCall, tool, denied: true,
106
126
  reason: "engineering design gate",
107
- hint: `Engineering mode: write the design document first(location per your project's document conventions), then call advisor with type='design' to review it, and wait for user approval. Implementation is done by eng-coder subagents.${convNote}`,
127
+ hint: `Engineering mode: write the design document first(location per your project's document conventions), then call advisor with type='design' to review it, and wait for user approval. Implementation is done by eng-coder subagents.${convNote} ` +
128
+ `If this is a light-channel code pen: the gate looks for the round from the session anchor and its direct subdirectories — book the round first (an in-flight batch record plus a 在途 ledger row carrying 「收尾链待跑」 pointing at the record); code pens pass once that is on disk.`,
108
129
  })
109
130
  continue
110
131
  }
@@ -113,14 +134,14 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
113
134
  // E(第 11 批·F17/ADVISOR-GUARDS.md §5 E-3d):D5 冻结窗口写前拦截——设计评审在途(点火 → 结算)期间,
114
135
  // 父侧对被审文件集(声明文档集 + 批次档)的写入会被拒绝:在途写使本轮结算 stale——pass 轮
115
136
  // = token 直接丢失(实证:第 10 批 id=20 整轮作废)。工具面 = FILE_MUTATORS ∪ `file_ops`
116
- // (E2——冻结腿覆盖面;批次档腿仍 = FILE_MUTATORS:与变更记账同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
117
- // agent/write-gate.mjs 的 freezeWindowConflict(声明文档集腿 = inflightDesignReviewConflict
118
- // 同 docAbs / 同 normAbs;批次档腿 = run.batchDoc——M4 合流点,仅扫 running 未取消的设计条目)。
137
+ // (E2——冻结分支覆盖范围;批次档分支仍 = FILE_MUTATORS:与变更记账同集——不记入日志的写面既不判 stale 也不拦);判据与 reviewIsStale 同源、单一权威源 =
138
+ // agent/write-gate.mjs 的 freezeWindowConflict(声明文档集分支 = inflightDesignReviewConflict
139
+ // 同 docAbs / 同 normAbs;批次档分支 = run.batchDoc——M4 合流点,仅扫 running 未取消的设计条目)。
119
140
  // 位置:只读 / autoApprove 短路之前——审批不得绕过冻结;拒绝 = 可见 denied + 逃生门
120
141
  // (先 cancel → 改 → 重发)。
121
142
  if (FILE_MUTATORS.has(toolCall.name) || toolCall.name === "file_ops") {
122
143
  // #327:单源谓词(同集合内两处消费共用);#309 批次档写门与 D5 冻结窗同区、共用同一路径集。
123
- // E2(parity-b1 §2.5——缺省取核形):外门扩 `file_ops`(冻结窗覆盖面与端壳对齐——`file_ops`
144
+ // E2(parity-b1 §2.5——缺省取核形):外门扩 `file_ops`(冻结窗覆盖范围与端壳对齐——`file_ops`
124
145
  // 的 move ∕ copy ∕ rename 亦为写面);**批次档写门判据集不变**(= `FILE_MUTATORS`,file_ops 不入)。
125
146
  const absPaths = toolTouchPaths(tool, args)
126
147
  .filter((p) => typeof p === "string" && p)
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * 登记册**动态**载入:`agent-tools.mjs` 静态图经 consult/subagent 族可达核 agent 栈
15
15
  * (`../agent/setup.mjs` → `../memory.mjs` → `node:sqlite`)——静态引入会破端壳 W8 契约②
16
- * (W8 契约②判据(现载体 = 批件 `docs/batches/2026-09-29-residuals-round2.test.mjs`;单测树重建时回迁端侧单测档):端壳静态链不得到达 `node:sqlite`)。
16
+ * (W8 契约②判据(现载体 = 单元测试档 `docs/batches/2026-09-29-residuals-round2.test.mjs`;单元测试树重建时回迁端侧单元测试档):端壳静态链不得到达 `node:sqlite`)。
17
17
  * 核内先例 = 登记册头注「Loaded from agent.mjs via dynamic import to avoid ESM circular
18
18
  * dependencies」(本档先于 `agent/setup.mjs` 的调用点载入,同一语义)。
19
19
  */
@@ -22,14 +22,17 @@ export async function assembleFamilyTools({
22
22
  role = null, // string 子代理角色(eng-coder / eng-designer / coder / consult / explore / …)
23
23
  engineering = false, // boolean depth-0 role enum 注入用(工程模式)
24
24
  consultModels = [], // array consult 池([] ⇒ consult 工具不注册)
25
- batchDoc = null, // string 批次档绑定路径(eng 角色 → batchTool 绑定)
25
+ batchDoc = null, // string 批次档绑定路径(eng 角色恒挂 ∥ 普通面 coder 携绑定挂——batchTool 绑定)
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, consultStartTool, consultStopTool, parentChannelTool } = await import("../agent-tools.mjs")
29
+ const { planTool, subagentTool, taskTool, skillTool, goalTool, verifyTool, recentChangesTool, timerTool, advisorTool, engTool, readHistoryTool, contextTool, batchTool, manifestTool, consultStartTool, consultStopTool, parentChannelTool } = 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")
33
+ // 团队台账核工具(2026-10-13 team-ledger 批——TEAM-LEDGER.md §2.5):全量五 action = depth-0 段 ∥ 只读
34
+ // 变体(query/count)= 全部 depth>0 段(写三 schema 级不可达——D-TL5)。动态 import 同律(沿上条)。
35
+ const { teamLedgerTool, teamLedgerReadTool } = await import("../team-ledger.mjs")
33
36
 
34
37
  // withPool: decorate the consult_start description with the CURRENT candidate pool
35
38
  // so the model knows which models it can pick (CLI parity with the plugin). The
@@ -143,6 +146,11 @@ export async function assembleFamilyTools({
143
146
  ...consultTools,
144
147
  // 台账全量变体(五 action——仅主 agent 装配;fail-closed:子代理段只挂读变体 = 写面机械不可达)
145
148
  ledgerTool,
149
+ // 团队台账全量变体(五 action——同律:子代理段只挂读变体;登录态门在工具内)
150
+ teamLedgerTool,
151
+ // manifest 工具(`MANIFEST.md` §2.10 · 2026-10-08 · 台账 #1098):主 agent 读写出口——
152
+ // **两模式同挂**(需求未设模式门);子代理各段零挂(未装配 = 权限面第一道)。
153
+ manifestTool,
146
154
  // 端差(decorate.settings——VSC depth-0 主 agent 面;缺省不追加):核默认形态里
147
155
  // settings 住**基础集**(`tools/index.mjs` `assembleBuiltinTools`),端侧自持清单
148
156
  // 无该面 ⇒ 端以 decorate 补位(收敛通道 = 将来去 decorate 项即归核位)。
@@ -160,19 +168,22 @@ export async function assembleFamilyTools({
160
168
  // BATCH-RECORD §4.3 挂载表(批次档生命周期工具化批):eng 两分支各挂主名 `batch`
161
169
  // (绑定段 append/status;目标 = spawn 绑定 `child._batchDoc`)——BATCH-RECORD.md §4.2 的 batch_segment
162
170
  // 挂载形态已随单名化收口退役;depth-0 主 agent 同表挂载
163
- // `batch`(D-BR18 扩权——create/close + append §1/§4/§6 + status §1(轮 2 裁定②:§4/§6
164
- // 状态面走普通文档写),目标走可选 path / 在飞扫描)。
171
+ // `batch`(D-BR18 扩权——create/close + append/status 域随模式(工程面 = append §1/§4/§6 ∥ status §1;普通面 =
172
+ // append §1/§2/§4/§5/§6 ∥ status §1/§2/§5)(轮 2 裁定②:§4/§6 状态面走普通文档写),目标走可选 path / 在飞扫描)。
173
+ // §4.16(2026-10-09):coder 分支**携绑定追加** `batchTool(batchDoc)`——未绑定不挂载
174
+ // (工具不在表 = 语法面不可达——同评审面先例);绑定值 = spawn 可选绑定 `child._batchDoc`。
165
175
  // SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-UPSTREAM.md §6.27.4 装配接线):子代理上行通道
166
176
  // (`notify_parent`)随 depth>0 段**前置**——4 处携带 = eng-coder / eng-designer / coder / 兜底段
167
177
  // (未列名 depth>0 role 落同一兜底段 ⇒ 亦装配;语义 =「depth>0 且非 consult 皆装配」);
168
178
  // 计数口径:`consult` 分支不入 ⇒ 「5 个插入点」读法已作废(实读 `thincoder-core/agent/family-tools.mjs:165-169`)。
169
179
  // consult 段不入(其角色语义 = 父发起的一次性会诊,父在其 settle 前不期望中途对话)。
170
- // 台账只读变体(query / count)——全部 depth>0 角色段(含 consult ∕ 兜底;写三 schema 级不可达)
171
- : engChildRole === "eng-coder" ? [parentChannelTool, ledgerReadTool, advisorTool, verifyTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
172
- : engChildRole === "eng-designer" ? [parentChannelTool, ledgerReadTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
173
- : role === "coder" ? [parentChannelTool, ledgerReadTool, verifyTool, advisorTool]
174
- : role === "consult" ? [recentChangesTool, ledgerReadTool]
175
- : [parentChannelTool, ledgerReadTool]
180
+ // 台账只读变体(query / count)——全部 depth>0 角色段(含 consult ∕ 兜底;写三 schema 级不可达);
181
+ // 团队台账只读变体(team-ledger 批)同律。
182
+ : engChildRole === "eng-coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, advisorTool, verifyTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
183
+ : engChildRole === "eng-designer" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, batchTool(batchDoc), ...(engChildSubagent ? [engChildSubagent] : [])]
184
+ : role === "coder" ? [parentChannelTool, ledgerReadTool, teamLedgerReadTool, verifyTool, advisorTool, ...(batchDoc ? [batchTool(batchDoc)] : [])]
185
+ : role === "consult" ? [recentChangesTool, ledgerReadTool, teamLedgerReadTool]
186
+ : [parentChannelTool, ledgerReadTool, teamLedgerReadTool]
176
187
 
177
188
  // 固定段(装配序 = agent.tools → 固定段 → 家族段 → extraTools):timer 全模式全深度;
178
189
  // plan 随模式位——ENG-PLAN-EXCLUSION(FR31 ① · KD8 卸载而非「注册 + 报错」· KD11 全深度两端);
package/agent/helpers.mjs CHANGED
@@ -203,7 +203,7 @@ export function _clearGitFailureCooldownForTests(cwd) {
203
203
  gitFailureCooldowns.delete(cwd)
204
204
  }
205
205
 
206
- /** Git context 纯格式化(branch/log/status = trim 后原串)——独立导出供单测锁字节
206
+ /** Git context 纯格式化(branch/log/status = trim 后原串)——独立导出供单元测试锁字节
207
207
  * parity(现拼装逐字节保留——detached/dirty>20 截断/clean 三形态——VSC 镜像)。 */
208
208
  export function composeGitContext({ branch, log, status }) {
209
209
  const dirty = status ? status.split("\n").length : 0
@@ -377,12 +377,13 @@ export const SUBAGENT_TOOL_EXCLUSIONS = new Set(["question"])
377
377
  /** Apply the depth>0 exclusion set — always returns a NEW array (parent table untouched).
378
378
  * #9(TOOLS.md §6.19):`bash` 的 `async` 参仅 depth-0——子代副本删参(照 `family-tools.mjs:98`
379
379
  * 先例;返回值仍恒新数组、未命中条目原对象直传)。运行期第二道在 `tools/bash.mjs` execute
380
- * (照 `read_image`「schema 门 + 运行期第二道」双线先例)。 */
380
+ * (照 `read_image`「schema 门 + 运行期第二道」双线先例);批 browser-async-fix(#1045)同线
381
+ * 扩至 `browser` 的 `async` 参(运行期第二道在 `tools/browser.mjs` execute)。 */
381
382
  export function excludeSubagentTools(tools) {
382
383
  return tools
383
384
  .filter((t) => !SUBAGENT_TOOL_EXCLUSIONS.has(t.name))
384
385
  .map((t) => {
385
- if (t.name !== "bash" || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
386
+ if (!(t.name === "bash" || t.name === "browser") || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
386
387
  const { async: _asyncDrop, ...restProps } = t.parameters.properties
387
388
  return { ...t, parameters: { ...t.parameters, properties: restProps } }
388
389
  })
@@ -0,0 +1,66 @@
1
+ /**
2
+ * agent/light-round.mjs — 轻通道代码笔通路谓词(F-LC5 · 设计 = `docs/core/design/LIGHT-CHANNEL.md` §2.8)。
3
+ *
4
+ * `lightRoundOpen(agent)`:闸读判据——**两件双在盘**才算轻通道轮在盘:
5
+ * ① 轮次台账行:`status = 在途` ∧ `title` 携「收尾链待跑」(轮型标记 = 闸凭据)∧ `task_book` 非空;
6
+ * ② 该指针指向的轮次批档:`§1` 状态行 = 「进行中」(判定器单源 = `readBatchStatusLine`)。
7
+ * 查找面(纯向下)= 会话锚 + 其直接子目录一层(候选枚举单源 = `ledgerChildren`——与族发现同级判据
8
+ * 同源;不递归 ∥ 不取同胞):逐候选查行 ∥ 以各候选为基解析指针(台账写门同式)。歧义候选
9
+ * (`projectRootView` 判 `ambiguous`——如工作区锚自身)⇒ 跳过该候选、照查其余(既定观察态,非读错);
10
+ * 候选读错 ∥ 不可判 ⇒ 停本候选(总判不因他候选读错而失——开路凭据仍是「两件校验全过」)。
11
+ * 语义:开门动作 ≡ 开轮两件落盘(「启动即挂账」本体——无独立于账的开门面);单件不放行
12
+ * (轮档独在 ∥ 行独在 ∥ 无标记 ∥ 行非在途 ∥ 指针失据 ∥ 档已收口 ⇒ 皆 false);收口即自闭
13
+ * (信号生命周期 = 账生命周期——零撤销动作 ∥ 零跨轮残留)。
14
+ * 读错 ∥ 不可判 ⇒ `false`(fail-closed——照拦;被拦时文案不因读错而变)。
15
+ * 只读:台账 = `ledgerQuery` **动态 import**(`ledger-db.mjs` 静态 import node:sqlite ⇒
16
+ * 消费侧静态 import 破 W8 契约②);批档 = 一次 `readFileSync`。
17
+ */
18
+ import { readFileSync } from "node:fs"
19
+ import { resolve } from "node:path"
20
+ import { projectRootView, resolveProjectRoot } from "../manifest.mjs"
21
+ import { resolveDeclaredRef } from "../declaration.mjs"
22
+ import { readBatchStatusLine } from "../agent-tools/batch-skeleton.mjs"
23
+
24
+ /** 轮型标记(§2.3 开轮形升格为闸凭据;全链批行无此标记 ⇒ 不构成信号)。 */
25
+ const ROUND_MARKER = "收尾链待跑"
26
+
27
+ /** 轻通道轮在盘谓词(异步 · 只读 · fail-closed)。契约见头注。
28
+ * @param {object} agent — 会话 agent(读 `cwd`)
29
+ * @returns {Promise<boolean>} true = 放行(在盘轮次);读错 ∥ 不可判 ⇒ false(照拦)
30
+ */
31
+ export async function lightRoundOpen(agent) {
32
+ try {
33
+ const cwd = agent?.cwd
34
+ if (typeof cwd !== "string" || cwd.trim() === "") return false
35
+ // W8 契约②:node:sqlite 链只经动态 import 进入(勿升静态)——候选枚举与逐候选查行同取本档公共面。
36
+ const { ledgerChildren, ledgerQuery } = await import("../ledger.mjs")
37
+ const anchor = resolve(cwd)
38
+ // 候选面 = 锚自身 + 直接子目录一层(纯向下;子项枚举单源 = `ledgerChildren`)。
39
+ const candidates = [anchor, ...ledgerChildren(anchor).map((p) => p.root)]
40
+ for (const cand of candidates) {
41
+ // 歧义候选(≥2 候选项目——`projectRootView`)⇒ 跳过、照查其余(歧义锚不充当项目 = AC-M2-19 同判;
42
+ // `openLedger` 歧义拒 = AC-M2-17——既定观察态,非读错)。
43
+ if (projectRootView(cand).state === "ambiguous") continue
44
+ let rows
45
+ try { rows = ledgerQuery({ cwd: cand, status: "在途" }) } catch { continue } // 候选读错 ⇒ 停本候选
46
+ if (!Array.isArray(rows) || rows.length === 0) continue
47
+ // 指针基准 = 台账写门同式(`ledger-cmd.mjs` assertTaskBookGate 同一子表达式)——以各候选为基。
48
+ const base = resolveProjectRoot(cand) ?? resolve(cand ?? ".")
49
+ for (const row of rows) {
50
+ if (!String(row?.title ?? "").includes(ROUND_MARKER)) continue // 无标记 ⇒ 非轻通道轮(全链批行)
51
+ const taskBook = row?.task_book
52
+ if (typeof taskBook !== "string" || taskBook.trim() === "") continue // 缺指针 ⇒ 单件不放行
53
+ const part = taskBook.split("§")[0].trim() // 台账写门同式:首个 § 前子串
54
+ if (part === "") continue
55
+ const hit = resolveDeclaredRef(base, part)
56
+ if (!hit.ok) continue // 指针失据 ⇒ 不放行
57
+ let src
58
+ try { src = readFileSync(hit.abs, "utf8") } catch { continue } // 档读不到 ⇒ 不放行
59
+ if (readBatchStatusLine(src) === "open") return true // 两件双在盘 ⇒ 放行
60
+ }
61
+ }
62
+ return false
63
+ } catch {
64
+ return false // 读错 ∥ 不可判 ⇒ fail-closed(照拦)
65
+ }
66
+ }
@@ -4,7 +4,7 @@
4
4
  * stall detection, and goal status tracking.
5
5
  */
6
6
  import { escapeXml, tryCanonicalize, DEFAULT_GOAL_TURNS } from "./helpers.mjs"
7
- import { takeExpiredTimers, injectTimerReminders } from "./timers.mjs"
7
+ import { takeExpiredTimers, injectTimerReminders, timerWakeEnabled } from "./timers.mjs"
8
8
 
9
9
  export const STALL_WINDOW_SIZE = 5
10
10
  export const STALL_THRESHOLD = 3
@@ -14,11 +14,15 @@ export const GOAL_BUDGET_WARN_RATIO = 0.75
14
14
  * Inject all post-turn events into agent history.
15
15
  * Must be called after every tool-execution turn within runAgent's loop.
16
16
  */
17
- export function injectPostTurn(agent, results, recentCallSigs, callbacks, turn) {
17
+ export function injectPostTurn(agent, results, recentCallSigs, callbacks, turn, { suspDriven = false } = {}) {
18
18
  // Expired timers — inject reminders when thinking budget is up
19
19
  // AGENT-LOOP-ASYNC-POOL.md §6.30.2:过滤 / 注入语义单源于 `agent/timers.mjs`(CLI 空闲闩 /
20
- // 挂起窗同读);本处 = 回合边界轮询调用点——行为零变(最近到期 / 出列幂等 / 逐字形态逐条不变)。
21
- injectTimerReminders(agent, takeExpiredTimers(agent))
20
+ // 挂起窗同读);本处 = 回合边界轮询调用点。
21
+ // 批 async-delivery-formalize(§6.34.2 行 5):驱动面 ∧ `timerWakeEnabled` ⇒ 不在工具轮收尾注入
22
+ // (到期件留在途零出列)——交付交空闲面(挂起窗 ∥ 前台空闲闩);开关关 ∨ 回退面 ⇒ 逐字保持。
23
+ if (!(suspDriven && timerWakeEnabled(agent))) {
24
+ injectTimerReminders(agent, takeExpiredTimers(agent))
25
+ }
22
26
 
23
27
  // Pending reminders
24
28
  if (agent._pendingReminders.length > 0) {
@@ -18,7 +18,7 @@ import { carrierField, getAsyncPool, releaseSettledEntry } from "../agent-tools/
18
18
  import { accountDigestRound, isDigestRound } from "./digest-account.mjs"
19
19
  // 批 4 CLI-ASYNC-DISCARD(AGENT-LOOP-ASYNC-POOL.md §6.20):中止分支「只清已死」收尾单点
20
20
  // (#9:+ 后台 bash 任务族——逐条杀树 + 出池 + 墓碑)
21
- import { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks } from "../agent-tools/async-discard.mjs"
21
+ import { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks, discardAbortedBrowserTasks } from "../agent-tools/async-discard.mjs"
22
22
  // R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3a):回合末域登记 flush(写工具钩子累积 →
23
23
  // 整写一次本实例 peers 文件——无写入跳过;失败容忍不抛)
24
24
  import { flushPeerDomains } from "../peer-domains.mjs"
@@ -206,14 +206,16 @@ export async function finalizeAgentTurn(agent, ctx) {
206
206
  const subPool = getAsyncPool(agent, "subagent")
207
207
  const advPool = getAsyncPool(agent, "advisor")
208
208
  const bgPool = getAsyncPool(agent, "bg") // #9:后台 bash 任务池(第三域)
209
- if ((subPool?.size ?? 0) > 0 || (advPool?.size ?? 0) > 0 || (bgPool?.size ?? 0) > 0) {
210
- logEvent("ev:stopped", { poolN: (subPool?.size ?? 0) + (advPool?.size ?? 0) + (bgPool?.size ?? 0), where: "turn-end-abort" })
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" })
211
212
  }
212
213
  // §6.20:只清已死条目(存活/已 settle 留池——收尾见 async-discard.mjs;原无条件清池已弃);
213
214
  // #9:后台 bash 任务同面(杀 ⟺ 控制器已中止——逐条杀树 + 出池 + 墓碑)。
214
215
  discardAbortedPool(agent)
215
216
  discardAbortedAdvisors(agent)
216
217
  discardAbortedBgTasks(agent)
218
+ discardAbortedBrowserTasks(agent) // 批 browser-async-fix:后台浏览器动作同面(不杀浏览器)
217
219
  // R17: the consult family dies with the user stop (marked stopped — no digest).
218
220
  cleanupConsultSessions(agent)
219
221
  // ASYNC-RESULT-CONTAINER.md D2:pending 单容器——中止丢弃 consult/escalate 族停靠
@@ -43,10 +43,11 @@ export async function beginRun(agent, input, callbacks, {
43
43
  const { injectAsyncResult } = await import("../agent-tools/subagent.mjs")
44
44
  const { injectConsultResult } = await import("../agent-tools/consult.mjs")
45
45
  const digestRound = isDigestRound({ autoTurn, upstreamTurn, timerTurn })
46
- // 投递门控两态(§6.31.4):首投任意回合 ∥ 重投仅消化轮;注入后**留容器**(不 splice)。
46
+ // 投递门控两态(§6.31.4 · §6.34 收窄):首投**仅系统轮**(`autoTurn`——用户回合零注入)∥ 重投仅消化轮;
47
+ // 注入后**留容器**(不 splice)。
47
48
  // 注入抛错 ⇒ 未投条目(`_daDelivered` 未置)留容器待下轮——收窄既有「splice 先取尽」的丢失窗。
48
49
  for (const e of [...pendingAsync]) {
49
- if (!shouldDeliverEntry(e, digestRound)) continue
50
+ if (!shouldDeliverEntry(e, digestRound, autoTurn)) continue
50
51
  if (e.role === "consult") await injectConsultResult(agent, e)
51
52
  else await injectAsyncResult(agent, e)
52
53
  e._daDelivered = true
@@ -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
- const docs = await docSearch(agent.memory, input, { limit: DOC_SEARCH_LIMIT })
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 形——越界回「20000+」;跨 origin 全扫不再回升)
116
- const origins = searchOrigins(agent.memory)
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(
@@ -12,7 +12,7 @@
12
12
  * 文法:`⟦ev⟧<小写名>\x1e<字段…>`(字段以 RS 分隔)。终止符约束 load-bearing:正文含
13
13
  * `⟦ev⟧` 而无 RS 终止符 ⇒ 非事件形态 ⇒ null ⇒ 消费方照常转发(不吞真实正文)。
14
14
  * 逐事件字段序 = 发射侧单源(各发射点既有构造——本批零改发射侧);投影映射以样本逐字
15
- * 钉住(批内件 T-862-1…6)——发射侧改字段序 = 契约面变更(设计档 §12.3)。
15
+ * 钉住(单元测试档 T-862-1…6)——发射侧改字段序 = 契约面变更(设计档 §12.3)。
16
16
  */
17
17
 
18
18
  /** 事件 token 哨兵串(D1)——LLM 正常内容混淆概率极低;字段分隔用 RS (\x1e)。 */