thincoder 0.12.62 → 0.12.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +13 -12
  3. package/bin/thincoder.mjs +53 -27
  4. package/package.json +6 -5
  5. package/src/acp/bridge.mjs +35 -15
  6. package/src/acp/client-caps.mjs +86 -0
  7. package/src/acp/ext.mjs +86 -0
  8. package/src/acp/handlers-session.mjs +240 -0
  9. package/src/acp/handlers-slots.mjs +196 -0
  10. package/src/acp/login.mjs +48 -0
  11. package/src/acp/session.mjs +6 -4
  12. package/src/acp.mjs +67 -379
  13. package/src/cli/distill-command.mjs +3 -3
  14. package/src/cli/make-agent.mjs +59 -17
  15. package/src/cli/memory-command.mjs +3 -3
  16. package/src/cli/permission.mjs +4 -48
  17. package/src/cli/setup-wizard.mjs +1 -1
  18. package/src/completions.mjs +3 -1
  19. package/src/crash-reports.mjs +1 -1
  20. package/src/distill.mjs +4 -4
  21. package/src/heap-watch.mjs +1 -1
  22. package/src/prompt-injections.mjs +20 -0
  23. package/src/tui/agent-turn.mjs +40 -9
  24. package/src/tui/cmd-advisor.mjs +5 -5
  25. package/src/tui/cmd-config.mjs +8 -8
  26. package/src/tui/cmd-eng.mjs +25 -9
  27. package/src/tui/cmd-mcp.mjs +9 -8
  28. package/src/tui/cmd-model.mjs +1 -1
  29. package/src/tui/cmd-new.mjs +6 -5
  30. package/src/tui/cmd-reindex.mjs +1 -1
  31. package/src/tui/cmd-restore.mjs +2 -2
  32. package/src/tui/cmd-session.mjs +24 -1
  33. package/src/tui/cmd-skills.mjs +1 -1
  34. package/src/tui/cmd-think.mjs +22 -9
  35. package/src/tui/config-helpers.mjs +1 -1
  36. package/src/tui/display-budget.mjs +33 -11
  37. package/src/tui/index.mjs +9 -9
  38. package/src/tui/interaction.mjs +16 -7
  39. package/src/tui/key-modes.mjs +9 -4
  40. package/src/tui/ledger-surface.mjs +26 -10
  41. package/src/tui/model-catalog.mjs +4 -4
  42. package/src/tui/model-picker.mjs +8 -7
  43. package/src/tui/mouse.mjs +11 -6
  44. package/src/tui/pickers.mjs +15 -2
  45. package/src/tui/render-conversation.mjs +1 -1
  46. package/src/tui/render-frame.mjs +10 -5
  47. package/src/tui/render-loop.mjs +1 -1
  48. package/src/tui/render-segments.mjs +3 -1
  49. package/src/tui/slash-commands.mjs +1 -1
  50. package/src/tui/startup.mjs +14 -14
  51. package/src/tui/subagent-blocks.mjs +20 -3
  52. package/src/tui/subagent-freeze.mjs +73 -2
  53. package/src/tui/suspension-drive.mjs +46 -22
  54. package/src/tui/tool-events.mjs +11 -8
  55. package/src/tui/wizard.mjs +3 -3
  56. package/src/abort-provenance.mjs +0 -116
  57. package/src/advisor/citations.mjs +0 -139
  58. package/src/advisor/compaction.mjs +0 -174
  59. package/src/advisor/convergence.mjs +0 -80
  60. package/src/advisor/history.mjs +0 -77
  61. package/src/advisor/loop.mjs +0 -293
  62. package/src/advisor/messages.mjs +0 -299
  63. package/src/advisor/project-context.mjs +0 -194
  64. package/src/advisor/repos.mjs +0 -150
  65. package/src/advisor/run.mjs +0 -293
  66. package/src/advisor/truncate.mjs +0 -57
  67. package/src/advisor.mjs +0 -290
  68. package/src/agent/completion.mjs +0 -146
  69. package/src/agent/dispatch.mjs +0 -489
  70. package/src/agent/helpers.mjs +0 -384
  71. package/src/agent/post-turn.mjs +0 -70
  72. package/src/agent/record-results.mjs +0 -174
  73. package/src/agent/relay-prefix.mjs +0 -39
  74. package/src/agent/run-stages.mjs +0 -244
  75. package/src/agent/setup-reminders.mjs +0 -69
  76. package/src/agent/setup.mjs +0 -354
  77. package/src/agent/spawn-child.mjs +0 -243
  78. package/src/agent-tools/advisor-async.mjs +0 -346
  79. package/src/agent-tools/advisor-settle.mjs +0 -231
  80. package/src/agent-tools/advisor.mjs +0 -260
  81. package/src/agent-tools/async-settle.mjs +0 -204
  82. package/src/agent-tools/batch-segment.mjs +0 -195
  83. package/src/agent-tools/consult.mjs +0 -473
  84. package/src/agent-tools/design-token.mjs +0 -117
  85. package/src/agent-tools/digest-budget.mjs +0 -76
  86. package/src/agent-tools/eng.mjs +0 -67
  87. package/src/agent-tools/escalate-async.mjs +0 -295
  88. package/src/agent-tools/goal.mjs +0 -119
  89. package/src/agent-tools/plan.mjs +0 -81
  90. package/src/agent-tools/read-history.mjs +0 -309
  91. package/src/agent-tools/recent-changes.mjs +0 -24
  92. package/src/agent-tools/review-streak.mjs +0 -93
  93. package/src/agent-tools/settings.mjs +0 -265
  94. package/src/agent-tools/skill.mjs +0 -47
  95. package/src/agent-tools/subagent-actions.mjs +0 -482
  96. package/src/agent-tools/subagent-async.mjs +0 -434
  97. package/src/agent-tools/subagent-panel.mjs +0 -160
  98. package/src/agent-tools/subagent-run.mjs +0 -205
  99. package/src/agent-tools/subagent-scheduler.mjs +0 -392
  100. package/src/agent-tools/subagent-spawn.mjs +0 -459
  101. package/src/agent-tools/subagent.mjs +0 -404
  102. package/src/agent-tools/task.mjs +0 -87
  103. package/src/agent-tools/timer.mjs +0 -46
  104. package/src/agent-tools/verify.mjs +0 -271
  105. package/src/agent-tools.mjs +0 -17
  106. package/src/agent.mjs +0 -417
  107. package/src/auto-think.mjs +0 -115
  108. package/src/config-migrate.mjs +0 -70
  109. package/src/config.mjs +0 -496
  110. package/src/context.mjs +0 -392
  111. package/src/conventions.mjs +0 -223
  112. package/src/embedding.mjs +0 -120
  113. package/src/escape.mjs +0 -152
  114. package/src/expand-home.mjs +0 -16
  115. package/src/explore-distill.mjs +0 -155
  116. package/src/generate-title.mjs +0 -88
  117. package/src/git/checkpoint.mjs +0 -448
  118. package/src/git/gitmem.mjs +0 -100
  119. package/src/hooks.mjs +0 -97
  120. package/src/ledger.mjs +0 -227
  121. package/src/log.mjs +0 -195
  122. package/src/markdown.mjs +0 -106
  123. package/src/mcp/helpers.mjs +0 -51
  124. package/src/mcp/transport-http.mjs +0 -248
  125. package/src/mcp/transport-stdio.mjs +0 -140
  126. package/src/mcp/transport-ws.mjs +0 -122
  127. package/src/mcp.mjs +0 -295
  128. package/src/memory/code-index.mjs +0 -219
  129. package/src/memory/code-sync.mjs +0 -415
  130. package/src/memory/core.mjs +0 -299
  131. package/src/memory/delete.mjs +0 -236
  132. package/src/memory/docs.mjs +0 -419
  133. package/src/memory/file-walk.mjs +0 -109
  134. package/src/memory/scan.mjs +0 -95
  135. package/src/memory/schema.mjs +0 -452
  136. package/src/memory.mjs +0 -21
  137. package/src/model-ref.mjs +0 -66
  138. package/src/model-specs.mjs +0 -179
  139. package/src/peer-domains.mjs +0 -265
  140. package/src/peer-instances.mjs +0 -231
  141. package/src/prompt-overlays.mjs +0 -82
  142. package/src/prompts/advisor-design.md +0 -41
  143. package/src/prompts/advisor-round1.md +0 -41
  144. package/src/prompts/advisor-round2.md +0 -46
  145. package/src/prompts/advisor-round3.md +0 -42
  146. package/src/prompts/common.md +0 -115
  147. package/src/prompts/consult-base.md +0 -19
  148. package/src/prompts/discipline-engineering.md +0 -258
  149. package/src/prompts/discipline-normal.md +0 -185
  150. package/src/prompts/persona-coder.md +0 -21
  151. package/src/prompts/persona-eng-coder.md +0 -37
  152. package/src/prompts/persona-eng-designer.md +0 -60
  153. package/src/prompts/persona-engineering.md +0 -55
  154. package/src/prompts/persona-explore.md +0 -15
  155. package/src/prompts/persona-normal.md +0 -27
  156. package/src/prompts/persona-plan.md +0 -26
  157. package/src/provider/anthropic.mjs +0 -225
  158. package/src/provider/core.mjs +0 -476
  159. package/src/provider/errors.mjs +0 -101
  160. package/src/provider/google.mjs +0 -257
  161. package/src/provider/index.mjs +0 -7
  162. package/src/provider/list-models.mjs +0 -93
  163. package/src/provider/normalize.mjs +0 -81
  164. package/src/provider/rate.mjs +0 -108
  165. package/src/provider/responses.mjs +0 -495
  166. package/src/provider/retry.mjs +0 -88
  167. package/src/provider/sse.mjs +0 -264
  168. package/src/proxy.mjs +0 -261
  169. package/src/rules.mjs +0 -53
  170. package/src/session-gc.mjs +0 -221
  171. package/src/session-guard.mjs +0 -59
  172. package/src/session-migrate.mjs +0 -48
  173. package/src/session-rename.mjs +0 -38
  174. package/src/session-segments.mjs +0 -100
  175. package/src/session-slots.mjs +0 -492
  176. package/src/session-store.mjs +0 -441
  177. package/src/session.mjs +0 -492
  178. package/src/skills.mjs +0 -153
  179. package/src/text-budget.mjs +0 -46
  180. package/src/token-ttl.mjs +0 -274
  181. package/src/tools/apply_patch.md +0 -15
  182. package/src/tools/bash.md +0 -37
  183. package/src/tools/bash.mjs +0 -268
  184. package/src/tools/checklist-sync.mjs +0 -181
  185. package/src/tools/checklist.md +0 -13
  186. package/src/tools/checklist.mjs +0 -299
  187. package/src/tools/delete.md +0 -13
  188. package/src/tools/edit-batch.mjs +0 -191
  189. package/src/tools/edit-diff.mjs +0 -348
  190. package/src/tools/edit.md +0 -30
  191. package/src/tools/execute.md +0 -21
  192. package/src/tools/execute.mjs +0 -228
  193. package/src/tools/fetch.md +0 -12
  194. package/src/tools/file.mjs +0 -469
  195. package/src/tools/file_ops.md +0 -17
  196. package/src/tools/get_current_time.md +0 -8
  197. package/src/tools/git-checkpoint.mjs +0 -143
  198. package/src/tools/git-ext.mjs +0 -173
  199. package/src/tools/git.md +0 -54
  200. package/src/tools/git.mjs +0 -356
  201. package/src/tools/glob-dialect.mjs +0 -130
  202. package/src/tools/glob.md +0 -11
  203. package/src/tools/grep.md +0 -19
  204. package/src/tools/hashline_edit.md +0 -14
  205. package/src/tools/index.mjs +0 -36
  206. package/src/tools/insert_after.md +0 -15
  207. package/src/tools/lint.md +0 -10
  208. package/src/tools/linter.mjs +0 -128
  209. package/src/tools/ls.md +0 -12
  210. package/src/tools/lsp.md +0 -10
  211. package/src/tools/lsp.mjs +0 -316
  212. package/src/tools/ops.mjs +0 -299
  213. package/src/tools/patch.mjs +0 -282
  214. package/src/tools/process.md +0 -10
  215. package/src/tools/question.md +0 -16
  216. package/src/tools/question.mjs +0 -26
  217. package/src/tools/read.md +0 -20
  218. package/src/tools/read_image.md +0 -8
  219. package/src/tools/repomap.mjs +0 -314
  220. package/src/tools/search.mjs +0 -236
  221. package/src/tools/shared.mjs +0 -446
  222. package/src/tools/tree.md +0 -14
  223. package/src/tools/tree.mjs +0 -66
  224. package/src/tools/wait_for.md +0 -22
  225. package/src/tools/web.mjs +0 -224
  226. package/src/tools/websearch.md +0 -16
  227. package/src/tools/write.md +0 -11
  228. package/src/traces/trace-store.mjs +0 -355
