@thincoder/core 0.10.7 → 0.10.9

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 (149) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/advisor/compaction.mjs +1 -1
  3. package/advisor/run.mjs +1 -1
  4. package/agent/chat-call.mjs +40 -27
  5. package/agent/child-marks.mjs +1 -1
  6. package/agent/dispatch-gates.mjs +7 -4
  7. package/agent/dispatch-run.mjs +1 -0
  8. package/agent/family-tools.mjs +13 -8
  9. package/agent/helpers.mjs +12 -10
  10. package/agent/image-heal.mjs +65 -0
  11. package/agent/light-round.mjs +4 -4
  12. package/agent/live-beat.mjs +4 -4
  13. package/agent/post-turn.mjs +34 -0
  14. package/agent/record-results.mjs +9 -1
  15. package/agent/run-stages.mjs +45 -10
  16. package/agent/setup-reminders.mjs +14 -13
  17. package/agent/setup.mjs +72 -48
  18. package/agent/spawn-child.mjs +1 -1
  19. package/agent/subagent-event.mjs +1 -1
  20. package/agent/suspension.mjs +2 -1
  21. package/agent/timers.mjs +8 -8
  22. package/agent/turn-loop.mjs +7 -1
  23. package/agent-tools/advisor.mjs +1 -1
  24. package/agent-tools/async-discard.mjs +40 -5
  25. package/agent-tools/async-settle.mjs +3 -1
  26. package/agent-tools/audit-block.mjs +5 -3
  27. package/agent-tools/batch-gate.mjs +198 -0
  28. package/agent-tools/batch-lifecycle.mjs +216 -36
  29. package/agent-tools/batch-paths.mjs +20 -20
  30. package/agent-tools/batch-skeleton.mjs +87 -0
  31. package/agent-tools/batch.mjs +11 -3
  32. package/agent-tools/checkpoint.mjs +1 -1
  33. package/agent-tools/context.mjs +28 -5
  34. package/agent-tools/design-slots.mjs +20 -7
  35. package/agent-tools/doc-check.mjs +75 -0
  36. package/agent-tools/eng.mjs +5 -3
  37. package/agent-tools/manifest.mjs +18 -18
  38. package/agent-tools/parent-channel.mjs +3 -3
  39. package/agent-tools/review-facts.mjs +2 -2
  40. package/agent-tools/settings.mjs +1 -1
  41. package/agent-tools/spawn-gates.mjs +108 -47
  42. package/agent-tools/ssh-session.mjs +486 -0
  43. package/agent-tools/ssh-transport.mjs +324 -0
  44. package/agent-tools/ssh-vault.mjs +291 -0
  45. package/agent-tools/subagent-scheduler.mjs +6 -5
  46. package/agent-tools/subagent-spawn.mjs +18 -11
  47. package/agent-tools/subagent.mjs +17 -9
  48. package/agent-tools/task.mjs +1 -1
  49. package/agent-tools/timer.mjs +8 -2
  50. package/agent-tools/zero-write-watch.mjs +2 -2
  51. package/agent-tools.mjs +3 -2
  52. package/attachments.mjs +4 -4
  53. package/browser/cdp.mjs +2 -2
  54. package/browser/launch.mjs +50 -4
  55. package/browser/snapshot.mjs +1 -1
  56. package/config-defaults.mjs +92 -0
  57. package/config-io.mjs +7 -2
  58. package/config-normalize.mjs +109 -0
  59. package/config-watch.mjs +2 -2
  60. package/config.mjs +88 -190
  61. package/declaration.mjs +5 -1
  62. package/doc-check/anchors.mjs +142 -0
  63. package/doc-check/index.mjs +210 -0
  64. package/doc-check/version.mjs +84 -0
  65. package/doc-check/width.mjs +132 -0
  66. package/file-refs.mjs +6 -6
  67. package/generate-title.mjs +1 -1
  68. package/hooks.mjs +1 -1
  69. package/i18n.mjs +13 -1
  70. package/ledger-cmd.mjs +314 -62
  71. package/ledger-db.mjs +122 -31
  72. package/ledger-migrate.mjs +18 -12
  73. package/ledger-read.mjs +6 -7
  74. package/ledger-tools.mjs +138 -77
  75. package/ledger.mjs +34 -62
  76. package/log.mjs +17 -3
  77. package/manifest-discovery.mjs +21 -18
  78. package/manifest-schema.mjs +17 -14
  79. package/manifest.mjs +37 -22
  80. package/mcp/transport-stdio.mjs +1 -1
  81. package/mcp.mjs +2 -2
  82. package/memory/core.mjs +1 -1
  83. package/memory/scan.mjs +1 -1
  84. package/memory/sweep.mjs +1 -1
  85. package/model-specs.mjs +7 -7
  86. package/notify-policy.mjs +6 -6
  87. package/package.json +2 -1
  88. package/permission.mjs +21 -1
  89. package/prompts/advisor-design.md +53 -23
  90. package/prompts/advisor-round1.md +34 -14
  91. package/prompts/advisor-round2.md +32 -19
  92. package/prompts/advisor-round3.md +34 -21
  93. package/prompts/common.md +148 -79
  94. package/prompts/consult-base.md +14 -8
  95. package/prompts/discipline-engineering.md +70 -66
  96. package/prompts/discipline-normal.md +86 -77
  97. package/prompts/persona-coder.md +6 -6
  98. package/prompts/persona-eng-coder.md +19 -18
  99. package/prompts/persona-eng-designer.md +40 -31
  100. package/prompts/persona-engineering.md +124 -92
  101. package/prompts/persona-explore.md +2 -2
  102. package/prompts/persona-normal.md +20 -17
  103. package/prompts/persona-plan.md +7 -7
  104. package/prompts/subagent-base.md +4 -4
  105. package/provider/anthropic.mjs +4 -2
  106. package/provider/core.mjs +4 -2
  107. package/provider/errors.mjs +8 -0
  108. package/provider/google.mjs +8 -1
  109. package/provider/normalize.mjs +42 -12
  110. package/provider/responses-request.mjs +4 -3
  111. package/provider/responses.mjs +2 -2
  112. package/provider/wait-status.mjs +1 -1
  113. package/provider-flows.mjs +1 -1
  114. package/queued.mjs +2 -2
  115. package/rules.mjs +4 -4
  116. package/session-gc.mjs +1 -1
  117. package/session-lifecycle.mjs +7 -7
  118. package/session-slot-write.mjs +2 -2
  119. package/session-slots-manifest.mjs +1 -1
  120. package/session.mjs +1 -1
  121. package/shell-candidates.mjs +2 -2
  122. package/shell-identity.mjs +1 -1
  123. package/team.mjs +63 -5
  124. package/think-off.mjs +3 -3
  125. package/token-ttl.mjs +1 -1
  126. package/token-window.mjs +1 -1
  127. package/tool-docs/bash.md +1 -1
  128. package/tool-docs/batch.md +1 -1
  129. package/tool-docs/browser.md +1 -1
  130. package/tool-docs/doc_check.md +54 -0
  131. package/tool-docs/fetch.md +3 -2
  132. package/tool-docs/ledger.md +7 -5
  133. package/tool-docs/manifest.md +14 -2
  134. package/tool-docs/ssh.md +51 -0
  135. package/tool-docs/subagent.md +6 -9
  136. package/tool-docs/timer.md +2 -1
  137. package/tool-docs/websearch.md +3 -1
  138. package/tools/browser.mjs +1 -1
  139. package/tools/execute.mjs +1 -1
  140. package/tools/git-run.mjs +2 -2
  141. package/tools/git.mjs +8 -8
  142. package/tools/index.mjs +3 -0
  143. package/tools/ops.mjs +1 -1
  144. package/tools/process-tree.mjs +2 -2
  145. package/tools/ssh-askpass.mjs +35 -0
  146. package/tools/ssh.mjs +105 -0
  147. package/tools/web-deepseek.mjs +172 -0
  148. package/tools/web.mjs +29 -4
  149. package/vision-reader.mjs +4 -4
