thincoder 0.12.58 → 0.12.60
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 +78 -2
- package/README.md +3 -3
- package/bin/thincoder.mjs +88 -19
- package/package.json +4 -3
- package/src/acp/bridge.mjs +135 -26
- package/src/advisor/messages.mjs +57 -4
- package/src/advisor/run.mjs +119 -79
- package/src/advisor.mjs +34 -7
- package/src/agent/completion.mjs +17 -11
- package/src/agent/dispatch.mjs +182 -22
- package/src/agent/helpers.mjs +71 -4
- package/src/agent/record-results.mjs +46 -10
- package/src/agent/run-stages.mjs +227 -0
- package/src/agent/setup-reminders.mjs +62 -0
- package/src/agent/setup.mjs +107 -20
- package/src/agent/spawn-child.mjs +54 -4
- package/src/agent-tools/advisor-async.mjs +456 -0
- package/src/agent-tools/advisor.mjs +133 -109
- package/src/agent-tools/async-settle.mjs +191 -0
- package/src/agent-tools/consult.mjs +154 -104
- package/src/agent-tools/design-token.mjs +104 -0
- package/src/agent-tools/eng.mjs +26 -30
- package/src/agent-tools/escalate-async.mjs +286 -0
- package/src/agent-tools/goal.mjs +11 -1
- package/src/agent-tools/read-history.mjs +284 -0
- package/src/agent-tools/recent-changes.mjs +2 -1
- package/src/agent-tools/settings.mjs +152 -0
- package/src/agent-tools/skill.mjs +2 -1
- package/src/agent-tools/subagent-actions.mjs +470 -0
- package/src/agent-tools/subagent-async.mjs +382 -0
- package/src/agent-tools/subagent-panel.mjs +153 -0
- package/src/agent-tools/subagent-run.mjs +202 -0
- package/src/agent-tools/subagent-scheduler.mjs +343 -0
- package/src/agent-tools/subagent-spawn.mjs +406 -0
- package/src/agent-tools/subagent.mjs +203 -377
- package/src/agent-tools/task.mjs +4 -3
- package/src/agent-tools/timer.mjs +9 -4
- package/src/agent-tools/verify.mjs +198 -238
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +145 -242
- package/src/auto-think.mjs +14 -0
- package/src/cli/distill-command.mjs +10 -4
- package/src/cli/make-agent.mjs +4 -1
- package/src/cli/memory-command.mjs +2 -1
- package/src/cli/permission.mjs +8 -1
- package/src/cli/setup-wizard.mjs +17 -12
- package/src/config.mjs +61 -8
- package/src/context.mjs +81 -163
- package/src/crash-reports.mjs +123 -0
- package/src/distill.mjs +30 -12
- package/src/escape.mjs +6 -4
- package/src/explore-distill.mjs +155 -0
- package/src/log.mjs +195 -0
- package/src/memory/code-sync.mjs +2 -1
- package/src/memory/core.mjs +11 -72
- package/src/memory/delete.mjs +234 -0
- package/src/memory/docs.mjs +206 -87
- package/src/memory.mjs +3 -1
- package/src/model-specs.mjs +15 -1
- package/src/peer-domains.mjs +265 -0
- package/src/peer-instances.mjs +231 -0
- package/src/prompt-overlays.mjs +25 -0
- package/src/prompts/advisor-design.md +18 -39
- package/src/prompts/advisor-round1.md +20 -32
- package/src/prompts/advisor-round2.md +16 -16
- package/src/prompts/advisor-round3.md +16 -16
- package/src/prompts/coder.md +7 -28
- package/src/prompts/consult-base.md +4 -11
- package/src/prompts/discipline.md +31 -44
- package/src/prompts/eng-coder.md +9 -34
- package/src/prompts/engineering-sub.md +10 -8
- package/src/prompts/engineering.md +61 -264
- package/src/prompts/explore.md +4 -14
- package/src/prompts/main.md +18 -35
- package/src/prompts/methodology-template.md +32 -38
- package/src/prompts/plan.md +2 -9
- package/src/prompts/system.md +18 -35
- package/src/provider/core.mjs +62 -69
- package/src/provider/errors.mjs +76 -0
- package/src/provider/retry.mjs +8 -45
- package/src/session-gc.mjs +214 -0
- package/src/session-guard.mjs +47 -0
- package/src/session-rename.mjs +38 -0
- package/src/session-slots.mjs +181 -58
- package/src/session.mjs +48 -89
- package/src/token-ttl.mjs +273 -0
- package/src/tools/apply_patch.md +3 -1
- package/src/tools/bash.md +1 -1
- package/src/tools/checklist-sync.mjs +181 -0
- package/src/tools/checklist.mjs +52 -39
- package/src/tools/delete.md +1 -0
- package/src/tools/edit-batch.mjs +131 -44
- package/src/tools/edit-diff.mjs +348 -0
- package/src/tools/edit.md +20 -13
- package/src/tools/execute.md +7 -7
- package/src/tools/execute.mjs +55 -24
- package/src/tools/file.mjs +25 -70
- package/src/tools/file_ops.md +2 -1
- package/src/tools/get_current_time.md +3 -1
- package/src/tools/git.mjs +14 -6
- package/src/tools/glob-dialect.mjs +130 -0
- package/src/tools/glob.md +3 -3
- package/src/tools/grep.md +1 -1
- package/src/tools/hashline_edit.md +2 -0
- package/src/tools/index.mjs +3 -3
- package/src/tools/insert_after.md +2 -1
- package/src/tools/lint.md +2 -0
- package/src/tools/lsp.md +4 -1
- package/src/tools/ops.mjs +175 -3
- package/src/tools/patch.mjs +84 -13
- package/src/tools/question.md +5 -1
- package/src/tools/repomap.mjs +1 -1
- package/src/tools/shared.mjs +18 -25
- package/src/tools/system.mjs +50 -30
- package/src/tools/tree.md +2 -1
- package/src/tools/wait_for.md +22 -0
- package/src/tools/web.mjs +5 -3
- package/src/tools/websearch.md +2 -1
- package/src/tools/write.md +2 -0
- package/src/traces/trace-store.mjs +224 -0
- package/src/tui/agent-turn.mjs +179 -27
- package/src/tui/clipboard.mjs +15 -4
- package/src/tui/cmd-config.mjs +77 -16
- package/src/tui/cmd-eng.mjs +20 -16
- package/src/tui/cmd-extract.mjs +1 -1
- package/src/tui/cmd-mcp.mjs +17 -2
- package/src/tui/cmd-new.mjs +3 -2
- package/src/tui/cmd-session.mjs +19 -4
- package/src/tui/cmd-think.mjs +11 -11
- package/src/tui/cmd-upgrade.mjs +19 -4
- package/src/tui/config-helpers.mjs +28 -16
- package/src/tui/distill-cmd.mjs +1 -1
- package/src/tui/index.mjs +31 -96
- package/src/tui/interaction.mjs +13 -2
- package/src/tui/key-handler.mjs +105 -155
- package/src/tui/key-modes.mjs +215 -0
- package/src/tui/layout.mjs +22 -1
- package/src/tui/mouse.mjs +46 -0
- package/src/tui/pickers.mjs +51 -25
- package/src/tui/render-conversation.mjs +13 -161
- package/src/tui/render-frame.mjs +27 -10
- package/src/tui/render-loop.mjs +4 -1
- package/src/tui/render-segments.mjs +182 -0
- package/src/tui/startup.mjs +40 -0
- package/src/tui/subagent-blocks.mjs +272 -262
- package/src/tui/subagent-children.mjs +176 -0
- package/src/tui/subagent-freeze.mjs +172 -0
- package/src/tui/subagent-panel.mjs +125 -12
- package/src/tui/suspension-drive.mjs +351 -0
- package/src/tui/tool-args.mjs +10 -2
- package/src/tui/tool-display.mjs +142 -0
- package/src/tui/tool-events.mjs +127 -231
- package/src/tui/tui-lifecycle.mjs +29 -0
- package/src/tui/update-notice.mjs +76 -0
- package/src/tui/wizard.mjs +48 -12
- package/src/agent-tools/escalate.mjs +0 -179
- package/src/agent-tools/subagent-check.mjs +0 -107
- package/src/tools/exec-prelude.mjs +0 -84
package/src/agent.mjs
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* LLM ↔ tool-call loop, until the task is done.
|
|
4
4
|
*/
|
|
5
5
|
import { chat } from "./provider/index.mjs"
|
|
6
|
-
import {
|
|
6
|
+
import { pushReal, summarizeRunExplorations } from "./context.mjs"
|
|
7
7
|
import { specForModel } from "./config.mjs"
|
|
8
8
|
import { readFileSync } from "node:fs"
|
|
9
|
-
import { join, dirname } from "node:path"
|
|
9
|
+
import { join, dirname, resolve } from "node:path"
|
|
10
10
|
import { fileURLToPath } from "node:url"
|
|
11
11
|
import { executeToolCalls } from "./agent/dispatch.mjs"
|
|
12
12
|
import { recordToolResults } from "./agent/record-results.mjs"
|
|
@@ -14,31 +14,28 @@ import { FILE_MUTATORS } from "./agent/helpers.mjs"
|
|
|
14
14
|
import { prepareRun } from "./agent/setup.mjs"
|
|
15
15
|
import { injectPostTurn } from "./agent/post-turn.mjs"
|
|
16
16
|
import { handleCompletion } from "./agent/completion.mjs"
|
|
17
|
-
|
|
17
|
+
// 主循环阶段函数(压缩检查/注入组/回合收尾)2026-09-05 实践轮迁 agent/run-stages.mjs
|
|
18
|
+
import { runCompactionCheck, injectTurnReminders, finalizeAgentTurn, injectResponseReminders } from "./agent/run-stages.mjs"
|
|
18
19
|
import {
|
|
19
|
-
escapeXml, repairHistory, listWorkDir,
|
|
20
|
+
escapeXml, repairHistory, listWorkDir,
|
|
20
21
|
readonlyToolNames, collectGitContext, loadProjectInstructions,
|
|
21
|
-
ContinueError,
|
|
22
|
+
ContinueError,
|
|
22
23
|
DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
|
|
23
24
|
MIN_REPORT_CHARS, REPORT_CONTINUATION,
|
|
25
|
+
AUTO_TURN_DIGEST_DOMAIN,
|
|
24
26
|
} from "./agent/helpers.mjs"
|
|
27
|
+
// ENG 提醒族 + auto-turn domain 2026-09-05 迁 agent/helpers.mjs(agent.mjs 530 > 500 硬限)
|
|
28
|
+
// overlay 载荷(explore/coder/plan/eng-coder/consult)迁 prompt-overlays.mjs——re-export 保面
|
|
29
|
+
export {
|
|
30
|
+
EXPLORE_OVERLAY, CODER_OVERLAY, PLAN_OVERLAY, ENG_CODER_OVERLAY, CONSULT_BASE,
|
|
31
|
+
} from "./prompt-overlays.mjs"
|
|
32
|
+
export { ENG_ON_REMINDER, ENG_OFF_REMINDER } from "./agent/helpers.mjs"
|
|
25
33
|
|
|
26
34
|
// Prompt files (byte-stable, loaded once)
|
|
27
35
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
28
36
|
const SYSTEM_PROMPT = readFileSync(join(__dirname, "prompts", "system.md"), "utf8")
|
|
29
37
|
const DISCIPLINE_RULES = readFileSync(join(__dirname, "prompts", "discipline.md"), "utf8")
|
|
30
38
|
const MAIN_OVERLAY = readFileSync(join(__dirname, "prompts", "main.md"), "utf8")
|
|
31
|
-
let _EXPLORE, _CODER, _PLAN, _ENG_CODER, _CONSULT_BASE
|
|
32
|
-
try { _EXPLORE = readFileSync(join(__dirname, "prompts", "explore.md"), "utf8") } catch { _EXPLORE = "" }
|
|
33
|
-
try { _CODER = readFileSync(join(__dirname, "prompts", "coder.md"), "utf8") } catch { _CODER = "" }
|
|
34
|
-
try { _PLAN = readFileSync(join(__dirname, "prompts", "plan.md"), "utf8") } catch { _PLAN = "" }
|
|
35
|
-
try { _ENG_CODER = readFileSync(join(__dirname, "prompts", "eng-coder.md"), "utf8") } catch { _ENG_CODER = "" }
|
|
36
|
-
try { _CONSULT_BASE = readFileSync(join(__dirname, "prompts", "consult-base.md"), "utf8") } catch { _CONSULT_BASE = "" }
|
|
37
|
-
export const EXPLORE_OVERLAY = _EXPLORE
|
|
38
|
-
export const CODER_OVERLAY = _CODER
|
|
39
|
-
export const PLAN_OVERLAY = _PLAN
|
|
40
|
-
export const ENG_CODER_OVERLAY = _ENG_CODER
|
|
41
|
-
export const CONSULT_BASE = _CONSULT_BASE
|
|
42
39
|
|
|
43
40
|
// exported for consumption by agent-tools.mjs
|
|
44
41
|
export {
|
|
@@ -49,34 +46,9 @@ export {
|
|
|
49
46
|
}
|
|
50
47
|
|
|
51
48
|
|
|
52
|
-
// Engineering mode reminder — shared with eng.mjs tool
|
|
53
|
-
export const ENG_ON_REMINDER =
|
|
54
|
-
"[System reminder: engineering mode is ON — design-before-code enforced. " +
|
|
55
|
-
"Workflow: Requirements doc → Design doc → advisor(type='design') → " +
|
|
56
|
-
"user approval → eng-coder implementation. Code changes go through eng-coder " +
|
|
57
|
-
"subagents only. Advisor calls are NOT per-turn-mandatory — call only at " +
|
|
58
|
-
"flow nodes or when the user asks.]"
|
|
59
|
-
|
|
60
49
|
// Re-exported for API compatibility (single source of truth: advisor/repos.mjs)
|
|
61
50
|
export { hasCodeMutations } from "./advisor/repos.mjs"
|
|
62
51
|
|
|
63
|
-
/** Engineering mode OFF reminder — shared with the eng tool and the injector. */
|
|
64
|
-
export const ENG_OFF_REMINDER =
|
|
65
|
-
"[System reminder: engineering mode is now OFF — standard discipline applies. " +
|
|
66
|
-
"Changes go through the normal workflow: you may edit files directly, advisor/verify " +
|
|
67
|
-
"guards apply per config.]"
|
|
68
|
-
|
|
69
|
-
/** Engineering-mode status injection — one reminder on EVERY transition (2026-08-25:
|
|
70
|
-
* OFF is announced too — the model must know the gates lifted; silence after /eng-off
|
|
71
|
-
* left it guessing. Covers TUI /eng, resume, and any path bypassing the eng tool.) */
|
|
72
|
-
function injectEngineeringReminder(agent) {
|
|
73
|
-
const eng = agent.config?.agent?.engineering ?? false
|
|
74
|
-
if (eng !== agent._lastEngState) {
|
|
75
|
-
agent.history.push({ role: "user", content: eng ? ENG_ON_REMINDER : ENG_OFF_REMINDER, transient: true })
|
|
76
|
-
}
|
|
77
|
-
agent._lastEngState = eng
|
|
78
|
-
}
|
|
79
|
-
|
|
80
52
|
/** Create a new agent state object with all fields initialized to defaults */
|
|
81
53
|
export function createAgent({
|
|
82
54
|
provider, tools, config, cwd, memory, overlay, role,
|
|
@@ -90,8 +62,12 @@ export function createAgent({
|
|
|
90
62
|
planMode, autoApprove, goal,
|
|
91
63
|
_mutatedThisRun: false, _verifiedThisRun: false, _verifyPassed: undefined, _calledAdvisorThisRun: false,
|
|
92
64
|
_engDesignReviewed: false, // eng-coder: design review gate passed (hard gate in dispatch.mjs)
|
|
93
|
-
|
|
65
|
+
// DESIGN-TOKEN-SETTLEMENT D3 (2026-09-08): single-value `_engDesignToken` mirror retired
|
|
66
|
+
// (AC3 零写) — no field initializer; the multi-slot Map `_engDesignTokens` is the
|
|
67
|
+
// authoritative ledger (hydrated by restoreEngTokens / written by settle).
|
|
94
68
|
_touchedFiles: [], _verifyRetries: 0, _advisorRound: 0, _advisorSession: null,
|
|
69
|
+
_advisorRuns: new Map(), // §24 D-24b: per-review convergence instances (rounds/prior/designId)
|
|
70
|
+
_mutationSeq: 0, _mutLog: [], // §24 D-24b: mutation log (in-flight review staleness scan)
|
|
95
71
|
_lastAdvisorOutput: null, // full review output from the most recent advisor call (convergence rounds inject it verbatim)
|
|
96
72
|
_lastEngState: false,
|
|
97
73
|
_pendingReminders: [],
|
|
@@ -107,62 +83,90 @@ export function createAgent({
|
|
|
107
83
|
}
|
|
108
84
|
|
|
109
85
|
/** Run the agent loop: LLM ↔ tool-call cycle until task completion or turn limit. Returns final text content. */
|
|
110
|
-
export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false } = {}) {
|
|
111
|
-
// Previous run's async exploration distillation must settle before this run pushes
|
|
112
|
-
// input (SEND-STALL-DISTILL §2.2
|
|
113
|
-
// point — await BEFORE prepareRun, or the history replacement would wipe the new input.
|
|
86
|
+
export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false, autoTurn = false, suspDriven = false, consumeInjected = null } = {}) {
|
|
87
|
+
// Previous run's async exploration distillation must settle before this run pushes
|
|
88
|
+
// input (SEND-STALL-DISTILL §2.2 N1) — await first, or its history replace wipes it.
|
|
114
89
|
if (agent._pendingDistill) {
|
|
115
90
|
const p = agent._pendingDistill
|
|
116
91
|
agent._pendingDistill = null
|
|
117
92
|
await p
|
|
118
93
|
}
|
|
94
|
+
// §17 D-S3: suspension-settled async results inject before EVERY run's prepareRun
|
|
95
|
+
// (user + auto-turn); spliced = consumed. collectSettledAsync owns a different
|
|
96
|
+
// container, so no double-inject across the two consumption points.
|
|
97
|
+
// ASYNC-RESULT-CONTAINER.md D2 (2026-09-08):pending 单容器 `_pendingAsyncResults`
|
|
98
|
+
// +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
|
|
99
|
+
// (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 §25 D-R17a/b)
|
|
100
|
+
// ——单容器一处清,不再逐族三段。
|
|
101
|
+
const pendingAsync = agent._pendingAsyncResults
|
|
102
|
+
if (pendingAsync?.length) {
|
|
103
|
+
const { injectAsyncResult } = await import("./agent-tools/subagent.mjs")
|
|
104
|
+
const { injectConsultResult } = await import("./agent-tools/consult.mjs")
|
|
105
|
+
for (const e of pendingAsync.splice(0)) {
|
|
106
|
+
if (e.role === "consult") await injectConsultResult(agent, e)
|
|
107
|
+
else await injectAsyncResult(agent, e)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (§17 D-S6/N3)
|
|
119
111
|
const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
|
|
120
112
|
agent, input, callbacks,
|
|
121
|
-
{ depth, signal, overrideTurns, resume, systemPrompt: SYSTEM_PROMPT, disciplineRules: DISCIPLINE_RULES, mainOverlay: MAIN_OVERLAY },
|
|
113
|
+
{ depth, signal, overrideTurns, resume: resume || autoTurn, systemPrompt: SYSTEM_PROMPT, disciplineRules: DISCIPLINE_RULES, mainOverlay: MAIN_OVERLAY },
|
|
122
114
|
)
|
|
123
115
|
|
|
124
|
-
//
|
|
125
|
-
// pushed
|
|
116
|
+
// Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
|
|
117
|
+
// pushed input + injections — appended from here counts as "this run's" work.
|
|
126
118
|
agent._runStartHistoryLen = agent.history.length
|
|
127
119
|
|
|
128
|
-
// Per-run bookkeeping reset
|
|
129
|
-
//
|
|
130
|
-
// guards stay active (a guard pushback on the last turn must not silently vanish),
|
|
131
|
-
// and the convergence budget must not be resettable by continuing the session.
|
|
120
|
+
// Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
|
|
121
|
+
// mutation/guard continuity and the convergence budget must survive a continuation.
|
|
132
122
|
if (!resume) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
123
|
+
// §17 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
|
|
124
|
+
// reset) so auto-turn changes never escape the guard silently.
|
|
125
|
+
const g = agent._inheritedGuard
|
|
126
|
+
if (g) {
|
|
127
|
+
for (const k of ["_mutatedThisRun", "_verifiedThisRun", "_verifyPassed", "_calledAdvisorThisRun", "_touchedFiles", "_verifyRetries", "_advisorRound"]) agent[k] = g[k]
|
|
128
|
+
agent._inheritedGuard = null
|
|
129
|
+
} else {
|
|
130
|
+
agent._mutatedThisRun = false
|
|
131
|
+
agent._verifiedThisRun = false
|
|
132
|
+
agent._verifyPassed = undefined
|
|
133
|
+
agent._calledAdvisorThisRun = false
|
|
134
|
+
agent._touchedFiles = []
|
|
135
|
+
agent._verifyRetries = 0
|
|
136
|
+
agent._advisorRound = 0
|
|
137
|
+
agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
|
|
138
|
+
agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
|
|
139
|
+
agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// §17 D-S6 manual tier: digest action-domain reminder (system-driven turn — organize only).
|
|
143
|
+
if (autoTurn && !agent.autoApprove) {
|
|
144
|
+
agent.history.push({ role: "user", content: AUTO_TURN_DIGEST_DOMAIN, transient: true })
|
|
144
145
|
}
|
|
145
|
-
// eng-coder authorization is set by subagent.mjs
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
//
|
|
146
|
+
// eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
|
|
147
|
+
// (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
|
|
148
|
+
// depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
|
|
149
|
+
// depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
|
|
150
|
+
// Design slots (_engDesignTokens Map) survive across turns (design review → approval →
|
|
151
|
+
// eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
|
|
152
|
+
// SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
|
|
150
153
|
let guardPushbacks = 0
|
|
151
154
|
let advisorPushbacks = 0
|
|
152
155
|
let honestReminderInjected = false
|
|
153
156
|
const recentCallSigs = []
|
|
154
|
-
//
|
|
155
|
-
//
|
|
157
|
+
// "once" stream rules fire at most once per runAgent call; the set survives across
|
|
158
|
+
// chat() calls (rule abort-retry, tool loop) within the turn.
|
|
156
159
|
const streamRuleFired = new Set()
|
|
157
160
|
|
|
158
161
|
// Compaction overhead for the pure-estimation path: system prompt + tools schema are
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
// path already includes both (prompt_tokens is the full context), so this only applies
|
|
162
|
-
// when _lastPromptTokens is null.
|
|
162
|
+
// in every request but not in history — without them the first-turn/just-compacted
|
|
163
|
+
// estimate under-counts and may never trigger. Measured path already includes both.
|
|
163
164
|
const compactionOverhead = {
|
|
164
165
|
systemPrompt,
|
|
165
166
|
tools: toolSchemas,
|
|
167
|
+
// §18.6 D-TR4:compress 轨迹 depth 元数据(runAgent 的 depth 在此作用域——
|
|
168
|
+
// context.mjs compressIfNeeded 经 extras 透出到 logCtx)
|
|
169
|
+
traceDepth: depth,
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
let thrownError = null
|
|
@@ -171,74 +175,33 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
171
175
|
// Update turn counter for status bar display
|
|
172
176
|
agent._currentTurn = turn + 1
|
|
173
177
|
agent._maxTurns = maxTurns
|
|
174
|
-
// D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// existing onToolCall/onToolResult prefix relay — no token for those).
|
|
178
|
+
// D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
|
|
179
|
+
// single emit point covering all three spawn tools; phase=llm (tool/done progress rides
|
|
180
|
+
// the onToolCall/onToolResult relay — no token for those).
|
|
178
181
|
if (depth > 0 && callbacks.onToken) {
|
|
179
182
|
callbacks.onToken(`⟦ev⟧turn\x1e${turn + 1}\x1e${maxTurns}\x1ellm\x1e`)
|
|
180
183
|
}
|
|
181
184
|
|
|
185
|
+
// SUBAGENT-OBSERVE-SEND D2: 子代理回合边界消费点——每轮开头把父侧经 subagent
|
|
186
|
+
// action:'send' 注入队列(entry._injected)的消息按普通 user 回合推入子历史
|
|
187
|
+
// (pushReal → 下一轮 chat 即含该指令)。由 executeAsyncSpawn 经 childRunOpts 贯通的
|
|
188
|
+
// consumeInjected 回调承载(异步子代理专属——缺省 null:主会话/阻塞子代理零开销)。
|
|
189
|
+
consumeInjected?.(agent)
|
|
190
|
+
|
|
182
191
|
const lastRole = agent.history.at(-1)?.role
|
|
183
192
|
if (lastRole === "user" || lastRole === "tool") {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
agent._planReminderAtLen = 0 // After compression history shrinks, reset cadence so reminders resume
|
|
188
|
-
recentCallSigs.length = 0 // After compression history is rebuilt, reset stall detection counter
|
|
189
|
-
// Completion info (CONTEXT-COMPACTION §7 D-C2): { mode: "summary", tokensFreed, elapsedMs }
|
|
190
|
-
// from compressIfNeeded, or { mode: "fallback", tailMessages } from compressFallback below —
|
|
191
|
-
// the TUI panel renders the matching completion state. Existing callers that ignore the
|
|
192
|
-
// argument keep the exact previous onCompress semantics.
|
|
193
|
-
callbacks.onCompress?.(agent._lastCompressInfo ?? {})
|
|
194
|
-
ensureAutoReminder(agent)
|
|
195
|
-
}
|
|
196
|
-
} catch (compressError) {
|
|
197
|
-
// AbortError must not be swallowed: user cancellation must propagate
|
|
198
|
-
if (compressError?.name === "AbortError" || signal?.aborted) throw compressError
|
|
199
|
-
agent._compressFailures = (agent._compressFailures ?? 0) + 1
|
|
200
|
-
// Q3 visibility (CONTEXT-COMPACTION §7 D-C1): a failed compression is no longer silent —
|
|
201
|
-
// the frontend updates the compression panel with the error text (and logs to stderr).
|
|
202
|
-
// Failure STRATEGY is unchanged: COMPRESS_FAILURE_LIMIT consecutive failures still degrade
|
|
203
|
-
// to compressFallback — this only adds observability.
|
|
204
|
-
callbacks?.onCompressFail?.(compressError)
|
|
205
|
-
if (agent._compressFailures >= COMPRESS_FAILURE_LIMIT) {
|
|
206
|
-
agent._compressFailures = 0
|
|
207
|
-
if (compressFallback(agent)) callbacks.onCompress?.(agent._lastCompressInfo ?? {})
|
|
208
|
-
}
|
|
209
|
-
}
|
|
193
|
+
// 2026-09-05 实践轮:压缩检查/降级计数提为 runCompactionCheck(agent/run-stages.mjs——
|
|
194
|
+
// CLI 对位 VS run-stages)——循环骨架此处只剩检查调用(recentCallSigs 对象引用回流)。
|
|
195
|
+
await runCompactionCheck(agent, { threshold, callbacks, compactionOverhead, signal, recentCallSigs })
|
|
210
196
|
}
|
|
211
197
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
// so the read-only restriction never fades from context.
|
|
215
|
-
if (agent.planMode) {
|
|
216
|
-
const lastMsg = agent.history.at(-1)
|
|
217
|
-
const realUserMsg = lastMsg?.role === "user"
|
|
218
|
-
&& typeof lastMsg.content === "string"
|
|
219
|
-
&& !lastMsg.content.startsWith("[System reminder:")
|
|
220
|
-
&& !lastMsg.content.startsWith("[User interrupt:")
|
|
221
|
-
const newUserSince = realUserMsg && agent.history.length > (agent._planReminderAtLen ?? 0)
|
|
222
|
-
const { planReminderForTurn } = await import("./agent-tools/plan.mjs")
|
|
223
|
-
const reminder = planReminderForTurn(agent, newUserSince)
|
|
224
|
-
if (reminder) {
|
|
225
|
-
agent._planReminderAtLen = agent.history.length + 1
|
|
226
|
-
agent.history.push({ role: "user", content: reminder, transient: true })
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Engineering-mode status injection: every new user message carries a
|
|
231
|
-
// reminder so the model always knows whether it's in design-before-code
|
|
232
|
-
// mode or standard discipline mode.
|
|
233
|
-
if (depth === 0) {
|
|
234
|
-
injectEngineeringReminder(agent)
|
|
235
|
-
}
|
|
198
|
+
// 2026-09-05 实践轮:plan cadence + eng 状态注入提为 injectTurnReminders(run-stages)
|
|
199
|
+
await injectTurnReminders(agent, { depth })
|
|
236
200
|
|
|
237
201
|
const messages = [{ role: "system", content: systemPrompt }, ...agent.history]
|
|
238
202
|
let response
|
|
239
203
|
|
|
240
|
-
// Auto-think: classify
|
|
241
|
-
// Runs only on turn 0 of user input; failure is silent — falls back to current setting.
|
|
204
|
+
// Auto-think: classify difficulty and set reasoning effort on turn 0; silent on failure.
|
|
242
205
|
if (agent.config?.agent?.autoThink && turn === 0) {
|
|
243
206
|
const { classifyAndApply } = await import("./auto-think.mjs")
|
|
244
207
|
await classifyAndApply(agent, turn).catch(() => {})
|
|
@@ -253,14 +216,29 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
253
216
|
signal,
|
|
254
217
|
streamRules: agent.config.agent?.streamRules ?? [],
|
|
255
218
|
firedPatterns: streamRuleFired,
|
|
219
|
+
// LOGGING(LOGGING.md):llm:* 事件的语义上下文(stage=turn 主循环回合——含
|
|
220
|
+
// digest 消化轮 auto=true;child=子代理 id(spawn 时 stamp 于 child._logId))
|
|
221
|
+
// §18.6 D-TR4:轨迹元数据增补(role/depth/kind/session/cwd——trace-store 只读
|
|
222
|
+
// logCtx,签名不变);kind:depth>0 = subagent(consult 孩子 = consult)——子代理
|
|
223
|
+
// 对回靠 role+depth+child id(children 无 _sessionStart——不经 depth-0 设置——
|
|
224
|
+
// session 字段对子代理轨迹为 null——见 trace-store/agent.mjs 注释)。
|
|
225
|
+
logCtx: {
|
|
226
|
+
stage: "turn", turn: turn + 1, auto: autoTurn, child: agent._logId,
|
|
227
|
+
role: agent._role ?? null,
|
|
228
|
+
depth,
|
|
229
|
+
kind: depth > 0 ? (agent._role === "consult" ? "consult" : "subagent") : "turn",
|
|
230
|
+
session: agent._sessionStart ?? null,
|
|
231
|
+
cwd: agent.cwd,
|
|
232
|
+
traces: agent.config?.traces?.enabled !== false,
|
|
233
|
+
},
|
|
256
234
|
})
|
|
257
235
|
} catch (e) {
|
|
258
|
-
// User interrupt (Ctrl+I): controller.abort({ interrupt: true, message
|
|
259
|
-
// Inject
|
|
236
|
+
// User interrupt (Ctrl+I): controller.abort({ interrupt: true, message }).
|
|
237
|
+
// Inject into history; the outer loop recreates the controller and resumes.
|
|
260
238
|
if (e.name === "AbortError" && signal?.reason?.interrupt) {
|
|
261
239
|
const msg = `[User interrupt: ${signal.reason.message}]`
|
|
262
|
-
// Dedup: if
|
|
263
|
-
// don't push a duplicate — the outer loop
|
|
240
|
+
// Dedup: if already handled during tool execution (interrupt branch below),
|
|
241
|
+
// don't push a duplicate — the outer loop still recreates the controller.
|
|
264
242
|
if (agent.history.at(-1)?.content !== msg) {
|
|
265
243
|
agent.history.push({ role: "user", content: msg })
|
|
266
244
|
}
|
|
@@ -268,11 +246,9 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
268
246
|
throw e
|
|
269
247
|
}
|
|
270
248
|
|
|
271
|
-
// 内置工具(Responses web_search)结果本地化:服务端已执行——入历史为 tool
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
// 原始 id 存入 content(真机冒烟 2026-08-31:直接用 msg_xxx 会被转成 function_call_output
|
|
275
|
-
// 与服务端不配对,属蒙对)。
|
|
249
|
+
// 内置工具(Responses web_search)结果本地化:服务端已执行——入历史为 tool 消息;
|
|
250
|
+
// 服务端 item id 是 msg_xxx 非 web_search_call_ 前缀——必须合成前缀(toItems 识别锚点),
|
|
251
|
+
// 原始 id 存入 content(真机冒烟 2026-08-31 验证)。
|
|
276
252
|
for (const btr of response.builtinToolResults ?? []) {
|
|
277
253
|
if (!btr?.id) continue
|
|
278
254
|
pushReal(agent, {
|
|
@@ -282,8 +258,8 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
282
258
|
})
|
|
283
259
|
}
|
|
284
260
|
|
|
285
|
-
// Stream rule triggered mid-generation (action: "abort"): halt
|
|
286
|
-
//
|
|
261
|
+
// Stream rule triggered mid-generation (action: "abort"): halt, inject the rule's
|
|
262
|
+
// message as a reminder, retry from the same context.
|
|
287
263
|
if (response.ruleTriggered) {
|
|
288
264
|
if (response.content) {
|
|
289
265
|
pushReal(agent, { role: "assistant", content: response.content })
|
|
@@ -296,20 +272,12 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
296
272
|
continue
|
|
297
273
|
}
|
|
298
274
|
|
|
299
|
-
// Stream rule warnings
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
if (response._warnings?.length) {
|
|
303
|
-
const deDuplicated = [...new Map(response._warnings.map(w => [w.name || w.pattern, w])).values()]
|
|
304
|
-
agent.history.push({
|
|
305
|
-
role: "user",
|
|
306
|
-
content: `[System reminder — stream rule warnings from your last response:\n${deDuplicated.map(w => `- ${w.name || w.pattern}: ${w.message}`).join("\n")}]`,
|
|
307
|
-
})
|
|
308
|
-
}
|
|
275
|
+
// Stream rule warnings / finish-reason 警告(2026-09-05 实践轮——提为
|
|
276
|
+
// injectResponseReminders,agent/run-stages.mjs——verbatim,语义零变)
|
|
277
|
+
injectResponseReminders(agent, response)
|
|
309
278
|
|
|
310
|
-
// User interrupted mid-generation (Ctrl+I):
|
|
311
|
-
//
|
|
312
|
-
// the outer loop to recreate the controller and resume.
|
|
279
|
+
// User interrupted mid-generation (Ctrl+I): commit partial output + inject the
|
|
280
|
+
// message, then signal the outer loop to recreate the controller and resume.
|
|
313
281
|
if (response.interrupted) {
|
|
314
282
|
if (response.content) {
|
|
315
283
|
pushReal(agent, { role: "assistant", content: response.content })
|
|
@@ -329,20 +297,8 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
329
297
|
}
|
|
330
298
|
}
|
|
331
299
|
|
|
332
|
-
// Warn on abnormal finish reasons — the
|
|
333
|
-
//
|
|
334
|
-
if (response.finishReason && response.finishReason !== "stop" && response.finishReason !== "tool_calls") {
|
|
335
|
-
const reasonMap = {
|
|
336
|
-
length: "output token limit reached after exhausting continuations",
|
|
337
|
-
insufficient_system_resource: "provider inference resources exhausted — consider retrying or switching models",
|
|
338
|
-
content_filter: "response blocked by provider content filtering",
|
|
339
|
-
}
|
|
340
|
-
const detail = reasonMap[response.finishReason] || `unknown reason "${response.finishReason}"`
|
|
341
|
-
agent.history.push({
|
|
342
|
-
role: "user",
|
|
343
|
-
content: `[System reminder: the previous turn ended abnormally — ${detail}. The assistant response that follows may be incomplete.]`,
|
|
344
|
-
})
|
|
345
|
-
}
|
|
300
|
+
// Warn on abnormal finish reasons — the response may be incomplete/truncated.
|
|
301
|
+
// 2026-09-05 实践轮:finish-reason 注入随流规则警告提为 injectResponseReminders。
|
|
346
302
|
|
|
347
303
|
if (response.toolCalls.length === 0) {
|
|
348
304
|
const cr = handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks)
|
|
@@ -351,13 +307,11 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
351
307
|
advisorPushbacks = cr.advisorPushbacks
|
|
352
308
|
if (cr.action === "continue") continue
|
|
353
309
|
if (depth === 0) {
|
|
354
|
-
// End-of-run exploration distillation (CONTEXT-COMPACTION §5
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
// history.
|
|
360
|
-
const distill = summarizeRunExplorations(agent, callbacks, signal).catch(() => {})
|
|
310
|
+
// End-of-run exploration distillation (CONTEXT-COMPACTION §5 + SEND-STALL-DISTILL
|
|
311
|
+
// §2.1): async — the promise hangs on _pendingDistill, settling at the next run's
|
|
312
|
+
// start or the TUI exit flush. Silent (N3): failure never blocks return/history.
|
|
313
|
+
// §18.6 D-TR4:depth 透传(distill 轨迹元数据——与 compress 同通道)
|
|
314
|
+
const distill = summarizeRunExplorations(agent, callbacks, signal, depth).catch(() => {})
|
|
361
315
|
agent._pendingDistill = distill
|
|
362
316
|
}
|
|
363
317
|
return cr.content
|
|
@@ -380,12 +334,15 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
380
334
|
|
|
381
335
|
const results = await executeToolCalls(agent, toolByName, response.toolCalls, callbacks, depth, signal)
|
|
382
336
|
|
|
383
|
-
// Ctrl+I interrupt during tool execution: skip committing partial results —
|
|
384
|
-
// the
|
|
337
|
+
// Ctrl+I interrupt during tool execution: skip committing partial results — inject
|
|
338
|
+
// the interrupt and retry (placeholder results keep strict providers pairable).
|
|
385
339
|
if (signal?.reason?.interrupt) {
|
|
386
340
|
// 中断变更记账(2026-08-31 评审 #4):此分支的工具已全部执行完成(磁盘已变,execute 已完成),
|
|
387
341
|
// 真实结果按语义不进历史(placeholder 替代)——但变更必须记账:否则 guard 看到
|
|
388
342
|
// "本轮未改代码" 放行,评审/verify 门禁被绕过(文件改了却没评审)。
|
|
343
|
+
// §29 fix A(2026-09-07):mutation-seq 记账已收敛到 dispatch runOne 执行成功即刻
|
|
344
|
+
// (唯一记账点——本分支不再 noteMutations——不双计——中断+同批 launch seq 单计
|
|
345
|
+
// 回归断言见 §29 T-A1i);此处仅剩 guard 标志 + touchedFiles 记账。
|
|
389
346
|
for (const { toolCall, ok } of results) {
|
|
390
347
|
const tool = toolByName.get(toolCall.name)
|
|
391
348
|
if (!ok || !tool || !FILE_MUTATORS.has(toolCall.name)) continue
|
|
@@ -397,16 +354,14 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
397
354
|
const args = JSON.parse(toolCall.arguments)
|
|
398
355
|
const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
|
|
399
356
|
for (const p of paths) {
|
|
400
|
-
const abs =
|
|
357
|
+
const abs = resolve(agent.cwd, p)
|
|
401
358
|
if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
|
|
402
359
|
}
|
|
403
360
|
} catch { /* 畸形 args 不影响记账(touchedFiles 尽力而为) */ }
|
|
404
361
|
}
|
|
405
|
-
// The assistant tool_calls were
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
// follow its assistant tool_calls). The retry turn then sees a clean,
|
|
409
|
-
// pairable history (consult P1, 2026-08-30).
|
|
362
|
+
// The assistant tool_calls were committed above — synthesize placeholder tool
|
|
363
|
+
// results BEFORE the interrupt message (strict providers 400 on dangling
|
|
364
|
+
// tool_calls; consult P1, 2026-08-30).
|
|
410
365
|
for (const tc of response.toolCalls) {
|
|
411
366
|
agent.history.push({ role: "tool", tool_call_id: tc.id, content: "[Tool execution interrupted — results discarded]" })
|
|
412
367
|
}
|
|
@@ -418,13 +373,11 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
418
373
|
continue
|
|
419
374
|
}
|
|
420
375
|
|
|
421
|
-
// Model is executing tools →
|
|
376
|
+
// Model is executing tools → real work: reset guard pushback counters
|
|
422
377
|
guardPushbacks = 0
|
|
423
378
|
advisorPushbacks = 0
|
|
424
379
|
|
|
425
|
-
// Commit tool results (pairing, multimodal deferral, mutation accounting,
|
|
426
|
-
// touched files, reindex) — split into record-results.mjs (consult P2,
|
|
427
|
-
// 2026-08-30).
|
|
380
|
+
// Commit tool results (pairing, multimodal deferral, mutation accounting, reindex)
|
|
428
381
|
await recordToolResults(agent, toolByName, results)
|
|
429
382
|
|
|
430
383
|
injectPostTurn(agent, results, recentCallSigs, callbacks, turn)
|
|
@@ -435,59 +388,9 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
435
388
|
thrownError = e
|
|
436
389
|
throw e
|
|
437
390
|
} finally {
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// - Ctrl+C / Ctrl+I (signal aborted): children were aborted with the parent
|
|
443
|
-
// signal — clear WITHOUT injecting stale errors (user explicitly stopped).
|
|
444
|
-
// - ContinueError (turn cap): no wait, no injection — children keep running
|
|
445
|
-
// and the RESUME run's turn-end collection takes over.
|
|
446
|
-
// - anything else: refill loop → wait for all → inject reports → clear.
|
|
447
|
-
if (signal?.aborted) {
|
|
448
|
-
agent._asyncSubagents?.clear()
|
|
449
|
-
agent._asyncQueue = []
|
|
450
|
-
agent._asyncCheckLastN = 0
|
|
451
|
-
} else if (thrownError instanceof ContinueError) {
|
|
452
|
-
// keep _asyncSubagents + the check counter — the resumed run continues them
|
|
453
|
-
} else {
|
|
454
|
-
await collectAsyncSubagents(agent)
|
|
455
|
-
agent._asyncCheckLastN = 0
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Turn-end async subagent collection (AGENT-LOOP.md §15 D-A3):
|
|
462
|
-
* 1. refill loop — start queued heads while slots free (each settle already
|
|
463
|
-
* refills via its finally; this drains the tail), keeping the cap ≤4 serial.
|
|
464
|
-
* 2. wait for every entry to settle (queued entries start through the refill
|
|
465
|
-
* chain — the drain loop converges when nothing is running and the queue is empty).
|
|
466
|
-
* 3. inject one user-role reminder per entry: the report/error text XML-escaped
|
|
467
|
-
* (child reports may carry content from files/webpages — reminder discipline),
|
|
468
|
-
* >64K offloaded to disk with a preview + path.
|
|
469
|
-
* 4. clear the map. (The ⟦ev⟧done freeze signal is NOT emitted here — D-A3
|
|
470
|
-
* 2026-09-02: each entry's settle callback emits it at completion time so
|
|
471
|
-
* blocks freeze at their completion position in the stream.)
|
|
472
|
-
*/
|
|
473
|
-
async function collectAsyncSubagents(agent) {
|
|
474
|
-
const map = agent._asyncSubagents
|
|
475
|
-
if (!map || map.size === 0) return
|
|
476
|
-
const { maybeRefillAsync } = await import("./agent-tools/subagent.mjs")
|
|
477
|
-
for (;;) {
|
|
478
|
-
maybeRefillAsync(agent)
|
|
479
|
-
const running = [...map.values()].filter((e) => e.status === "running")
|
|
480
|
-
if (running.length === 0) break
|
|
481
|
-
await Promise.allSettled(running.map((e) => e.promise))
|
|
482
|
-
}
|
|
483
|
-
for (const e of [...map.values()]) {
|
|
484
|
-
const body = e.error ?? e.report ?? "(no report)"
|
|
485
|
-
const preview = await offloadToolResult(String(body), `async-subagent-${e.id}`)
|
|
486
|
-
pushReal(agent, {
|
|
487
|
-
role: "user",
|
|
488
|
-
content: `[System reminder: async subagent #${e.id} (${e.role}) finished]\n${escapeXml(preview)}`,
|
|
489
|
-
})
|
|
391
|
+
// 2026-09-05 实践轮:回合收尾(consult 清理/async 池分流/guard 继承——原 425-462
|
|
392
|
+
// 段 + collectSettledAsync 466-494 整体迁 agent/run-stages.mjs finalizeAgentTurn——
|
|
393
|
+
// CLI 对位 VS run-stages——finally 只剩一行调用 + 骨架注释)。
|
|
394
|
+
await finalizeAgentTurn(agent, { signal, autoTurn, suspDriven, thrownError })
|
|
490
395
|
}
|
|
491
|
-
map.clear()
|
|
492
|
-
agent._asyncQueue = []
|
|
493
396
|
}
|
package/src/auto-think.mjs
CHANGED
|
@@ -81,6 +81,20 @@ export async function classifyAndApply(agent, turn) {
|
|
|
81
81
|
],
|
|
82
82
|
tools: [],
|
|
83
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
|
+
},
|
|
84
98
|
})
|
|
85
99
|
const word = (response.content ?? "").trim().toLowerCase()
|
|
86
100
|
if (word.startsWith("low")) level = "low"
|
|
@@ -10,7 +10,7 @@ function noKeyMessage() {
|
|
|
10
10
|
return `还没有配置 API key。运行 thincoder 进入 TUI,用 /provider add 和 /provider key 配置;或直接编辑 ${configPath}`
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/** thincoder distill <transcript-file> [--yes] [--
|
|
13
|
+
/** thincoder distill <transcript-file> [--yes] [--layer=...]
|
|
14
14
|
* Returns exit code: 0=success, 1=error */
|
|
15
15
|
export async function distillCommand(args, exitSoon) {
|
|
16
16
|
const flags = {}
|
|
@@ -20,9 +20,15 @@ export async function distillCommand(args, exitSoon) {
|
|
|
20
20
|
if (m) flags[m[1]] = m[2] ?? true
|
|
21
21
|
else positional.push(a)
|
|
22
22
|
}
|
|
23
|
+
// AC2(MEMORY.md §6.5):显式拦截旧 flag——`--scope=X` 与 `--scope X` 两形态落入 flags.scope,
|
|
24
|
+
// 报错防静默吞参(解析器本不校验未知 flag——旧 --scope=project 静默落 personal 最危险)。
|
|
25
|
+
if (flags.scope !== undefined) {
|
|
26
|
+
console.error("distill: --scope renamed to --layer — update your invocation")
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
23
29
|
const file = positional[0]
|
|
24
30
|
if (!file) {
|
|
25
|
-
console.error("Usage: thincoder distill <transcript-file> [--yes] [--
|
|
31
|
+
console.error("Usage: thincoder distill <transcript-file> [--yes] [--layer=personal|project|team]")
|
|
26
32
|
return 1
|
|
27
33
|
}
|
|
28
34
|
const { readFile } = await import("node:fs/promises")
|
|
@@ -64,9 +70,9 @@ export async function distillCommand(args, exitSoon) {
|
|
|
64
70
|
}
|
|
65
71
|
let saved = 0
|
|
66
72
|
for (const c of candidates) {
|
|
67
|
-
if (flags.
|
|
73
|
+
if (flags.layer) c.layer = flags.layer
|
|
68
74
|
console.log(`\n--- candidate ---`)
|
|
69
|
-
console.log(`[${c.type}] ${c.title} (
|
|
75
|
+
console.log(`[${c.type}] ${c.title} (layer: ${c.layer})`)
|
|
70
76
|
console.log(c.content)
|
|
71
77
|
if (c.type === "rule") {
|
|
72
78
|
console.log("(rule 类知识通常建议手动撰写;确认提取吗?)")
|