@@ -1,14 +1,14 @@
1
1
  import { execSync } from "node:child_process"
2
2
  import { isAbsolute, join } from "node:path"
3
- import { createAgent } from "../agent.mjs"
4
- import { loadConfig, configDir } from "../config.mjs"
5
- import { createMemory, memoryTools, syncDir, codeSearchTool, docSearchTool } from "../memory.mjs"
6
- import { settingsTool } from "../agent-tools/settings.mjs"
7
- import { repoOutlineTool } from "../tools/repomap.mjs"
8
- import { builtinTools } from "../tools/index.mjs"
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"
3
+ import { createAgent } from "@thincoder/core/agent.mjs"
4
+ import { loadConfig, configDir } from "@thincoder/core/config.mjs"
5
+ import { createMemory, syncDir } from "@thincoder/core/memory.mjs"
6
+ import { discoverRules } from "@thincoder/core/rules.mjs"
7
+ // #70(CORE-UNIFICATION TOOLS):注册表与消费侧拼装面归位核内——单源 `assembleBuiltinTools`
8
+ // (静态表 ∪ memory / code_search / doc_search / repo_outline / settings / peer_instances 六面 ∪ 门控 read_image)。
9
+ // 传 `model` 必需:漏传 ⇒ `read_image` 对所有模型静默消失(门判据 = specForModel(model)?.multimodal)。
10
+ import { assembleBuiltinTools } from "@thincoder/core/tools/index.mjs"
11
+ import { resolveEngineeringManifest } from "@thincoder/core/manifest.mjs"
12
12
 