@@ -1,26 +1,26 @@
1
1
  <!-- slot:[1] consumers:[plan subagent (engineering + normal)] -->
2
2
 
3
- ## 身份:只读规划
3
+ ## Role: read-only planner
4
4
  You are a planning subagent. The parent agent dispatched you to design an implementation plan for a coding task.
5
- You are READ-ONLY: you can read and search files and consult the web, but you have no file-editing or mutation tools—do not attempt to modify anything.
5
+ You are READ-ONLY: you can read and search files and consult the web, but you have no file-editing or mutation tools — an attempted edit errors out and spends a turn.
6
6
  Your deliverable IS the plan itself, returned as your final message.
7
7
 
8
- ## 权限边界(只读)
9
- - You are READ-ONLY: no file-editing or mutation tools — do not attempt to modify anything.
8
+ ## Permission boundary (read-only)
9
+ - You are READ-ONLY: no file-editing or mutation tools — an attempted edit errors out and spends a turn.
10
10
  - If something is ambiguous, note it in your plan.
11
11
 
12
- ## 报告义务
12
+ ## Reporting duty
13
13
  - Before planning, use repo_outline to understand the project structure, doc_search for conventions and design docs, and code_search
14
14
  to locate relevant symbols. Ground the plan in real paths, not guesses.
15
15
  - First judge whether you understand the codebase areas the task touches. If not, say so instead of guessing—structure your reply as:
16
16
  1. What you already know from the provided information
17
17
  2. Which open questions would benefit from an explore subagent's investigation (the parent can dispatch one)
18
18
  3. Your plan—preliminary if questions remain, final if context is sufficient
19
- - Ground the plan in reality: cite real file paths and line numbers, name actual functions and modules. No invented architecture.
19
+ - Ground the plan in reality: cite real file paths and line numbers, name actual functions and modules — an invented architecture sends the implementer to build something that does not exist.
20
20
  - Make steps concrete and verifiable: each step specific enough to check, ordered so dependencies come first.
21
21
  - Identify edge cases and failure modes in the plan. What boundary conditions does the implementation need to handle?
22
22
  Each step that encounters a risk must specify its fallback — not "handle error", but the concrete recovery path.
23
- - Where a real design choice exists, call out the trade-offs and recommend ONE option with reasoning—don't list possibilities without taking a stance.
23
+ - Where a real design choice exists, call out the trade-offs and recommend ONE option with reasoning — a list without a stance hands the decision back unchanged, and the asker wanted the analysis and a recommendation.
24
24
  - Stick to the task: the plan should solve the task, not redesign the codebase. Prefer modifying existing files over creating new ones—
25
25
  new files should only appear when the task genuinely demands a new module. List every file that will be modified, so the implementer knows the blast radius.
26
26
  - If something is ambiguous, note it in the plan.
@@ -3,15 +3,15 @@
3
3
  ## You and the parent agent (subagent-only)
4
4
 
5
5
  You are a subagent: **the parent agent IS your user** — every user-facing move you make is addressed to it; the authorization has already been exercised upstream.
6
- It is **not a confirmation gate** (it cannot approve anything, and it may be busy) — so never send a confirmation request; **but decision-grade questions are exactly what it wants**.
6
+ It is **not a confirmation gate** (it has no approval power, and it may be busy) — a confirmation request buys one refusal and one turn of waiting, so none is sent; **but decision-grade questions are exactly what it wants**.
7
7
 
8
- - **Never idle-wait for an answer, never end your turn "waiting for approval", never send a confirmation request** — a confirmation request asks it to approve before you go on.
9
- **What is banned is the confirmation-type question, not the decision-type question** — keep the two apart.
8
+ - **Idling for an answer, ending your turn "waiting for approval", or sending a confirmation request** each spends a turn and approves nothing — the parent is not a confirmation gate.
9
+ **The confirmation-type question — a question that asks for a nod before you continue — is ruled out: the parent cannot approve anything, so the nod buys nothing**; the decision-type question is exactly what it wants — keep the two apart.
10
10
  - **Send decision-grade questions**: the answer changes your next step and the materials cannot supply it ⇒ send `notify_parent` (`ask`) at once —
11
11
  one line with the question and your leaning; **send-and-go**: keep working on the unaffected parts; mark the affected part pending until the reply arrives.
12
12
  - **Decision-grade = four classes**: ① a stated premise the facts contradict; ② two requirements that conflict and you cannot arbitrate
13
13
  (two documents describing the same mechanism differently ⇒ **judged the same as a conflict**); ③ whether the action is inside your task domain; ④ a choice that would waste work already done.
14
- - **The conflict class must be sent, never deferred to the final report**: two requirements in conflict ⇒ send an `ask` at once — never settle it by picking a side, never keep weighing, never park it for the final report.
14
+ - **The conflict class is sent at once**: two requirements in conflict ⇒ send an `ask` right away — weighing produces no new information, picking a side settles what the user has not settled, and a round parked to the final report has already been done on the wrong premise.
15
15
  - **Markers in your report**: items awaiting the parent's decision (including an ask left unanswered
16
16
  when you finish) head with `[上抛·待裁]`; FYI items (including a note you sent up) — `[上抛·知会]`.
17
17
  The engine marks the delivered message mechanically; in your report, the marker is yours to write.
@@ -4,7 +4,7 @@
4
4
  * Docs: https://docs.anthropic.com/en/api/messages
5
5
  */
6
6
 
7
- import { specForModel } from "../config.mjs"
7
+ import { specForModel, requestBudget } from "../config.mjs"
8
8
  import { proxyFetch } from "../proxy.mjs"
9
9
  import { requestWithRetry } from "./retry.mjs"
10
10
  import { effectiveFetchTimeoutMs } from "./core.mjs"
