thincoder 0.12.59 → 0.12.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -4
- package/README.md +10 -8
- package/bin/thincoder.mjs +99 -133
- package/package.json +6 -4
- package/src/abort-provenance.mjs +116 -0
- package/src/acp/bridge.mjs +45 -21
- package/src/acp.mjs +6 -1
- package/src/advisor/citations.mjs +83 -21
- package/src/advisor/compaction.mjs +174 -0
- package/src/advisor/loop.mjs +293 -0
- package/src/advisor/messages.mjs +59 -137
- package/src/advisor/project-context.mjs +194 -0
- package/src/advisor/repos.mjs +17 -40
- package/src/advisor/run.mjs +156 -359
- package/src/advisor/truncate.mjs +57 -0
- package/src/advisor.mjs +27 -7
- package/src/agent/completion.mjs +17 -11
- package/src/agent/dispatch.mjs +145 -27
- package/src/agent/helpers.mjs +107 -13
- package/src/agent/record-results.mjs +55 -11
- package/src/agent/relay-prefix.mjs +39 -0
- package/src/agent/run-stages.mjs +242 -0
- package/src/agent/setup-reminders.mjs +69 -0
- package/src/agent/setup.mjs +107 -127
- package/src/agent/spawn-child.mjs +55 -13
- package/src/agent-tools/advisor-async.mjs +346 -0
- package/src/agent-tools/advisor-settle.mjs +231 -0
- package/src/agent-tools/advisor.mjs +167 -116
- package/src/agent-tools/async-settle.mjs +191 -0
- package/src/agent-tools/batch-segment.mjs +195 -0
- package/src/agent-tools/consult.mjs +139 -107
- package/src/agent-tools/design-token.mjs +117 -0
- package/src/agent-tools/digest-budget.mjs +76 -0
- package/src/agent-tools/eng.mjs +24 -29
- package/src/agent-tools/escalate-async.mjs +289 -0
- package/src/agent-tools/read-history.mjs +166 -32
- package/src/agent-tools/recent-changes.mjs +2 -1
- package/src/agent-tools/review-streak.mjs +93 -0
- package/src/agent-tools/settings.mjs +137 -34
- package/src/agent-tools/subagent-actions.mjs +180 -133
- package/src/agent-tools/subagent-async.mjs +184 -177
- package/src/agent-tools/subagent-panel.mjs +160 -0
- package/src/agent-tools/subagent-run.mjs +205 -0
- package/src/agent-tools/subagent-scheduler.mjs +100 -27
- package/src/agent-tools/subagent-spawn.mjs +453 -0
- package/src/agent-tools/subagent.mjs +256 -578
- package/src/agent-tools/verify.mjs +119 -292
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +89 -205
- package/src/cli/distill-command.mjs +12 -6
- package/src/cli/make-agent.mjs +26 -8
- package/src/cli/memory-command.mjs +4 -3
- package/src/cli/permission.mjs +2 -2
- package/src/cli/setup-wizard.mjs +42 -17
- package/src/completions.mjs +114 -0
- package/src/config-migrate.mjs +70 -0
- package/src/config.mjs +180 -63
- package/src/context.mjs +5 -147
- package/src/conventions.mjs +223 -0
- package/src/crash-reports.mjs +128 -0
- package/src/distill.mjs +11 -11
- package/src/expand-home.mjs +16 -0
- package/src/explore-distill.mjs +155 -0
- package/src/generate-title.mjs +1 -1
- package/src/hooks.mjs +7 -3
- package/src/memory/code-index.mjs +9 -3
- package/src/memory/code-sync.mjs +72 -32
- package/src/memory/core.mjs +6 -193
- package/src/memory/delete.mjs +236 -0
- package/src/memory/docs.mjs +68 -54
- package/src/memory/file-walk.mjs +109 -0
- package/src/memory/schema.mjs +15 -3
- package/src/memory.mjs +3 -1
- package/src/model-ref.mjs +66 -0
- package/src/model-specs.mjs +42 -8
- package/src/peer-domains.mjs +265 -0
- package/src/peer-instances.mjs +231 -0
- package/src/prompt-overlays.mjs +82 -0
- package/src/prompts/advisor-design.md +18 -75
- package/src/prompts/advisor-round1.md +14 -67
- package/src/prompts/advisor-round2.md +15 -51
- package/src/prompts/advisor-round3.md +15 -51
- package/src/prompts/common.md +115 -0
- package/src/prompts/consult-base.md +5 -23
- package/src/prompts/discipline-engineering.md +217 -0
- package/src/prompts/discipline-normal.md +179 -0
- package/src/prompts/persona-coder.md +21 -0
- package/src/prompts/persona-eng-coder.md +37 -0
- package/src/prompts/persona-eng-designer.md +55 -0
- package/src/prompts/persona-engineering.md +54 -0
- package/src/prompts/persona-explore.md +15 -0
- package/src/prompts/persona-normal.md +27 -0
- package/src/prompts/persona-plan.md +26 -0
- package/src/provider/anthropic.mjs +4 -4
- package/src/provider/core.mjs +18 -98
- package/src/provider/errors.mjs +101 -0
- package/src/provider/google.mjs +5 -6
- package/src/provider/index.mjs +2 -1
- package/src/provider/list-models.mjs +93 -0
- package/src/provider/rate.mjs +2 -1
- package/src/provider/responses.mjs +5 -3
- package/src/provider/retry.mjs +8 -45
- package/src/provider/sse.mjs +3 -4
- package/src/proxy.mjs +9 -14
- 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 +188 -60
- package/src/session.mjs +104 -124
- package/src/token-ttl.mjs +274 -0
- package/src/tools/{system.mjs → bash.mjs} +19 -221
- package/src/tools/checklist-sync.mjs +181 -0
- package/src/tools/checklist.mjs +52 -39
- package/src/tools/edit-batch.mjs +109 -10
- package/src/tools/edit-diff.mjs +110 -27
- package/src/tools/edit.md +17 -12
- package/src/tools/execute.mjs +31 -4
- package/src/tools/file.mjs +41 -16
- package/src/tools/git.md +1 -1
- package/src/tools/git.mjs +23 -34
- 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/index.mjs +9 -8
- package/src/tools/ops.mjs +188 -3
- package/src/tools/patch.mjs +3 -3
- package/src/tools/question.md +4 -0
- package/src/tools/question.mjs +26 -0
- package/src/tools/read.md +1 -2
- package/src/tools/read_image.md +1 -1
- package/src/tools/search.mjs +236 -0
- package/src/tools/shared.mjs +14 -13
- package/src/tools/wait_for.md +22 -0
- package/src/tui/agent-turn.mjs +36 -228
- package/src/tui/ansi.mjs +2 -0
- package/src/tui/clipboard.mjs +7 -1
- package/src/tui/cmd-advisor.mjs +3 -2
- package/src/tui/cmd-config.mjs +142 -30
- package/src/tui/cmd-eng.mjs +28 -40
- package/src/tui/cmd-exit.mjs +6 -8
- package/src/tui/cmd-mcp.mjs +8 -2
- package/src/tui/cmd-model.mjs +14 -12
- package/src/tui/cmd-new.mjs +3 -2
- package/src/tui/cmd-reindex.mjs +7 -0
- package/src/tui/cmd-session.mjs +19 -4
- package/src/tui/cmd-submodel.mjs +8 -5
- package/src/tui/cmd-think.mjs +10 -10
- package/src/tui/cmd-undo.mjs +4 -3
- 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 +40 -38
- package/src/tui/interaction.mjs +3 -3
- package/src/tui/key-handler.mjs +61 -17
- package/src/tui/key-modes.mjs +86 -8
- package/src/tui/layout.mjs +18 -10
- package/src/tui/model-catalog.mjs +89 -0
- package/src/tui/model-picker.mjs +498 -0
- package/src/tui/mouse.mjs +52 -9
- package/src/tui/pickers.mjs +28 -392
- package/src/tui/render-frame.mjs +32 -16
- package/src/tui/render-loop.mjs +2 -0
- package/src/tui/render-segments.mjs +12 -9
- package/src/tui/render.mjs +37 -5
- package/src/tui/slash-commands.mjs +2 -2
- package/src/tui/startup.mjs +4 -0
- package/src/tui/subagent-blocks.mjs +106 -295
- package/src/tui/subagent-children.mjs +162 -0
- package/src/tui/subagent-freeze.mjs +169 -0
- package/src/tui/subagent-panel.mjs +24 -31
- package/src/tui/suspension-drive.mjs +297 -0
- package/src/tui/tool-args.mjs +7 -5
- package/src/tui/tool-display.mjs +143 -0
- package/src/tui/tool-events.mjs +56 -185
- package/src/tui/tui-lifecycle.mjs +46 -4
- package/src/tui/update-notice.mjs +4 -0
- package/src/tui/wizard.mjs +61 -21
- package/src/tui/wrapped-spawn.mjs +38 -0
- package/src/prompts/coder.md +0 -56
- package/src/prompts/discipline.md +0 -102
- package/src/prompts/eng-coder.md +0 -44
- package/src/prompts/engineering-sub.md +0 -34
- package/src/prompts/engineering.md +0 -340
- package/src/prompts/explore.md +0 -21
- package/src/prompts/main.md +0 -56
- package/src/prompts/methodology-template.md +0 -58
- package/src/prompts/plan.md +0 -16
- package/src/prompts/system.md +0 -63
- package/src/tools/pdf-parse-text.mjs +0 -497
- package/src/tools/pdf-parse-xref.mjs +0 -499
- package/src/tools/pdf.mjs +0 -155
- package/src/tools/read_pdf.md +0 -21
package/src/agent.mjs
CHANGED
|
@@ -3,43 +3,43 @@
|
|
|
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 { abortError, annotateAbort } from "./abort-provenance.mjs"
|
|
7
|
+
import { pushReal, summarizeRunExplorations } from "./context.mjs"
|
|
7
8
|
import { specForModel } from "./config.mjs"
|
|
8
|
-
import {
|
|
9
|
-
import { join, dirname } from "node:path"
|
|
10
|
-
import { fileURLToPath } from "node:url"
|
|
9
|
+
import { resolve } from "node:path"
|
|
11
10
|
import { executeToolCalls } from "./agent/dispatch.mjs"
|
|
12
11
|
import { recordToolResults } from "./agent/record-results.mjs"
|
|
13
12
|
import { FILE_MUTATORS } from "./agent/helpers.mjs"
|
|
14
13
|
import { prepareRun } from "./agent/setup.mjs"
|
|
15
14
|
import { injectPostTurn } from "./agent/post-turn.mjs"
|
|
16
15
|
import { handleCompletion } from "./agent/completion.mjs"
|
|
17
|
-
|
|
18
|
-
|
|
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"
|
|
19
21
|
import {
|
|
20
|
-
escapeXml, repairHistory, listWorkDir,
|
|
22
|
+
escapeXml, repairHistory, listWorkDir,
|
|
21
23
|
readonlyToolNames, collectGitContext, loadProjectInstructions,
|
|
22
24
|
ContinueError,
|
|
25
|
+
turnFrame, // 第 19 批(TURN-ACROSS-SEGMENTS):跨段累计编号帧(设计 §19.3)
|
|
23
26
|
DEFAULT_MAX_TURNS, DEFAULT_SUBAGENT_TURNS,
|
|
24
27
|
MIN_REPORT_CHARS, REPORT_CONTINUATION,
|
|
28
|
+
AUTO_TURN_DIGEST_DOMAIN,
|
|
25
29
|
} from "./agent/helpers.mjs"
|
|
30
|
+
// ENG 提醒族 + auto-turn domain 2026-09-05 迁 agent/helpers.mjs(agent.mjs 530 > 500 硬限)
|
|
31
|
+
// PROMPT-SYSTEM 施工② G1(2026-09-10):六件槽位常量装载收口 prompt-overlays.mjs
|
|
32
|
+
// (与子代理角色常量同源——单一权威锚 D1);本文件 re-export 保 import 面。
|
|
33
|
+
export {
|
|
34
|
+
PERSONA_ENGINEERING, PERSONA_NORMAL, COMMON,
|
|
35
|
+
DISCIPLINE_ENGINEERING, DISCIPLINE_NORMAL,
|
|
36
|
+
CONSULT_BASE,
|
|
37
|
+
} from "./prompt-overlays.mjs"
|
|
38
|
+
export { ENG_ON_REMINDER, ENG_OFF_REMINDER } from "./agent/helpers.mjs"
|
|
26
39
|
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const DISCIPLINE_RULES = readFileSync(join(__dirname, "prompts", "discipline.md"), "utf8")
|
|
31
|
-
const MAIN_OVERLAY = readFileSync(join(__dirname, "prompts", "main.md"), "utf8")
|
|
32
|
-
let _EXPLORE, _CODER, _PLAN, _ENG_CODER, _CONSULT_BASE
|
|
33
|
-
try { _EXPLORE = readFileSync(join(__dirname, "prompts", "explore.md"), "utf8") } catch { _EXPLORE = "" }
|
|
34
|
-
try { _CODER = readFileSync(join(__dirname, "prompts", "coder.md"), "utf8") } catch { _CODER = "" }
|
|
35
|
-
try { _PLAN = readFileSync(join(__dirname, "prompts", "plan.md"), "utf8") } catch { _PLAN = "" }
|
|
36
|
-
try { _ENG_CODER = readFileSync(join(__dirname, "prompts", "eng-coder.md"), "utf8") } catch { _ENG_CODER = "" }
|
|
37
|
-
try { _CONSULT_BASE = readFileSync(join(__dirname, "prompts", "consult-base.md"), "utf8") } catch { _CONSULT_BASE = "" }
|
|
38
|
-
export const EXPLORE_OVERLAY = _EXPLORE
|
|
39
|
-
export const CODER_OVERLAY = _CODER
|
|
40
|
-
export const PLAN_OVERLAY = _PLAN
|
|
41
|
-
export const ENG_CODER_OVERLAY = _ENG_CODER
|
|
42
|
-
export const CONSULT_BASE = _CONSULT_BASE
|
|
40
|
+
// Role → persona slot mapping note(施工② G3): 子代理人格由 assemblePrompt 场景表按
|
|
41
|
+
// role 承载(persona-explore/coder/plan/eng-coder.md——D1 表 = 蓝图 §3.2 1:1);
|
|
42
|
+
// spawn 侧 overlay 概念已退役(prompt-overlays.mjs 不再导出 OVERLAY 别名常量)。
|
|
43
43
|
|
|
44
44
|
// exported for consumption by agent-tools.mjs
|
|
45
45
|
export {
|
|
@@ -50,40 +50,9 @@ export {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
// Engineering mode reminder — shared with eng.mjs tool
|
|
54
|
-
export const ENG_ON_REMINDER =
|
|
55
|
-
"[System reminder: engineering mode is ON — design-before-code enforced. " +
|
|
56
|
-
"Workflow: Requirements doc → Design doc → advisor(type='design') → " +
|
|
57
|
-
"user approval → eng-coder implementation. Code changes go through eng-coder " +
|
|
58
|
-
"subagents only. Advisor calls are NOT per-turn-mandatory — call only at " +
|
|
59
|
-
"flow nodes or when the user asks.]"
|
|
60
|
-
|
|
61
53
|
// Re-exported for API compatibility (single source of truth: advisor/repos.mjs)
|
|
62
54
|
export { hasCodeMutations } from "./advisor/repos.mjs"
|
|
63
55
|
|
|
64
|
-
/** Engineering mode OFF reminder — shared with the eng tool and the injector. */
|
|
65
|
-
export const ENG_OFF_REMINDER =
|
|
66
|
-
"[System reminder: engineering mode is now OFF — standard discipline applies. " +
|
|
67
|
-
"Changes go through the normal workflow: you may edit files directly, advisor/verify " +
|
|
68
|
-
"guards apply per config.]"
|
|
69
|
-
|
|
70
|
-
/** Manual-tier auto-turn digest domain (AGENT-LOOP.md §17 D-S6): organize-only.
|
|
71
|
-
* Injected per manual auto-turn run — writes/execute/spawns/questions are also
|
|
72
|
-
* mechanically denied (no permission handler + spawn gate); this steers first. */
|
|
73
|
-
const AUTO_TURN_DIGEST_DOMAIN =
|
|
74
|
-
"[System reminder: auto-turn — background async subagents finished while there was no user message, and this turn runs automatically to digest their reports (the finished-report reminders above). No one is waiting for this reply, so organize only: 1) summarize each finished report's key points into this conversation for the user to read later; 2) update the task list with the task tool (allowed) to mark finished work done; 3) write decision points with a suggested next step as text — do not execute it. 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 summaries are written.]"
|
|
75
|
-
|
|
76
|
-
/** Engineering-mode status injection — one reminder on EVERY transition (2026-08-25:
|
|
77
|
-
* OFF is announced too — the model must know the gates lifted; silence after /eng-off
|
|
78
|
-
* left it guessing. Covers TUI /eng, resume, and any path bypassing the eng tool.) */
|
|
79
|
-
function injectEngineeringReminder(agent) {
|
|
80
|
-
const eng = agent.config?.agent?.engineering ?? false
|
|
81
|
-
if (eng !== agent._lastEngState) {
|
|
82
|
-
agent.history.push({ role: "user", content: eng ? ENG_ON_REMINDER : ENG_OFF_REMINDER, transient: true })
|
|
83
|
-
}
|
|
84
|
-
agent._lastEngState = eng
|
|
85
|
-
}
|
|
86
|
-
|
|
87
56
|
/** Create a new agent state object with all fields initialized to defaults */
|
|
88
57
|
export function createAgent({
|
|
89
58
|
provider, tools, config, cwd, memory, overlay, role,
|
|
@@ -97,8 +66,12 @@ export function createAgent({
|
|
|
97
66
|
planMode, autoApprove, goal,
|
|
98
67
|
_mutatedThisRun: false, _verifiedThisRun: false, _verifyPassed: undefined, _calledAdvisorThisRun: false,
|
|
99
68
|
_engDesignReviewed: false, // eng-coder: design review gate passed (hard gate in dispatch.mjs)
|
|
100
|
-
|
|
69
|
+
// DESIGN-TOKEN-SETTLEMENT D3 (2026-09-08): single-value `_engDesignToken` mirror retired
|
|
70
|
+
// (AC3 零写) — no field initializer; the multi-slot Map `_engDesignTokens` is the
|
|
71
|
+
// authoritative ledger (hydrated by restoreEngTokens / written by settle).
|
|
101
72
|
_touchedFiles: [], _verifyRetries: 0, _advisorRound: 0, _advisorSession: null,
|
|
73
|
+
_advisorRuns: new Map(), // §11.2 D-24b: per-review convergence instances (rounds/prior/designId)
|
|
74
|
+
_mutationSeq: 0, _mutLog: [], // §11.2 D-24b: mutation log (in-flight review staleness scan)
|
|
102
75
|
_lastAdvisorOutput: null, // full review output from the most recent advisor call (convergence rounds inject it verbatim)
|
|
103
76
|
_lastEngState: false,
|
|
104
77
|
_pendingReminders: [],
|
|
@@ -114,7 +87,7 @@ export function createAgent({
|
|
|
114
87
|
}
|
|
115
88
|
|
|
116
89
|
/** Run the agent loop: LLM ↔ tool-call cycle until task completion or turn limit. Returns final text content. */
|
|
117
|
-
export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false, autoTurn = false, suspDriven = false } = {}) {
|
|
90
|
+
export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal, maxTurns: overrideTurns, resume = false, autoTurn = false, suspDriven = false, consumeInjected = null } = {}) {
|
|
118
91
|
// Previous run's async exploration distillation must settle before this run pushes
|
|
119
92
|
// input (SEND-STALL-DISTILL §2.2 N1) — await first, or its history replace wipes it.
|
|
120
93
|
if (agent._pendingDistill) {
|
|
@@ -125,15 +98,25 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
125
98
|
// §17 D-S3: suspension-settled async results inject before EVERY run's prepareRun
|
|
126
99
|
// (user + auto-turn); spliced = consumed. collectSettledAsync owns a different
|
|
127
100
|
// container, so no double-inject across the two consumption points.
|
|
101
|
+
// ASYNC-RESULT-CONTAINER.md D2 (2026-09-08):pending 单容器 `_pendingAsyncResults`
|
|
102
|
+
// +role——四族(subagent/advisor/escalate/consult)统一停靠;注入器按 role 分发
|
|
103
|
+
// (consult → injectConsultResult;其余 → injectAsyncResult——族分支同 §25 D-R17a/b)
|
|
104
|
+
// ——单容器一处清,不再逐族三段。
|
|
128
105
|
const pendingAsync = agent._pendingAsyncResults
|
|
129
106
|
if (pendingAsync?.length) {
|
|
130
107
|
const { injectAsyncResult } = await import("./agent-tools/subagent.mjs")
|
|
131
|
-
|
|
108
|
+
const { injectConsultResult } = await import("./agent-tools/consult.mjs")
|
|
109
|
+
for (const e of pendingAsync.splice(0)) {
|
|
110
|
+
if (e.role === "consult") await injectConsultResult(agent, e)
|
|
111
|
+
else await injectAsyncResult(agent, e)
|
|
112
|
+
}
|
|
132
113
|
}
|
|
133
114
|
agent._inAutoTurn = autoTurn // spawn gate for manual-tier digests (§17 D-S6/N3)
|
|
134
115
|
const { maxTurns, threshold, tools, toolSchemas, toolByName, systemPrompt } = await prepareRun(
|
|
116
|
+
// G1/G2(施工②):prompt 装配收口 prepareRun 内部(assemblePrompt——prompt-overlays.mjs
|
|
117
|
+
// 槽位常量,与子代理角色常量同源——单一权威锚 D1);本调用不再携带 prompt 常量。
|
|
135
118
|
agent, input, callbacks,
|
|
136
|
-
{ depth, signal, overrideTurns, resume: resume || autoTurn
|
|
119
|
+
{ depth, signal, overrideTurns, resume: resume || autoTurn },
|
|
137
120
|
)
|
|
138
121
|
|
|
139
122
|
// Exploration-distillation boundary (CONTEXT-COMPACTION §5): prepareRun already
|
|
@@ -143,6 +126,10 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
143
126
|
// Per-run bookkeeping reset — PRESERVED on `resume` (ContinueError continuation):
|
|
144
127
|
// mutation/guard continuity and the convergence budget must survive a continuation.
|
|
145
128
|
if (!resume) {
|
|
129
|
+
// 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):链内累计编号
|
|
130
|
+
// 只在链起点复位——续跑(resume:true)不重置、不回退(编号帧公式见 helpers.mjs
|
|
131
|
+
// turnFrame)。与下方 mutation/guard 复位同条件同点(全档唯一复位点)。
|
|
132
|
+
agent._turnSeq = 0
|
|
146
133
|
// §17 D-S6: an auto-turn's guard marks are inherited by the next USER run (not
|
|
147
134
|
// reset) so auto-turn changes never escape the guard silently.
|
|
148
135
|
const g = agent._inheritedGuard
|
|
@@ -160,18 +147,19 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
160
147
|
agent._advisorSession = null // advisor session is per-run: discard when the task ends, next task starts fresh
|
|
161
148
|
agent._emptyRetries = 0 // empty-response retry budget is per-run: a fresh user turn restarts from zero
|
|
162
149
|
agent._compressFailures = 0 // compaction summary-failure counter is per-run: a fresh user turn restarts from zero
|
|
163
|
-
agent._asyncCheckLastN = 0 // check action read counter is per-run (§15 D-A2): a fresh user turn restarts from 1
|
|
164
150
|
}
|
|
165
151
|
}
|
|
166
152
|
// §17 D-S6 manual tier: digest action-domain reminder (system-driven turn — organize only).
|
|
167
153
|
if (autoTurn && !agent.autoApprove) {
|
|
168
154
|
agent.history.push({ role: "user", content: AUTO_TURN_DIGEST_DOMAIN, transient: true })
|
|
169
155
|
}
|
|
170
|
-
// eng-coder authorization is set by subagent.mjs
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
//
|
|
156
|
+
// eng-coder authorization (_engDesignReviewed) is eng-coder-only: set by subagent-spawn.mjs
|
|
157
|
+
// (spawn gate) / design-token.mjs (design review pass) BEFORE the child runAgent — the
|
|
158
|
+
// depth-0 parent never reads or writes it (the parent gate reads anyLiveDesignSlot; the
|
|
159
|
+
// depth-0 per-turn reset was removed 2026-09-08, ENG-SESSION-PROVIDER-CLEANUP D1.3).
|
|
160
|
+
// Design slots (_engDesignTokens Map) survive across turns (design review → approval →
|
|
161
|
+
// eng-coder spawn) — persisted to the session slot at settle time (DESIGN-TOKEN-
|
|
162
|
+
// SETTLEMENT D1); lifecycle: issued on a passing review, consumed by consume-design / TTL.
|
|
175
163
|
let guardPushbacks = 0
|
|
176
164
|
let advisorPushbacks = 0
|
|
177
165
|
let honestReminderInjected = false
|
|
@@ -194,64 +182,37 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
194
182
|
let thrownError = null
|
|
195
183
|
try {
|
|
196
184
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
185
|
+
// 第 19 批(TURN-ACROSS-SEGMENTS——设计 TURN-CAP-CONTINUE.md §19.3):编号帧——
|
|
186
|
+
// `_turnSeq` 每轮 +1(跨段累计,仅 `!resume` 链起点复位);面向消费面的两字段
|
|
187
|
+
// (状态行 + ⟦ev⟧turn / ⟦ev⟧approval 事件共用)在此同点赋值(编号唯一权威;帧
|
|
188
|
+
// 公式 = helpers.mjs turnFrame)。段内帽判定不读帧(下行循环条件只读段内
|
|
189
|
+
// turn / maxTurns——N6)。
|
|
190
|
+
const frame = turnFrame(++agent._turnSeq, turn, maxTurns)
|
|
191
|
+
agent._currentTurn = frame.turn
|
|
192
|
+
agent._maxTurns = frame.maxTurns
|
|
200
193
|
// D2 (AGENT-LOOP.md §7.2): depth>0 children emit a ⟦ev⟧turn progress token each turn —
|
|
201
194
|
// single emit point covering all three spawn tools; phase=llm (tool/done progress rides
|
|
202
|
-
// the onToolCall/onToolResult relay — no token for those).
|
|
195
|
+
// the onToolCall/onToolResult relay — no token for those). 第 19 批:载荷取上方帧值
|
|
196
|
+
// (agent._currentTurn / _maxTurns——字段形态 / 字段数 / phase 零变化,N5)。
|
|
203
197
|
if (depth > 0 && callbacks.onToken) {
|
|
204
|
-
callbacks.onToken(`⟦ev⟧turn\x1e${
|
|
198
|
+
callbacks.onToken(`⟦ev⟧turn\x1e${agent._currentTurn}\x1e${agent._maxTurns}\x1ellm\x1e`)
|
|
205
199
|
}
|
|
206
200
|
|
|
201
|
+
// SUBAGENT-OBSERVE-SEND D2: 子代理回合边界消费点——每轮开头把父侧经 subagent
|
|
202
|
+
// action:'send' 注入队列(entry._injected)的消息按普通 user 回合推入子历史
|
|
203
|
+
// (pushReal → 下一轮 chat 即含该指令)。由 executeAsyncSpawn 经 childRunOpts 贯通的
|
|
204
|
+
// consumeInjected 回调承载(异步子代理专属——缺省 null:主会话/阻塞子代理零开销)。
|
|
205
|
+
consumeInjected?.(agent)
|
|
206
|
+
|
|
207
207
|
const lastRole = agent.history.at(-1)?.role
|
|
208
208
|
if (lastRole === "user" || lastRole === "tool") {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
agent._planReminderAtLen = 0 // After compression history shrinks, reset cadence so reminders resume
|
|
213
|
-
recentCallSigs.length = 0 // After compression history is rebuilt, reset stall detection counter
|
|
214
|
-
// Completion info (CONTEXT-COMPACTION §7 D-C2): { mode, tokensFreed, elapsedMs } —
|
|
215
|
-
// the TUI panel renders it; callers that ignore the arg keep prior onCompress semantics.
|
|
216
|
-
callbacks.onCompress?.(agent._lastCompressInfo ?? {})
|
|
217
|
-
ensureAutoReminder(agent)
|
|
218
|
-
}
|
|
219
|
-
} catch (compressError) {
|
|
220
|
-
// AbortError must not be swallowed: user cancellation must propagate
|
|
221
|
-
if (compressError?.name === "AbortError" || signal?.aborted) throw compressError
|
|
222
|
-
agent._compressFailures = (agent._compressFailures ?? 0) + 1
|
|
223
|
-
// Q3 (CONTEXT-COMPACTION §7 D-C1): a failed compression is surfaced to the panel;
|
|
224
|
-
// COMPRESS_FAILURE_LIMIT consecutive failures still degrade to compressFallback.
|
|
225
|
-
callbacks?.onCompressFail?.(compressError)
|
|
226
|
-
if (agent._compressFailures >= COMPRESS_FAILURE_LIMIT) {
|
|
227
|
-
agent._compressFailures = 0
|
|
228
|
-
if (compressFallback(agent)) callbacks.onCompress?.(agent._lastCompressInfo ?? {})
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Plan-mode reminder cadence: re-inject constraint reminders while plan mode is active
|
|
234
|
-
// (sparse every 2 turns, full every 5 / on new user message) so the restriction never fades.
|
|
235
|
-
if (agent.planMode) {
|
|
236
|
-
const lastMsg = agent.history.at(-1)
|
|
237
|
-
const realUserMsg = lastMsg?.role === "user"
|
|
238
|
-
&& typeof lastMsg.content === "string"
|
|
239
|
-
&& !lastMsg.content.startsWith("[System reminder:")
|
|
240
|
-
&& !lastMsg.content.startsWith("[User interrupt:")
|
|
241
|
-
const newUserSince = realUserMsg && agent.history.length > (agent._planReminderAtLen ?? 0)
|
|
242
|
-
const { planReminderForTurn } = await import("./agent-tools/plan.mjs")
|
|
243
|
-
const reminder = planReminderForTurn(agent, newUserSince)
|
|
244
|
-
if (reminder) {
|
|
245
|
-
agent._planReminderAtLen = agent.history.length + 1
|
|
246
|
-
agent.history.push({ role: "user", content: reminder, transient: true })
|
|
247
|
-
}
|
|
209
|
+
// 2026-09-05 实践轮:压缩检查/降级计数提为 runCompactionCheck(agent/run-stages.mjs——
|
|
210
|
+
// CLI 对位 VS run-stages)——循环骨架此处只剩检查调用(recentCallSigs 对象引用回流)。
|
|
211
|
+
await runCompactionCheck(agent, { threshold, callbacks, compactionOverhead, signal, recentCallSigs })
|
|
248
212
|
}
|
|
249
213
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
if (depth === 0) {
|
|
253
|
-
injectEngineeringReminder(agent)
|
|
254
|
-
}
|
|
214
|
+
// 2026-09-05 实践轮:plan cadence + eng 状态注入提为 injectTurnReminders(run-stages)
|
|
215
|
+
await injectTurnReminders(agent, { depth })
|
|
255
216
|
|
|
256
217
|
const messages = [{ role: "system", content: systemPrompt }, ...agent.history]
|
|
257
218
|
let response
|
|
@@ -327,15 +288,9 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
327
288
|
continue
|
|
328
289
|
}
|
|
329
290
|
|
|
330
|
-
// Stream rule warnings
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
const deDuplicated = [...new Map(response._warnings.map(w => [w.name || w.pattern, w])).values()]
|
|
334
|
-
agent.history.push({
|
|
335
|
-
role: "user",
|
|
336
|
-
content: `[System reminder — stream rule warnings from your last response:\n${deDuplicated.map(w => `- ${w.name || w.pattern}: ${w.message}`).join("\n")}]`,
|
|
337
|
-
})
|
|
338
|
-
}
|
|
291
|
+
// Stream rule warnings / finish-reason 警告(2026-09-05 实践轮——提为
|
|
292
|
+
// injectResponseReminders,agent/run-stages.mjs——verbatim,语义零变)
|
|
293
|
+
injectResponseReminders(agent, response)
|
|
339
294
|
|
|
340
295
|
// User interrupted mid-generation (Ctrl+I): commit partial output + inject the
|
|
341
296
|
// message, then signal the outer loop to recreate the controller and resume.
|
|
@@ -347,7 +302,8 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
347
302
|
role: "user",
|
|
348
303
|
content: `[User interrupt: ${response.interruptMessage}]`,
|
|
349
304
|
})
|
|
350
|
-
|
|
305
|
+
// §20.3 站点 #8(第 24 批):错误对象已自带 name/message——只补来源标注(缺 abortInfo 才补)
|
|
306
|
+
throw annotateAbort(Object.assign(new Error("User interrupted"), { name: "AbortError" }), signal, "agent", "interrupted-response")
|
|
351
307
|
}
|
|
352
308
|
|
|
353
309
|
if (response.usage) {
|
|
@@ -359,18 +315,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
359
315
|
}
|
|
360
316
|
|
|
361
317
|
// Warn on abnormal finish reasons — the response may be incomplete/truncated.
|
|
362
|
-
|
|
363
|
-
const reasonMap = {
|
|
364
|
-
length: "output token limit reached after exhausting continuations",
|
|
365
|
-
insufficient_system_resource: "provider inference resources exhausted — consider retrying or switching models",
|
|
366
|
-
content_filter: "response blocked by provider content filtering",
|
|
367
|
-
}
|
|
368
|
-
const detail = reasonMap[response.finishReason] || `unknown reason "${response.finishReason}"`
|
|
369
|
-
agent.history.push({
|
|
370
|
-
role: "user",
|
|
371
|
-
content: `[System reminder: the previous turn ended abnormally — ${detail}. The assistant response that follows may be incomplete.]`,
|
|
372
|
-
})
|
|
373
|
-
}
|
|
318
|
+
// 2026-09-05 实践轮:finish-reason 注入随流规则警告提为 injectResponseReminders。
|
|
374
319
|
|
|
375
320
|
if (response.toolCalls.length === 0) {
|
|
376
321
|
const cr = handleCompletion(agent, response, depth, turn, guardPushbacks, honestReminderInjected, advisorPushbacks, callbacks)
|
|
@@ -390,7 +335,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
390
335
|
}
|
|
391
336
|
|
|
392
337
|
// abort after chat completes, before committing history: don't commit a half-finished turn
|
|
393
|
-
if (signal?.aborted) throw
|
|
338
|
+
if (signal?.aborted) throw abortError(signal, "agent", "post-chat")
|
|
394
339
|
|
|
395
340
|
pushReal(agent, {
|
|
396
341
|
role: "assistant",
|
|
@@ -412,6 +357,9 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
412
357
|
// 中断变更记账(2026-08-31 评审 #4):此分支的工具已全部执行完成(磁盘已变,execute 已完成),
|
|
413
358
|
// 真实结果按语义不进历史(placeholder 替代)——但变更必须记账:否则 guard 看到
|
|
414
359
|
// "本轮未改代码" 放行,评审/verify 门禁被绕过(文件改了却没评审)。
|
|
360
|
+
// §29 fix A(2026-09-07):mutation-seq 记账已收敛到 dispatch runOne 执行成功即刻
|
|
361
|
+
// (唯一记账点——本分支不再 noteMutations——不双计——中断+同批 launch seq 单计
|
|
362
|
+
// 回归断言见 §29 T-A1i);此处仅剩 guard 标志 + touchedFiles 记账。
|
|
415
363
|
for (const { toolCall, ok } of results) {
|
|
416
364
|
const tool = toolByName.get(toolCall.name)
|
|
417
365
|
if (!ok || !tool || !FILE_MUTATORS.has(toolCall.name)) continue
|
|
@@ -423,7 +371,7 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
423
371
|
const args = JSON.parse(toolCall.arguments)
|
|
424
372
|
const paths = tool.touchedPaths ? tool.touchedPaths(args) : [args.path]
|
|
425
373
|
for (const p of paths) {
|
|
426
|
-
const abs =
|
|
374
|
+
const abs = resolve(agent.cwd, p)
|
|
427
375
|
if (!agent._touchedFiles.includes(abs)) agent._touchedFiles.push(abs)
|
|
428
376
|
}
|
|
429
377
|
} catch { /* 畸形 args 不影响记账(touchedFiles 尽力而为) */ }
|
|
@@ -457,73 +405,9 @@ export async function runAgent(agent, input, callbacks = {}, { depth = 0, signal
|
|
|
457
405
|
thrownError = e
|
|
458
406
|
throw e
|
|
459
407
|
} finally {
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// - Ctrl+C (plain abort): children were aborted with the parent signal — clear
|
|
465
|
-
// WITHOUT injecting stale errors (user explicitly stopped). Ctrl+I (interrupt)
|
|
466
|
-
// keeps the pool: the turn resumes with the interrupt message, children stay
|
|
467
|
-
// tracked (in a suspension session children hold agent._sessionSignal and a
|
|
468
|
-
// digest's own Ctrl+I must not orphan them).
|
|
469
|
-
// - ContinueError (turn cap): no wait, no injection — children keep running and
|
|
470
|
-
// the RESUME run's turn-end collection takes over.
|
|
471
|
-
// - anything else: inject the SETTLED entries only; running/queued stay in the
|
|
472
|
-
// pool for the suspension session (D-S1 — no allSettled turn-end wait).
|
|
473
|
-
if (signal?.aborted && !signal?.reason?.interrupt) {
|
|
474
|
-
if (agent._asyncSubagents?.size > 0) {
|
|
475
|
-
logEvent("ev:stopped", { poolN: agent._asyncSubagents?.size ?? 0, where: "turn-end-abort" })
|
|
476
|
-
}
|
|
477
|
-
agent._asyncSubagents?.clear()
|
|
478
|
-
agent._asyncQueue = []
|
|
479
|
-
agent._asyncCheckLastN = 0
|
|
480
|
-
} else if (thrownError instanceof ContinueError) {
|
|
481
|
-
// keep _asyncSubagents + the check counter — the resumed run continues them
|
|
482
|
-
} else {
|
|
483
|
-
await collectSettledAsync(agent, { suspDriven })
|
|
484
|
-
agent._asyncCheckLastN = 0
|
|
485
|
-
}
|
|
486
|
-
agent._inAutoTurn = false
|
|
487
|
-
// §17 D-S6: auto-turn guard marks survive into the next USER run (restored at its
|
|
488
|
-
// !resume reset above). Normal ends only — abort discards; ContinueError lets the
|
|
489
|
-
// auto-resumed run snapshot at its own end.
|
|
490
|
-
if (autoTurn && !(signal?.aborted && !signal?.reason?.interrupt) && !(thrownError instanceof ContinueError)) {
|
|
491
|
-
agent._inheritedGuard = {
|
|
492
|
-
_mutatedThisRun: agent._mutatedThisRun, _verifiedThisRun: agent._verifiedThisRun,
|
|
493
|
-
_verifyPassed: agent._verifyPassed, _calledAdvisorThisRun: agent._calledAdvisorThisRun,
|
|
494
|
-
_touchedFiles: agent._touchedFiles, _verifyRetries: agent._verifyRetries,
|
|
495
|
-
_advisorRound: agent._advisorRound,
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Turn-end async subagent collection (AGENT-LOOP.md §17 D-S1 + §17.5 supersede):
|
|
503
|
-
* two modes, selected by the caller's driver context (17.5.2/17.5.4 #2):
|
|
504
|
-
* - suspDriven=false (fallback — headless/direct runAgent callers without a
|
|
505
|
-
* suspension driver): inject every entry that SETTLED during this run
|
|
506
|
-
* (XML-escaped report/error — child reports may carry file/webpage content;
|
|
507
|
-
* >64K offloaded with preview + path) and remove it from the pool. Running/
|
|
508
|
-
* queued STAY — no allSettled wait. Results never lost without a session.
|
|
509
|
-
* - suspDriven=true (the interaction layer runs suspensionSession after this
|
|
510
|
-
* run): NO direct inject — settled entries STAY pooled (settled not consumed)
|
|
511
|
-
* for the session's first sweepSettledToPending → digest turn (§17.5 — done
|
|
512
|
-
* 条目留池等消化轮注入;check/status 在 sweep 前仍从池读——17.5.2 不变面)。
|
|
513
|
-
* maybeRefillAsync runs in both modes (starts queued heads whose slot freed).
|
|
514
|
-
* Single ownership: entries settled inside a suspension session were moved to
|
|
515
|
-
* _pendingAsyncResults by the settle callback, so this only sees user-turn
|
|
516
|
-
* settles (no double inject — D-S3 points ①/②). The ⟦ev⟧done freeze is NOT
|
|
517
|
-
* emitted here — each settle callback emits it (§15 D-A3). */
|
|
518
|
-
async function collectSettledAsync(agent, { suspDriven = false } = {}) {
|
|
519
|
-
const map = agent._asyncSubagents
|
|
520
|
-
if (!map || map.size === 0) return
|
|
521
|
-
const { maybeRefillAsync, injectAsyncResult } = await import("./agent-tools/subagent.mjs")
|
|
522
|
-
maybeRefillAsync(agent) // start queued heads now that slots may have freed — no waiting
|
|
523
|
-
if (suspDriven) return // §17.5: settled stays pooled — the suspension session digests it
|
|
524
|
-
for (const e of [...map.values()]) {
|
|
525
|
-
if (!e.done) continue // still running — stays in the pool (D-S1)
|
|
526
|
-
await injectAsyncResult(agent, e)
|
|
527
|
-
map.delete(String(e.id))
|
|
408
|
+
// 2026-09-05 实践轮:回合收尾(consult 清理/async 池分流/guard 继承——原 425-462
|
|
409
|
+
// 段 + collectSettledAsync 466-494 整体迁 agent/run-stages.mjs finalizeAgentTurn——
|
|
410
|
+
// CLI 对位 VS run-stages——finally 只剩一行调用 + 骨架注释)。
|
|
411
|
+
await finalizeAgentTurn(agent, { signal, autoTurn, suspDriven, thrownError, depth })
|
|
528
412
|
}
|
|
529
413
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path"
|
|
1
|
+
import { isAbsolute, join } from "node:path"
|
|
2
2
|
import { loadConfig, configPath } from "../config.mjs"
|
|
3
3
|
import { createMemory } from "../memory.mjs"
|
|
4
4
|
import { teamConfig, gitAuthor } from "./make-agent.mjs"
|
|
@@ -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")
|
|
@@ -58,15 +64,15 @@ export async function distillCommand(args, exitSoon) {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
const opts = {
|
|
61
|
-
projectDir: config.memory.projectDir ? join(process.cwd(), config.memory.projectDir) : null,
|
|
67
|
+
projectDir: config.memory.projectDir ? (isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)) : null,
|
|
62
68
|
team,
|
|
63
69
|
author: gitAuthor(),
|
|
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 类知识通常建议手动撰写;确认提取吗?)")
|
package/src/cli/make-agent.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from "node:child_process"
|
|
2
|
-
import { join } from "node:path"
|
|
2
|
+
import { isAbsolute, join } from "node:path"
|
|
3
3
|
import { createAgent } from "../agent.mjs"
|
|
4
4
|
import { loadConfig, configDir } from "../config.mjs"
|
|
5
5
|
import { createMemory, memoryTools, syncDir, codeSearchTool, docSearchTool } from "../memory.mjs"
|
|
@@ -7,9 +7,21 @@ import { settingsTool } from "../agent-tools/settings.mjs"
|
|
|
7
7
|
import { repoOutlineTool } from "../tools/repomap.mjs"
|
|
8
8
|
import { builtinTools } from "../tools/index.mjs"
|
|
9
9
|
import { discoverRules } from "../rules.mjs"
|
|
10
|
+
// R10 L2(MULTI-INSTANCE-COLLAB §2a.4 D-L2b):peer_instances 只读工具——挂感知模块导出
|
|
11
|
+
import { peerInstancesTool } from "../peer-instances.mjs"
|
|
12
|
+
|
|
13
|
+
/** 第 27 批 §12.3⑤(R-A1.1):装配期工具剔除——按 `name` 过滤的纯函数(机验锚)。
|
|
14
|
+
* 恒等语义:空列表 / 零命中 → 原数组原样返回(零意外剔除——T15)。
|
|
15
|
+
* ACP 通道经 `assembleAgent({ excludeTools })` 传入(acp.mjs `ACP_EXCLUDED_TOOLS`)。 */
|
|
16
|
+
export function applyToolExclusions(tools, excludeTools = []) {
|
|
17
|
+
const excluded = new Set(excludeTools ?? [])
|
|
18
|
+
if (excluded.size === 0) return tools
|
|
19
|
+
const kept = tools.filter((t) => !excluded.has(t?.name))
|
|
20
|
+
return kept.length === tools.length ? tools : kept
|
|
21
|
+
}
|
|
10
22
|
|
|
11
23
|
/** Assemble an agent with memory, MCP tools, and code/doc indices attached (sync all layers, then return) */
|
|
12
|
-
export async function assembleAgent() {
|
|
24
|
+
export async function assembleAgent({ excludeTools = [] } = {}) {
|
|
13
25
|
const config = loadConfig()
|
|
14
26
|
const provider = config.provider
|
|
15
27
|
const providers = config.providersList
|
|
@@ -18,7 +30,7 @@ export async function assembleAgent() {
|
|
|
18
30
|
const { injectProxy } = await import("../proxy.mjs")
|
|
19
31
|
injectProxy(providers, config)
|
|
20
32
|
// config.provider 是 loadConfig 里的独立拷贝,同步注入结果
|
|
21
|
-
provider.proxyUri = providers.find((p) => p.name ===
|
|
33
|
+
if (provider?.name) provider.proxyUri = providers.find((p) => p.name === provider.name)?.proxyUri
|
|
22
34
|
|
|
23
35
|
const memory = createMemory({ dbPath: config.memory.dbPath })
|
|
24
36
|
// Vector retrieval: enabled if embedding is configured (lazy vector generation, computed on first search)
|
|
@@ -39,7 +51,7 @@ export async function assembleAgent() {
|
|
|
39
51
|
memory.codeOrigin = cwd
|
|
40
52
|
// Project layer: sync .thincoder/memory/ dir to index on startup (sync if present, skip otherwise)
|
|
41
53
|
if (config.memory.projectDir) {
|
|
42
|
-
memory.projectOrigin = join(cwd, config.memory.projectDir)
|
|
54
|
+
memory.projectOrigin = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(cwd, config.memory.projectDir)
|
|
43
55
|
await syncDir(memory, { layer: "project", dir: memory.projectOrigin })
|
|
44
56
|
}
|
|
45
57
|
// Team layer (optional): auto-clone on first use; startup only indexes local dir, remote pull via explicit thincoder sync
|
|
@@ -49,7 +61,7 @@ export async function assembleAgent() {
|
|
|
49
61
|
await ensureClone(team)
|
|
50
62
|
await syncDir(memory, { layer: "team", dir: team.dir })
|
|
51
63
|
}
|
|
52
|
-
const baseTools = [...builtinTools, ...memoryTools(memory, { cwd, projectDir: config.memory.projectDir, author: gitAuthor(), team }), codeSearchTool(memory), docSearchTool(memory), repoOutlineTool(memory.db, cwd), settingsTool()]
|
|
64
|
+
const baseTools = [...builtinTools, ...memoryTools(memory, { cwd, projectDir: config.memory.projectDir, author: gitAuthor(), team }), codeSearchTool(memory), docSearchTool(memory), repoOutlineTool(memory.db, cwd), settingsTool(), peerInstancesTool]
|
|
53
65
|
|
|
54
66
|
// MCP servers: connect in parallel (a dead server won't block startup), collect failures as warnings (stderr invisible in TUI, passed via agent object)
|
|
55
67
|
const mcpServers = config.mcp?.servers ?? []
|
|
@@ -99,18 +111,24 @@ export async function assembleAgent() {
|
|
|
99
111
|
|
|
100
112
|
const agent = createAgent({
|
|
101
113
|
provider,
|
|
102
|
-
tools: [...baseTools, ...mcpTools],
|
|
114
|
+
tools: applyToolExclusions([...baseTools, ...mcpTools], excludeTools),
|
|
103
115
|
config,
|
|
104
116
|
cwd,
|
|
105
117
|
memory,
|
|
106
118
|
})
|
|
107
119
|
agent.providers = providers
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
// MODEL-MERGE-SESSION:config 无 active*——会话运行时起点 = defaultModel 复合解析
|
|
121
|
+
// (provider 对象带解析后 model——API/spec 消费点零改);无效 → {} + 原因走 D-S1 标记
|
|
122
|
+
agent.activeProvider = provider.name ?? ""
|
|
123
|
+
agent.activeModel = provider.model ?? null
|
|
110
124
|
agent._mcpWarnings = mcpWarnings
|
|
111
125
|
// SESSION.md §8 D-S1:assembleAgent 后唯一校验点(TUI/chat 两路径同源)——不抛错不退出,
|
|
112
126
|
// 标记由调用侧消费(TUI 弹重选 / headless 报错)。空 provider 由 TUI 路径在 startTUI 前清空。
|
|
113
127
|
validateProvider(agent)
|
|
128
|
+
// defaultModel 无效/未设的具体原因覆盖通用判据文案(providers 存在时更有指导性)
|
|
129
|
+
if (agent._providerInvalid && config.providerInvalidReason) {
|
|
130
|
+
agent._providerInvalidReason = config.providerInvalidReason
|
|
131
|
+
}
|
|
114
132
|
return agent
|
|
115
133
|
}
|
|
116
134
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { join } from "node:path"
|
|
1
|
+
import { isAbsolute, join } from "node:path"
|
|
2
2
|
import { loadConfig } from "../config.mjs"
|
|
3
3
|
import { teamConfig } from "./make-agent.mjs"
|
|
4
|
-
import { put, search, list
|
|
4
|
+
import { put, search, list } from "../memory/core.mjs"
|
|
5
|
+
import { deleteByUid } from "../memory/delete.mjs"
|
|
5
6
|
|
|
6
7
|
/** thincoder memory <list|search|put|remove> subcommands.
|
|
7
8
|
* opts.dirs: { project, team } layer directories for project/team file deletion (tests inject their own);
|
|
@@ -66,7 +67,7 @@ export async function memoryCommand(memory, args, opts = {}) {
|
|
|
66
67
|
function cliDirs() {
|
|
67
68
|
const config = loadConfig()
|
|
68
69
|
const dirs = { project: null, team: null }
|
|
69
|
-
if (config.memory?.projectDir) dirs.project = join(process.cwd(), config.memory.projectDir)
|
|
70
|
+
if (config.memory?.projectDir) dirs.project = isAbsolute(config.memory.projectDir) ? config.memory.projectDir : join(process.cwd(), config.memory.projectDir)
|
|
70
71
|
const team = teamConfig(config)
|
|
71
72
|
if (team) dirs.team = team.dir
|
|
72
73
|
return dirs
|
package/src/cli/permission.mjs
CHANGED
|
@@ -23,8 +23,8 @@ export function formatPermission(name, args) {
|
|
|
23
23
|
// §6 action-routed preview: put shows content, batch delete/clear show the gate args
|
|
24
24
|
const action = String(args.action ?? "")
|
|
25
25
|
if (action === "put") return `[${args.type ?? ""}] ${args.title ?? ""}\n${cap(args.content ?? "", 500)}`
|
|
26
|
-
if (action === "delete") return args.id ? `id=${args.id}
|
|
27
|
-
if (action === "clear") return `clear
|
|
26
|
+
if (action === "delete") return args.id ? `id=${args.id}${args.layer ? ` layer=${args.layer}` : ""}` : `batch delete layer=${args.layer ?? ""} type=${args.type ?? ""} keyword=${args.keyword ?? ""} confirm=${args.confirm}`
|
|
27
|
+
if (action === "clear") return `clear layer=${args.layer ?? ""} confirm=${args.confirm}`
|
|
28
28
|
return cap(summarize(args), 300)
|
|
29
29
|
}
|
|
30
30
|
return cap(summarize(args), 300)
|