@thincoder/core 0.9.4 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +1 -1
- package/abort-provenance.mjs +10 -10
- package/advisor/citations.mjs +5 -5
- package/advisor/compaction.mjs +16 -15
- package/advisor/loop.mjs +14 -14
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +23 -13
- package/advisor.mjs +4 -4
- package/agent/assemble.mjs +111 -0
- package/agent/chat-call.mjs +68 -0
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +9 -2
- package/agent/continue-decision.mjs +25 -0
- package/agent/dispatch-gates.mjs +140 -0
- package/agent/dispatch-run.mjs +167 -0
- package/agent/dispatch.mjs +55 -294
- package/agent/family-tools.mjs +11 -11
- package/agent/helpers.mjs +63 -3
- package/agent/live-beat.mjs +49 -0
- package/agent/post-turn.mjs +4 -8
- package/agent/record-results.mjs +23 -20
- package/agent/relay-prefix.mjs +2 -2
- package/agent/run-stages.mjs +19 -15
- package/agent/run-start.mjs +111 -0
- package/agent/setup-reminders.mjs +9 -9
- package/agent/setup.mjs +32 -4
- package/agent/spawn-child.mjs +9 -9
- package/agent/suspension.mjs +90 -30
- package/agent/timers.mjs +113 -0
- package/agent/turn-loop.mjs +244 -0
- package/agent/write-gate.mjs +103 -6
- package/agent-tools/advisor-async.mjs +11 -11
- package/agent-tools/advisor-settle.mjs +17 -17
- package/agent-tools/advisor.mjs +38 -43
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +23 -20
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +65 -34
- package/agent-tools/batch-paths.mjs +186 -0
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +22 -119
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +58 -58
- package/agent-tools/context.mjs +4 -20
- package/agent-tools/design-token.mjs +3 -3
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/eng.mjs +5 -5
- package/agent-tools/escalate-async.mjs +18 -13
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +3 -3
- package/agent-tools/parent-channel.mjs +3 -13
- package/agent-tools/plan.mjs +3 -4
- package/agent-tools/read-history.mjs +12 -28
- package/agent-tools/recent-changes.mjs +4 -5
- package/agent-tools/review-facts.mjs +1 -1
- package/agent-tools/settings.mjs +47 -20
- package/agent-tools/skill.mjs +3 -5
- package/agent-tools/spawn-gates.mjs +3 -3
- package/agent-tools/subagent-actions-query.mjs +270 -0
- package/agent-tools/subagent-actions.mjs +29 -273
- package/agent-tools/subagent-async.mjs +36 -31
- package/agent-tools/subagent-panel.mjs +38 -9
- package/agent-tools/subagent-run.mjs +44 -29
- package/agent-tools/subagent-scheduler.mjs +29 -28
- package/agent-tools/subagent-spawn.mjs +57 -25
- package/agent-tools/subagent.mjs +46 -66
- package/agent-tools/task.mjs +3 -9
- package/agent-tools/timer.mjs +12 -12
- package/agent-tools/verify.mjs +14 -13
- package/agent-tools.mjs +0 -1
- package/agent.mjs +20 -351
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +12 -7
- package/config-watch.mjs +83 -0
- package/config.mjs +17 -4
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/git/checkpoint.mjs +4 -8
- package/history-window.mjs +17 -2
- package/i18n.mjs +2 -0
- package/index-discover.mjs +7 -7
- package/ledger-cmd.mjs +34 -126
- package/ledger-db.mjs +35 -7
- package/ledger-migrate.mjs +299 -0
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +11 -5
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-specs.mjs +131 -66
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +263 -0
- package/peer-domains.mjs +100 -71
- package/peer-instances.mjs +9 -10
- package/process-probe-exec.mjs +187 -0
- package/process-probe.mjs +27 -175
- package/prompt-overlays.mjs +11 -10
- package/prompts/advisor-design.md +2 -2
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +19 -7
- package/prompts/discipline-engineering.md +57 -16
- package/prompts/discipline-normal.md +6 -3
- package/prompts/persona-coder.md +0 -1
- package/prompts/persona-eng-coder.md +1 -5
- package/prompts/persona-eng-designer.md +1 -2
- package/prompts/persona-engineering.md +41 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +10 -1
- package/prompts/persona-plan.md +3 -4
- package/prompts/subagent-base.md +14 -0
- package/provider/core.mjs +11 -48
- package/provider/list-models.mjs +3 -0
- package/provider/responses-request.mjs +237 -0
- package/provider/responses.mjs +6 -228
- package/provider-flows.mjs +248 -0
- package/queued.mjs +90 -0
- package/rules.mjs +209 -2
- package/session-gc.mjs +67 -6
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +90 -18
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -236
- package/session-slots.mjs +99 -71
- package/session.mjs +30 -10
- package/shell-candidates.mjs +78 -0
- package/text-budget.mjs +2 -2
- package/think-off.mjs +49 -0
- package/token-ttl.mjs +15 -4
- package/tool-args.mjs +73 -0
- package/tool-docs/advisor.md +9 -0
- package/tool-docs/batch.md +1 -0
- package/tool-docs/code_search.md +1 -0
- package/tool-docs/consult_start.md +5 -0
- package/tool-docs/consult_stop.md +3 -0
- package/tool-docs/context.md +7 -0
- package/tool-docs/doc_search.md +1 -0
- package/tool-docs/edit.md +2 -13
- package/tool-docs/eng.md +1 -0
- package/tool-docs/execute.md +1 -9
- package/tool-docs/git.md +25 -51
- package/tool-docs/goal.md +6 -0
- package/tool-docs/ledger_add.md +1 -0
- package/tool-docs/ledger_close.md +1 -0
- package/tool-docs/ledger_count.md +1 -0
- package/tool-docs/ledger_query.md +1 -0
- package/tool-docs/ledger_update.md +1 -0
- package/tool-docs/main_history.md +1 -0
- package/tool-docs/memory.md +10 -0
- package/tool-docs/notify_parent.md +9 -0
- package/tool-docs/peer_instances.md +1 -0
- package/tool-docs/plan.md +5 -0
- package/tool-docs/process.md +7 -3
- package/tool-docs/question.md +1 -0
- package/tool-docs/read_history.md +7 -0
- package/tool-docs/recent_changes.md +1 -0
- package/tool-docs/repo_outline.md +1 -0
- package/tool-docs/settings.md +6 -0
- package/tool-docs/skill.md +5 -0
- package/tool-docs/subagent.md +31 -0
- package/tool-docs/task.md +7 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/verify.md +1 -0
- package/tool-docs/wait_for.md +2 -1
- package/tools/bash.mjs +28 -4
- package/tools/edit-batch.mjs +10 -11
- package/tools/edit-diff.mjs +33 -25
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +11 -12
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/glob-dialect.mjs +3 -3
- package/tools/index.mjs +2 -1
- package/tools/linter.mjs +2 -1
- package/tools/node-child.mjs +21 -0
- package/tools/ops.mjs +50 -13
- package/tools/patch.mjs +4 -2
- package/tools/repomap.mjs +20 -9
- package/tools/search.mjs +2 -2
- package/tools/shared.mjs +9 -7
- package/tools/web.mjs +5 -2
- package/tools/write-path.mjs +1 -1
- package/traces/trace-store.mjs +17 -14
- package/vision-reader.mjs +76 -0
- package/agent-tools/batch-segment.mjs +0 -18
package/agent/helpers.mjs
CHANGED
|
@@ -85,6 +85,38 @@ const MAX_GIT_CHANGES_DISPLAY = 20
|
|
|
85
85
|
export const OUTLINE_INJECT_PREFIX = "[System reminder: project dependency outline:"
|
|
86
86
|
export const FILE_MUTATORS = new Set(["write", "edit", "insert_after", "apply_patch", "delete", "hashline_edit"])
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* 工具自报触达面提取**单源谓词**(`docs/core/design/TOOLS.md` §6.17 / D-TO12——批 GUARD-SCHEDULER ·
|
|
90
|
+
* 台账 #327):与 `FILE_MUTATORS` 同址(门禁 / 变更记账 / L3 足迹 / 作用域规则四条面的公共输入)。
|
|
91
|
+
* 契约(恒数组 · 恒零抛):
|
|
92
|
+
* - `args` 为 null **或非对象** ⇒ 一律规范化为 `{}` 再交钩子;
|
|
93
|
+
* - 有钩子 ⇒ **钩子裁决**;无钩子 ⇒ `file_ops` **动作感知**(D-TO13 · 台账 #333)——`action === "copy"`
|
|
94
|
+
* ⇒ `[args.dest]`(源仅读取,不属写域);`move` / `rename` / 未知或缺失 `action` ⇒ `[args.source,
|
|
95
|
+
* args.dest]`(保守双算);其余工具单参兜底(形态零变——缺 path 同样返含 `undefined` 的单元素
|
|
96
|
+
* 数组,由消费方按「未知路径 ⇒ 保守」自行判定);
|
|
97
|
+
* - 钩子 throw / 返回非数组 ⇒ `[]`;
|
|
98
|
+
* - **不过滤非字符串项**(未知路径的保守判据归门禁自身——`thincoder-cli/test/portability-classification.test.mjs`
|
|
99
|
+
* T-22 语义零变)。
|
|
100
|
+
* @param {Object|undefined} tool — 工具对象(可选钩子 `touchedPaths(args)`)
|
|
101
|
+
* @param {unknown} args — 工具入参(畸形入参不抛)
|
|
102
|
+
* @returns {unknown[]} 触达路径候选(可能含非字符串项——消费方过滤)
|
|
103
|
+
*/
|
|
104
|
+
export function toolTouchPaths(tool, args) {
|
|
105
|
+
const a = args && typeof args === "object" ? args : {}
|
|
106
|
+
const hook = tool?.touchedPaths
|
|
107
|
+
if (typeof hook !== "function") {
|
|
108
|
+
if (tool?.name === "file_ops") return a.action === "copy" ? [a.dest] : [a.source, a.dest]
|
|
109
|
+
return [a.path]
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const out = hook(a)
|
|
113
|
+
return Array.isArray(out) ? out : []
|
|
114
|
+
} catch {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
88
120
|
/** Escape XML special characters in a string for safe embedding in XML/HTML */
|
|
89
121
|
export function escapeXml(s) {
|
|
90
122
|
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'")
|
|
@@ -210,7 +242,7 @@ export class ContinueError extends Error {
|
|
|
210
242
|
}
|
|
211
243
|
}
|
|
212
244
|
|
|
213
|
-
/** §
|
|
245
|
+
/** AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6 auto-turn guard 标记集(核内单源):快照/回填两 helper 共用本清单——
|
|
214
246
|
* 端侧宿主载体(`panel._guardCarry`)与核载体(`agent._inheritedGuard`)同清单。 */
|
|
215
247
|
export const INHERITED_GUARD_KEYS = [
|
|
216
248
|
"_mutatedThisRun", "_verifiedThisRun", "_verifyPassed", "_calledAdvisorThisRun",
|
|
@@ -231,7 +263,7 @@ export function restoreGuard(target, snap) {
|
|
|
231
263
|
for (const k of INHERITED_GUARD_KEYS) if (k in snap) target[k] = snap[k]
|
|
232
264
|
}
|
|
233
265
|
|
|
234
|
-
/** 跨段累计编号帧(TURN-CAP-CONTINUE.md §
|
|
266
|
+
/** 跨段累计编号帧(TURN-CAP-CONTINUE.md §4——第 19 批 TURN-ACROSS-SEGMENTS):
|
|
235
267
|
* 唯一计算点(纯函数)——把链内累计序数换算成面向消费面的编号载荷。
|
|
236
268
|
* - seq = 该轮链内累计序数(1 起——`agent._turnSeq` 每轮 +1,续跑不重置)
|
|
237
269
|
* - turn = 段内轮号(0 起——段内帽判定的循环变量)
|
|
@@ -337,6 +369,25 @@ export function readonlyToolNames(tools) {
|
|
|
337
369
|
return new Set(tools.filter((t) => t.readonly).map((t) => t.name))
|
|
338
370
|
}
|
|
339
371
|
|
|
372
|
+
/** Depth>0 tool exclusions — subagents never get the interactive main-session tools (`question`:
|
|
373
|
+
* no user to ask; the routing guidance already lives in every persona file). Single source for
|
|
374
|
+
* the repo: the shell-side depth>0 filter consumes this set (design = TOOLS.md §6.16 / D-TO11). */
|
|
375
|
+
export const SUBAGENT_TOOL_EXCLUSIONS = new Set(["question"])
|
|
376
|
+
|
|
377
|
+
/** Apply the depth>0 exclusion set — always returns a NEW array (parent table untouched).
|
|
378
|
+
* #9(TOOLS.md §6.19):`bash` 的 `async` 参仅 depth-0——子代副本删参(照 `family-tools.mjs:98`
|
|
379
|
+
* 先例;返回值仍恒新数组、未命中条目原对象直传)。运行期第二道在 `tools/bash.mjs` execute
|
|
380
|
+
* (照 `read_image`「schema 门 + 运行期第二道」双线先例)。 */
|
|
381
|
+
export function excludeSubagentTools(tools) {
|
|
382
|
+
return tools
|
|
383
|
+
.filter((t) => !SUBAGENT_TOOL_EXCLUSIONS.has(t.name))
|
|
384
|
+
.map((t) => {
|
|
385
|
+
if (t.name !== "bash" || !t.parameters?.properties || !("async" in t.parameters.properties)) return t
|
|
386
|
+
const { async: _asyncDrop, ...restProps } = t.parameters.properties
|
|
387
|
+
return { ...t, parameters: { ...t.parameters, properties: restProps } }
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
340
391
|
const MAX_INSTRUCTION_CHARS = 32_000
|
|
341
392
|
|
|
342
393
|
/** Load AGENTS.md / project_rules.md from user home and project root.
|
|
@@ -374,7 +425,7 @@ export async function loadProjectInstructions(cwd) {
|
|
|
374
425
|
|
|
375
426
|
/** Engineering mode reminder — shared with eng.mjs tool. */
|
|
376
427
|
export const ENG_ON_REMINDER =
|
|
377
|
-
"[System reminder: engineering mode is ON — design-before-code enforced. " +
|
|
428
|
+
"[System reminder: engineering mode is ON (session-scoped) — design-before-code enforced. " +
|
|
378
429
|
"Workflow: Requirements doc → Design doc → advisor(type='design') → " +
|
|
379
430
|
"user approval → eng-coder implementation. Code changes go through eng-coder " +
|
|
380
431
|
"subagents only. Advisor calls are NOT per-turn-mandatory — call only at " +
|
|
@@ -408,6 +459,15 @@ export const AUTO_TURN_DIGEST_DOMAIN_ENG =
|
|
|
408
459
|
export const UPSTREAM_TURN_DOMAIN =
|
|
409
460
|
"[System reminder: auto-turn — a running subagent sent you an in-flight message (shown below). No user message is waiting. Decide it now and reply with subagent action:'send' (id + message) — the child consumes the reply at its next turn boundary and keeps working on the unaffected parts; if the message needs no answer, say so in one line and move on. If finished subagent reports are also present above, summarize them as usual in the same turn. Do not start new work: FORBIDDEN this turn (mechanically enforced): modifying files, bash/execute/verify, spawning subagents, asking questions — those need a real user message. End the turn once the reply is sent.]"
|
|
410
461
|
|
|
462
|
+
/** Timer wake-turn domain (AGENT-LOOP-ASYNC-POOL.md §6.30.3 D-TW4): a pending timer
|
|
463
|
+
* expired while the session was idle and the end's latch opened this turn automatically —
|
|
464
|
+
* neither the digest domain ("no one is waiting", organize-only) nor the up-stream domain
|
|
465
|
+
* (a subagent waiting for a reply) fits: the timer's own semantics is "act on what the
|
|
466
|
+
* timer was for". Verbatim from the parent-side final text (single line — no newlines).
|
|
467
|
+
* Content authority = parent side. */
|
|
468
|
+
export const TIMER_TURN_DOMAIN =
|
|
469
|
+
"[System reminder: auto-turn — a timer you set earlier has expired (reminder above). No user message is waiting. Act on what the timer was for now: pull the data or run the check rather than continuing to reason in the abstract. Stay within the work that was in flight when the timer was set; do not open unrelated new work. Wrap up as soon as the action is done or blocked.]"
|
|
470
|
+
|
|
411
471
|
/** Engineering-mode status injection — one reminder on EVERY transition (2026-08-25:
|
|
412
472
|
* OFF is announced too — the model must know the gates lifted; silence after /eng-off
|
|
413
473
|
* left it guessing. Covers TUI /eng, resume, and any path bypassing the eng tool.) */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent/live-beat.mjs — 存活投影心跳(拍间隔 ∕ 单拍 ∕ 起 ∕ 停 —— 幂等 · 清点)——「桌面处理流 · VSC 对齐」批 R6 上提产物。
|
|
3
|
+
*
|
|
4
|
+
* 上提源(迁移前坐标)= VSC `thincoder-vscode/src/extension/panel-messages.mjs:45`(`LIVE_HEARTBEAT_MS = 2000`)
|
|
5
|
+
* `:50`(单拍 `liveHeartbeatBeat`)`:62-75`(起 ∕ 停 `startLiveHeartbeat` ∕ `stopLiveHeartbeat`);
|
|
6
|
+
* 桌面先例 = `thincoder-desktop/src/main/subagent-face.mjs`(拍体逐键清点)。**纯搬 + 转口,零语义改**:
|
|
7
|
+
* 拍间隔 ∕ 单拍 ∕ 起停 ∕ 幂等 ∕ 清点入核。两处 VSC 端面判据**随拍体留端**(非本档语义):
|
|
8
|
+
* 「未就绪不拍」前置(`panel._wvReady`)与拍日志(`ev:subreassert`)。
|
|
9
|
+
*
|
|
10
|
+
* 拍体(`beat` 注入)= 各端存活再断言本体:VSC = `reassertLiveChildren(panel)`(`thincoder-vscode/src/
|
|
11
|
+
* extension/suspension.mjs:148` 投影面);桌面 = 逐键 `bridge(key).reassertLive(agent)`(只发在飞实例)。
|
|
12
|
+
* 回值 = 本拍投递条数(**清点**读数 —— 拍体自清点,本档原样透传)。
|
|
13
|
+
* **`setInterval` ∕ `unref` 转注入缝**:`timer(fn, ms) ⇒ handle` ∕ `clear(handle)` 由调用方注入
|
|
14
|
+
* (缺省 = 平台 `setInterval` ∕ `clearInterval`;句柄 `unref` = 可选面 —— 在场即调,不阻进程退出)。
|
|
15
|
+
* 消费面 = 桌面 `thincoder-desktop/src/main/subagent-face.mjs` · VSC `panel-messages.mjs` 拍家族——已随 parity-b4
|
|
16
|
+
* 迁移轮改指本档(2026-09-29,双写窗口收口;两处 VSC 端面判据随拍体留端)。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** 拍间隔(毫秒)——单源(VSC 同值 `panel-messages.mjs:45`):起拍即按此周期。 */
|
|
20
|
+
export const LIVE_HEARTBEAT_MS = 2000
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 起 ∕ 停 ∕ 单拍(幂等):
|
|
24
|
+
* - `beat()` = 单拍直驱(回值 = 本拍投递条数 —— 测试直驱面 ∕ 手动补拍);
|
|
25
|
+
* - `start()` = 起拍(**单拍幂等**:已起 ⇒ 返既有句柄、零重起);回值 = 定时器句柄;
|
|
26
|
+
* - `stop()` = 停拍(未起 ⇒ 零动作);停后 `start()` 可再起(新句柄 —— 起 ∕ 停两向)。
|
|
27
|
+
* `timer` ∕ `clear` = 定时器注入缝(缺省 = 平台全局;假钟注入 ⇒ 平 node 直测)。`beat` 缺 ∕ 非函数 ⇒ 抛
|
|
28
|
+
* (fail-loud —— 拍体未接线不得静默空转成「零投拍」)。
|
|
29
|
+
*/
|
|
30
|
+
export function createLiveBeat({ beat, timer = (fn, ms) => setInterval(fn, ms), clear = (handle) => clearInterval(handle) } = {}) {
|
|
31
|
+
if (typeof beat !== "function") {
|
|
32
|
+
throw new TypeError("createLiveBeat: beat seam missing — inject the beat function (the projection must not silently idle)")
|
|
33
|
+
}
|
|
34
|
+
let handle = null
|
|
35
|
+
return {
|
|
36
|
+
beat,
|
|
37
|
+
start() {
|
|
38
|
+
if (handle !== null) return handle
|
|
39
|
+
handle = timer(beat, LIVE_HEARTBEAT_MS)
|
|
40
|
+
handle?.unref?.() // 不阻进程退出(句柄可选面)
|
|
41
|
+
return handle
|
|
42
|
+
},
|
|
43
|
+
stop() {
|
|
44
|
+
if (handle === null) return
|
|
45
|
+
clear(handle)
|
|
46
|
+
handle = null
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
package/agent/post-turn.mjs
CHANGED
|
@@ -4,6 +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
8
|
|
|
8
9
|
export const STALL_WINDOW_SIZE = 5
|
|
9
10
|
export const STALL_THRESHOLD = 3
|
|
@@ -15,14 +16,9 @@ export const GOAL_BUDGET_WARN_RATIO = 0.75
|
|
|
15
16
|
*/
|
|
16
17
|
export function injectPostTurn(agent, results, recentCallSigs, callbacks, turn) {
|
|
17
18
|
// Expired timers — inject reminders when thinking budget is up
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
agent._pendingTimers = agent._pendingTimers.filter((t) => t.expiresAt > now)
|
|
22
|
-
for (const t of expired) {
|
|
23
|
-
agent.history.push({ role: "user", content: `[System reminder: ⏰ timer — ${t.message}]` })
|
|
24
|
-
}
|
|
25
|
-
}
|
|
19
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.30.2:过滤 / 注入语义单源于 `agent/timers.mjs`(CLI 空闲闩 /
|
|
20
|
+
// 挂起窗同读);本处 = 回合边界轮询调用点——行为零变(最近到期 / 出列幂等 / 逐字形态逐条不变)。
|
|
21
|
+
injectTimerReminders(agent, takeExpiredTimers(agent))
|
|
26
22
|
|
|
27
23
|
// Pending reminders
|
|
28
24
|
if (agent._pendingReminders.length > 0) {
|
package/agent/record-results.mjs
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { pushReal } from "../context.mjs"
|
|
27
27
|
import { specForModel } from "../config.mjs"
|
|
28
|
-
import { FILE_MUTATORS } from "./helpers.mjs"
|
|
28
|
+
import { FILE_MUTATORS, toolTouchPaths } from "./helpers.mjs"
|
|
29
29
|
import { resolve } from "node:path"
|
|
30
30
|
import { advisorRuns, stripApprovedSuffix } from "../agent-tools/advisor-async.mjs"
|
|
31
31
|
import { looksLikeReviewOutput, advisorIncompleteMarker } from "../advisor/run.mjs"
|
|
@@ -78,7 +78,7 @@ export async function recordToolResults(agent, toolByName, results) {
|
|
|
78
78
|
// Direct file edit — code was changed. The prior advisor review and
|
|
79
79
|
// verify are stale: a review that ran before the edit no longer
|
|
80
80
|
// covers the current file state.
|
|
81
|
-
//
|
|
81
|
+
// fix A(2026-09-07):mutation-seq 记账已移到 dispatch
|
|
82
82
|
// runOne 执行成功即刻(唯一记账点——取代本批后段 + agent.mjs 中断分支——不双计)——
|
|
83
83
|
// 此处仅剩 guard 标志失效(顺序语义:批内同消息的 sync advisor 提交仍在其后置位)。
|
|
84
84
|
agent._mutatedThisRun = true
|
|
@@ -100,7 +100,7 @@ export async function recordToolResults(agent, toolByName, results) {
|
|
|
100
100
|
}
|
|
101
101
|
if (toolCall.name === "verify") agent._verifiedThisRun = true
|
|
102
102
|
if (toolCall.name === "advisor") {
|
|
103
|
-
// §
|
|
103
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.10 D-24b (settle accounting split — fix #2): an ASYNC launch returns an
|
|
104
104
|
// ack and settles later — the settle callback owns its called/round/token
|
|
105
105
|
// accounting. Only the SYNC path (depth>0 / explicit async:false) accounts
|
|
106
106
|
// here — per-review instance round++ (marker-keyed by tool call id) + the
|
|
@@ -117,7 +117,7 @@ export async function recordToolResults(agent, toolByName, results) {
|
|
|
117
117
|
} else {
|
|
118
118
|
const reviewId = agent._advisorSyncCalls?.get(toolCall.id)
|
|
119
119
|
const run = reviewId !== undefined ? advisorRuns(agent).get(reviewId) : undefined
|
|
120
|
-
// F16 同步面(第 13
|
|
120
|
+
// F16 同步面(第 13 批——ADVISOR-GUARDS.md §6):未完成尾 + 非设计面
|
|
121
121
|
// (含类型不可判——run 缺失的 legacy 直调)⇒ 不置「已覆盖」(与 settle 面
|
|
122
122
|
// 逐条 parity;设计评审保持计入——无代码面)。
|
|
123
123
|
const incomplete = advisorIncompleteMarker(String(result))
|
|
@@ -127,7 +127,7 @@ export async function recordToolResults(agent, toolByName, results) {
|
|
|
127
127
|
run.round++
|
|
128
128
|
agent._advisorRound = run.round
|
|
129
129
|
// Prior of round 2+ = the last REVIEW-LOOKING output (run.mjs parity).
|
|
130
|
-
// F2e
|
|
130
|
+
// F2e: the sync settle's engine-approved suffix (stored on the
|
|
131
131
|
// run by settleDesignReview) is stripped with exact truncation — the
|
|
132
132
|
// prior never carries the raw token / designId.
|
|
133
133
|
if (looksLikeReviewOutput(result)) {
|
|
@@ -145,23 +145,26 @@ export async function recordToolResults(agent, toolByName, results) {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
if (FILE_MUTATORS.has(toolCall.name)) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
_reindexFile
|
|
148
|
+
try {
|
|
149
|
+
const args = JSON.parse(toolCall.arguments)
|
|
150
|
+
const paths = toolTouchPaths(tool, args)
|
|
151
|
+
for (const p of paths) {
|
|
152
|
+
if (typeof p !== "string" || !p) continue // 非字符串项不拖累合法路径(与 dispatch / VSC 消费面同口径)
|
|
153
|
+
const abs = resolve(agent.cwd, p)
|
|
154
|
+
if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
|
|
155
|
+
if (agent.memory) {
|
|
156
|
+
// Fire-and-forget: don't block the agent loop on indexing.
|
|
157
|
+
// Reuses a single cached import; errors surface as pending reminders on next turn.
|
|
158
|
+
if (!_reindexFile) {
|
|
159
|
+
const mod = await import("../memory.mjs")
|
|
160
|
+
_reindexFile = mod.reindexFile
|
|
161
|
+
}
|
|
162
|
+
_reindexFile(agent.memory, agent.cwd, abs).catch((e) => {
|
|
163
|
+
agent._pendingReminders.push(`[System reminder: background indexing failed for ${toolCall.name} on ${abs}: ${e.message}. This does not affect your work — the code index will catch up on next reindex.]`)
|
|
164
|
+
})
|
|
159
165
|
}
|
|
160
|
-
_reindexFile(agent.memory, agent.cwd, abs).catch((e) => {
|
|
161
|
-
agent._pendingReminders.push(`[System reminder: background indexing failed for ${toolCall.name} on ${abs}: ${e.message}. This does not affect your work — the code index will catch up on next reindex.]`)
|
|
162
|
-
})
|
|
163
166
|
}
|
|
164
|
-
}
|
|
167
|
+
} catch { /* #327:畸形 args(含 null)/ 畸形路径不再裸抛——touchedFiles 尽力而为 */ }
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
}
|
package/agent/relay-prefix.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* relay-prefix.mjs — relay 前缀(`role#id/`)文法**单一权威**(第 27
|
|
2
|
+
* relay-prefix.mjs — relay 前缀(`role#id/`)文法**单一权威**(第 27 批——R-A2.3)。
|
|
3
3
|
*
|
|
4
4
|
* 零依赖(不 import 任何模块)——TUI(`src/tui/`)与 ACP(`src/acp/`)双向可导入,无环;
|
|
5
5
|
* 生成侧 `src/agent/spawn-child.mjs` 再导出(枢纽)。文法 = 自
|
|
6
|
-
* `src/tui/subagent-blocks.mjs` 逐字迁入(第 27
|
|
6
|
+
* `src/tui/subagent-blocks.mjs` 逐字迁入(第 27 批——零语义改动)。
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/** `role#id/` prefix router — hyphen included since the eng-coder fix (2026-08-21). */
|
package/agent/run-stages.mjs
CHANGED
|
@@ -15,11 +15,12 @@ import { logEvent, errText } from "../log.mjs"
|
|
|
15
15
|
// ASYNC-RESULT-CONTAINER.md D1:池 accessor(absorb 双池——advisor 独立池无队列)
|
|
16
16
|
import { getAsyncPool, releaseSettledEntry } from "../agent-tools/async-settle.mjs"
|
|
17
17
|
// 批 4 CLI-ASYNC-DISCARD(AGENT-LOOP-ASYNC-POOL.md §6.20):中止分支「只清已死」收尾单点
|
|
18
|
-
|
|
18
|
+
// (#9:+ 后台 bash 任务族——逐条杀树 + 出池 + 墓碑)
|
|
19
|
+
import { discardAbortedPool, discardAbortedAdvisors, discardAbortedBgTasks } from "../agent-tools/async-discard.mjs"
|
|
19
20
|
// R10 L3 (MULTI-INSTANCE-COLLAB §2a.5 D-L3a):回合末域登记 flush(写工具钩子累积 →
|
|
20
21
|
// 整写一次本实例 peers 文件——无写入跳过;失败容忍不抛)
|
|
21
22
|
import { flushPeerDomains } from "../peer-domains.mjs"
|
|
22
|
-
// 第 30 批 D1(Stop 钩子——AGENT-LOOP.md §
|
|
23
|
+
// 第 30 批 D1(Stop 钩子——AGENT-LOOP.md §6.13):主会话 run 终止事件(触发块见 finalizeAgentTurn)
|
|
23
24
|
import { runHooks } from "../hooks.mjs"
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -164,14 +165,14 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
164
165
|
},
|
|
165
166
|
}).catch(() => {})
|
|
166
167
|
}
|
|
167
|
-
// R17(
|
|
168
|
+
// R17(CONSULTATION.md §6.2): consultation sessions are cross-turn
|
|
168
169
|
// background work now — NO unconditional turn-end cleanup (the old rule aborted
|
|
169
170
|
// leftover consult children at every turn end because check-loop consumption was
|
|
170
171
|
// turn-scoped). Sessions stay alive across normal turn ends (and ContinueError
|
|
171
172
|
// resumes); the Ctrl+C abort branch below is the only path that aborts them
|
|
172
173
|
// (cleanupConsultSessions — marked stopped → no digest), and the suspension
|
|
173
174
|
// driver aborts them on its own abort unwind.
|
|
174
|
-
// Async subagent turn-end handling (AGENT-LOOP.md §
|
|
175
|
+
// Async subagent turn-end handling (AGENT-LOOP-SUBAGENT.md §6.7.3 D-A3 + AGENT-LOOP-ASYNC-POOL.md §6.8 D-S1). Lifecycle:
|
|
175
176
|
// - Ctrl+C (plain abort): children were aborted with the parent signal — discard dead
|
|
176
177
|
// entries (tombstone/out-of-pool/notice — AGENT-LOOP-ASYNC-POOL.md §6.20; no stale
|
|
177
178
|
// errors injected — user explicitly stopped); consultation sessions are cross-turn
|
|
@@ -188,12 +189,15 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
188
189
|
if (signal?.aborted && !signal?.reason?.interrupt) {
|
|
189
190
|
const subPool = getAsyncPool(agent, "subagent")
|
|
190
191
|
const advPool = getAsyncPool(agent, "advisor")
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
const bgPool = getAsyncPool(agent, "bg") // #9:后台 bash 任务池(第三域)
|
|
193
|
+
if ((subPool?.size ?? 0) > 0 || (advPool?.size ?? 0) > 0 || (bgPool?.size ?? 0) > 0) {
|
|
194
|
+
logEvent("ev:stopped", { poolN: (subPool?.size ?? 0) + (advPool?.size ?? 0) + (bgPool?.size ?? 0), where: "turn-end-abort" })
|
|
193
195
|
}
|
|
194
|
-
// §6.20:只清已死条目(存活/已 settle 留池——收尾见 async-discard.mjs
|
|
196
|
+
// §6.20:只清已死条目(存活/已 settle 留池——收尾见 async-discard.mjs;原无条件清池已弃);
|
|
197
|
+
// #9:后台 bash 任务同面(杀 ⟺ 控制器已中止——逐条杀树 + 出池 + 墓碑)。
|
|
195
198
|
discardAbortedPool(agent)
|
|
196
199
|
discardAbortedAdvisors(agent)
|
|
200
|
+
discardAbortedBgTasks(agent)
|
|
197
201
|
// R17: the consult family dies with the user stop (marked stopped — no digest).
|
|
198
202
|
cleanupConsultSessions(agent)
|
|
199
203
|
// ASYNC-RESULT-CONTAINER.md D2:pending 单容器——中止丢弃 consult/escalate 族停靠
|
|
@@ -204,7 +208,7 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
204
208
|
// keep _asyncSubagents/_asyncAdvisors/_consultSessions — the resumed run continues them
|
|
205
209
|
} else {
|
|
206
210
|
const injectedAdvisor = await collectSettledAsync(agent, { suspDriven })
|
|
207
|
-
// §
|
|
211
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.10 D-24b: a normally-ended USER run with no review/child activity closes
|
|
208
212
|
// the OPEN code instances — the converged/abandoned thread must not burn the
|
|
209
213
|
// 5-round cap of a later task (the next code review starts a fresh round 1).
|
|
210
214
|
// Digest auto-turns are exempt (their disposition precedes the fix round).
|
|
@@ -217,7 +221,7 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
217
221
|
}
|
|
218
222
|
}
|
|
219
223
|
agent._inAutoTurn = false
|
|
220
|
-
// §
|
|
224
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6: auto-turn guard marks survive into the next USER run (restored at its
|
|
221
225
|
// !resume reset above). Normal ends only — abort discards; ContinueError lets the
|
|
222
226
|
// auto-resumed run snapshot at its own end.
|
|
223
227
|
if (autoTurn && !(signal?.aborted && !signal?.reason?.interrupt) && !(thrownError instanceof ContinueError)) {
|
|
@@ -236,13 +240,13 @@ export async function finalizeAgentTurn(agent, ctx) {
|
|
|
236
240
|
* queued STAY — no allSettled wait. Results never lost without a session.
|
|
237
241
|
* - suspDriven=true (the interaction layer runs suspensionSession after this
|
|
238
242
|
* run): NO direct inject — settled entries STAY pooled (settled not consumed)
|
|
239
|
-
* for the session's first sweepSettledToPending → digest turn (§
|
|
240
|
-
* 条目留池等消化轮注入;status 在 sweep 前仍从池读——17.5.2
|
|
243
|
+
* for the session's first sweepSettledToPending → digest turn (AGENT-LOOP-ASYNC-POOL.md §6.8 — done
|
|
244
|
+
* 条目留池等消化轮注入;status 在 sweep 前仍从池读——17.5.2 不变面(AGENT-LOOP-SUBAGENT.md §6.7.5:check 已删——自动通道为唯一消费方))。
|
|
241
245
|
* maybeRefillAsync runs in both modes (starts queued heads whose slot freed).
|
|
242
246
|
* Single ownership: entries settled inside a suspension session were moved to
|
|
243
247
|
* _pendingAsyncResults by the settle callback, so this only sees user-turn
|
|
244
|
-
* settles (no double inject — D-S3 points ①/②).
|
|
245
|
-
*
|
|
248
|
+
* settles (no double inject — D-S3 points ①/②). settle 回调只发 ⟦ev⟧settled;
|
|
249
|
+
* ⟦ev⟧done 冻结落消费窗(#746 · ASYNC-POOL §6.8)。
|
|
246
250
|
* 2026-09-05 实践轮:自 agent.mjs 迁入(agent.mjs 内仅 finalize 引用——随收尾同迁)。
|
|
247
251
|
*/
|
|
248
252
|
async function collectSettledAsync(agent, { suspDriven = false } = {}) {
|
|
@@ -250,14 +254,14 @@ async function collectSettledAsync(agent, { suspDriven = false } = {}) {
|
|
|
250
254
|
if (maps.length === 0) return false
|
|
251
255
|
const { maybeRefillAsync, injectAsyncResult } = await import("../agent-tools/subagent.mjs")
|
|
252
256
|
maybeRefillAsync(agent) // start queued heads now that slots may have freed — no waiting
|
|
253
|
-
if (suspDriven) return false // §
|
|
257
|
+
if (suspDriven) return false // AGENT-LOOP-ASYNC-POOL.md §6.8: settled stays pooled — the suspension session digests it
|
|
254
258
|
let injectedAdvisor = false
|
|
255
259
|
for (const map of maps) {
|
|
256
260
|
for (const e of [...map.values()]) {
|
|
257
261
|
if (!e.done) continue // still running — stays in the pool (D-S1)
|
|
258
262
|
await injectAsyncResult(agent, e)
|
|
259
263
|
if (e.role === "advisor") injectedAdvisor = true
|
|
260
|
-
// TUI-OOM-ROOTCAUSE
|
|
264
|
+
// TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15 消费点①——回合尾收集):注入完成 → 释放条目持有
|
|
261
265
|
releaseSettledEntry(e)
|
|
262
266
|
map.delete(String(e.id))
|
|
263
267
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent/run-start.mjs — runAgent 起跑前段(2026-09-29 P2 三拆:agent.mjs runAgent 段 1
|
|
3
|
+
* verbatim 迁入——distill 等待 ∕ pendingAsync 注入 ∕ prepareRun ∕ 复位块 ∕ 域文本 ∕ 授权注释块)。
|
|
4
|
+
* 接口 = beginRun(agent, input, callbacks, opts) → 主循环装配面(`tools` 死绑定去——KD-8)。
|
|
5
|
+
*/
|
|
6
|
+
import { prepareRun } from "./setup.mjs"
|
|
7
|
+
import {
|
|
8
|
+
restoreGuard, // digest D-S6 读侧单点(AGENT-LOOP-ASYNC-POOL.md §6.8;P2 机制层端差批 §2.18——键清单归核)
|
|
9
|
+
AUTO_TURN_DIGEST_DOMAIN,
|
|
10
|
+
AUTO_TURN_DIGEST_DOMAIN_ENG, // §6.15.3(F10 第三面):工程模式 digest 基座变体(task 指针改批次档 + 台账)
|
|
11
|
+
UPSTREAM_TURN_DOMAIN, // §6.27.12.8:上行唤醒轮域文本(手动档——ask 轮不沿用 digest 域文本)
|
|
12
|
+
TIMER_TURN_DOMAIN, // §6.30.3 D-TW4:timer 唤醒轮域文本(第三变体——到期 timer 自动开轮)
|
|
13
|
+
} from "./helpers.mjs"
|
|
14
|
+
|
|
15
|
+
export async function beginRun(agent, input, callbacks, {
|
|
16
|
+
depth = 0, signal, overrideTurns, resume = false, autoTurn = false, upstreamTurn = false, timerTurn = false,
|
|
17
|
+
extraTools = null, injections = null, turnDomainText = null, toolDecorate = undefined,
|
|
18
|
+
} = {}) {
|
|
19
|
+
// Previous run's async exploration distillation must settle before this run pushes
|
|
20
|
+
// input (SEND-STALL-DISTILL §2.2 N1) — await first, or its history replace wipes it.
|
|
21
|
+
if (agent._pendingDistill) {
|
|
22
|
+
const p = agent._pendingDistill
|
|
23
|
+
agent._pendingDistill = null
|
|
24
|
+
await p
|
|
25
|
+
}
|
|
26
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.8 D-S3: suspension-settled async results inject before EVERY run's prepareRun
|
|
27
|
+
// (user + auto-turn); spliced = consumed. collectSettledAsync owns a different
|
|
28
|
+
// container, so no double-inject across the two consumption points.
|
|
29
|
+
// ASYNC-RESULT-CONTAINER.md D2 (2026-09-08):pending 单容器 `_pendingAsyncResults`
|
|
30
|
+
// +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
|
|
31
|
+
// (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 CONSULTATION.md §6.2 D-R17a/b)
|
|
32
|
+
// ——单容器一处清,不再逐族三段。
|
|
33
|
+
const pendingAsync = agent._pendingAsyncResults
|
|
34
|
+
if (pendingAsync?.length) {
|
|
35
|
+
const { injectAsyncResult } = await import("../agent-tools/subagent.mjs")
|
|
36
|
+
const { injectConsultResult } = await import("../agent-tools/consult.mjs")
|
|
37
|
+
// TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §6.15 消费点②——run 起始 pending 注入):
|
|
38
|
+
// 注入完成后释放条目对子代理对象的持有(childAgent/report 置空——幂等 helper)。
|
|
39
|
+
const { releaseSettledEntry } = await import("../agent-tools/async-settle.mjs")
|
|
40
|
+
for (const e of pendingAsync.splice(0)) {
|
|
41
|
+
if (e.role === "consult") await injectConsultResult(agent, e)
|
|
42
|
+
else await injectAsyncResult(agent, e)
|
|
43
|
+
releaseSettledEntry(e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6/N3;上行唤醒轮同持 autoTurn——§6.27.12.4 ①)
|
|
47
|
+
const { maxTurns, threshold, toolSchemas, toolByName, systemPrompt } = await prepareRun(
|
|
48
|
+
// G1/G2(施工②):prompt 装配收口 prepareRun 内部(assemblePrompt——prompt-overlays.mjs
|
|
49
|
+
// 槽位常量,与子代理角色常量同源——单一权威锚 D1);本调用不再携带 prompt 常量。
|
|
50
|
+
agent, input, callbacks,
|
|
51
|
+
{ depth, signal, overrideTurns, resume: resume || autoTurn, extraTools, injections, toolDecorate },
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
// Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
|
|
55
|
+
// pushed input + injections — appended from here counts as "this run's" work.
|
|
56
|
+
agent._runStartHistoryLen = agent.history.length
|
|
57
|
+
|
|
58
|
+
// Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
|
|
59
|
+
// mutation/guard continuity and the convergence budget must survive a continuation.
|
|
60
|
+
// §2.3 链内段数(TURN-CAP-CONTINUE.md §4):首段 1、每次续跑 +1——复位条件同 `_turnSeq`
|
|
61
|
+
// (链起点复位、续跑不回退);消费面 = 检查点 ask 载荷 / 池条目留痕二元。
|
|
62
|
+
agent._continueSegments = resume ? (agent._continueSegments ?? 1) + 1 : 1
|
|
63
|
+
// #417(撞帽载荷「本段零落盘轮数」——只报数:零阈值常量 / 零自动动作):段起点复位(首段 /
|
|
64
|
+
// 续跑新段——与 `_continueSegments` 同点);段内累加,采集点在回合环(与 `_turnSeq` 同源面)。
|
|
65
|
+
agent._zeroWriteTurns = 0
|
|
66
|
+
if (!resume) {
|
|
67
|
+
// 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §4):链内累计编号
|
|
68
|
+
// 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
|
|
69
|
+
// turnFrame)。与下方 mutation/guard 复位同条件同点(全档唯一复位点)。
|
|
70
|
+
agent._turnSeq = 0
|
|
71
|
+
// AGENT-LOOP-ASYNC-POOL.md §6.8 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
|
|
72
|
+
// reset) so auto-turn changes never escape the guard silently.
|
|
73
|
+
const g = agent._inheritedGuard
|
|
74
|
+
if (g) {
|
|
75
|
+
restoreGuard(agent, g)
|
|
76
|
+
agent._inheritedGuard = null
|
|
77
|
+
} else {
|
|
78
|
+
agent._mutatedThisRun = false
|
|
79
|
+
agent._verifiedThisRun = false
|
|
80
|
+
agent._verifyPassed = undefined
|
|
81
|
+
agent._calledAdvisorThisRun = false
|
|
82
|
+
agent._touchedFiles = []
|
|
83
|
+
agent._verifyRetries = 0
|
|
84
|
+
agent._advisorRound = 0
|
|
85
|
+
agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
|
|
86
|
+
agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
|
|
87
|
+
agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
|
|
88
|
+
// F-CC2(§6.16.2):模型主动压缩的排队槽也是回合级——上一回合末尾未被安全点消费的请求不得跨回合生效
|
|
89
|
+
agent._pendingCompact = null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// digest D-S6 manual tier(AGENT-LOOP-ASYNC-POOL.md §6.8): action-domain reminder (system-driven turn — organize only).
|
|
93
|
+
// §6.27.12.4 ②: an up-stream wake turn answers a RUNNING subagent waiting for the reply — it
|
|
94
|
+
// must not reuse the digest text ("no one is waiting" is the opposite of the truth).
|
|
95
|
+
// §6.30.3 D-TW4: timer wake turn (third auto-turn variant) — 到期 timer 自动开轮,仅作域文本选择
|
|
96
|
+
// (`upstreamTurn` 优先不回归:ask 轮是「有人在等回复」,比 timer 轮更紧)。
|
|
97
|
+
if ((autoTurn || upstreamTurn) && !agent.autoApprove) {
|
|
98
|
+
// §2.5-A3(P4-I):端壳经 `opts.turnDomainText` 供「核基座 + 端 overlay」组合串(组合点 =
|
|
99
|
+
// 端 `turn-domains.mjs` composeTurnDomain);缺省(null)⇒ 核基座逐字零变(选择序不变)。
|
|
100
|
+
const domainBase = upstreamTurn ? UPSTREAM_TURN_DOMAIN : timerTurn ? TIMER_TURN_DOMAIN : (agent.config?.agent?.engineering === true ? AUTO_TURN_DIGEST_DOMAIN_ENG : AUTO_TURN_DIGEST_DOMAIN)
|
|
101
|
+
agent.history.push({ role: "user", content: turnDomainText ?? domainBase, transient: true })
|
|
102
|
+
}
|
|
103
|
+
// eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
|
|
104
|
+
// (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
|
|
105
|
+
// depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
|
|
106
|
+
// depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
|
|
107
|
+
// Design slots (_engDesignTokens Map) survive across turns (design review → approval →
|
|
108
|
+
// eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
|
|
109
|
+
// SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
|
|
110
|
+
return { maxTurns, threshold, toolSchemas, toolByName, systemPrompt }
|
|
111
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* (SESSION.md §6.11 — 2026-09-06 需求池 R5/R8/R9/R11 合并设计; R10 L1 peer 注入同文件).
|
|
4
4
|
*
|
|
5
5
|
* One unified transient user reminder per turn covers the whole self-awareness
|
|
6
|
-
* family: env identity (R8 — §6.10 D-1
|
|
6
|
+
* family: env identity (R8 — §6.10 D-1 端名先例:值 = 进程端名(§6.20 端名缝 sessionEnd()),不做 cmdline 判别),
|
|
7
7
|
* engineering mode (R9), active model (R11), session slot (F1), restart awareness
|
|
8
8
|
* (R5 resumed). §6.11(2026-09-08):resumed 按会话跟踪——恢复事件由 session.mjs
|
|
9
9
|
* applySession 武装(agent._envResumed——载入历史非空 = 恢复);进程重启句独立走
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { statSync } from "node:fs"
|
|
31
31
|
import { resolve } from "node:path"
|
|
32
|
-
import {
|
|
32
|
+
import { sessionEnd } from "../session-slots.mjs"
|
|
33
33
|
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 —
|
|
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 }) {
|
|
41
|
-
return `[System reminder: env: ${
|
|
41
|
+
return `[System reminder: env: ${sessionEnd()}, mode: ${mode}, model: ${model}, slot: ${slot}, resumed: ${resumed ? "yes" : "no"}.]`
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -63,16 +63,16 @@ export function pushEnvStateReminder(agent) {
|
|
|
63
63
|
/** 情境行前缀——本行族识别符(摘旧行 / 会话重建认领同用;行形逐字见 manifestStateLine)。 */
|
|
64
64
|
const MANIFEST_LINE_PREFIX = "[System reminder: project state: "
|
|
65
65
|
|
|
66
|
-
/** phase →
|
|
67
|
-
const
|
|
66
|
+
/** phase → rigor 标签映射(判据单源 = 需求 v2);未知值不在表内 → 无标签。 */
|
|
67
|
+
const MANIFEST_RIGOR = Object.freeze({ "initial-dev": "light", production: "strict" })
|
|
68
68
|
|
|
69
69
|
/** 情境行构造 — pure, unit-testable(模块契约 `docs/core/design/MANIFEST.md` §2.6,与
|
|
70
70
|
* envStateLine 同族)。行形逐字(**单字段**——2026-09-17 裁撤批后):
|
|
71
|
-
* `[System reminder: project state: phase: <值> (
|
|
71
|
+
* `[System reminder: project state: phase: <值> (rigor: <light|strict>).]`
|
|
72
72
|
* 未知 phase → 只出值、不编判据(无标签)。 */
|
|
73
73
|
export function manifestStateLine({ phase }) {
|
|
74
|
-
const
|
|
75
|
-
return `${MANIFEST_LINE_PREFIX}phase: ${phase}${
|
|
74
|
+
const rigor = MANIFEST_RIGOR[phase]
|
|
75
|
+
return `${MANIFEST_LINE_PREFIX}phase: ${phase}${rigor ? ` (rigor: ${rigor})` : ""}.]`
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|