@thincoder/core 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abort-provenance.mjs +116 -0
- package/advisor/citations.mjs +139 -0
- package/advisor/compaction.mjs +174 -0
- package/advisor/convergence.mjs +80 -0
- package/advisor/history.mjs +77 -0
- package/advisor/loop.mjs +288 -0
- package/advisor/messages.mjs +299 -0
- package/advisor/notice.mjs +141 -0
- package/advisor/project-context.mjs +197 -0
- package/advisor/repos.mjs +150 -0
- package/advisor/run.mjs +190 -0
- package/advisor/truncate.mjs +57 -0
- package/advisor.mjs +281 -0
- package/agent/child-marks.mjs +24 -0
- package/agent/completion.mjs +145 -0
- package/agent/dispatch.mjs +493 -0
- package/agent/family-tools.mjs +174 -0
- package/agent/helpers.mjs +412 -0
- package/agent/post-turn.mjs +70 -0
- package/agent/record-results.mjs +174 -0
- package/agent/relay-prefix.mjs +39 -0
- package/agent/run-stages.mjs +244 -0
- package/agent/setup-reminders.mjs +199 -0
- package/agent/setup.mjs +234 -0
- package/agent/spawn-child.mjs +258 -0
- package/agent/suspension.mjs +240 -0
- package/agent/write-gate.mjs +87 -0
- package/agent-tools/advisor-async.mjs +481 -0
- package/agent-tools/advisor-settle.mjs +240 -0
- package/agent-tools/advisor.mjs +280 -0
- package/agent-tools/async-discard.mjs +143 -0
- package/agent-tools/async-settle.mjs +299 -0
- package/agent-tools/batch-segment.mjs +265 -0
- package/agent-tools/child-permission.mjs +45 -0
- package/agent-tools/consult.mjs +471 -0
- package/agent-tools/design-token.mjs +117 -0
- package/agent-tools/digest-budget.mjs +76 -0
- package/agent-tools/eng.mjs +102 -0
- package/agent-tools/escalate-async.mjs +302 -0
- package/agent-tools/goal.mjs +119 -0
- package/agent-tools/panel-blocks.mjs +24 -0
- package/agent-tools/parent-channel.mjs +231 -0
- package/agent-tools/plan.mjs +86 -0
- package/agent-tools/read-history.mjs +309 -0
- package/agent-tools/recent-changes.mjs +24 -0
- package/agent-tools/review-facts.mjs +31 -0
- package/agent-tools/settings.mjs +268 -0
- package/agent-tools/skill.mjs +63 -0
- package/agent-tools/spawn-gates.mjs +109 -0
- package/agent-tools/subagent-actions.mjs +496 -0
- package/agent-tools/subagent-async.mjs +456 -0
- package/agent-tools/subagent-panel.mjs +160 -0
- package/agent-tools/subagent-run.mjs +208 -0
- package/agent-tools/subagent-scheduler.mjs +446 -0
- package/agent-tools/subagent-spawn.mjs +478 -0
- package/agent-tools/subagent.mjs +419 -0
- package/agent-tools/task.mjs +87 -0
- package/agent-tools/timer.mjs +46 -0
- package/agent-tools/verify.mjs +295 -0
- package/agent-tools.mjs +23 -0
- package/agent.mjs +430 -0
- package/auto-think.mjs +115 -0
- package/compress-form.mjs +24 -0
- package/config-io.mjs +277 -0
- package/config-migrate.mjs +178 -0
- package/config-presets.mjs +49 -0
- package/config.mjs +419 -0
- package/context.mjs +495 -0
- package/conventions.mjs +223 -0
- package/embedding.mjs +120 -0
- package/escape.mjs +152 -0
- package/expand-home.mjs +16 -0
- package/explore-distill.mjs +152 -0
- package/generate-title.mjs +123 -0
- package/git/checkpoint.mjs +448 -0
- package/git/gitmem.mjs +100 -0
- package/history-window.mjs +179 -0
- package/hooks.mjs +108 -0
- package/i18n.mjs +106 -0
- package/index-bin.mjs +48 -0
- package/index-discover.mjs +176 -0
- package/ledger-cmd.mjs +209 -0
- package/ledger-db.mjs +86 -0
- package/ledger-surface.mjs +76 -0
- package/ledger.mjs +202 -0
- package/log.mjs +195 -0
- package/manifest.mjs +338 -0
- package/markdown.mjs +106 -0
- package/mcp/helpers.mjs +51 -0
- package/mcp/transport-http.mjs +248 -0
- package/mcp/transport-stdio.mjs +140 -0
- package/mcp/transport-ws.mjs +122 -0
- package/mcp.mjs +295 -0
- package/memory/code-index.mjs +219 -0
- package/memory/code-sync.mjs +427 -0
- package/memory/core.mjs +318 -0
- package/memory/delete.mjs +242 -0
- package/memory/docs.mjs +431 -0
- package/memory/file-walk.mjs +109 -0
- package/memory/origin.mjs +24 -0
- package/memory/scan.mjs +177 -0
- package/memory/schema.mjs +460 -0
- package/memory.mjs +21 -0
- package/model-ref.mjs +66 -0
- package/model-specs.mjs +277 -0
- package/package.json +32 -0
- package/peer-domains.mjs +265 -0
- package/peer-instances.mjs +178 -0
- package/permission.mjs +79 -0
- package/process-probe.mjs +315 -0
- package/prompt-files.mjs +113 -0
- package/prompt-overlays.mjs +78 -0
- package/prompts/advisor-design.md +43 -0
- package/prompts/advisor-round1.md +41 -0
- package/prompts/advisor-round2.md +46 -0
- package/prompts/advisor-round3.md +42 -0
- package/prompts/common.md +158 -0
- package/prompts/consult-base.md +19 -0
- package/prompts/discipline-engineering.md +123 -0
- package/prompts/discipline-normal.md +206 -0
- package/prompts/persona-coder.md +21 -0
- package/prompts/persona-eng-coder.md +41 -0
- package/prompts/persona-eng-designer.md +80 -0
- package/prompts/persona-engineering.md +160 -0
- package/prompts/persona-explore.md +15 -0
- package/prompts/persona-normal.md +35 -0
- package/prompts/persona-plan.md +27 -0
- package/provider/anthropic.mjs +225 -0
- package/provider/core.mjs +491 -0
- package/provider/errors.mjs +101 -0
- package/provider/google.mjs +257 -0
- package/provider/index.mjs +7 -0
- package/provider/list-models.mjs +163 -0
- package/provider/normalize.mjs +81 -0
- package/provider/rate.mjs +168 -0
- package/provider/responses.mjs +495 -0
- package/provider/retry.mjs +88 -0
- package/provider/sse.mjs +264 -0
- package/provider/wait-status.mjs +59 -0
- package/proxy.mjs +274 -0
- package/rules.mjs +53 -0
- package/session-gc.mjs +248 -0
- package/session-guard.mjs +59 -0
- package/session-lifecycle.mjs +305 -0
- package/session-migrate.mjs +48 -0
- package/session-rename.mjs +38 -0
- package/session-segments.mjs +99 -0
- package/session-slot-write.mjs +168 -0
- package/session-slots-manifest.mjs +264 -0
- package/session-slots.mjs +298 -0
- package/session-store.mjs +441 -0
- package/session.mjs +244 -0
- package/skills.mjs +234 -0
- package/text-budget.mjs +79 -0
- package/token-ttl.mjs +285 -0
- package/tool-docs/apply_patch.md +15 -0
- package/tool-docs/bash.md +38 -0
- package/tool-docs/delete.md +13 -0
- package/tool-docs/edit.md +30 -0
- package/tool-docs/execute.md +21 -0
- package/tool-docs/fetch.md +12 -0
- package/tool-docs/file_ops.md +17 -0
- package/tool-docs/get_current_time.md +8 -0
- package/tool-docs/git.md +54 -0
- package/tool-docs/glob.md +11 -0
- package/tool-docs/grep.md +19 -0
- package/tool-docs/hashline_edit.md +14 -0
- package/tool-docs/insert_after.md +15 -0
- package/tool-docs/lint.md +10 -0
- package/tool-docs/ls.md +12 -0
- package/tool-docs/lsp.md +10 -0
- package/tool-docs/process.md +10 -0
- package/tool-docs/question.md +16 -0
- package/tool-docs/read.md +20 -0
- package/tool-docs/read_image.md +8 -0
- package/tool-docs/tree.md +14 -0
- package/tool-docs/wait_for.md +22 -0
- package/tool-docs/websearch.md +16 -0
- package/tool-docs/write.md +11 -0
- package/tools/bash.mjs +276 -0
- package/tools/edit-batch.mjs +204 -0
- package/tools/edit-diff.mjs +388 -0
- package/tools/exec-run.mjs +43 -0
- package/tools/execute.mjs +243 -0
- package/tools/file.mjs +464 -0
- package/tools/git-checkpoint.mjs +143 -0
- package/tools/git-ext.mjs +173 -0
- package/tools/git.mjs +415 -0
- package/tools/glob-dialect.mjs +130 -0
- package/tools/index.mjs +76 -0
- package/tools/linter.mjs +120 -0
- package/tools/lsp.mjs +335 -0
- package/tools/ops.mjs +293 -0
- package/tools/patch.mjs +290 -0
- package/tools/question.mjs +26 -0
- package/tools/repomap.mjs +314 -0
- package/tools/search.mjs +248 -0
- package/tools/shared.mjs +467 -0
- package/tools/tree.mjs +81 -0
- package/tools/web.mjs +224 -0
- package/tools/write-path.mjs +191 -0
- package/traces/trace-store.mjs +303 -0
- package/undo-stack.mjs +47 -0
package/agent.mjs
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent.mjs — Agent main loop
|
|
3
|
+
* LLM ↔ tool-call loop, until the task is done.
|
|
4
|
+
*/
|
|
5
|
+
import { chat } from "./provider/index.mjs"
|
|
6
|
+
import { abortError, annotateAbort } from "./abort-provenance.mjs"
|
|
7
|
+
import { pushReal, summarizeRunExplorations } from "./context.mjs"
|
|
8
|
+
import { specForModel, assistantToolCallMessage } from "./config.mjs"
|
|
9
|
+
import { resolve } from "node:path"
|
|
10
|
+
import { executeToolCalls } from "./agent/dispatch.mjs"
|
|
11
|
+
import { recordToolResults } from "./agent/record-results.mjs"
|
|
12
|
+
import { FILE_MUTATORS } from "./agent/helpers.mjs"
|
|
13
|
+
import { prepareRun } from "./agent/setup.mjs"
|
|
14
|
+
import { injectPostTurn } from "./agent/post-turn.mjs"
|
|
15
|
+
import { handleCompletion } from "./agent/completion.mjs"
|
|
16
|
+
// PROMPT-SYSTEM 施工② G1(2026-09-10):六件槽位常量装载收口 prompt-overlays.mjs
|
|
17
|
+
// (与子代理角色常量同源——单一权威锚 D1);旧三件文件读取随本批退役。
|
|
18
|
+
// 槽位常量通过下方 re-export 面(mod: rel — re-export 保 import 面);本文件自身零直接消费。
|
|
19
|
+
// 主循环阶段函数(压缩检查/注入组/回合收尾)2026-09-05 实践轮迁 agent/run-stages.mjs
|
|
20
|
+
import { runCompactionCheck, injectTurnReminders, finalizeAgentTurn, injectResponseReminders } from "./agent/run-stages.mjs"
|
|
21
|
+
import {
|
|
22
|
+
escapeXml, repairHistory, listWorkDir,
|
|
23
|
+
readonlyToolNames, collectGitContext, loadProjectInstructions,
|
|
24
|
+
ContinueError,
|
|
25
|
+
turnFrame, // 第 19 批(TURN-ACROSS-SEGMENTS):跨段累计编号帧(设计 §19.3)
|
|
26
|
+
DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
|
|
27
|
+
MIN_REPORT_CHARS, REPORT_CONTINUATION,
|
|
28
|
+
AUTO_TURN_DIGEST_DOMAIN,
|
|
29
|
+
UPSTREAM_TURN_DOMAIN, // §6.27.12.8:上行唤醒轮域文本(手动档——ask 轮不沿用 digest 域文本)
|
|
30
|
+
restoreGuard, // §17 D-S6 读侧单点(P2 机制层端差批 §2.18——键清单归核)
|
|
31
|
+
} from "./agent/helpers.mjs"
|
|
32
|
+
// ENG 提醒族 + auto-turn domain 2026-09-05 迁 agent/helpers.mjs(agent.mjs 530 > 500 硬限)
|
|
33
|
+
// PROMPT-SYSTEM 施工② G1(2026-09-10):六件槽位常量装载收口 prompt-overlays.mjs
|
|
34
|
+
// (与子代理角色常量同源——单一权威锚 D1);本文件 re-export 保 import 面。
|
|
35
|
+
export {
|
|
36
|
+
PERSONA_ENGINEERING, PERSONA_NORMAL, COMMON,
|
|
37
|
+
DISCIPLINE_ENGINEERING, DISCIPLINE_NORMAL,
|
|
38
|
+
CONSULT_BASE,
|
|
39
|
+
} from "./prompt-overlays.mjs"
|
|
40
|
+
export { ENG_ON_REMINDER, ENG_OFF_REMINDER } from "./agent/helpers.mjs"
|
|
41
|
+
|
|
42
|
+
// Role → persona slot mapping note(施工② G3): 子代理人格由 assemblePrompt 场景表按
|
|
43
|
+
// role 承载(persona-explore/coder/plan/eng-coder.md——D1 表 = 蓝图 §3.2 1:1);
|
|
44
|
+
// spawn 侧 overlay 概念已退役(prompt-overlays.mjs 不再导出 OVERLAY 别名常量)。
|
|
45
|
+
|
|
46
|
+
// exported for consumption by agent-tools.mjs
|
|
47
|
+
export {
|
|
48
|
+
ContinueError,
|
|
49
|
+
listWorkDir, loadProjectInstructions,
|
|
50
|
+
readonlyToolNames, collectGitContext, escapeXml,
|
|
51
|
+
MIN_REPORT_CHARS, REPORT_CONTINUATION, DEFAULT_SUBAGENT_TURNS,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
// Re-exported for API compatibility (single source of truth: advisor/repos.mjs)
|
|
56
|
+
export { hasCodeMutations } from "./advisor/repos.mjs"
|
|
57
|
+
|
|
58
|
+
/** Create a new agent state object with all fields initialized to defaults */
|
|
59
|
+
export function createAgent({
|
|
60
|
+
provider, tools, config, cwd, memory, overlay, role,
|
|
61
|
+
tasks = [], history = [],
|
|
62
|
+
planMode = false, autoApprove = false,
|
|
63
|
+
goal = null, sessionStart = null,
|
|
64
|
+
}) {
|
|
65
|
+
return {
|
|
66
|
+
provider, tools, config, cwd, memory, _role: role,
|
|
67
|
+
overlay, tasks, history,
|
|
68
|
+
planMode, autoApprove, goal,
|
|
69
|
+
_mutatedThisRun: false, _verifiedThisRun: false, _verifyPassed: undefined, _calledAdvisorThisRun: false,
|
|
70
|
+
_engDesignReviewed: false, // eng-coder: design review gate passed (hard gate in dispatch.mjs)
|
|
71
|
+
// DESIGN-TOKEN-SETTLEMENT D3 (2026-09-08): single-value `_engDesignToken` mirror retired
|
|
72
|
+
// (AC3 零写) — no field initializer; the multi-slot Map `_engDesignTokens` is the
|
|
73
|
+
// authoritative ledger (hydrated by restoreEngTokens / written by settle).
|
|
74
|
+
_touchedFiles: [], _verifyRetries: 0, _advisorRound: 0, _advisorSession: null,
|
|
75
|
+
_advisorRuns: new Map(), // §11.2 D-24b: per-review convergence instances (rounds/prior/designId)
|
|
76
|
+
_mutationSeq: 0, _mutLog: [], // §11.2 D-24b: mutation log (in-flight review staleness scan)
|
|
77
|
+
_lastAdvisorOutput: null, // full review output from the most recent advisor call (convergence rounds inject it verbatim)
|
|
78
|
+
_lastEngState: false,
|
|
79
|
+
_pendingReminders: [],
|
|
80
|
+
_pendingTimers: [],
|
|
81
|
+
_sessionStart: sessionStart,
|
|
82
|
+
_lastPromptTokens: null, _usageAtLen: null,
|
|
83
|
+
_compressFailures: 0,
|
|
84
|
+
_emptyRetries: 0, // empty-response retry budget (per-run; reset on a fresh user turn)
|
|
85
|
+
_runStartHistoryLen: 0, // machine-line length at the start of the current run — end-of-run exploration distillation slices from here
|
|
86
|
+
_pendingDistill: null, // in-flight end-of-run exploration distillation (SEND-STALL-DISTILL §2.1) — awaited at next run start / TUI exit flush
|
|
87
|
+
_currentTurn: 0, _maxTurns: DEFAULT_MAX_TURNS, // turn counter for status bar display
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 流式输出门(§2.5 #78 并入——VSC 三态门,纯函数可直驱测试):depth 0 恒通;
|
|
92
|
+
* consult 子代理豁免(其输出进会诊面板);其余经 `streamOutput` 显式选择进入。 */
|
|
93
|
+
export function streamOutputAllowed(depth, role, streamOutput = false) {
|
|
94
|
+
return depth === 0 || role === "consult" || streamOutput === true
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Run the agent loop: LLM ↔ tool-call cycle until task completion or turn limit. Returns final text content. */
|
|
98
|
+
export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false, autoTurn = false, upstreamTurn = false, suspDriven = false, consumeInjected = null, streamOutput = false, extraTools = null } = {}) {
|
|
99
|
+
// Previous run's async exploration distillation must settle before this run pushes
|
|
100
|
+
// input (SEND-STALL-DISTILL §2.2 N1) — await first, or its history replace wipes it.
|
|
101
|
+
if (agent._pendingDistill) {
|
|
102
|
+
const p = agent._pendingDistill
|
|
103
|
+
agent._pendingDistill = null
|
|
104
|
+
await p
|
|
105
|
+
}
|
|
106
|
+
// §17 D-S3: suspension-settled async results inject before EVERY run's prepareRun
|
|
107
|
+
// (user + auto-turn); spliced = consumed. collectSettledAsync owns a different
|
|
108
|
+
// container, so no double-inject across the two consumption points.
|
|
109
|
+
// ASYNC-RESULT-CONTAINER.md D2 (2026-09-08):pending 单容器 `_pendingAsyncResults`
|
|
110
|
+
// +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
|
|
111
|
+
// (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 §25 D-R17a/b)
|
|
112
|
+
// ——单容器一处清,不再逐族三段。
|
|
113
|
+
const pendingAsync = agent._pendingAsyncResults
|
|
114
|
+
if (pendingAsync?.length) {
|
|
115
|
+
const { injectAsyncResult } = await import("./agent-tools/subagent.mjs")
|
|
116
|
+
const { injectConsultResult } = await import("./agent-tools/consult.mjs")
|
|
117
|
+
// TUI-OOM-ROOTCAUSE(AGENT-LOOP.md §23.3.1 消费点②——run 起始 pending 注入):
|
|
118
|
+
// 注入完成后释放条目对子代理对象的持有(childAgent/report 置空——幂等 helper)。
|
|
119
|
+
const { releaseSettledEntry } = await import("./agent-tools/async-settle.mjs")
|
|
120
|
+
for (const e of pendingAsync.splice(0)) {
|
|
121
|
+
if (e.role === "consult") await injectConsultResult(agent, e)
|
|
122
|
+
else await injectAsyncResult(agent, e)
|
|
123
|
+
releaseSettledEntry(e)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (§17 D-S6/N3;上行唤醒轮同持 autoTurn——§6.27.12.4 ①)
|
|
127
|
+
const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
|
|
128
|
+
// G1/G2(施工②):prompt 装配收口 prepareRun 内部(assemblePrompt——prompt-overlays.mjs
|
|
129
|
+
// 槽位常量,与子代理角色常量同源——单一权威锚 D1);本调用不再携带 prompt 常量。
|
|
130
|
+
agent, input, callbacks,
|
|
131
|
+
{ depth, signal, overrideTurns, resume: resume || autoTurn, extraTools },
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
// Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
|
|
135
|
+
// pushed input + injections — appended from here counts as "this run's" work.
|
|
136
|
+
agent._runStartHistoryLen = agent.history.length
|
|
137
|
+
|
|
138
|
+
// Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
|
|
139
|
+
// mutation/guard continuity and the convergence budget must survive a continuation.
|
|
140
|
+
if (!resume) {
|
|
141
|
+
// 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):链内累计编号
|
|
142
|
+
// 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
|
|
143
|
+
// turnFrame)。与下方 mutation/guard 复位同条件同点(全档唯一复位点)。
|
|
144
|
+
agent._turnSeq = 0
|
|
145
|
+
// §17 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
|
|
146
|
+
// reset) so auto-turn changes never escape the guard silently.
|
|
147
|
+
const g = agent._inheritedGuard
|
|
148
|
+
if (g) {
|
|
149
|
+
restoreGuard(agent, g)
|
|
150
|
+
agent._inheritedGuard = null
|
|
151
|
+
} else {
|
|
152
|
+
agent._mutatedThisRun = false
|
|
153
|
+
agent._verifiedThisRun = false
|
|
154
|
+
agent._verifyPassed = undefined
|
|
155
|
+
agent._calledAdvisorThisRun = false
|
|
156
|
+
agent._touchedFiles = []
|
|
157
|
+
agent._verifyRetries = 0
|
|
158
|
+
agent._advisorRound = 0
|
|
159
|
+
agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
|
|
160
|
+
agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
|
|
161
|
+
agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// §17 D-S6 manual tier: digest action-domain reminder (system-driven turn — organize only).
|
|
165
|
+
// §6.27.12.4 ②: an up-stream wake turn answers a RUNNING subagent waiting for the reply — it
|
|
166
|
+
// must not reuse the digest text ("no one is waiting" is the opposite of the truth).
|
|
167
|
+
if ((autoTurn || upstreamTurn) && !agent.autoApprove) {
|
|
168
|
+
agent.history.push({ role: "user", content: upstreamTurn ? UPSTREAM_TURN_DOMAIN : AUTO_TURN_DIGEST_DOMAIN, transient: true })
|
|
169
|
+
}
|
|
170
|
+
// eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
|
|
171
|
+
// (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
|
|
172
|
+
// depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
|
|
173
|
+
// depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
|
|
174
|
+
// Design slots (_engDesignTokens Map) survive across turns (design review → approval →
|
|
175
|
+
// eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
|
|
176
|
+
// SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
|
|
177
|
+
let guardPushbacks = 0
|
|
178
|
+
let advisorPushbacks = 0
|
|
179
|
+
let honestReminderInjected = false
|
|
180
|
+
const recentCallSigs = []
|
|
181
|
+
// "once" stream rules fire at most once per runAgent call; the set survives across
|
|
182
|
+
// chat() calls (rule abort-retry, tool loop) within the turn.
|
|
183
|
+
const streamRuleFired = new Set()
|
|
184
|
+
|
|
185
|
+
// Compaction overhead for the pure-estimation path: system prompt + tools schema are
|
|
186
|
+
// in every request but not in history — without them the first-turn/just-compacted
|
|
187
|
+
// estimate under-counts and may never trigger. Measured path already includes both.
|
|
188
|
+
const compactionOverhead = {
|
|
189
|
+
systemPrompt,
|
|
190
|
+
tools: toolSchemas,
|
|
191
|
+
// §18.6 D-TR4:compress 轨迹 depth 元数据(runAgent 的 depth 在此作用域——
|
|
192
|
+
// context.mjs compressIfNeeded 经 extras 透出到 logCtx)
|
|
193
|
+
traceDepth: depth,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// SUBAGENT-UPSTREAM-CHANNEL(AGENT-LOOP-SUBAGENT.md §6.27.4 消费点):子 → 父在飞消息的
|
|
197
|
+
// 回合边界注入单点取用一次(模块缓存 ⇒ 每 run 一次代价);动态 import = 零新增静态边
|
|
198
|
+
// (先例 = 上方 injectAsyncResult :113-117)。
|
|
199
|
+
const { drainChildUpstream } = await import("./agent-tools/parent-channel.mjs")
|
|
200
|
+
|
|
201
|
+
let thrownError = null
|
|
202
|
+
try {
|
|
203
|
+
for (let turn = 0; turn < maxTurns; turn++) {
|
|
204
|
+
// 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):编号帧——
|
|
205
|
+
// `_turnSeq` 每轮 +1(跨段累计,仅 `!resume` 链起点复位);面向消费面的两字段
|
|
206
|
+
// (状态行 + ⟦ev⟧turn / ⟦ev⟧approval 事件共用)在此同点赋值(编号唯一权威;帧
|
|
207
|
+
// 公式 = helpers.mjs turnFrame)。段内帽判定不读帧(下行循环条件只读段内
|
|
208
|
+
// turn / maxTurns——N6)。
|
|
209
|
+
const frame = turnFrame(++agent._turnSeq, turn, maxTurns)
|
|
210
|
+
agent._currentTurn = frame.turn
|
|
211
|
+
agent._maxTurns = frame.maxTurns
|
|
212
|
+
// D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
|
|
213
|
+
// single emit point covering all three spawn tools; phase=llm (tool/done progress rides
|
|
214
|
+
// the onToolCall/onToolResult relay — no token for those). 第 19 批:载荷取上方帧值
|
|
215
|
+
// (agent._currentTurn / _maxTurns——字段形态 / 字段数 / phase 零变化,N5)。
|
|
216
|
+
if (depth > 0 && callbacks.onToken) {
|
|
217
|
+
callbacks.onToken(`⟦ev⟧turn\x1e${agent._currentTurn}\x1e${agent._maxTurns}\x1ellm\x1e`)
|
|
218
|
+
}
|
|
219
|
+
// §2.5 #78 并入(VSC 帧回调):每轮帧另经结构化回调发出(池条目 entry.turn 的
|
|
220
|
+
// VSC 等价通道——⟦ev⟧turn token 解析的宿主面随时可用;签名扩展向后兼容)。
|
|
221
|
+
callbacks.onAgentTurn?.(frame.turn, frame.maxTurns)
|
|
222
|
+
|
|
223
|
+
// SUBAGENT-OBSERVE-SEND D2: 子代理回合边界消费点——每轮开头把父侧经 subagent
|
|
224
|
+
// action:'send' 注入队列(entry._injected)的消息按普通 user 回合推入子历史
|
|
225
|
+
// (pushReal → 下一轮 chat 即含该指令)。由 executeAsyncSpawn 经 childRunOpts 贯通的
|
|
226
|
+
// consumeInjected 回调承载(异步子代理专属——缺省 null:主会话/阻塞子代理零开销)。
|
|
227
|
+
consumeInjected?.(agent)
|
|
228
|
+
// 子代理在飞消息(子 → 父;§6.27):空队列 no-op;非空 ⇒ 恰一条合并 user 消息注入。
|
|
229
|
+
drainChildUpstream(agent)
|
|
230
|
+
|
|
231
|
+
const lastRole = agent.history.at(-1)?.role
|
|
232
|
+
if (lastRole === "user" || lastRole === "tool") {
|
|
233
|
+
// 2026-09-05 实践轮:压缩检查/降级计数提为 runCompactionCheck(agent/run-stages.mjs——
|
|
234
|
+
// CLI 对位 VS run-stages)——循环骨架此处只剩检查调用(recentCallSigs 对象引用回流)。
|
|
235
|
+
await runCompactionCheck(agent, { threshold, callbacks, compactionOverhead, signal, recentCallSigs })
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 2026-09-05 实践轮:plan cadence + eng 状态注入提为 injectTurnReminders(run-stages)
|
|
239
|
+
await injectTurnReminders(agent, { depth })
|
|
240
|
+
|
|
241
|
+
const messages = [{ role: "system", content: systemPrompt }, ...agent.history]
|
|
242
|
+
let response
|
|
243
|
+
|
|
244
|
+
// Auto-think: classify difficulty and set reasoning effort on turn 0; silent on failure.
|
|
245
|
+
if (agent.config?.agent?.autoThink && turn === 0) {
|
|
246
|
+
const { classifyAndApply } = await import("./auto-think.mjs")
|
|
247
|
+
await classifyAndApply(agent, turn).catch(() => {})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (process.env.ADVISOR_DEBUG) console.error("[chat-call]", JSON.stringify({ turn, histLen: agent.history.length, lastRole: agent.history.at(-1)?.role }))
|
|
251
|
+
try { response = await chat(agent.provider, {
|
|
252
|
+
messages, tools: toolSchemas,
|
|
253
|
+
// §2.5 #78 并入(VSC onToken 三态门):depth 0 恒通;consult 子代理豁免(其输出进
|
|
254
|
+
// 会诊面板);escalate 等经 opts.streamOutput 显式选择进入;其余子代理不流式。
|
|
255
|
+
onToken: streamOutputAllowed(depth, agent._role, streamOutput) ? callbacks.onToken : null,
|
|
256
|
+
onReasoning: callbacks.onReasoning,
|
|
257
|
+
onWait: callbacks.onWait,
|
|
258
|
+
signal,
|
|
259
|
+
streamRules: agent.config.agent?.streamRules ?? [],
|
|
260
|
+
firedPatterns: streamRuleFired,
|
|
261
|
+
// LOGGING(LOGGING.md):llm:* 事件的语义上下文(stage=turn 主循环回合——含
|
|
262
|
+
// digest 消化轮 auto=true;child=子代理 id(spawn 时 stamp 于 child._logId))
|
|
263
|
+
// §18.6 D-TR4:轨迹元数据增补(role/depth/kind/session/cwd——trace-store 只读
|
|
264
|
+
// logCtx,签名不变);kind:depth>0 = subagent(consult 孩子 = consult)——子代理
|
|
265
|
+
// 对回靠 role+depth+child id(children 无 _sessionStart——不经 depth-0 设置——
|
|
266
|
+
// session 字段对子代理轨迹为 null——见 trace-store/agent.mjs 注释)。
|
|
267
|
+
logCtx: {
|
|
268
|
+
stage: "turn", turn: turn + 1, auto: autoTurn, child: agent._logId,
|
|
269
|
+
role: agent._role ?? null,
|
|
270
|
+
depth,
|
|
271
|
+
kind: depth > 0 ? (agent._role === "consult" ? "consult" : "subagent") : "turn",
|
|
272
|
+
session: agent._sessionStart ?? null,
|
|
273
|
+
cwd: agent.cwd,
|
|
274
|
+
traces: agent.config?.traces?.enabled !== false,
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
} catch (e) {
|
|
278
|
+
// User interrupt (Ctrl+I): controller.abort({ interrupt: true, message }).
|
|
279
|
+
// Inject into history; the outer loop recreates the controller and resumes.
|
|
280
|
+
if (e.name === "AbortError" && signal?.reason?.interrupt) {
|
|
281
|
+
const msg = `[User interrupt: ${signal.reason.message}]`
|
|
282
|
+
// Dedup: if already handled during tool execution (interrupt branch below),
|
|
283
|
+
// don't push a duplicate — the outer loop still recreates the controller.
|
|
284
|
+
if (agent.history.at(-1)?.content !== msg) {
|
|
285
|
+
agent.history.push({ role: "user", content: msg })
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
throw e
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 内置工具(Responses web_search)结果本地化:服务端已执行——入历史为 tool 消息;
|
|
292
|
+
// 服务端 item id 是 msg_xxx 非 web_search_call_ 前缀——必须合成前缀(toItems 识别锚点),
|
|
293
|
+
// 原始 id 存入 content(真机冒烟 2026-08-31 验证)。
|
|
294
|
+
for (const btr of response.builtinToolResults ?? []) {
|
|
295
|
+
if (!btr?.id) continue
|
|
296
|
+
pushReal(agent, {
|
|
297
|
+
role: "tool",
|
|
298
|
+
tool_call_id: `web_search_call_${btr.id}`,
|
|
299
|
+
content: JSON.stringify({ id: btr.id, query: btr.query ?? "", sources: btr.sources ?? [], status: btr.status ?? "completed" }),
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Stream rule triggered mid-generation (action: "abort"): halt, inject the rule's
|
|
304
|
+
// message as a reminder, retry from the same context.
|
|
305
|
+
if (response.ruleTriggered) {
|
|
306
|
+
if (response.content) {
|
|
307
|
+
pushReal(agent, { role: "assistant", content: response.content })
|
|
308
|
+
}
|
|
309
|
+
const label = response.ruleName ? ` — stream rule "${response.ruleName}"` : ""
|
|
310
|
+
agent.history.push({
|
|
311
|
+
role: "user",
|
|
312
|
+
content: `[System reminder${label}: ${response.ruleMessage}]`,
|
|
313
|
+
})
|
|
314
|
+
continue
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Stream rule warnings / finish-reason 警告(2026-09-05 实践轮——提为
|
|
318
|
+
// injectResponseReminders,agent/run-stages.mjs——verbatim,语义零变)
|
|
319
|
+
injectResponseReminders(agent, response)
|
|
320
|
+
|
|
321
|
+
// User interrupted mid-generation (Ctrl+I): commit partial output + inject the
|
|
322
|
+
// message, then signal the outer loop to recreate the controller and resume.
|
|
323
|
+
if (response.interrupted) {
|
|
324
|
+
if (response.content) {
|
|
325
|
+
pushReal(agent, { role: "assistant", content: response.content })
|
|
326
|
+
}
|
|
327
|
+
agent.history.push({
|
|
328
|
+
role: "user",
|
|
329
|
+
content: `[User interrupt: ${response.interruptMessage}]`,
|
|
330
|
+
})
|
|
331
|
+
// §20.3 站点 #8(第 24 批):错误对象已自带 name/message——只补来源标注(缺 abortInfo 才补)
|
|
332
|
+
throw annotateAbort(Object.assign(new Error("User interrupted"), { name: "AbortError" }), signal, "agent", "interrupted-response")
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (response.usage) {
|
|
336
|
+
callbacks.onUsage?.(response.usage)
|
|
337
|
+
if (response.usage.prompt_tokens != null) {
|
|
338
|
+
agent._lastPromptTokens = response.usage.prompt_tokens
|
|
339
|
+
agent._usageAtLen = agent.history.length
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Warn on abnormal finish reasons — the response may be incomplete/truncated.
|
|
344
|
+
// 2026-09-05 实践轮:finish-reason 注入随流规则警告提为 injectResponseReminders。
|
|
345
|
+
|
|
346
|
+
if (response.toolCalls.length === 0) {
|
|
347
|
+
const cr = handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks)
|
|
348
|
+
guardPushbacks = cr.guardPushbacks
|
|
349
|
+
honestReminderInjected = cr.honestReminderInjected
|
|
350
|
+
advisorPushbacks = cr.advisorPushbacks
|
|
351
|
+
if (cr.action === "continue") continue
|
|
352
|
+
if (depth === 0) {
|
|
353
|
+
// End-of-run exploration distillation (CONTEXT-COMPACTION §5 + SEND-STALL-DISTILL
|
|
354
|
+
// §2.1): async — the promise hangs on _pendingDistill, settling at the next run's
|
|
355
|
+
// start or the TUI exit flush. Silent (N3): failure never blocks return/history.
|
|
356
|
+
// §18.6 D-TR4:depth 透传(distill 轨迹元数据——与 compress 同通道);extras = 会话续写前缀面(§6.15)
|
|
357
|
+
const distill = summarizeRunExplorations(agent, callbacks, signal, depth,
|
|
358
|
+
{ systemPrompt, tools: toolSchemas }).catch(() => {}) // 与回合请求同源(无第二构造点)
|
|
359
|
+
agent._pendingDistill = distill
|
|
360
|
+
}
|
|
361
|
+
return cr.content
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// abort after chat completes, before committing history: don't commit a half-finished turn
|
|
365
|
+
if (signal?.aborted) throw abortError(signal, "agent", "post-chat")
|
|
366
|
+
|
|
367
|
+
pushReal(agent, assistantToolCallMessage(response, specForModel(agent.provider.model)))
|
|
368
|
+
|
|
369
|
+
const results = await executeToolCalls(agent, toolByName, response.toolCalls, callbacks, depth, signal)
|
|
370
|
+
|
|
371
|
+
// Ctrl+I interrupt during tool execution: skip committing partial results — inject
|
|
372
|
+
// the interrupt and retry (placeholder results keep strict providers pairable).
|
|
373
|
+
if (signal?.reason?.interrupt) {
|
|
374
|
+
// 中断变更记账(2026-08-31 评审 #4):此分支的工具已全部执行完成(磁盘已变,execute 已完成),
|
|
375
|
+
// 真实结果按语义不进历史(placeholder 替代)——但变更必须记账:否则 guard 看到
|
|
376
|
+
// "本轮未改代码" 放行,评审/verify 门禁被绕过(文件改了却没评审)。
|
|
377
|
+
// §29 fix A(2026-09-07):mutation-seq 记账已收敛到 dispatch runOne 执行成功即刻
|
|
378
|
+
// (唯一记账点——本分支不再 noteMutations——不双计——中断+同批 launch seq 单计
|
|
379
|
+
// 回归断言见 §29 T-A1i);此处仅剩 guard 标志 + touchedFiles 记账。
|
|
380
|
+
for (const { toolCall, ok } of results) {
|
|
381
|
+
const tool = toolByName.get(toolCall.name)
|
|
382
|
+
if (!ok || !tool || !FILE_MUTATORS.has(toolCall.name)) continue
|
|
383
|
+
agent._mutatedThisRun = true
|
|
384
|
+
agent._calledAdvisorThisRun = false
|
|
385
|
+
agent._verifiedThisRun = false
|
|
386
|
+
agent._verifyPassed = undefined
|
|
387
|
+
try {
|
|
388
|
+
const args = JSON.parse(toolCall.arguments)
|
|
389
|
+
const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
|
|
390
|
+
for (const p of paths) {
|
|
391
|
+
const abs = resolve(agent.cwd, p)
|
|
392
|
+
if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
|
|
393
|
+
}
|
|
394
|
+
} catch { /* 畸形 args 不影响记账(touchedFiles 尽力而为) */ }
|
|
395
|
+
}
|
|
396
|
+
// The assistant tool_calls were committed above — synthesize placeholder tool
|
|
397
|
+
// results BEFORE the interrupt message (strict providers 400 on dangling
|
|
398
|
+
// tool_calls; consult P1, 2026-08-30).
|
|
399
|
+
for (const tc of response.toolCalls) {
|
|
400
|
+
agent.history.push({ role: "tool", tool_call_id: tc.id, content: "[Tool execution interrupted — results discarded]" })
|
|
401
|
+
}
|
|
402
|
+
agent.history.push({
|
|
403
|
+
role: "user",
|
|
404
|
+
content: `[User interrupt: ${signal.reason.message}]`,
|
|
405
|
+
})
|
|
406
|
+
callbacks.onTurnEnd?.(agent, turn)
|
|
407
|
+
continue
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Model is executing tools → real work: reset guard pushback counters
|
|
411
|
+
guardPushbacks = 0
|
|
412
|
+
advisorPushbacks = 0
|
|
413
|
+
|
|
414
|
+
// Commit tool results (pairing, multimodal deferral, mutation accounting, reindex)
|
|
415
|
+
await recordToolResults(agent, toolByName, results)
|
|
416
|
+
|
|
417
|
+
injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
throw new ContinueError(maxTurns)
|
|
421
|
+
} catch (e) {
|
|
422
|
+
thrownError = e
|
|
423
|
+
throw e
|
|
424
|
+
} finally {
|
|
425
|
+
// 2026-09-05 实践轮:回合收尾(consult 清理/async 池分流/guard 继承——原 425-462
|
|
426
|
+
// 段 + collectSettledAsync 466-494 整体迁 agent/run-stages.mjs finalizeAgentTurn——
|
|
427
|
+
// CLI 对位 VS run-stages——finally 只剩一行调用 + 骨架注释)。
|
|
428
|
+
await finalizeAgentTurn(agent, { signal, autoTurn, suspDriven, thrownError, depth })
|
|
429
|
+
}
|
|
430
|
+
}
|
package/auto-think.mjs
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto-think.mjs — automatic difficulty classification for reasoning effort.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, before each user-facing turn a cheap classification call determines
|
|
5
|
+
* the task difficulty, then maps it to the model's reasoning effort. This replaces
|
|
6
|
+
* manual /think toggling with per-prompt automatic selection.
|
|
7
|
+
*
|
|
8
|
+
* Config:
|
|
9
|
+
* { agent: { autoThink: true } }
|
|
10
|
+
*
|
|
11
|
+
* Mechanism:
|
|
12
|
+
* 1. Take the last user message from history
|
|
13
|
+
* 2. Send a minimal classification prompt (expects one-word reply)
|
|
14
|
+
* 3. Map difficulty → reasoningEffort using the model's valid effort enum
|
|
15
|
+
* 4. Set agent.provider.reasoningEffort before the real chat() call
|
|
16
|
+
*/
|
|
17
|
+
import { chat } from "./provider/core.mjs"
|
|
18
|
+
import { specForModel } from "./config.mjs"
|
|
19
|
+
|
|
20
|
+
const CLASSIFY_PROMPT = `Classify this coding task's difficulty: low, medium, or high.
|
|
21
|
+
|
|
22
|
+
- low — trivial: rename, typo, formatting, one-liner, direct question
|
|
23
|
+
- medium — localized: small feature, straightforward bug fix, moderate change
|
|
24
|
+
- high — complex: multi-file, debugging, design decisions, large refactor
|
|
25
|
+
|
|
26
|
+
Reply with exactly one word.`
|
|
27
|
+
|
|
28
|
+
const EFFORT_MAP = {
|
|
29
|
+
low: ["low", "minimal", "none", "low"],
|
|
30
|
+
medium: ["high", "medium", "high"],
|
|
31
|
+
high: ["max", "max", "xhigh", "max"],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build classifier input from history: the latest real user message (reminders and
|
|
36
|
+
* interrupt injections excluded), plus the previous user message as context when the
|
|
37
|
+
* latest is too short to classify on its own (e.g. "继续" / "还有几个问题").
|
|
38
|
+
* Exported for tests.
|
|
39
|
+
*/
|
|
40
|
+
export function buildClassifierInput(history) {
|
|
41
|
+
const isRealUser = (m) =>
|
|
42
|
+
m.role === "user" && typeof m.content === "string"
|
|
43
|
+
&& !m.content.startsWith("[System reminder:") && !m.content.startsWith("[User interrupt:")
|
|
44
|
+
const users = history.filter(isRealUser)
|
|
45
|
+
const last = users.at(-1)
|
|
46
|
+
if (!last) return null
|
|
47
|
+
let prompt = last.content
|
|
48
|
+
if (prompt.length < 200 && users.length > 1) {
|
|
49
|
+
prompt = `Previous request (context):\n${users.at(-2).content.slice(0, 1200)}\n\nLatest message:\n${prompt}`
|
|
50
|
+
}
|
|
51
|
+
return prompt.slice(0, 2000)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Classify the difficulty of the user's prompt and adjust reasoning effort.
|
|
56
|
+
* Only runs on the first turn (turn === 0) of a user message.
|
|
57
|
+
* Returns the resolved level or null if auto-thinking is disabled or classification fails.
|
|
58
|
+
* @param {object} agent
|
|
59
|
+
* @param {number} turn
|
|
60
|
+
* @returns {Promise<string|null>}
|
|
61
|
+
*/
|
|
62
|
+
export async function classifyAndApply(agent, turn) {
|
|
63
|
+
if (!agent.config?.agent?.autoThink) return null
|
|
64
|
+
if (turn !== 0) return null // Only classify on the first turn of user input
|
|
65
|
+
|
|
66
|
+
const spec = specForModel(agent.provider.model)
|
|
67
|
+
const validEfforts = spec.reasoningEffortEnum
|
|
68
|
+
if (!validEfforts) return null // Model doesn't support reasoning effort
|
|
69
|
+
|
|
70
|
+
const prompt = buildClassifierInput(agent.history)
|
|
71
|
+
if (prompt == null) return null
|
|
72
|
+
|
|
73
|
+
// Classification call: use same provider, minimal tokens, no tools, no streaming
|
|
74
|
+
let level
|
|
75
|
+
try {
|
|
76
|
+
const classifierProvider = { ...agent.provider, maxTokens: 10 }
|
|
77
|
+
const response = await chat(classifierProvider, {
|
|
78
|
+
messages: [
|
|
79
|
+
{ role: "system", content: CLASSIFY_PROMPT },
|
|
80
|
+
{ role: "user", content: prompt },
|
|
81
|
+
],
|
|
82
|
+
tools: [],
|
|
83
|
+
signal: AbortSignal.timeout(5_000),
|
|
84
|
+
// D-TS12 (AGENT-LOOP.md §18.7): full logCtx field set at the chat call
|
|
85
|
+
// point — traces/session/cwd/role/depth/kind (this call point carried
|
|
86
|
+
// only {stage,turn,child}). The traces field closes the D-TR6 "off = no
|
|
87
|
+
// persist" switch: without it the tracer treated the auto-think call as
|
|
88
|
+
// enabled and persisted even when agent.config.traces.enabled was false.
|
|
89
|
+
logCtx: {
|
|
90
|
+
stage: "autothink", turn, child: agent._logId,
|
|
91
|
+
traces: agent.config?.traces?.enabled !== false,
|
|
92
|
+
session: agent._sessionStart ?? null,
|
|
93
|
+
cwd: agent.cwd,
|
|
94
|
+
role: agent._role ?? null,
|
|
95
|
+
depth: agent._depth ?? 0, // agent state carries no depth stamp (the call site passes none) — 0 for the top-level agent
|
|
96
|
+
kind: "autothink",
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
const word = (response.content ?? "").trim().toLowerCase()
|
|
100
|
+
if (word.startsWith("low")) level = "low"
|
|
101
|
+
else if (word.startsWith("medium") || word.startsWith("med")) level = "medium"
|
|
102
|
+
else if (word.startsWith("high")) level = "high"
|
|
103
|
+
else return null // Unparseable
|
|
104
|
+
} catch {
|
|
105
|
+
return null // Classification failure → fall back to current setting
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Map difficulty to the closest valid reasoning effort
|
|
109
|
+
const candidates = EFFORT_MAP[level] || EFFORT_MAP.medium
|
|
110
|
+
const matched = candidates.find(e => validEfforts.includes(e))
|
|
111
|
+
if (!matched) return null
|
|
112
|
+
|
|
113
|
+
agent.provider.reasoningEffort = matched
|
|
114
|
+
return matched
|
|
115
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* compress-form.mjs — compression request form(CONTEXT-COMPACTION.md §6.14 / D-CC20 ·
|
|
3
|
+
* 批次档 2026-09-18-compression-continuation §2.2):会话续写形态。
|
|
4
|
+
*
|
|
5
|
+
* 压缩请求复用同会话回合请求的消息前缀(同一个 system + 中段「真身」消息),尾部附加指令一条。
|
|
6
|
+
* 消费点(两处同形 · 同一构造):① 压缩 = `context.mjs`(中段切点 = `splitHistory.tailStart`);
|
|
7
|
+
* ② 蒸馏 = `explore-distill.mjs`(切点 = 本 run 末块 `end` → §6.15)——两处均按引用原样携带中段,
|
|
8
|
+
* 均不持声明面(`tools` / `systemPrompt` 由调用点经 extras 随带)。
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ 缓存命中口径(v2 探针复测 2026-09-18 · 批次档 §5):压缩形态**首现复用回合所建前缀**的条件 = 带 tools 声明
|
|
11
|
+
* (**不带** `tool_choice`)且 `reasoning_effort` 同值——8 格 84–98%;不带 tools / 带 `tool_choice:"none"`(该参数使
|
|
12
|
+
* 服务端丢弃 tools 区)/ effort 异值 ⇒ 首现 0%(自建项复跑命中为例外)。v2(设计档 §6.14 备选②)由**调用点**
|
|
13
|
+
* (context.mjs)随带与回合请求同一数组的 `tools`;本模块只构消息序,不持声明面。
|
|
14
|
+
*
|
|
15
|
+
* 纯函数、零 import:指令文本由实参传入(SUMMARIZE_PROMPT 住 context.mjs——不反向 import,免成环)。
|
|
16
|
+
* 中段消息按引用原样携带(不拷贝 / 不截断);切点与摘要段同源(split.tailStart),不新增第二处切割判据。
|
|
17
|
+
*/
|
|
18
|
+
export function buildCompressMessages(history, tailStart, systemPrompt, instruction) {
|
|
19
|
+
const messages = []
|
|
20
|
+
if (systemPrompt) messages.push({ role: "system", content: systemPrompt }) // 缺省(undefined)/ 空串 ⇒ 不带头(退化面 1)
|
|
21
|
+
messages.push(...history.slice(0, tailStart))
|
|
22
|
+
messages.push({ role: "user", content: instruction })
|
|
23
|
+
return messages
|
|
24
|
+
}
|