13
13
  /** 第 27 批 §12.3⑤(R-A1.1):装配期工具剔除——按 `name` 过滤的纯函数(机验锚)。
14
14
  * 恒等语义:空列表 / 零命中 → 原数组原样返回(零意外剔除——T15)。
@@ -21,13 +21,13 @@ export function applyToolExclusions(tools, excludeTools = []) {
21
21
  }
22
22
 
23
23
  /** Assemble an agent with memory, MCP tools, and code/doc indices attached (sync all layers, then return) */
24
- export async function assembleAgent({ excludeTools = [] } = {}) {
24
+ export async function assembleAgent({ excludeTools = [], slotData } = {}) {
25
25
  const config = loadConfig()
26
26
  const provider = config.provider
27
27
  const providers = config.providersList
28
28
 
29
29
  // Inject proxy URI into providers (double opt-in: provider.proxy + config.proxy.model)
30
- const { injectProxy } = await import("../proxy.mjs")
30
+ const { injectProxy } = await import("@thincoder/core/proxy.mjs")
31
31
  injectProxy(providers, config)
32
32
  // config.provider 是 loadConfig 里的独立拷贝,同步注入结果
33
33
  if (provider?.name) provider.proxyUri = providers.find((p) => p.name === provider.name)?.proxyUri
@@ -35,7 +35,7 @@ export async function assembleAgent({ excludeTools = [] } = {}) {
35
35
  const memory = createMemory({ dbPath: config.memory.dbPath })
36
36
  // Vector retrieval: enabled if embedding is configured (lazy vector generation, computed on first search)
37
37
  if (config.embedding?.apiKey) {
38
- const { createEmbedder } = await import("../embedding.mjs")
38
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
39
39
  memory.embedder = createEmbedder(config.embedding)
40
40
  }
41
41
  const cwd = process.cwd()
@@ -57,11 +57,11 @@ export async function assembleAgent({ excludeTools = [] } = {}) {
57
57
  // Team layer (optional): auto-clone on first use; startup only indexes local dir, remote pull via explicit thincoder sync
58
58
  const team = teamConfig(config)
59
59
  if (team) {
60
- const { ensureClone } = await import("../git/gitmem.mjs")
60
+ const { ensureClone } = await import("@thincoder/core/git/gitmem.mjs")
61
61
  await ensureClone(team)
62
62
  await syncDir(memory, { layer: "team", dir: team.dir })
63
63
  }
64
- const baseTools = [...builtinTools, ...memoryTools(memory, { cwd, projectDir: config.memory.projectDir, author: gitAuthor(), team }), codeSearchTool(memory), docSearchTool(memory), repoOutlineTool(memory.db, cwd), settingsTool(), peerInstancesTool]
64
+ const baseTools = await assembleBuiltinTools({ memory, cwd, projectDir: config.memory.projectDir, author: gitAuthor(), team, model: provider?.model ?? null })
65
65
 
66
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)
67
67
  const mcpServers = config.mcp?.servers ?? []
@@ -94,7 +94,7 @@ export async function assembleAgent({ excludeTools = [] } = {}) {
94
94
  let mcpTools = []
95
95
  const mcpWarnings = []
96
96
  if (mcpServers.length) {
97
- const { connectMcpServer } = await import("../mcp.mjs")
97
+ const { connectMcpServer } = await import("@thincoder/core/mcp.mjs")
98
98
  const results = await Promise.allSettled(mcpServers.map((srv) => connectMcpServer(srv)))
99
99
  for (let i = 0; i < results.length; i++) {
100
100
  const r = results[i]
@@ -122,7 +122,12 @@ export async function assembleAgent({ excludeTools = [] } = {}) {
122
122
  agent.activeProvider = provider.name ?? ""
123
123
  agent.activeModel = provider.model ?? null
124
124
  agent._mcpWarnings = mcpWarnings
125
- // SESSION.md §8 D-S1:assembleAgent 后唯一校验点(TUI/chat 两路径同源)——不抛错不退出,
125
+ // M1 装配钩子(会话起点①——装配期):判据 = 会话权威值(`slotData` 槽值优先 + config 回退);
126
+ // 附着 agent.manifest 供下游 M2–M9 读面(普通会话 = null)。钩子后移的可观察后果(拒 / 建档
127
+ // 晚于 memory sync / MCP 连接 / 工具装配——功能等价,仍在进入正常循环之前)见
128
+ // docs/core/design/MANIFEST.md §2.2。
129
+ attachManifest(agent, { cwd, slotData })
130
+ // SESSION.md §6.8 D-S1:assembleAgent 后唯一校验点(TUI/chat 两路径同源)——不抛错不退出,
126
131
  // 标记由调用侧消费(TUI 弹重选 / headless 报错)。空 provider 由 TUI 路径在 startTUI 前清空。
127
132
  validateProvider(agent)
128
133
  // defaultModel 无效/未设的具体原因覆盖通用判据文案(providers 存在时更有指导性)
@@ -133,7 +138,44 @@ export async function assembleAgent({ excludeTools = [] } = {}) {
133
138
  }
134
139
 
135
140
  /**
136
- * SESSION.md §8 D-S1 — provider 有效性校验(assembleAgent 后唯一校验点,TUI/chat 两路径同源)。
141
+ * M1 装配钩子(docs/core/design/MANIFEST.md §2.2 两端装配钩子——CLI 端):
142
+ * 会话起点调用(装配期 ① + `bin` 启动恢复后重估 ②——幂等:重估即重读,无状态位)。
143
+ *
144
+ * 模式门(判据单源 = 会话权威值,槽优先 + config 回退——KD-M1-12 / KD-M1-13):
145
+ * 「恢复槽带 `engineering` 字段 ? 槽值 : `agent.config.agent.engineering`」(槽字段在场判据
146
+ * 与 `applySession` 同款——`thincoder-core/session.mjs:314-317`)。普通会话 → **装配钩子
147
+ * 零 manifest I/O**(不读 / 不拒 / 不建档)+ `agent.manifest = null`(清残留附着——复用
148
+ * agent 跨模式防陈旧);工程模式四态:合法档 → 附着;缺档 + 根可解析 → `initManifest`
149
+ * 建档(拒进正常循环直到生成——E2 前置门槛);缺档 + 根不可解析 → 抛「工程模式启动拒绝」
150
+ * **不自动建档**;非法档 → fail-closed 抛出。
151
+ *
152
+ * `slotData` = 恢复槽记录,由调用方经**参数注入**(`bin` TUI 分支既有 `resumeSlot` 调用
153
+ * 前移——KD-M1-15:钩子内不调 `resumeSlot`(该函数非纯读:GC / 认领 / 端标写);`chat` /
154
+ * ACP 装配无会话 ⇒ 不传 ⇒ config 回退)。重估点不传(`applySession` 已按槽订正 config)。
155
+ *
156
+ * #41(薄包装——KD-M1-20 / AC-19):工程模式分支的决策树 = 核单源
157
+ * `resolveEngineeringManifest`(非抛错形态);本函数只把它翻回「抛错」——四条出口
158
+ * (附着 / 缺档建档 / 根不可解析抛 / 档非法抛)的**结果与文案逐字不变**(原句由决策树构造)。
159
+ * @param {object} agent — createAgent 产物(读 agent.config;写 agent.manifest)
160
+ * @param {{cwd?: string, slotData?: object|null}} [opts]
161
+ * @returns {object|null} 附着后的 manifest / 普通会话 null
162
+ */
163
+ export function attachManifest(agent, { cwd = process.cwd(), slotData } = {}) {
164
+ const slotEng = slotData?.engineering
165
+ const engineering = slotEng !== undefined ? slotEng === true : agent.config?.agent?.engineering === true
166
+ if (!engineering) {
167
+ agent.manifest = null // 清残留附着(KD-M1-12)——普通会话不读 / 不拒 / 不建档
168
+ return null
169
+ }
170
+ // 缺档 + 根可解析 → 就地建档(writer 显式 'main'——写门 fail-closed 缺省拒,KD-M1-3)。
171
+ const r = resolveEngineeringManifest(cwd, { writer: "main" })
172
+ if (!r.ok) throw new Error(r.message)
173
+ agent.manifest = r.manifest
174
+ return agent.manifest
175
+ }
176
+
177
+ /**
178
+ * SESSION.md §6.8 D-S1 — provider 有效性校验(assembleAgent 后唯一校验点,TUI/chat 两路径同源)。
137
179
  * 判据(评审 #1/#2):仅 model/baseURL 缺失判 invalid——**不得用 MODEL_SPECS 成员资格判无效**
138
180
  * (未知模型 = 受支持场景:自定义端点模型不在 spec 表是常态,误判会让自定义模型用户每次恢复都弹重选)。
139
181
  * apiKey 缺失不判(既有 wizard /model 流程处理)。幂等:有效时清标记,无效时置标记 + 原因。
@@ -1,8 +1,8 @@
1
1
  import { isAbsolute, join } from "node:path"
2
- import { loadConfig } from "../config.mjs"
2
+ import { loadConfig } from "@thincoder/core/config.mjs"
3
3
  import { teamConfig } from "./make-agent.mjs"
4
- import { put, search, list } from "../memory/core.mjs"
5
- import { deleteByUid } from "../memory/delete.mjs"
4
+ import { put, search, list } from "@thincoder/core/memory/core.mjs"
5
+ import { deleteByUid } from "@thincoder/core/memory/delete.mjs"
6
6
 
7
7
  /** thincoder memory <list|search|put|remove> subcommands.
8
8
  * opts.dirs: { project, team } layer directories for project/team file deletion (tests inject their own);
@@ -1,48 +1,4 @@
1
- import { createInterface } from "node:readline"
2
-
3
- /** CLI tool arg summary (truncated long JSON) */
4
- export function summarize(toolArgs) {
5
- const s = JSON.stringify(toolArgs)
6
- return s.length > 120 ? s.slice(0, 120) + "..." : s
7
- }
8
-
9
- /** Permission request key info (per-tool customized), aligned with TUI formatPermission. name may include sub-agent prefix ("coder/bash") — extract basename for matching */
10
- export function formatPermission(name, args) {
11
- const cap = (s, n = 1000) => (s.length > n ? `${s.slice(0, n)}…(共 ${s.length} 字符)` : s)
12
- const base = name.includes("/") ? name.split("/").pop() : name
13
- if (base === "bash") return cap(args.command ?? "")
14
- if (base === "write") return `${args.path}(写入 ${(args.content ?? "").length} 字符)\n${cap(args.content ?? "", 1000)}`
15
- if (base === "edit") {
16
- const oldLines = cap(args.old_string ?? "", 500).split("\n").map((l) => `- ${l}`).join("\n")
17
- const newLines = cap(args.new_string ?? "", 500).split("\n").map((l) => `+ ${l}`).join("\n")
18
- return `${args.path}\n${oldLines}\n ↓\n${newLines}`
19
- }
20
- if (base === "delete") return `${args.path}${args.force ? "(force:跟踪文件也删)" : ""}`
21
- if (base === "subagent") return cap(args.task ?? "", 500)
22
- if (base === "memory") {
23
- // §6 action-routed preview: put shows content, batch delete/clear show the gate args
24
- const action = String(args.action ?? "")
25
- if (action === "put") return `[${args.type ?? ""}] ${args.title ?? ""}\n${cap(args.content ?? "", 500)}`
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
- return cap(summarize(args), 300)
29
- }
30
- return cap(summarize(args), 300)
31
- }
32
-
33
- /** Permission confirmation: interactive y/n on TTY; non-interactive defaults to deny (safety-first) */
34
- export async function askPermission(name, toolArgs) {
35
- if (!process.stdin.isTTY) {
36
- console.error(`\n[deny] ${name} (non-interactive, side-effect tools require a TTY)`)
37
- return false
38
- }
39
- const rl = createInterface({ input: process.stdin, output: process.stderr })
40
- try {
41
- const answer = await new Promise((resolve) => {
42
- rl.question(`\n[allow?] ${name}\n${formatPermission(name, toolArgs)}\n(y/N) `, resolve)
43
- })
44
- return answer.trim().toLowerCase() === "y"
45
- } finally {
46
- rl.close()
47
- }
48
- }
1
+ /** permission.mjs — CLI 转口档(P2 机制层端差批 §2.20):闸语义 + 每工具请示文案单源 = 核
2
+ * `permission.mjs`(#165 落点——本端零第二实现);展示面由核的 `io.ask` 缝承载。
3
+ * 导出面与核逐字对齐,保既有 import 面(`bin/thincoder.mjs` · `distill-command.mjs`)。 */
4
+ export { summarize, formatPermission, askPermission } from "@thincoder/core/permission.mjs"
@@ -1,5 +1,5 @@
1
1
  import { createInterface } from "node:readline"
2
- import { configPath, writeConfigAtomic, PROVIDER_PRESETS } from "../config.mjs"
2
+ import { configPath, writeConfigAtomic, PROVIDER_PRESETS } from "@thincoder/core/config.mjs"
3
3
  import { probeChannelModels } from "../tui/model-catalog.mjs"
4
4
 
5
5
  /** First-time setup (TTY chat / distill): ask a few questions to configure a provider, save to disk, return runtime provider. Cancel returns null. */
@@ -21,7 +21,7 @@ export function printCompletion(shell) {
21
21
  distill) COMPREPLY=( \\$(compgen -W "--yes --layer=" -- "\\$cur") ) ;;
22
22
  completion) COMPREPLY=( \\$(compgen -W "bash zsh fish" -- "\\$cur") ) ;;
23
23
  *)
24
- COMPREPLY=( \\$(compgen -W "chat acp memory sync reindex distill upgrade completion session -v --version -h --help" -- "\\$cur") ) ;;
24
+ COMPREPLY=( \\$(compgen -W "tui chat acp memory sync reindex distill upgrade completion session -v --version -h --help" -- "\\$cur") ) ;;
25
25
  esac
26
26
  }
27
27
  complete -F _thincoder thincoder
@@ -39,6 +39,7 @@ _thincoder() {
39
39
  case "\\$state" in
40
40
  cmd)
41
41
  _values 'command' \\
42
+ 'tui[Launch the interactive TUI]' \\
42
43
  'chat[One-shot agent run with tools]' \\
43
44
  'acp[Agent Client Protocol server for IDEs]' \\
44
45
  'memory[Manage long-term memory]' \\
@@ -69,6 +70,7 @@ _thincoder
69
70
  complete -c thincoder -f
70
71
 
71
72
  # Subcommands
73
+ complete -c thincoder -a tui -d 'Launch the interactive TUI'
72
74
  complete -c thincoder -a chat -d 'One-shot agent run with tools'
73
75
  complete -c thincoder -a memory -d 'Manage long-term memory'
74
76
  complete -c thincoder -a sync -d 'Sync team memory repo'
@@ -27,7 +27,7 @@ import { chmodSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, wr
27
27
  import { join } from "node:path"
28
28
  // F3① 命名空间 import:API 缺失(旧 Node)降级为调用期异常并被武装 try 吞掉——不做 import 期硬失败
29
29
  import * as v8 from "node:v8"
30
- import { configDir } from "./config.mjs"
30
+ import { configDir } from "@thincoder/core/config.mjs"
31
31
 
32
32
  /** F3② 关值集合(trim + 大小写不敏感);其余取值(未设 / 空串 / 未知串)默认开——fail-open 向取证。 */
33
33
  const HEAP_SNAPSHOT_OFF_VALUES = new Set(["0", "false", "off", "no"])
package/src/distill.mjs CHANGED
@@ -4,10 +4,10 @@
4
4
  * Absolutely no automatic storage at session end.
5
5
  */
6
6
 
7
- import { chat } from "./provider/index.mjs"
8
- import { loadConfig } from "./config.mjs"
9
- import { put, putMarkdown } from "./memory.mjs"
10
- import { commitAndPush } from "./git/gitmem.mjs"
7
+ import { chat } from "@thincoder/core/provider/index.mjs"
8
+ import { loadConfig } from "@thincoder/core/config.mjs"
9
+ import { put, putMarkdown } from "@thincoder/core/memory.mjs"
10
+ import { commitAndPush } from "@thincoder/core/git/gitmem.mjs"
11
11
 
12
12
  const DISTILL_PROMPT = `You are a knowledge extractor. Read the following agent work session log and extract knowledge worth remembering across sessions.
13
13
 
@@ -16,7 +16,7 @@
16
16
  * 直驱(零等待);失败面全吞(采样抛错不阻断——样本失败静默跳过本次)。
17
17
  */
18
18
  import { getHeapStatistics } from "node:v8"
19
- import { logEvent } from "./log.mjs"
19
+ import { logEvent } from "@thincoder/core/log.mjs"
20
20
 
21
21
  /** 关值集合(单点判定——§3.2 同约定复用)。 */
22
22
  export const HEAP_WATCH_OFF = new Set(["0", "false", "off", "no"])
@@ -0,0 +1,20 @@
1
+ /**
2
+ * prompt-injections.mjs — the CLI end's prompt-anchor value table.
3
+ *
4
+ * The core's single resolution face (`@thincoder/core/prompt-files.mjs`) owns the anchors
5
+ * (`{{inject:<name>}}` in `thincoder-core/prompts/` + `thincoder-core/tool-docs/`); this end
6
+ * supplies the values. The table is registered ONCE at the process entry (`bin/thincoder.mjs`)
7
+ * — before any assembly — via `configurePromptInjections`.
8
+ *
9
+ * 2026-09-17 消端差:提示词面(persona / discipline / common)注入锚全消——导航注记删
10
+ * (正文自足)、端特有段通用化并入核内正文、文档地图路径统一写死 `docs/README.md`。
11
+ * 剩 = 工具面 2 锚(bash-terminal-face / question-ui-face——真实工具差异,待工具面 review 处置)。
12
+ */
13
+
14
+ /** 工具面 2 锚(锚名 → 本端取值)。 */
15
+ export const CLI_PROMPT_INJECTIONS = {
16
+ // bash 宿主终端面——CLI 无 terminal 参数(工具面 review 再统一)
17
+ "bash-terminal-face": "",
18
+ // question 工具可用性行(核内正文该行已移出正文、锚原位替换——本端填回 CLI 措辞)
19
+ "question-ui-face": "- Availability: this tool needs an interactive UI — in contexts without one (headless runs, subagent children) it returns an error instead of asking; put the question in your reply text instead.",
20
+ }
@@ -14,13 +14,13 @@
14
14
  * 递归回本文件(函数级静态环——模块求值期无顶层调用,环安全——session-slots ↔
15
15
  * session.mjs 同款先例)。
16
16
  */
17
- import { runAgent, ContinueError } from "../agent.mjs"
18
- import { saveSession } from "../session.mjs"
17
+ import { runAgent, ContinueError } from "@thincoder/core/agent.mjs"
18
+ import { saveSession } from "@thincoder/core/session.mjs"
19
19
  import { ansi, C } from "./ansi.mjs"
20
20
  import { buildToolCallbacks, sweepToolBlocks } from "./tool-events.mjs"
21
21
  import { freezeAllSubTasks } from "./subagent-blocks.mjs" // freezeReclaimDigestedBlocks 随 §17 段迁 suspension-drive.mjs
22
- import { ensureSessionTitle } from "../generate-title.mjs"
23
- import { logEvent, errText } from "../log.mjs"
22
+ import { ensureSessionTitle } from "@thincoder/core/generate-title.mjs"
23
+ import { logEvent, errText } from "@thincoder/core/log.mjs"
24
24
  import { suspensionSession, poolLive } from "./suspension-drive.mjs"
25
25
 
26
26
  /** Exit-flush bound for the async end-of-run distillation (SEND-STALL-DISTILL §2.5):
@@ -28,6 +28,10 @@ import { suspensionSession, poolLive } from "./suspension-drive.mjs"
28
28
  * never let shutdown hang on the background summary call. */
29
29
  const DISTILL_FLUSH_TIMEOUT_MS = 5000
30
30
 
31
+ /** Provider 失败面 URL 脱敏(X8——判据与 VSC `panel-turn-loop.mjs:153` 同式;两端各自持有
32
+ * 字面 = 双写登记——单源化须动 VSC/核写域,出本批射程,见批档 §2.3)。 */
33
+ const PROVIDER_URL_RE = /https?:\/\/[^\s,)"]+/g
34
+
31
35
  /**
32
36
  * 第 33 批(TUI §14.3(d)——纯函数,供测试直驱):回合链尾「需要用户」谓词——agent 已停
33
37
  * 且无自动续跑 ⇒ 置 attention 位。排除项语义(F13):`skipSession`(digest / 会话内回合
@@ -45,6 +49,8 @@ export function userNeededAtTurnEnd(state, agent, skipSession) {
45
49
  * user/auto;result ok/stopped/error)。内层经 _logOutcome 载具回传终止原因(中止/
46
50
  * turn-cap 拒绝/错误 vs 正常完成)——嵌套回合(队列递归/挂起会话内 digest 轮)各自
47
51
  * 新开载具(每次包装调用独立),互不串扰。err:internal = 逃出内层的未分类异常。
52
+ * @returns {Promise<"ok"|"stopped"|"error">} 本回合终态(同一载具外化——X9 消化轮
53
+ * 收尾可见行的 ok/aborted 判据;VSC digest end ok 旗标同口径)。
48
54
  */
49
55
  export async function runAgentTurn(ctx, text, opts = {}) {
50
56
  const kind = opts?.autoTurn ? "auto" : "user"
@@ -55,8 +61,8 @@ export async function runAgentTurn(ctx, text, opts = {}) {
55
61
  const t0 = Date.now()
56
62
  logEvent("turn:start", { kind })
57
63
  try {
58
- const result = await runAgentTurnInner(ctx, text, runOpts)
59
- return result
64
+ await runAgentTurnInner(ctx, text, runOpts)
65
+ return carrier.result ?? "ok"
60
66
  } catch (e) {
61
67
  carrier.result = "error"
62
68
  logEvent("err:internal", { msg: errText(e, 200), where: "runAgentTurn" })
@@ -68,7 +74,7 @@ export async function runAgentTurn(ctx, text, opts = {}) {
68
74
 
69
75
  /** runAgentTurn 本体(LOGGING 包装之外——见上方包装器)。 */
70
76
  async function runAgentTurnInner(ctx, text, opts) {
71
- const { autoTurn = false, skipSession = false } = opts ?? {}
77
+ const { autoTurn = false, skipSession = false, upstreamTurn = false } = opts ?? {} // upstreamTurn:上行 ask 唤醒轮旗标(§6.27.12.5 D/I——透传核 runAgent,仅供域文本选择)
72
78
  const { agent, state, pushLine, pushLabel, render, scheduleRender, ensureAssistantLabel, askPermission, askBatchPermission, askQuestion, handleSlash } = ctx
73
79
  // 可注入覆盖(测试用);默认走真实实现
74
80
  const runAgentImpl = ctx.runAgent ?? runAgent
@@ -144,7 +150,7 @@ async function runAgentTurnInner(ctx, text, opts) {
144
150
  try {
145
151
  for (let resume = false; ; resume = true) {
146
152
  try {
147
- await runAgentImpl(agent, text, callbacks, { signal: state.controller.signal, resume, autoTurn, suspDriven: true })
153
+ await runAgentImpl(agent, text, callbacks, { signal: state.controller.signal, resume, autoTurn, upstreamTurn, suspDriven: true })
148
154
  flushStream()
149
155
  break // Normal completion, exit loop
150
156
  } catch (error) {
@@ -216,7 +222,32 @@ async function runAgentTurnInner(ctx, text, opts) {
216
222
  state.controller = makeController()
217
223
  continue
218
224
  }
219
- pushLine(`[error] ${error.message}`, C.error)
225
+ // X8(2026-09-20 端差·显示面消差批):provider 失败面 = 友好首行(判据同 VSC
226
+ // panel-turn-loop.mjs:152-153:原文首行 + URL 脱敏)+ 诊断两行(Provider/Model)+ Retry
227
+ // 询问(复用 permission 机制——同意 ⇒ 重建 controller + resume 重入,与 ContinueError
228
+ // 同法;拒绝 ⇒ 现状 break,[error] 行保留)。
229
+ const rawMsg = error?.message || String(error)
230
+ const errLine = rawMsg.split("\n")[0].replace(PROVIDER_URL_RE, "[endpoint]")
231
+ // X8 跟进②(用户 2026-09-20 裁定):原文余行 = **log-only**(表面零膨胀——不增 UI 行)。
232
+ // `head` 与 `err` **同过脱敏管道**(PROVIDER_URL_RE ⇒ `[endpoint]`——LOGGING.md §6.3
233
+ // D-LG4「URL 不入事件」)**先脱敏后截断**(防截断留 URL 残段),自限 ≤200(`err` 200 +
234
+ // `head` 200 + 固定字段 < 512——log.mjs NF-L2);无余行 ⇒ 传 `null`(勿传空串)。
235
+ const restRaw = rawMsg.split("\n").slice(1).map((l) => l.trim()).filter(Boolean).join(" / ")
236
+ const restSan = restRaw.replace(PROVIDER_URL_RE, "[endpoint]")
237
+ logEvent("err:provider", { err: errLine, head: restSan ? (restSan.length > 200 ? restSan.slice(0, 199) + "…" : restSan) : null })
238
+ pushLine(`[error] ${errLine}`, C.error)
239
+ pushLine(`→ Provider: ${agent.provider?.baseURL}`, C.dim)
240
+ pushLine(`→ Model: ${agent.provider?.model}`, C.dim)
241
+ const willRetry = await new Promise((resolve) => {
242
+ state.permission = { name: "retry", args: { message: errLine }, resolve }
243
+ state.status = "Retry after provider error?"
244
+ render()
245
+ })
246
+ state.permission = null
247
+ if (willRetry) {
248
+ state.controller = makeController()
249
+ continue
250
+ }
220
251
  if (opts?._logOutcome) opts._logOutcome.result = "error"
221
252
  break
222
253
  }
@@ -3,7 +3,7 @@
3
3
  * ctx: { agent, showPicker, pushLine, pushLabel, persistRaw } */
4
4
  import { readFileSync } from "node:fs"
5
5
  import { ansi, C } from "./ansi.mjs"
6
- import { activeSlot, slotPath } from "../session.mjs"
6
+ import { activeSlot, slotPath } from "@thincoder/core/session.mjs"
7
7
  import { writeSessionFile } from "./cmd-eng.mjs"
8
8
 
9
9
  export async function handleAdvisorCommand(ctx) {
@@ -98,7 +98,7 @@ export async function handleAdvisorCommand(ctx) {
98
98
  pushLabel("❯ Advisor", ansi.bold + C.tool)
99
99
  pushLine("Thinking: using main model settings", C.tool)
100
100
  } else if (c.action === "think_on") {
101
- const { specForModel } = await import("../config.mjs")
101
+ const { specForModel } = await import("@thincoder/core/config.mjs")
102
102
  const spec = specForModel(getEffectiveModel(agent, cfg))
103
103
  cfg.thinking = { type: spec.thinkEnabledValue ?? "enabled" }
104
104
  if (spec.thinkApi === "effort") delete cfg.thinking
@@ -106,7 +106,7 @@ export async function handleAdvisorCommand(ctx) {
106
106
  pushLabel("❯ Advisor", ansi.bold + C.tool)
107
107
  pushLine(`Thinking: ON`, C.tool)
108
108
  } else if (c.action === "think_off") {
109
- const { specForModel } = await import("../config.mjs")
109
+ const { specForModel } = await import("@thincoder/core/config.mjs")
110
110
  const spec = specForModel(getEffectiveModel(agent, cfg))
111
111
  const isCustomThink = (spec.thinkEnabledValue ?? "enabled") !== "enabled"
112
112
  cfg.thinking = isCustomThink ? null : { type: "disabled" }
@@ -179,7 +179,7 @@ function getEffectiveModel(agent, cfg) {
179
179
  }
180
180
 
181
181
  async function fetchAdvisorModels(agent) {
182
- const { listModels } = await import("../provider/index.mjs")
182
+ const { listModels } = await import("@thincoder/core/provider/index.mjs")
183
183
  const result = new Map()
184
184
  await Promise.all((agent.providers || []).map(async (p) => {
185
185
  try {
@@ -223,7 +223,7 @@ function buildModelEntries(agent, cfg, cache) {
223
223
  }
224
224
 
225
225
  async function buildThinkingEntries(agent, cfg) {
226
- const { specForModel } = await import("../config.mjs")
226
+ const { specForModel } = await import("@thincoder/core/config.mjs")
227
227
  const providerForDefaults = cfg.provider
228
228
  ? agent.providers?.find(p => p.name === cfg.provider) || agent.provider
229
229
  : agent.provider
@@ -23,7 +23,7 @@ export function poolCur(pl, defaults) {
23
23
  /** /config command: view and set agent/embedding/proxy config. */
24
24
  export async function handleConfigCommand(ctx, args = []) {
25
25
  const { agent, pushLine, pushLabel, showPicker, askQuestion, persistRaw, maskKey, pickModelForSlot } = ctx
26
- const { configPath, DEFAULTS } = await import("../config.mjs")
26
+ const { configPath, DEFAULTS } = await import("@thincoder/core/config.mjs")
27
27
  let ac = agent.config?.agent ?? {}
28
28
  let ec = agent.config?.embedding ?? {}
29
29
  let tc = agent.config?.traces ?? {}
@@ -43,7 +43,7 @@ export async function handleConfigCommand(ctx, args = []) {
43
43
  agent.config.embedding ??= {}
44
44
  agent.config.embedding.apiKey = embKey
45
45
  if (agent.memory) {
46
- const { createEmbedder } = await import("../embedding.mjs")
46
+ const { createEmbedder } = await import("@thincoder/core/embedding.mjs")
47
47
  agent.memory.embedder = createEmbedder(agent.config.embedding)
48
48
  }
49
49
  pushLabel("❯ Config", ansi.bold + C.tool)
@@ -55,8 +55,8 @@ export async function handleConfigCommand(ctx, args = []) {
55
55
  * MODEL-MERGE-SESSION 三支改写(cfg 无 active*):运行时由「会话槽复合仍在 providers 中 →
56
56
  * 保持槽值」或「config defaultModel(新会话起点)」决定——不再有渠道默认字段可回退。 */
57
57
  async function reloadConfig() {
58
- const { loadConfig } = await import("../config.mjs")
59
- const { injectProxy } = await import("../proxy.mjs")
58
+ const { loadConfig } = await import("@thincoder/core/config.mjs")
59
+ const { injectProxy } = await import("@thincoder/core/proxy.mjs")
60
60
  const cfg = loadConfig()
61
61
  injectProxy(cfg.providersList, cfg)
62
62
  const sessionName = agent.activeProvider
@@ -82,7 +82,7 @@ export async function handleConfigCommand(ctx, args = []) {
82
82
  // 运行时重建后同步注入结果(cfg.provider 系 loadConfig 内建——injectProxy 后建)
83
83
  agent.provider.proxyUri = cfg.providersList.find((p) => p.name === agent.activeProvider)?.proxyUri
84
84
  if (agent.config?.agent?.compactThresholdAuto) {
85
- const { resolveCompactThreshold } = await import("../config.mjs")
85
+ const { resolveCompactThreshold } = await import("@thincoder/core/config.mjs")
86
86
  agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
87
87
  }
88
88
  }
@@ -136,8 +136,8 @@ export async function handleConfigCommand(ctx, args = []) {
136
136
  pushLabel("❯ Config", ansi.bold + C.tool)
137
137
  pushLine(`proxy.${key} = ${!pc[key] ? "on" : "off"}`, C.tool)
138
138
  } else if (c.action === "test") {
139
- const { proxyFetch, resolveWebProxy } = await import("../proxy.mjs")
140
- const { UA } = await import("../tools/web.mjs")
139
+ const { proxyFetch, resolveWebProxy } = await import("@thincoder/core/proxy.mjs")
140
+ const { UA } = await import("@thincoder/core/tools/web.mjs")
141
141
  const uri = resolveWebProxy({ agent })
142
142
  pushLabel("❯ Config", ansi.bold + C.tool)
143
143
  pushLine(`Testing ${uri ? `via proxy ${uri}` : "direct (no proxy)"}...`, C.dim)
@@ -170,7 +170,7 @@ export async function handleConfigCommand(ctx, args = []) {
170
170
 
171
171
  /** 会诊/飞刀候选池子菜单:列出 / 添加 / 编辑 effort / 删除 consultModels 条目。 */
172
172
  async function pickEffort(current, model) {
173
- const { specForModel } = await import("../config.mjs")
173
+ const { specForModel } = await import("@thincoder/core/config.mjs")
174
174
  const enumList = model ? specForModel(model).reasoningEffortEnum : null
175
175
  // The model's reasoning-effort enum is HETEROGENEOUS across providers (deepseek:
176
176
  // low/high/max; qwen3.8-max: xhigh/medium/low; kimi: 7 levels). A fixed
@@ -1,15 +1,17 @@
1
1
  /** /eng command: toggle engineering mode.
2
- * No prerequisites (FR11): the mode works on any project — no methodology file,
3
- * no docs tree, no git. The retired gate demanded a methodology file and offered
4
- * to create it from a template that no longer ships — that option crashed
5
- * (PORTABILITY P14). ctx: { agent, pushLine, pushLabel } */
2
+ * No retired-concept prerequisites (FR11 收正口径 — `docs/core/requirements/PORTABILITY.md:62`):
3
+ * no methodology file, no docs tree. The **repo-root anchor(git)= E2 既有入口前提**:
4
+ * 非锚 cwd 上 ON = fail-closed 拒翻 + 明示原因、模式保持 OFF(#41 —
5
+ * `docs/core/design/MANIFEST.md` §2.8 F2/F3;判定面 = 入口决策树单源)。
6
+ * ctx: { agent, pushLine, pushLabel } */
6
7
  import { readFileSync, writeFileSync, mkdirSync, renameSync, unlinkSync } from "node:fs"
7
8
  import { dirname } from "node:path"
8
9
  import { ansi, C } from "./ansi.mjs"
9
- import { activeSlot, slotPath } from "../session.mjs"
10
- import { purgeExpiredDesignTokens } from "../token-ttl.mjs"
10
+ import { activeSlot, slotPath } from "@thincoder/core/session.mjs"
11
+ import { purgeExpiredDesignTokens } from "@thincoder/core/token-ttl.mjs"
12
+ import { resolveEngineeringManifest } from "@thincoder/core/manifest.mjs"
11
13
 
12
- import { ENG_OFF_REMINDER } from "../agent.mjs"
14
+ import { ENG_OFF_REMINDER } from "@thincoder/core/agent.mjs"
13
15
 
14
16
  /** Atomic slot write (same shape as session.mjs writeSessionFile — kept local to avoid a
15
17
  * private-import; cmd-advisor's guard toggle shares this helper). */
@@ -29,8 +31,22 @@ export async function handleEngCommand(ctx) {
29
31
  const { agent, pushLine, pushLabel } = ctx
30
32
  agent.config.agent ??= {}
31
33
 
32
- // Toggle on/off — no prerequisite check (FR11: engineering mode depends on no
33
- // project file at all; a missing docs tree or git repo must not block it).
34
+ // No retired-concept prerequisite check (FR11 收正口径;退场原因见档头注)。ON 方向的门
35
+ // = 入口决策树(下方);OFF 方向恒放行(F2 表 OFF 行——零 manifest I/O)。
36
+ //
37
+ // #41 先判后翻(MANIFEST.md §2.8 F2/F3 · KD-M1-21):**ON 方向**先走入口决策树单源
38
+ // (resolveEngineeringManifest——KD-M1-20),判据通过才写态;OFF 方向零改(F2 表 OFF 行)。
39
+ // 拒 ⇒ warn 行 + **零标签**(标签 = 成功回显——拒时零标签防假成功)+ 零副作用
40
+ // (模式未翻 / token 未清 / `_advisorRuns` 未重置 / 提醒未入列 / 槽未写)。
41
+ // 准 ⇒ 缺档格就地建档(writer:'main')+ `agent.manifest` ← 结果(相位行当回合起活)。
42
+ if (!agent.config.agent.engineering) {
43
+ const r = resolveEngineeringManifest(agent.cwd ?? process.cwd(), { writer: "main" })
44
+ if (!r.ok) {
45
+ pushLine(`Engineering mode not enabled — ${r.message} (mode unchanged)`, C.warn)
46
+ return
47
+ }
48
+ agent.manifest = r.manifest
49
+ }
34
50
  agent.config.agent.engineering = !agent.config.agent.engineering
35
51
  // §11.2 D-24b: per-review instances die with the mode (fresh convergence cycles
36
52
  // on the next toggle).
@@ -2,7 +2,7 @@ import { ansi, C } from "./ansi.mjs"
2
2
  import { cloneEntry, fieldPicker, maskToken } from "./cmd-mcp-form.mjs"
3
3
 
4
4
  /** /mcp command handler: view/add/edit/remove/test/reconnect MCP server.
5
- * ctx: { agent, pushLine, pushLabel, showPicker, askQuestion, persistRaw, ansi, C }
5
+ * ctx: { agent, pushLine, pushLabel, showPicker, askQuestion, persistRaw, ansi, C, confirmDelete }
6
6
  * MCP.md §5(2026-09-02,v2):列表即菜单(F7/D-2)、edit/add 统一字段 picker 表单
7
7
  * (F3/F3b/D-1——表单机制在 cmd-mcp-form.mjs,本文件只调 fieldPicker)、保存前预览
8
8
  * +探活(F2:探活失败回同一 fieldPicker——AC2)、AI 降 transport picker 末位(F4)、
@@ -37,7 +37,7 @@ async function addAndConnect(ctx, srv) {
37
37
  agent.config.mcp.servers.push(srv)
38
38
  try {
39
39
  pushLine(`[mcp] Connecting ${srv.name}...`, C.dim)
40
- const { connectMcpServer } = await import("../mcp.mjs")
40
+ const { connectMcpServer } = await import("@thincoder/core/mcp.mjs")
41
41
  const tools = await connectMcpServer(srv)
42
42
  agent.tools.push(...tools)
43
43
  pushLabel(`❯ MCP`, ansi.bold + C.tool)
@@ -50,7 +50,7 @@ async function addAndConnect(ctx, srv) {
50
50
  }
51
51
 
52
52
  export async function handleMcpCommand(ctx, args = []) {
53
- const { agent, pushLine, pushLabel, showPicker, askQuestion, persistRaw } = ctx
53
+ const { agent, pushLine, pushLabel, showPicker, askQuestion, persistRaw, confirmDelete = () => { throw new Error("ctx.confirmDelete missing — deletion refused (fail-closed)") } } = ctx
54
54
  // 每轮重读:原本无 mcp 配置时 `?? []` 会拿到游离数组,Add server 后快照过期
55
55
  const getServers = () => agent.config?.mcp?.servers ?? []
56
56
  // D-3/T24:本菜单会话内最新一次磁盘重读是否失败(畸形 config.json → 内存态兜底)
@@ -61,7 +61,7 @@ export async function handleMcpCommand(ctx, args = []) {
61
61
  * 测试注入 tmp config 路径用(生产 undefined → 默认 ~/.thincoder)。 */
62
62
  let changedNames = [] // 最近一次成功重读的对账结果(T23 ⚠ 标记)
63
63
  async function reloadFromDisk() {
64
- const { reloadMcpFromDisk } = await import("../config.mjs")
64
+ const { reloadMcpFromDisk } = await import("@thincoder/core/config.mjs")
65
65
  const r = reloadMcpFromDisk(agent, ctx.configPath)
66
66
  diskUnreadable = !r.ok
67
67
  if (r.ok) changedNames = r.changedNames
@@ -85,6 +85,7 @@ export async function handleMcpCommand(ctx, args = []) {
85
85
  // D-F5a(#7)先盘后存:磁盘 fresh raw.mcp.servers 上只删目标条目——不整节写回内存
86
86
  // agent.config.mcp.servers(内存含 reloadMcpFromDisk 的 keptConnected 尾巴——
87
87
  // 整节写回会把对端磁盘上其他 server 的新改动一起抹掉);冲突放弃不留内存 ghost
88
+ if (!(await confirmDelete(`Remove MCP server "${name}"?`))) return
88
89
  await persistRaw((raw) => {
89
90
  raw.mcp ??= { servers: [] }
90
91
  if (!Array.isArray(raw.mcp.servers)) return
@@ -92,14 +93,14 @@ export async function handleMcpCommand(ctx, args = []) {
92
93
  })
93
94
  agent.config.mcp.servers = getServers().filter((s) => s.name !== name)
94
95
  // Remove from tool list
95
- const { removeMcpTools } = await import("../mcp.mjs")
96
+ const { removeMcpTools } = await import("@thincoder/core/mcp.mjs")
96
97
  removeMcpTools(agent, name)
97
98
  pushLine(`[mcp] ${name} removed`, C.tool)
98
99
  }
99
100
 
100
101
  async function connectServer(name) {
101
102
  const srv = getServers().find((s) => s.name === name)
102
- const { removeMcpTools, connectMcpServer } = await import("../mcp.mjs")
103
+ const { removeMcpTools, connectMcpServer } = await import("@thincoder/core/mcp.mjs")
103
104
  removeMcpTools(agent, name)
104
105
  try {
105
106
  pushLine(`[mcp] Reconnecting ${name}...`, C.dim)
@@ -130,7 +131,7 @@ export async function handleMcpCommand(ctx, args = []) {
130
131
  * agent.tools、探完即关)。未保存的临时 entry 直接传 probe(§5 D-1)。 */
131
132
  async function probeLineFor(entry) {
132
133
  pushLine(`[mcp] Probing ${entry.name}...`, C.dim)
133
- const { probeMcpServer } = await import("../mcp.mjs")
134
+ const { probeMcpServer } = await import("@thincoder/core/mcp.mjs")
134
135
  const r = await probeMcpServer(entry)
135
136
  return r.ok ? `✓ ${r.toolCount} tools, ${r.latencyMs}ms` : `✗ ${r.error}`
136
137
  }
@@ -160,7 +161,7 @@ export async function handleMcpCommand(ctx, args = []) {
160
161
  if (!description) return
161
162
  pushLine("[mcp] Generating config from description...", C.dim)
162
163
  try {
163
- const { chat } = await import("../provider/index.mjs")
164
+ const { chat } = await import("@thincoder/core/provider/index.mjs")
164
165
  const res = await chat(agent.provider, {
165
166
  messages: [{
166
167
  role: "user",
@@ -1,5 +1,5 @@
1
1
  import { C } from "./ansi.mjs"
2
- import { parseModelRef } from "../config.mjs"
2
+ import { parseModelRef } from "@thincoder/core/config.mjs"
3
3
 
4
4
  /** /model command: open the session model picker, or switch the SESSION model directly via
5
5
  * `/model <provider>:<model>`(MODEL-MERGE-SESSION——裁定③:裸 provider 拒——显式 p:m;