@@ -53,7 +53,9 @@ export async function chat(provider, { messages, tools, onToken, onReasoning, on
53
53
  model: provider.model,
54
54
  messages: chatMessages,
55
55
  stream: true,
56
- max_tokens: provider.maxTokens || (spec.maxOutput || 8192),
56
+ // §6.17 发送面同源:anthropic 恒发 max_tokens(显式 ?? 规格 maxOutput——原第三级兜底不可达已随本批删净,
57
+ // §6.17「装配面不留第二算式」)
58
+ max_tokens: requestBudget(provider).maxTokens,
57
59
  }
58
60
  if (systemMessages.length > 0) body.system = systemMessages.join("\n\n")
59
61
  if (tools?.length) body.tools = tools
package/provider/core.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  * SSE parsing → provider/sse.mjs
5
5
  */
6
6
 
7
- import { providerSpec, resolveEnableThinking } from "../config.mjs"
7
+ import { providerSpec, requestBudget, resolveEnableThinking } from "../config.mjs"
8
8
  import { resolveProviderSecrets } from "../env-ref.mjs"
9
9
  import { proxyFetch } from "../proxy.mjs"
10
10
  import { escapeMessages, stripLocalMessageFields } from "../escape.mjs"
@@ -189,7 +189,9 @@ async function chatImpl(provider, { messages, tools, onToken, onReasoning, onWai
189
189
  }
190
190
  // 保守抑制(非能力断言):带 `noUsageStream` 的行 = 核不主动发 stream_options.include_usage(glm 族已实测带该参数全 200);逐族等级 = MODEL-SPECS.md §15.2
191
191
  if (!spec.noUsageStream) body.stream_options = { include_usage: true }
192
- if (provider.maxTokens) body.max_tokens = provider.maxTokens
192
+ // §6.17 发送面同源:max_tokens = requestBudget().maxTokens(显式才发——spec.maxOutput 是预留面非合同值)
193
+ const budget = requestBudget(provider, spec)
194
+ if (budget.maxTokens !== undefined) body.max_tokens = budget.maxTokens
193
195
  if (provider.temperature != null) {
194
196
  let t = provider.temperature
195
197
  if (spec.tempRange) {
@@ -53,6 +53,14 @@ export function isNonRetryableError(status, text) {
53
53
  return false
54
54
  }
55
55
 
56
+ /** 图像语义拒绝词表(PROVIDER.md §6.26——词表可扩 = 一处改)。 */
57
+ export const IMAGE_REJECTION_RE = /image|图片|图像|视觉|vision|multimodal/i
58
+
59
+ /** 错误文本具图像语义否(§6.26 T1 判据件——纯函数;未知文本 ⇒ false)。 */
60
+ export function looksLikeImageRejection(text) {
61
+ return IMAGE_REJECTION_RE.test(String(text ?? ""))
62
+ }
63
+
56
64
  export function betaBaseURL(baseURL) {
57
65
  // DeepSeek prefix continuation uses /beta endpoint; only handle /v1 suffix, append /beta when /v1 is missing
58
66
  if (/\/v1$/.test(baseURL)) return baseURL.replace(/\/v1$/, "/beta")
@@ -9,6 +9,7 @@ import { requestWithRetry } from "./retry.mjs"
9
9
  import { effectiveFetchTimeoutMs } from "./core.mjs"
10
10
  import { abortError, timeoutError } from "../abort-provenance.mjs"
11
11
  import { terminateBody } from "../stream-destroy.mjs"
12
+ import { requestBudget } from "../config.mjs"
12
13
 
13
14
  /** OpenAI 语义 tool_choice → Gemini FunctionCallingConfig(2026-08-31 能力层)。 */
14
15
  function mapFunctionCallingConfig(choice) {
@@ -83,7 +84,13 @@ export async function chat(provider, { messages, tools, onToken, onReasoning, on
83
84
  contents,
84
85
  generationConfig: {
85
86
  ...(provider.temperature != null ? { temperature: provider.temperature } : {}),
86
- ...(provider.maxTokens ? { maxOutputTokens: provider.maxTokens } : {}),
87
+ // §6.17 发送面同源:显式才发(requestBudget().maxTokens——无显式 ⇒ undefined ⇒ 字段不落)
88
+ ...(requestBudget(provider).maxTokens !== undefined
89
+ ? { maxOutputTokens: requestBudget(provider).maxTokens }
90
+ : {}),
91
+ // #1200(MODEL-SPECS.md §18.8):off 形在场 ⇒ 禁思考形——载荷逐字同标题径先例
92
+ // (`generate-title.mjs` 的 `generateTitle`);其余值(缺 ∥ on 形 ∥ null)⇒ 零发(误发 none = 反向禁用)。
93
+ ...(provider.thinking?.type === "disabled" ? { thinkingConfig: { thinkingLevel: "none" } } : {}),
87
94
  },
88
95
  safetySettings: [
89
96
  { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
@@ -16,6 +16,9 @@ export const IMAGE_HISTORY_BYTES_BUDGET = 32 * 1024 * 1024
16
16
  /** 图片驱逐占位文本(D-PR32 逐字)。 */
17
17
  const IMAGE_BUDGET_PLACEHOLDER = "[image omitted — dropped to keep the request under the history image budget]"
18
18
 
19
+ /** 图片拒绝剥除占位文本(PROVIDER.md §6.26 逐字——逐图原位替换)。 */
20
+ export const IMAGE_REJECTED_PLACEHOLDER = "[image omitted — the provider rejected the image payload; dropped from history so the session can continue. Re-read a smaller copy if the image is still needed.]"
21
+
19
22
  /** 单 part 的 base64 载荷长度(仅 data URL;非 data 引用不过请求体、零计账)。 */
20
23
  function imagePartBytes(p) {
21
24
  if (p?.type !== "image_url") return 0
@@ -25,14 +28,31 @@ function imagePartBytes(p) {
25
28
  return comma >= 0 ? url.length - comma - 1 : 0
26
29
  }
27
30
 
31
+ /** 图像 part 替换核(D-PR32 字节驱逐 ∥ §6.26 拒绝剥除共用——`drop` = `${mi}:${pi}` 键集;
32
+ * 命中 part 原位换 `placeholder`;copy-on-write:未命中消息原引用返回)。 */
33
+ function replaceImageParts(messages, drop, placeholder) {
34
+ return messages.map((m, mi) => {
35
+ const parts = m?.content
36
+ if (!Array.isArray(parts)) return m
37
+ let changed = false
38
+ const next = parts.map((p, pi) => {
39
+ if (!drop.has(`${mi}:${pi}`)) return p
40
+ changed = true
41
+ return { type: "text", text: placeholder }
42
+ })
43
+ return changed ? { ...m, content: next } : m
44
+ })
45
+ }
46
+
28
47
  /**
29
48
  * 历史图片累计字节预算(D-PR32):全部 image part 的 base64 载荷和超 budgetBytes 时,
30
49
  * 从**最老**图起替换为占位文本,直到剩余 ≤ 预算。纯函数 + 拷贝语义(未改动时返回原引用;
31
50
  * 改动时仅重建涉及的消息对象)——发送前对消息副本用;注入面对 `history` 本体回写(同函数同预算)。
32
51
  * @param {Array} messages
33
52
  * @param {number} [budgetBytes]
53
+ * @param {string} [placeholder] 占位文本(缺省 = D-PR32 逐字原文——§6.26 新增参零扰)
34
54
  */
35
- export function capHistoryImages(messages, budgetBytes = IMAGE_HISTORY_BYTES_BUDGET) {
55
+ export function capHistoryImages(messages, budgetBytes = IMAGE_HISTORY_BYTES_BUDGET, placeholder = IMAGE_BUDGET_PLACEHOLDER) {
36
56
  if (!Array.isArray(messages)) return messages
37
57
  const hits = []
38
58
  let total = 0
@@ -54,17 +74,27 @@ export function capHistoryImages(messages, budgetBytes = IMAGE_HISTORY_BYTES_BUD
54
74
  drop.add(`${hit.mi}:${hit.pi}`)
55
75
  remaining -= hit.bytes
56
76
  }
57
- return messages.map((m, mi) => {
58
- const parts = m?.content
59
- if (!Array.isArray(parts)) return m
60
- let changed = false
61
- const next = parts.map((p, pi) => {
62
- if (!drop.has(`${mi}:${pi}`)) return p
63
- changed = true
64
- return { type: "text", text: IMAGE_BUDGET_PLACEHOLDER }
65
- })
66
- return changed ? { ...m, content: next } : m
67
- })
77
+ return replaceImageParts(messages, drop, placeholder)
78
+ }
79
+
80
+ /**
81
+ * 图片拒绝剥除(PROVIDER.md §6.26):全部 image part 一次原位换 `IMAGE_REJECTED_PLACEHOLDER`
82
+ * (全剥——单图归因信息不存在;只剥最新 ⇒ 二连败)。copy-on-write:仅重建含图消息对象
83
+ * (两线共享消息对象——人读线 `_fullHistory` 内图不动);无图 ⇒ 原引用返回。
84
+ * @param {Array} messages
85
+ */
86
+ export function stripRejectedImages(messages) {
87
+ if (!Array.isArray(messages)) return messages
88
+ const drop = new Set()
89
+ for (let mi = 0; mi < messages.length; mi++) {
90
+ const parts = messages[mi]?.content
91
+ if (!Array.isArray(parts)) continue
92
+ for (let pi = 0; pi < parts.length; pi++) {
93
+ if (parts[pi]?.type === "image_url") drop.add(`${mi}:${pi}`)
94
+ }
95
+ }
96
+ if (drop.size === 0) return messages
97
+ return replaceImageParts(messages, drop, IMAGE_REJECTED_PLACEHOLDER)
68
98
  }
69
99
 
70
100
  export function stripImagesForTextModel(messages, spec) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * provider/responses-request.mjs — Responses **请求构造面**出档(2026-09-29 structure-split-2 · 台账 #620):自
3
- * `provider/responses.mjs` 全量迁出〔原 `:22-245`〕——host 白名单判定(`isStatefulHost` ∕ `isStoreRequiredHost` ∕
3
+ * `provider/responses.mjs` 全量迁出——host 白名单判定(`isStatefulHost` ∕ `isStoreRequiredHost` ∕
4
4
  * `isNonStatefulHost`)· `chainKey` · `builtinToolsFor` · `toItems` / `toTools` / `normalizeUsage` · `buildBody`;
5
5
  * **结构拆分零语义**(面不变 ∕ 判据不变,只换宿主档;切点 ∕ 缝单源 = 批档 `docs/batches/2026-09-29-structure-split-2.md` §2.2-B)。
6
6
  *
@@ -9,7 +9,7 @@
9
9
  * 零环:本档不引宿主(方向单行);档外依赖 = `../config.mjs`(`specForModel` ∥ `isBailianHost`)。
10
10
  * 链语义(双轨 ∕ 白名单 ∕ store 必开 ∥ 链重置)详述见宿主档头;本档 = 请求侧构造 ∥ 链状态机决策。
11
11
  */
12
- import { specForModel, isBailianHost } from "../config.mjs"
12
+ import { specForModel, isBailianHost, requestBudget } from "../config.mjs"
13
13
 
14
14
  /** 白名单:已实证 previous_response_id 的官方端(2026-08-31 真机验证:
15
15
  * 百炼 store:true 全链路 ✅;GLM(open.bigmodel.cn/api/v1)store:true 全链路 ✅)。 */
@@ -198,7 +198,8 @@ export function buildBody(provider, messages, tools, opts = {}) {
198
198
  ...(toolsFlat ? { tools: toolsFlat } : {}),
199
199
  // 显式才发(#907-D5):仅 provider.maxTokens 显式设定时发送——spec.maxOutput 是规格侧
200
200
  // 保守猜测非渠道合同值,恒发 = 渠道口径漂移 400 风险;无显式值 ⇒ 服务端默认(模型上限兜底)。
201
- ...(provider.maxTokens != null ? { max_output_tokens: provider.maxTokens } : {}),
201
+ // §6.17 发送面同源:requestBudget().maxTokens 未发 ⇒ undefined ⇒ 字段不落。
202
+ ...(requestBudget(provider).maxTokens !== undefined ? { max_output_tokens: requestBudget(provider).maxTokens } : {}),
202
203
  }
203
204
  // 内置工具声明追加(2026-08-31 用户拍板):web_search 与本地 function 工具共存
204
205
  const builtin = builtinToolsFor(provider.baseURL, provider.builtinTools)
@@ -84,7 +84,7 @@ export async function parseStream(response, { onToken, onReasoning, signal }) {
84
84
  onCompleted: (resp) => { result.finishReason = "stop"; seal(resp) }, // §6.31.7 F-DA5:completed ⇒ stop(原恒 null ⇒ agent 面缺席)
85
85
  onIncomplete: (resp) => {
86
86
  seal(resp)
87
- // 非长度原因(content_filter 等)不能报成 "length"——agent 层按原因给用户提示(`agent/run-stages.mjs:43`)
87
+ // 非长度原因(content_filter 等)不能报成 "length"——agent 层按原因给用户提示(`agent/run-stages.mjs` 的 `injectResponseReminders`)
88
88
  result.finishReason = resp?.incomplete_details?.reason === "content_filter" ? "content_filter" : "length"
89
89
  },
90
90
  onFailed: (resp) => {
@@ -252,7 +252,7 @@ export async function chat(provider, { messages, tools, onToken, onReasoning, on
252
252
  } catch (chainErr) {
253
253
  if (!isChainInvalidError(chainErr?.status)) throw chainErr
254
254
  // 链失效(404/400):先清残留链再重建——D6 语义是真·全量重发(body.input=items)。
255
- // 不清链会走 buildBody 的增量分支(`responses-request.mjs:220-227`):body.input = 裸 function_call_output 且
255
+ // 不清链会走 buildBody 的增量分支(`responses-request.mjs` 的 `buildBody`):body.input = 裸 function_call_output 且
256
256
  // previousResponseId 未随 fullBody 带走 → 服务端 call_id 无归属 → 二次 400(2026-08-31 评审 #1)
257
257
  provider._responsesChain = null
258
258
  const fresh = buildBody(provider, messages, tools, { toolChoice, stateful, forceStateful: true })
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { t } from "../i18n.mjs"
22
22
 
23
- /** 发射面回显前缀(`provider/retry.mjs:60` 的 `quota exhausted: `)——由 i18n 模板去占位符
23
+ /** 发射面回显前缀(`provider/retry.mjs` 的 `requestWithRetry` 的 `quota exhausted: `)——由 i18n 模板去占位符
24
24
  * 派生(核内零第二套文案字面);发射侧恒为 en 字面,故剥前缀不随 locale 变。 */
25
25
  const QUOTA_ECHO_PREFIX = t("status.quota", { msg: "" })
26
26
 
@@ -70,7 +70,7 @@ export function customFieldsError(fields) {
70
70
  }
71
71
 
72
72
  /**
73
- * M9 探针目标构造(上提源 = VSC `provider-flows.mjs:38` 的实参面 `presets.mjs` `probeTargetFromEntry`——
73
+ * M9 探针目标构造(上提源 = VSC `provider-flows.mjs` 的实参面 `presets.mjs` `probeTargetFromEntry`——
74
74
  * 逐条随迁):渠道条目 ⇒ `listModels` 可消费的探针目标。代理链与 chat 请求同规则(per-provider `proxy: true`
75
75
  * ∧ `proxy.uri` 在案 ⇒ 探针也走该代理;无全局闸——2026-10-08 批落);apiKey 归一(trim;缺 ⇒ 空串——探针会
76
76
  * 如实失败 ⇒ 渠道标「不可用」——准入判据 M8)。`headers` 字段面(#1048①):仅 plain-object 才携;缺 ∥ 非法 ⇒
package/queued.mjs CHANGED
@@ -2,8 +2,8 @@
2
2
  * queued.mjs — busy 排队消息的合并消费纯函数族(核件——「桌面处理流 · VSC 对齐」批 R3 上提产物)。
3
3
  *
4
4
  * 机制单源 = `docs/core/design/AGENT-LOOP-ASYNC-POOL.md` §6.8(「合并消费」条 + 「步边界 pickup」块)。
5
- * 上提源 = VSC `thincoder-vscode/src/extension/queued-merge.mjs:15-36`(容量常量 ∕ 合并串 ∕ 批计划)
6
- * + `queued-pickup.mjs:49`(`takeQueuedBatchItem` 取项)——**纯搬 + 转口,零语义改**(常量名 / 值 /
5
+ * 上提源 = VSC `thincoder-vscode/src/extension/queued-merge.mjs`(容量常量 ∕ 合并串 ∕ 批计划)
6
+ * + `queued-pickup.mjs`(`takeQueuedBatchItem` 取项)——**纯搬 + 转口,零语义改**(常量名 / 值 /
7
7
  * 形态文案 / 批语义逐字);对拍锚 = CLI 副本 `thincoder-cli/src/tui/queued-merge.mjs`(同名同值)。
8
8
  *
9
9
  * 消费面:计划面(常量 ∕ 合并串 ∕ 计划)由桌面排队面消费(`thincoder-desktop/src/main/queued-input.mjs`);
package/rules.mjs CHANGED
@@ -69,7 +69,7 @@ export function discoverRules(projectRoot) {
69
69
  // ─── A 面:stream 规则装配合并(`.thincoder/rules`——B7 3a 自核装配内联块抽取单源)────────
70
70
 
71
71
  /** 装配合并:`.thincoder/rules/` 文件规则**置前**,config 规则按 `pattern` 去重(文件侧优先;
72
- * 逐字镜像原 CLI `make-agent.mjs:44-48` 语义)。文件规则为空 ⇒ 归一数组原样返回(零拷贝零改)。
72
+ * 逐字镜像原 CLI `make-agent.mjs` 语义)。文件规则为空 ⇒ 归一数组原样返回(零拷贝零改)。
73
73
  * `discover` 可注入(核装配 `DEFAULT_DEPS.discoverRules` 缝——U79 装配序可机验;VSC 端壳同取本件)。 */
74
74
  export function mergeFileRules(configRules, cwd, discover = discoverRules) {
75
75
  const rules = Array.isArray(configRules) ? configRules : []
@@ -79,7 +79,7 @@ export function mergeFileRules(configRules, cwd, discover = discoverRules) {
79
79
  return [...fileRules, ...rules.filter((r) => !filePatterns.has(r.pattern))]
80
80
  }
81
81
 
82
- // ─── B 面:作用域规则读取面(`.cursor/rules`——R10 上提;纯搬自 VSC `src/extension/rules.mjs:28-81`)──
82
+ // ─── B 面:作用域规则读取面(`.cursor/rules`——R10 上提;纯搬自 VSC 端壳)──
83
83
 
84
84
  /**
85
85
  * Load all scoped rules from `.cursor/rules/`.
@@ -171,8 +171,8 @@ export function scopedRulesBlock(agent, cwd) {
171
171
  return `\n\nProject rules (.cursor/rules):\n${rules.always.map((r) => `- ${r.name}: ${r.content}`).join("\n")}`
172
172
  }
173
173
 
174
- /** 文件路径 glob 匹配(简单 `*` / `**` / `?` 通配——无完整 glob 库;纯搬自 VSC
175
- * `src/extension/rules.mjs:37-74`,零语义改)。 */
174
+ /** 文件路径 glob 匹配(简单 `*` / `**` / `?` 通配——无完整 glob 库;纯搬自 VSC 端壳,零语义改)。
175
+ * */
176
176
  export function matchesGlob(filePath, patterns) {
177
177
  if (!patterns || patterns.length === 0) return false
178
178
  // Normalize to forward slashes
package/session-gc.mjs CHANGED
@@ -37,7 +37,7 @@ import { configDir } from "./config.mjs"
37
37
  import { sessionPath, ownerPid } from "./session-slots.mjs"
38
38
  import { probeOwnersAsync, ownerState } from "./process-probe.mjs"
39
39
  import { listStaleCwds, deleteStaleCwd, recycleGroup, sweepStale, trashRootFor } from "./session-stale.mjs"
40
- // sidecar 记录目录后缀常量(单源 = `session-segments.mjs:13`——本档经该常量判后缀形态;
40
+ // sidecar 记录目录后缀常量(单源 = `session-segments.mjs` 的 `RECORD_DIR_SUFFIX`——本档经该常量判后缀形态;
41
41
  // 两处名形匹配式(`classifyResidue` / `sweepOrphanRecordDirs`)按 `{prefix}.{N}` + 后缀内联,不构成第二单源)。
42
42
  import { RECORD_DIR_SUFFIX } from "./session-segments.mjs"
43
43
 
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import { existsSync } from "node:fs"
21
+ // §6.17:auto 派生值只进显示面(compactThresholdResolved)——显式键 agent.compactThreshold 永不被改写
21
22
  import { resolveCompactThreshold } from "./config.mjs"
22
23
  // 槽面判据单源(#841 KD-841-2:槽渠道 key 门 + 模型面序——PROVIDER.md §6.22)。
23
24
  import { hasKey, resolveChannelModel, resolveProviderPlan } from "./model-ref.mjs"
@@ -79,8 +80,8 @@ function stripTruncatedToolArgs(m) {
79
80
  * "三支"误读):
80
81
  * ① 槽 provider 存在 → provider/model 按槽值设(模型链 = 槽模型 → `defaultModel` 属本渠道 ⇒ 其模型段;
81
82
  * 两档皆无 ⇒ `null`——2026-10-09 清除批:原 `providers[].model` 回落退场;`defaultModel` 经
82
- * `resolveProviderPlan` 参与模型面)+ 重算
83
- * compactThreshold(auto 时——按恢复后模型);
83
+ * `resolveProviderPlan` 参与模型面)+ 刷新显示面
84
+ * `compactThresholdResolved`(auto 时——按恢复后模型;显式键 `compactThreshold` 零改写);
84
85
  * ② 槽 provider 没了 → D-S3 保留——静默保持现状(config 有效则有效——两方都无效由调用侧
85
86
  * 复验 validateProvider 弹重选)。
86
87
  * 删旧支:"activeModel==null 清 stale override 回渠道默认"——前提 = 渠道默认字段——已随
@@ -131,7 +132,7 @@ export function applySession(agent, data, opts = {}) {
131
132
  }
132
133
  // ENG-PLAN-EXCLUSION(FR31 ③ / KD10——恢复点①·CLI `applySession`):工程模式 ⇒ planMode 恒
133
134
  // false(内存位 + 未注入的 plan 提示语)——防「工程纪律 × plan 只读」半状态随槽恢复复活。
134
- // **槽不就地回写**:内存值在下一次 `saveSession` 随 `planMode` 字段自然收正(`session.mjs:128`)。
135
+ // **槽不就地回写**:内存值在下一次 `saveSession` 随 `planMode` 字段自然收正(`session.mjs`)。
135
136
  if (agent.config?.agent?.engineering === true) clearPlanMode(agent)
136
137
  if (data.advisor) {
137
138
  agent.config.advisor = { ...data.advisor }
@@ -184,11 +185,10 @@ export function applySession(agent, data, opts = {}) {
184
185
  agent.activeModel = slotModel
185
186
  agent.provider = { ...slotProvider }
186
187
  if (slotModel) agent.provider.model = slotModel
187
- // 重算 compactThreshold(auto 时——阈值跟模型走;原在 bin 的 switched 分支——收拢本处)。
188
- // 非串 model(外端脏载 / 手工档)⇒ 不重算(#638 后查表族恒 total,本门保留为同形防御——与
189
- // 下方档位块同口径);类型脏载不中断恢复。
188
+ // §6.17:恢复后 auto 派生线随槽模型跟手——只刷新显示面 compactThresholdResolved(显式键不写;
189
+ // 判定阈值本身由 setup.mjs 每 run 现算,不依赖本值)
190
190
  if (switched && agent.config?.agent?.compactThresholdAuto && typeof agent.provider.model === "string" && agent.provider.model) {
191
- agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
191
+ agent.config.agent.compactThresholdResolved = resolveCompactThreshold(null, agent.provider).value
192
192
  }
193
193
  // 会话级档位施加(§6.21 判据句 4——在模型合并支之后,以**合并后**的 `provider.model` 判 spec;
194
194
  // 三端共用本施加面):`"off"` 记号 ⇒ 该模型有 off 路径才设关思考形(effort 族该形为 `null`
@@ -108,7 +108,7 @@ export function saveSlotData(cwd, slot, data) {
108
108
  if (data.createdBy === undefined && !existsSync(p)) data.createdBy = sessionEnd()
109
109
  writeSessionFile(p, data)
110
110
  // 写后取 mtime 单次采集(读失败 ⇒ `0` = 不可得):缓存(本档路径键)+ 摘要 `ts` 地板入参
111
- // (#503——保存面写后取 mtime,同族先例 `session-slot-verify.mjs:99`)。
111
+ // (#503——保存面写后取 mtime,同族先例 `session-slot-verify.mjs` 的 `runPass`)。
112
112
  const wroteMtime = (() => { try { return statSync(p).mtimeMs } catch { return 0 } })()
113
113
  slotMtimeCache.set(p, wroteMtime)
114
114
  try {
@@ -214,7 +214,7 @@ export function resolveEffortPatch(level, model) {
214
214
  * `activeModel`(不新增字段;`null` = 缺键语义——`provider` / `model` 无「未设」态);`effort` 经
215
215
  * `resolveEffortPatch` 归一(`model` 取 `patch.model ?? 槽现值 activeModel`),归一 `null` = 未设
216
216
  * (回落配置面 / 渠道默认)——**值置 `null` 不删键**(与 `newSlotData` 规范结构同源)。
217
- * 只写盘:不碰当前内存态(施加面唯一 = `applySession`——§6.21 判据句 4 / 边界表第 5 行)。
217
+ * 只写盘:不碰当前内存态(施加面唯一 = `applySession`——§6.21 判据句 4 / 边界表「写非活动槽」行)。
218
218
  *
219
219
  * **值域面(#441② 明文——核侧有意不校)**:本出口只校**键闭集**,**不校值域**——`provider` /
220
220
  * `model` 传 `null`(或表外字面)按**缺键语义**零改(不报错;`null` 仅对 `effort` 有「未设」
@@ -68,7 +68,7 @@ export function slotDigest(data, mtimeMs) {
68
68
  const isNum = (v) => typeof v === "number" && Number.isFinite(v)
69
69
 
70
70
  /** 摘要 `ts` 打点(**单源** · #503 地板):`max(墙钟, mtimeMs)`;`mtimeMs` 不可得(缺省 / 非数)⇒
71
- * 裸墙钟回溯。沿先例 `session-slot-verify.mjs:99`(回写面地板):**平台文件时间戳可领先
71
+ * 裸墙钟回溯。沿先例 `session-slot-verify.mjs` 的 `runPass`(回写面地板):**平台文件时间戳可领先
72
72
  * `Date.now()` 数 ms**——无地板则保存面刚写的摘要会被 `needsVerify`(`ts < mtime`)判「陈旧」
73
73
  * 一次(有界自纠,非数据面);地板后 `ts ≥ mtime` 恒成立(同一 stat 值即比较对象)。 */
74
74
  export function digestTs(mtimeMs) {
package/session.mjs CHANGED
@@ -183,7 +183,7 @@ export function saveSession(agent, opts = {}) {
183
183
  if (agent._recordStore) saveProjectedSlot(agent, p, fields, contextHistory)
184
184
  else writeSessionFile(p, { ...fields, history: legacyHistory(agent), contextHistory })
185
185
  // 记录我们刚写的 mtime——下次保存跳过解析(缓存 = `{p, mtimeMs}` 按路径命中:#386)+
186
- // 摘要 `ts` 地板入参(#503:保存面**写后取** mtime——同族先例 session-slot-verify.mjs:99)。
186
+ // 摘要 `ts` 地板入参(#503:保存面**写后取** mtime——同族先例 `session-slot-verify.mjs` 的 `runPass`)。
187
187
  let wroteMtimeMs
188
188
  try {
189
189
  wroteMtimeMs = statSync(p).mtimeMs
@@ -1,6 +1,6 @@
1
1
  /**
2
- * shell-candidates.mjs — 平台 shell 候选探测**单源**(B10 S17 收编:桌面 `settings-env.mjs:27-62` ∥
3
- * VSC `src/extension/settings.mjs:72-115` 两造 ≈逐字 ⇒ 候选表 ∕ 探测序 ∕ 超时 ∕ memo 上提本档;
2
+ * shell-candidates.mjs — 平台 shell 候选探测**单源**(B10 S17 收编:桌面 `settings-env.mjs` ∥
3
+ * VSC `src/extension/settings.mjs` 两造 ≈逐字 ⇒ 候选表 ∕ 探测序 ∕ 超时 ∕ memo 上提本档;
4
4
  * 两端薄壳只留 re-export —— 端侧零自持候选表)。
5
5
  *
6
6
  * 面:`shellCandidates()` ⇒ `Promise<{ name, value }[]>`——「System default」恒首 + 平台候选
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * memo(进程生命周期 + 按配置值键控)先例 = `shell-candidates.mjs`(「shell 路径不热变化」同判据);
12
12
  * 配置值变化 ⇒ memo 未命中重探(K2 配置变更刷新 ∥ 裁定①零静态同时成立)。
13
- * 测试缝 `_setComSpecForTest` 先例 = `shell-candidates.mjs:34` `_setShellDetectForTest`。
13
+ * 测试缝 `_setComSpecForTest` 先例 = `shell-candidates.mjs` `_setShellDetectForTest`。
14
14
  */
15
15
 
16
16
  // 识别表(raw 尾段词 → 显示名 + 语法族)。匹配大小写不敏感,尾段词先剥 `.exe` 后缀(U3——
package/team.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * team.mjs — 团队登录机制(TEAM.md §2——三端共用单源:CLI ∥ VSC ∥ 桌面同 import)。
3
3
  *
4
- * 面:登录 ∥ 退出 ∥ 登录态读面 ∥ 活校验(`teamVerify`——只读三值,登录面补全批增)∥ 端标签(自动生成)∥
5
- * 派生 provider 条目(登录写入 / 退出停用)∥ 服务地址归一 ∥ 失败分类。**写面单源** = 本档(`writeConfigAtomic`
4
+ * 面:登录 ∥ 退出 ∥ 轮换(`teamRotate`——team-token-rotation 批增)∥ 登录态读面 ∥ 活校验(`teamVerify`——只读三值,
5
+ * 登录面补全批增)∥ 端标签(自动生成)∥ 派生 provider 条目(登录写入 / 退出停用 / 轮换同步)∥ 服务地址归一 ∥
6
+ * 失败分类。**写面单源** = 本档(`writeConfigAtomic`
6
7
  * 一次 mutate——经 `persistRaw`;端侧零自写盘,三端同源)——`teamVerify` **只读**(零写盘、零状态)。
7
8
  *
8
9
  * 形(TEAM.md §2.1):顶层 `team` 段 `{ server, member{username,name}, label, token }`——`token` 在场 ⇔
@@ -14,9 +15,11 @@
14
15
  * 返回形(B1 批端侧契约——父侧裁定 2026-10-10):
15
16
  * `teamLogin` ⇒ `{ ok:true, notice?:"manual-name-conflict" }` ∥ `{ ok:false, reason }`
16
17
  * `teamLogout` ⇒ `{ ok:true, revokeDelivered:boolean }` ∥ `{ ok:false, reason }`
18
+ * `teamRotate` ⇒ `{ ok:true }` ∥ `{ ok:false, reason }`(子集:`network` ∥ `invalid`(401——旧 token 已失效)∥
19
+ * `write_failed`——TEAM.md §2.7)
17
20
  * `reason` 取值域:`network`(不可达 ∥ DNS/TLS ∥ 超时 ∥ 非 401/429 应答——服务不符合预期面)∥
18
- * `credentials`(401——同措辞)∥ `rate_limited`(429——锁定期)∥ `write_failed`(服务端已应答、本地
19
- * 写盘失败——mtime 冲突 ∥ 畸形档拒写)。`notice` 携**码**不携文(文本归端侧 i18n);
21
+ * `credentials`(401——登录流程同措辞)∥ `invalid`(401——轮换流程:旧 token 已失效)∥ `rate_limited`(429——锁定期)∥
22
+ * `write_failed`(服务端已应答、本地写盘失败——mtime 冲突 ∥ 畸形档拒写)。`notice` 携**码**不携文(文本归端侧 i18n);
20
23
  * `revokeDelivered:false` = 服务端吊销未达(网络失败——本地照清)。
21
24
  */
22
25
  import { hostname } from "node:os"
@@ -82,7 +85,8 @@ export function teamStatus() {
82
85
  * (Bearer = `team.token`)⇒ **三值闭集**:`{ state: "valid" }`(200——token 有效)∥ `{ state: "invalid" }`(401——
83
86
  * 被吊销 ∥ 无效)∥ `{ state: "unreachable" }`(网络不可达 ∥ 其他非 401 失败——**不判失效**:离线容忍)。
84
87
  * **只读**——零写盘、零状态(调用面持结果);零周期轮询(触发点制 = 启动一次 ∥ 登/退面开合 ∥ CLI `/team status`)。
85
- * token ∥ 地址缺席(未登录 ∥ 畸形档)⇒ `unreachable`(无从校验——不假报失效;端侧未登录面零调本函数)。
88
+ * token ∥ 地址缺席(已登录但缺 token 或缺地址)⇒ `unreachable`(无从校验——不假报失效;端侧未登录面零调本函数)。
89
+ * **畸形档不到本函数**:`loadConfig()` 畸形即抛,端侧拒绝直传(零 catch 吞)——故本行不列畸形档。
86
90
  */
87
91
  export async function teamVerify() {
88
92
  const team = loadConfig().team
@@ -104,6 +108,60 @@ export async function teamVerify() {
104
108
  return { state: "unreachable" } // 其余(403 ∥ 404 地址非本服务 ∥ 5xx)⇒ 不判失效
105
109
  }
106
110
 
111
+ /**
112
+ * 轮换(TEAM.md §2.7;三端同构——team-token-rotation 批):`POST <server>/api/client/rotate`(Bearer = `team.token`;
113
+ * 体 `{}`——写端点 JSON 门照常)⇒ 成 ⇒ **一次写盘**:`team.token` = 新明文(替换)+ 派生条目 `apiKey` 同步
114
+ * (条目缺失 ∥ 同名手工 ⇒ 仅落 token——沿登录不覆盖口径);败 ⇒ 零写盘 + 出词。**非 best-effort**:网络败 ⇒
115
+ * 服务端零变更(旧 token 仍有效——重试即复原);401 ⇒ `invalid`(旧 token 已失效——端侧转「已失效」)。
116
+ * 无 token ∥ 无地址 ⇒ `network`(零请求旁路——端侧管理面本应已登录门);服务端语义单源 = CLIENT.md §2 rotate 行。
117
+ */
118
+ export async function teamRotate() {
119
+ const team = loadConfig().team
120
+ const token = typeof team?.token === "string" && team.token !== "" ? team.token : null
121
+ const server = typeof team?.server === "string" && team.server !== "" ? team.server : null
122
+ if (token === null || server === null) return { ok: false, reason: "network" } // 无凭据/无地址 ⇒ 兜底归类(零请求)
123
+ let res
124
+ try {
125
+ res = await fetch(`${server}/api/client/rotate`, {
126
+ method: "POST",
127
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
128
+ body: "{}",
129
+ signal: AbortSignal.timeout(TEAM_FETCH_TIMEOUT_MS),
130
+ })
131
+ } catch {
132
+ return { ok: false, reason: "network" } // 不可达 ∥ DNS ∥ TLS ∥ 超时(服务端零变更——旧 token 仍有效)
133
+ }
134
+ if (!res.ok) {
135
+ await res.text().catch(() => {}) // 错误体零消费面(分类只看状态——零新码)
136
+ if (res.status === 401) return { ok: false, reason: "invalid" } // 旧 token 已失效(端侧转「已失效」)
137
+ return { ok: false, reason: "network" } // 404(地址不是本服务)∥ 5xx 等——服务不符合预期面
138
+ }
139
+ let body = null
140
+ try {
141
+ body = await res.json()
142
+ } catch {
143
+ body = null
144
+ }
145
+ const next = typeof body?.token === "string" && body.token !== "" ? body.token : null
146
+ if (next === null) return { ok: false, reason: "network" } // 200 而形不符(代理 ∥ 非本服务应答)
147
+ try {
148
+ const result = persistRaw((raw) => {
149
+ assertWritableRoot(raw)
150
+ const section = raw.team
151
+ if (section === null || typeof section !== "object" || Array.isArray(section)) throw new Error("team 段缺失——拒写")
152
+ section.token = next // 新明文替换(一次写盘)
153
+ if (Array.isArray(raw.providers)) {
154
+ const entry = raw.providers.find((p) => p !== null && typeof p === "object" && p.name === TEAM_PROVIDER_NAME && p.derived === true)
155
+ if (entry !== undefined) entry.apiKey = next // 派生条目同步;缺失 ∥ 同名手工 ⇒ 仅落 token(零覆盖)
156
+ }
157
+ })
158
+ if (result !== null && result.ok === false) return { ok: false, reason: "write_failed" } // mtime 冲突(不自动合并)
159
+ } catch {
160
+ return { ok: false, reason: "write_failed" } // 畸形档拒写等(writeConfigAtomic fail-closed)
161
+ }
162
+ return { ok: true }
163
+ }
164
+
107
165
  /**
108
166
  * 登录(TEAM.md §2.2;三端同构):请求 `POST <server>/api/client/login` ⇒ 成 ⇒ **一次写盘**(`team` 段 +
109
167
  * 派生条目 upsert);败 ⇒ 分类出词、零写盘。`label` 缺省 ⇒ 自动生成(`defaultTeamLabel()`);显式传入
package/think-off.mjs CHANGED
@@ -17,15 +17,15 @@ export function thinkOffShape(spec) {
17
17
 
18
18
  /** 有效 off 路径(「何时可宣称 OFF」判据本体 = §16.4)——由 `thinkOffShape` 派生(防两处漂移):
19
19
  * `null` 形只有 effort 族的载荷门据它补发 `reasoning_effort:"none"` ⇒ 须枚举含 `"none"`
20
- * (无枚举 / 无 `none` ⇒ 形不发任何字段,§16.4 表第 1 行);`{type:"disabled"}` 形 ⇒ 达载荷层。
21
- * 两形共门 = `thinkAlwaysOn !== true`(服务端强制族:形被拒 / 恒思考 ⇒ 无效,§16.3 / §16.4 表第 2 行)。 */
20
+ * (无枚举 / 无 `none` ⇒ 形不发任何字段,§16.4 表「effort 族」行);`{type:"disabled"}` 形 ⇒ 达载荷层。
21
+ * 两形共门 = `thinkAlwaysOn !== true`(服务端强制族:形被拒 / 恒思考 ⇒ 无效,§16.3 / §16.4 表「服务端强制族」行)。 */
22
22
  export function thinkOffPath(spec) {
23
23
  if (spec?.thinkAlwaysOn === true) return false
24
24
  if (thinkOffShape(spec) === null) return (spec?.reasoningEffortEnum ?? []).includes("none")
25
25
  return true
26
26
  }
27
27
 
28
- /** advisor 推理档写面三态(**写语义单源** —— 由 VSC `settings-panel-write.mjs:149-164` 上提;B10 S11:
28
+ /** advisor 推理档写面三态(**写语义单源** —— 由 VSC `settings-panel-write.mjs` 的 `applyAdvisorEffort` 上提;B10 S11:
29
29
  * 桌面主侧 ∕ VSC 写面两端同引,零副本):`advisor` 对象原地作用于 `{ thinking, reasoningEffort }` 两键 ——
30
30
  * ① `"none"`(关思考)⇒ `thinking = thinkOffShape(spec)`(族别 off 形)+ 删 `reasoningEffort`;
31
31
  * ② 非空档字面 ⇒ `reasoningEffort = 值` + 清 off 形残记(`thinking` 为 `null` 字面 ∕ `{type:"disabled"}`
package/token-ttl.mjs CHANGED
@@ -277,7 +277,7 @@ export function persistEngTokens(agent) {
277
277
  data.updatedAt = Date.now()
278
278
  writeSessionFile(p, data)
279
279
  // 记录我们刚写的 mtime——下次保存/守卫跳过重复解析(saveSession 同款;缓存按路径命中 #386)
280
- // + 摘要 `ts` 地板入参(#503:保存面写后取 mtime——同族先例 session-slot-verify.mjs:99)。
280
+ // + 摘要 `ts` 地板入参(#503:保存面写后取 mtime——同族先例 `session-slot-verify.mjs` 的 `runPass`)。
281
281
  let wroteMtimeMs
282
282
  try {
283
283
  wroteMtimeMs = statSync(p).mtimeMs
package/token-window.mjs CHANGED
@@ -38,7 +38,7 @@ const KEEP_HEAD = 0 // No dedicated head: earliest messages may be a COMPLETED e
38
38
  // Tail count formula (D4): window-adaptive (~30 msgs per 100K — old fixed 10 too thin on 1M), capped
39
39
  // at 40% of history; §6.4④ D-T1/D-T2 make the count only a CANDIDATE — a token budget (TAIL_BUDGET_FRACTION
40
40
  // × window − SUMMARY_TOKEN_ESTIMATE ≈1K, §6.9) tightens it over pair-safe boundaries when compaction runs,
41
- // never below TAIL_FLOOR_MESSAGES; ordinary sessions never reach it (D-T4: trigger 0.6 untouched).
41
+ // never below TAIL_FLOOR_MESSAGES; ordinary sessions never reach it (D-T4: the trigger ratio lives in `config.mjs` — untouched here).
42
42
  const TAIL_BUDGET_FRACTION = 0.15
43
43
  const SUMMARY_TOKEN_ESTIMATE = 1000 // §6.9: summary output target ~1K tokens — reserved from the 15%
44
44
  const TAIL_FLOOR_MESSAGES = 10 // §6.4④ D-T2: the tail keeps ≥10 verbatim messages — floor beats budget
package/tool-docs/bash.md CHANGED
@@ -10,7 +10,7 @@ Execute a shell command and return stdout+stderr. Use for running commands, buil
10
10
 
11
11
  Parameters:
12
12
  - command (required): Shell command to execute
13
- - timeout: Timeout in milliseconds (default 120000, max ~300000)
13
+ - timeout: Timeout in milliseconds (default 120000 for synchronous runs; background runs have no default — an explicit timeout still applies; no upper bound)
14
14
  - filter: Optional — a regex; only output lines matching it are returned (case-insensitive). Use instead of hand-writing a pipe into `findstr`/`grep`.
15
15
  - async: Run in the background (default false) — returns an ack at once (bash#<id>, log path); the result arrives as a digest when the process exits. Depth-0 only.
16
16
  {{inject:bash-terminal-face}}