thincoder 0.12.61 → 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.
- package/CHANGELOG.md +34 -1
- package/README.md +13 -12
- package/bin/thincoder.mjs +63 -28
- package/package.json +6 -5
- package/src/acp/bridge.mjs +35 -15
- package/src/acp/client-caps.mjs +86 -0
- package/src/acp/ext.mjs +86 -0
- package/src/acp/handlers-session.mjs +240 -0
- package/src/acp/handlers-slots.mjs +196 -0
- package/src/acp/login.mjs +48 -0
- package/src/acp/session.mjs +6 -4
- package/src/acp.mjs +67 -371
- package/src/cli/distill-command.mjs +3 -3
- package/src/cli/make-agent.mjs +59 -17
- package/src/cli/memory-command.mjs +3 -3
- package/src/cli/permission.mjs +4 -48
- package/src/cli/setup-wizard.mjs +1 -1
- package/src/completions.mjs +3 -1
- package/src/crash-reports.mjs +32 -10
- package/src/distill.mjs +4 -4
- package/src/heap-watch.mjs +88 -0
- package/src/prompt-injections.mjs +20 -0
- package/src/tui/agent-turn.mjs +40 -9
- package/src/tui/cmd-advisor.mjs +5 -5
- package/src/tui/cmd-clear.mjs +2 -0
- package/src/tui/cmd-config.mjs +8 -8
- package/src/tui/cmd-eng.mjs +25 -9
- package/src/tui/cmd-mcp.mjs +9 -8
- package/src/tui/cmd-model.mjs +1 -1
- package/src/tui/cmd-new.mjs +10 -5
- package/src/tui/cmd-reindex.mjs +1 -1
- package/src/tui/cmd-restore.mjs +2 -2
- package/src/tui/cmd-session.mjs +31 -4
- package/src/tui/cmd-skills.mjs +1 -1
- package/src/tui/cmd-think.mjs +22 -9
- package/src/tui/config-helpers.mjs +1 -1
- package/src/tui/display-budget.mjs +206 -0
- package/src/tui/index.mjs +40 -12
- package/src/tui/interaction.mjs +16 -7
- package/src/tui/key-handler-search.mjs +9 -1
- package/src/tui/key-modes.mjs +9 -4
- package/src/tui/ledger-surface.mjs +85 -0
- package/src/tui/model-catalog.mjs +4 -4
- package/src/tui/model-picker.mjs +8 -7
- package/src/tui/mouse.mjs +11 -6
- package/src/tui/pickers.mjs +15 -2
- package/src/tui/render-conversation.mjs +1 -1
- package/src/tui/render-frame.mjs +17 -6
- package/src/tui/render-loop.mjs +1 -1
- package/src/tui/render-segments.mjs +3 -1
- package/src/tui/slash-commands.mjs +1 -1
- package/src/tui/startup.mjs +49 -17
- package/src/tui/subagent-blocks.mjs +21 -3
- package/src/tui/subagent-children.mjs +86 -14
- package/src/tui/subagent-freeze.mjs +80 -3
- package/src/tui/suspension-drive.mjs +48 -22
- package/src/tui/tool-args.mjs +5 -2
- package/src/tui/tool-display.mjs +16 -2
- package/src/tui/tool-events.mjs +64 -22
- package/src/tui/tui-lifecycle.mjs +9 -2
- package/src/tui/wizard.mjs +3 -3
- package/src/tui/wrapped-spawn.mjs +21 -5
- package/src/abort-provenance.mjs +0 -116
- package/src/advisor/citations.mjs +0 -139
- package/src/advisor/compaction.mjs +0 -174
- package/src/advisor/convergence.mjs +0 -80
- package/src/advisor/history.mjs +0 -77
- package/src/advisor/loop.mjs +0 -293
- package/src/advisor/messages.mjs +0 -299
- package/src/advisor/project-context.mjs +0 -194
- package/src/advisor/repos.mjs +0 -150
- package/src/advisor/run.mjs +0 -293
- package/src/advisor/truncate.mjs +0 -57
- package/src/advisor.mjs +0 -290
- package/src/agent/completion.mjs +0 -146
- package/src/agent/dispatch.mjs +0 -489
- package/src/agent/helpers.mjs +0 -384
- package/src/agent/post-turn.mjs +0 -70
- package/src/agent/record-results.mjs +0 -174
- package/src/agent/relay-prefix.mjs +0 -39
- package/src/agent/run-stages.mjs +0 -242
- package/src/agent/setup-reminders.mjs +0 -69
- package/src/agent/setup.mjs +0 -354
- package/src/agent/spawn-child.mjs +0 -228
- package/src/agent-tools/advisor-async.mjs +0 -346
- package/src/agent-tools/advisor-settle.mjs +0 -231
- package/src/agent-tools/advisor.mjs +0 -260
- package/src/agent-tools/async-settle.mjs +0 -191
- package/src/agent-tools/batch-segment.mjs +0 -195
- package/src/agent-tools/consult.mjs +0 -468
- package/src/agent-tools/design-token.mjs +0 -117
- package/src/agent-tools/digest-budget.mjs +0 -76
- package/src/agent-tools/eng.mjs +0 -67
- package/src/agent-tools/escalate-async.mjs +0 -289
- package/src/agent-tools/goal.mjs +0 -119
- package/src/agent-tools/plan.mjs +0 -81
- package/src/agent-tools/read-history.mjs +0 -294
- package/src/agent-tools/recent-changes.mjs +0 -24
- package/src/agent-tools/review-streak.mjs +0 -93
- package/src/agent-tools/settings.mjs +0 -265
- package/src/agent-tools/skill.mjs +0 -47
- package/src/agent-tools/subagent-actions.mjs +0 -479
- package/src/agent-tools/subagent-async.mjs +0 -434
- package/src/agent-tools/subagent-panel.mjs +0 -160
- package/src/agent-tools/subagent-run.mjs +0 -205
- package/src/agent-tools/subagent-scheduler.mjs +0 -392
- package/src/agent-tools/subagent-spawn.mjs +0 -453
- package/src/agent-tools/subagent.mjs +0 -404
- package/src/agent-tools/task.mjs +0 -87
- package/src/agent-tools/timer.mjs +0 -46
- package/src/agent-tools/verify.mjs +0 -271
- package/src/agent-tools.mjs +0 -17
- package/src/agent.mjs +0 -413
- package/src/auto-think.mjs +0 -115
- package/src/config-migrate.mjs +0 -70
- package/src/config.mjs +0 -496
- package/src/context.mjs +0 -381
- package/src/conventions.mjs +0 -223
- package/src/embedding.mjs +0 -120
- package/src/escape.mjs +0 -152
- package/src/expand-home.mjs +0 -16
- package/src/explore-distill.mjs +0 -155
- package/src/generate-title.mjs +0 -83
- package/src/git/checkpoint.mjs +0 -448
- package/src/git/gitmem.mjs +0 -100
- package/src/hooks.mjs +0 -97
- package/src/log.mjs +0 -195
- package/src/markdown.mjs +0 -106
- package/src/mcp/helpers.mjs +0 -51
- package/src/mcp/transport-http.mjs +0 -248
- package/src/mcp/transport-stdio.mjs +0 -140
- package/src/mcp/transport-ws.mjs +0 -122
- package/src/mcp.mjs +0 -295
- package/src/memory/code-index.mjs +0 -219
- package/src/memory/code-sync.mjs +0 -413
- package/src/memory/core.mjs +0 -300
- package/src/memory/delete.mjs +0 -236
- package/src/memory/docs.mjs +0 -417
- package/src/memory/file-walk.mjs +0 -109
- package/src/memory/schema.mjs +0 -452
- package/src/memory.mjs +0 -21
- package/src/model-ref.mjs +0 -66
- package/src/model-specs.mjs +0 -179
- package/src/peer-domains.mjs +0 -265
- package/src/peer-instances.mjs +0 -231
- package/src/prompt-overlays.mjs +0 -82
- package/src/prompts/advisor-design.md +0 -41
- package/src/prompts/advisor-round1.md +0 -41
- package/src/prompts/advisor-round2.md +0 -46
- package/src/prompts/advisor-round3.md +0 -42
- package/src/prompts/common.md +0 -115
- package/src/prompts/consult-base.md +0 -19
- package/src/prompts/discipline-engineering.md +0 -217
- package/src/prompts/discipline-normal.md +0 -179
- package/src/prompts/persona-coder.md +0 -21
- package/src/prompts/persona-eng-coder.md +0 -37
- package/src/prompts/persona-eng-designer.md +0 -55
- package/src/prompts/persona-engineering.md +0 -54
- package/src/prompts/persona-explore.md +0 -15
- package/src/prompts/persona-normal.md +0 -27
- package/src/prompts/persona-plan.md +0 -26
- package/src/provider/anthropic.mjs +0 -225
- package/src/provider/core.mjs +0 -476
- package/src/provider/errors.mjs +0 -101
- package/src/provider/google.mjs +0 -257
- package/src/provider/index.mjs +0 -7
- package/src/provider/list-models.mjs +0 -93
- package/src/provider/normalize.mjs +0 -81
- package/src/provider/rate.mjs +0 -108
- package/src/provider/responses.mjs +0 -495
- package/src/provider/retry.mjs +0 -88
- package/src/provider/sse.mjs +0 -264
- package/src/proxy.mjs +0 -261
- package/src/rules.mjs +0 -53
- package/src/session-gc.mjs +0 -214
- package/src/session-guard.mjs +0 -47
- package/src/session-migrate.mjs +0 -48
- package/src/session-rename.mjs +0 -38
- package/src/session-slots.mjs +0 -489
- package/src/session.mjs +0 -475
- package/src/skills.mjs +0 -153
- package/src/token-ttl.mjs +0 -274
- package/src/tools/apply_patch.md +0 -15
- package/src/tools/bash.md +0 -37
- package/src/tools/bash.mjs +0 -268
- package/src/tools/checklist-sync.mjs +0 -181
- package/src/tools/checklist.md +0 -13
- package/src/tools/checklist.mjs +0 -299
- package/src/tools/delete.md +0 -13
- package/src/tools/edit-batch.mjs +0 -191
- package/src/tools/edit-diff.mjs +0 -348
- package/src/tools/edit.md +0 -30
- package/src/tools/execute.md +0 -21
- package/src/tools/execute.mjs +0 -228
- package/src/tools/fetch.md +0 -12
- package/src/tools/file.mjs +0 -469
- package/src/tools/file_ops.md +0 -17
- package/src/tools/get_current_time.md +0 -8
- package/src/tools/git-checkpoint.mjs +0 -143
- package/src/tools/git-ext.mjs +0 -173
- package/src/tools/git.md +0 -54
- package/src/tools/git.mjs +0 -356
- package/src/tools/glob-dialect.mjs +0 -130
- package/src/tools/glob.md +0 -11
- package/src/tools/grep.md +0 -19
- package/src/tools/hashline_edit.md +0 -14
- package/src/tools/index.mjs +0 -36
- package/src/tools/insert_after.md +0 -15
- package/src/tools/lint.md +0 -10
- package/src/tools/linter.mjs +0 -128
- package/src/tools/ls.md +0 -12
- package/src/tools/lsp.md +0 -10
- package/src/tools/lsp.mjs +0 -316
- package/src/tools/ops.mjs +0 -299
- package/src/tools/patch.mjs +0 -282
- package/src/tools/process.md +0 -10
- package/src/tools/question.md +0 -16
- package/src/tools/question.mjs +0 -26
- package/src/tools/read.md +0 -20
- package/src/tools/read_image.md +0 -8
- package/src/tools/repomap.mjs +0 -314
- package/src/tools/search.mjs +0 -236
- package/src/tools/shared.mjs +0 -446
- package/src/tools/tree.md +0 -14
- package/src/tools/tree.mjs +0 -66
- package/src/tools/wait_for.md +0 -22
- package/src/tools/web.mjs +0 -224
- package/src/tools/websearch.md +0 -16
- package/src/tools/write.md +0 -11
- package/src/traces/trace-store.mjs +0 -224
package/src/model-specs.mjs
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* model-specs.mjs — known model capability table + spec lookup (2026-08-31 extract).
|
|
3
|
-
*
|
|
4
|
-
* Split from config.mjs (which had grown to 358 lines, past the 300 advisory
|
|
5
|
-
* line — TODO #1). config.mjs re-exports specForModel so the 23 existing
|
|
6
|
-
* importers stay untouched. PROVIDER_PRESETS stays in config.mjs (only 23
|
|
7
|
-
* lines; extracting it would churn wizard/pickers/setup-wizard for no gain).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Known model capability spec table (prefix match, longer first).
|
|
12
|
-
* Used for compaction threshold derivation, continuation protocol selection, and capability-aware optimization.
|
|
13
|
-
*
|
|
14
|
-
* context: context window (tokens)
|
|
15
|
-
* maxOutput: max output tokens (defaults to context)
|
|
16
|
-
* thinking: whether thinking/reasoning mode is supported
|
|
17
|
-
* partialMode: Kimi/Qwen Partial Mode truncation continuation (assistant message with partial:true)
|
|
18
|
-
* prefixMode: DeepSeek Prefix Completion truncation continuation (uses /beta endpoint, with prefix:true)
|
|
19
|
-
* multimodal: whether multimodal (image/vision input supported)
|
|
20
|
-
* cacheMode: context caching mode: "auto"=automatic / "prompt"=needs explicit / "none"=unsupported
|
|
21
|
-
* thinkApi: thinking API type: "type"=thinking.type field / "effort"=reasoning_effort field
|
|
22
|
-
* thinkEnabledValue: when thinkApi is "type", the value used to enable thinking (default "enabled"; MiniMax uses "adaptive")
|
|
23
|
-
* reasoningEcho: reasoning_content cross-turn echo strategy: "required"=must echo (error if missing) / "optional"=echo optional (default: don't echo)
|
|
24
|
-
* reasoningEffortEnum: valid reasoning_effort enum values (if undeclared, no validation — passed through as-is)
|
|
25
|
-
* tempRange: valid temperature range [min, max] (if undeclared, no clamping)
|
|
26
|
-
*/
|
|
27
|
-
const MODEL_SPECS = [
|
|
28
|
-
// DeepSeek V4.1 series (2026-09-11). `deepseek-flash` = DeepSeek-V4.1-Flash (in service):
|
|
29
|
-
// 1M ctx / 384K out, thinking on by default (effort low/high/max), Chat Prefix Completion
|
|
30
|
-
// beta, automatic disk cache, vision. The two legacy names below are RETIRED — the server
|
|
31
|
-
// still accepts them and serves them from V4.1-Flash today (no switch window), so their
|
|
32
|
-
// rows carry the V4.1-Flash parameters (v4-flash gains multimodal).
|
|
33
|
-
["deepseek-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
|
|
34
|
-
// deepseek-v4-pro (V4-Pro-0813): fields unchanged — vision capability NOT verified, so no
|
|
35
|
-
// multimodal (conservative). From 2026-09-14 12:00 Beijing all requests route to V4.1-Flash.
|
|
36
|
-
["deepseek-v4-pro", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2] }],
|
|
37
|
-
// deepseek-v4-flash: RETIRED name — still accepted, served by V4.1-Flash today → aligned row
|
|
38
|
-
["deepseek-v4-flash", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
|
|
39
|
-
// deepseek-v4-flash-vision-exp: retired experimental vision name — still accepted (V4.1-Flash)
|
|
40
|
-
["deepseek-v4-flash-vision-exp", { context: 1_000_000, maxOutput: 384_000, thinking: true, prefixMode: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 2], multimodal: true }],
|
|
41
|
-
// Kimi series
|
|
42
|
-
["kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
43
|
-
// Qwen router prefixes model IDs with provider namespace: kimi/kimi-k3 → kimi-k3 (IK7K4V)
|
|
44
|
-
["kimi/kimi-k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
45
|
-
// Kimi For Coding endpoint uses the short model ID "k3" (same specs as kimi-k3) — IK5VGJ
|
|
46
|
-
["k3", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "auto", thinkApi: "effort", reasoningEcho: "required", reasoningEffortEnum: ["low", "high", "max"] }],
|
|
47
|
-
// GLM series
|
|
48
|
-
// GLM-5.3: thinking always-on (no "disabled"); effort converges to low/high/max — NOT the
|
|
49
|
-
// 7-level glm-5.2 enum (verified vs docs.bigmodel.cn GLM-5.3 page, 2026-08)
|
|
50
|
-
["glm-5.3", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
|
|
51
|
-
["glm-5.3-flash", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["low", "high", "max"], tempRange: [0, 1], noUsageStream: true }],
|
|
52
|
-
["glm-5.2", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
|
|
53
|
-
["glm-5", { context: 1_000_000, maxOutput: 128_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", reasoningEffortEnum: ["max", "xhigh", "high", "medium", "low", "minimal", "none"], tempRange: [0, 1], noUsageStream: true }],
|
|
54
|
-
["glm-4", { context: 128_000, maxOutput: 32_000, thinking: true, cacheMode: "auto", thinkApi: "type", reasoningEcho: "optional", tempRange: [0, 1], noUsageStream: true }],
|
|
55
|
-
// GPT series
|
|
56
|
-
["gpt-5.6-sol", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
57
|
-
["gpt-5.6", { context: 1_050_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
58
|
-
["gpt-4.1", { context: 1_000_000, maxOutput: 128_000, thinking: false, cacheMode: "prompt" }],
|
|
59
|
-
["gpt-4o", { context: 128_000, maxOutput: 16_000, thinking: false, multimodal: true, cacheMode: "prompt" }],
|
|
60
|
-
// Qwen series
|
|
61
|
-
["qwen3.8-max-preview", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
|
|
62
|
-
// qwen3.7-max rejects image parts outright (DashScope 400 "Unexpected item type in content") — text-only
|
|
63
|
-
["qwen3.7-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "high"], tempRange: [0, 2] }],
|
|
64
|
-
["qwen3.8-max", { context: 1_000_000, maxOutput: 131_072, thinking: true, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", reasoningEffortEnum: ["xhigh", "medium", "low"], tempRange: [0, 2] }],
|
|
65
|
-
["qwen-max", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
66
|
-
["qwen-plus", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
67
|
-
["qwen", { context: 1_000_000, maxOutput: 131_072, thinking: false, partialMode: true, multimodal: true, cacheMode: "none", thinkApi: "effort", tempRange: [0, 2] }],
|
|
68
|
-
// MiniMax series
|
|
69
|
-
["MiniMax-M3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
70
|
-
// MiMo series (Xiaomi — OpenAI-compatible https://api.xiaomimimo.com/v1;
|
|
71
|
-
// deep thinking via thinking.type, default ON; multi-turn tool calls MUST echo
|
|
72
|
-
// reasoning_content back exactly like DeepSeek V4, else 400 on follow-ups)
|
|
73
|
-
["mimo-v2.5-pro", { context: 1_000_000, maxOutput: 128_000, thinking: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
|
|
74
|
-
["mimo-v2.5", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, thinkApi: "type", reasoningEcho: "required", tempRange: [0, 1.5] }],
|
|
75
|
-
["minimax-m3", { context: 1_000_000, maxOutput: 128_000, thinking: true, multimodal: true, cacheMode: "auto", thinkApi: "type", thinkEnabledValue: "adaptive", tempRange: [0, 2], noUsageStream: true }],
|
|
76
|
-
["minimax-m1", { context: 256_000, maxOutput: 128_000, thinking: false, cacheMode: "auto", noUsageStream: true }],
|
|
77
|
-
// Grok series (xAI — OpenAI-compatible)
|
|
78
|
-
// grok-4.x: 500K context per xAI Grok 4.6 spec (corrected 2026-08; earlier entries said 1M)
|
|
79
|
-
["grok-4.6", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
80
|
-
["grok-4.5", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
81
|
-
["grok-4", { context: 500_000, maxOutput: 64_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
82
|
-
["grok-4-mini", { context: 128_000, maxOutput: 16_000, thinking: false, tempRange: [0, 2] }],
|
|
83
|
-
// Mistral series (OpenAI-compatible)
|
|
84
|
-
["mistral-large", { context: 128_000, maxOutput: 32_000, thinking: false, multimodal: true, tempRange: [0, 2] }],
|
|
85
|
-
["codestral", { context: 256_000, maxOutput: 32_000, thinking: false, tempRange: [0, 2] }],
|
|
86
|
-
// Claude series (Anthropic)
|
|
87
|
-
["claude-opus-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
88
|
-
["claude-sonnet-5", { context: 1_000_000, maxOutput: 128_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
89
|
-
["claude-opus-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
90
|
-
["claude-sonnet-4", { context: 200_000, maxOutput: 32_000, thinking: false, multimodal: true, cacheMode: "none", format: "anthropic" }],
|
|
91
|
-
["claude-3.5-haiku", { context: 200_000, maxOutput: 8_192, thinking: false, cacheMode: "none", format: "anthropic" }],
|
|
92
|
-
// Gemini series (Google)
|
|
93
|
-
["gemini-3-pro", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
94
|
-
["gemini-2.5-pro", { context: 2_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
95
|
-
["gemini-2.5-flash", { context: 1_000_000, maxOutput: 64_000, thinking: false, multimodal: true, cacheMode: "none", format: "google", noUsageStream: true }],
|
|
96
|
-
]
|
|
97
|
-
const DEFAULT_SPEC = { context: 128_000, maxOutput: 32_000, cacheMode: "none" }
|
|
98
|
-
|
|
99
|
-
/** Look up spec by model name prefix (case-insensitive), conservative default for unknown models.
|
|
100
|
-
*
|
|
101
|
-
* Vendor-namespace prefix stripping (2026-09-04):第三方 token 市场(roapi/new-api/one-api/
|
|
102
|
-
* aiproxy 聚合网关)惯例在模型名前加厂商前缀(zhipu/glm-5.3、openai/gpt-4o)。完整名未命中
|
|
103
|
-
* 且含 "/" 时,剥掉第一个 "/" 前的 namespace 再按前缀匹配一次——ZHIPU/GLM-5.3 → glm-5.3 命中
|
|
104
|
-
* 真实规格,不再降级 128K 默认。kimi/kimi-k3 的显式 alias 行保留为文档锚(发送路径
|
|
105
|
-
* provider.core isRouter 依赖含 "/" 判定),通用机制已覆盖同类。 */
|
|
106
|
-
const warnedModels = new Set() // warn once per model name — spec lookup runs on every request (hot path)
|
|
107
|
-
// Pre-sorted once at module scope — spec lookup runs on every request (agent, provider core,
|
|
108
|
-
// context, auto-think, TUI rendering); re-sorting per call was wasteful.
|
|
109
|
-
const SORTED_SPECS = [...MODEL_SPECS].sort((a, b) => b[0].length - a[0].length)
|
|
110
|
-
|
|
111
|
-
/** Single table lookup shared by specForModel / specMatch — prefix match (case-insensitive)
|
|
112
|
-
* with vendor-namespace stripping. Returns null on a miss (DEFAULT_SPEC is the caller-side
|
|
113
|
-
* fallback and is deliberately NOT returned here — `matched` needs the miss itself). */
|
|
114
|
-
function lookupSpec(model) {
|
|
115
|
-
const m = (model ?? "").toLowerCase()
|
|
116
|
-
for (const [prefix, spec] of SORTED_SPECS) {
|
|
117
|
-
if (m.startsWith(prefix.toLowerCase())) return spec
|
|
118
|
-
}
|
|
119
|
-
// Vendor-namespace strip: vendor/model — retry the prefix match on the bare model part.
|
|
120
|
-
const slash = m.indexOf("/")
|
|
121
|
-
if (slash > 0) {
|
|
122
|
-
const bare = m.slice(slash + 1)
|
|
123
|
-
for (const [prefix, spec] of SORTED_SPECS) {
|
|
124
|
-
if (bare.startsWith(prefix.toLowerCase())) return spec
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return null
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Unknown model: warn ONCE (not per request) so a typo'd ID or a missing alias surfaces
|
|
131
|
-
* instead of silently degrading to the 128K default (IK5VGJ). The dedupe set is shared by
|
|
132
|
-
* specForModel / specMatch (PROVIDER.md §16 M5). */
|
|
133
|
-
function warnUnknownModel(model) {
|
|
134
|
-
const m = (model ?? "").toLowerCase()
|
|
135
|
-
if (m && !warnedModels.has(m)) {
|
|
136
|
-
warnedModels.add(m)
|
|
137
|
-
console.warn(`[config] model "${model}" not found in MODEL_SPECS — using default spec (128K context, 32K output). Check the model ID or add an alias.`)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function specForModel(model) {
|
|
142
|
-
const hit = lookupSpec(model)
|
|
143
|
-
if (hit) return hit
|
|
144
|
-
warnUnknownModel(model)
|
|
145
|
-
return DEFAULT_SPEC
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** specMatch(model) → { spec, matched }(PROVIDER.md §16 M5——切换回显的来源判定)。
|
|
149
|
-
* `matched:false` = DEFAULT_SPEC 兜底(未知模型);与 specForModel 共享同一查表实现(单次),
|
|
150
|
-
* specForModel 的返回形状与共享对象契约零改(热路径零变)。 */
|
|
151
|
-
export function specMatch(model) {
|
|
152
|
-
const hit = lookupSpec(model)
|
|
153
|
-
if (hit) return { spec: hit, matched: true }
|
|
154
|
-
warnUnknownModel(model)
|
|
155
|
-
return { spec: DEFAULT_SPEC, matched: false }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* providerSpec(provider) — spec with a provider-level context override (PROVIDER.md §15, 2026-09-02).
|
|
160
|
-
*
|
|
161
|
-
* providers[].context is configured in K units (128 = 128K = 131072 tokens) and overrides the
|
|
162
|
-
* MODEL_SPECS value for THIS provider only — the same model can have different real context
|
|
163
|
-
* windows on different endpoints (official vs local deployment). The ×1024 conversion happens
|
|
164
|
-
* HERE and nowhere else.
|
|
165
|
-
*
|
|
166
|
-
* Returns a COPY ({ ...spec, context }) — the shared spec object from the SORTED_SPECS lookup
|
|
167
|
-
* must never be mutated, or the override would leak across providers (T-C1).
|
|
168
|
-
*
|
|
169
|
-
* Validation is defensive (pure function): absent/invalid context falls back to the plain spec
|
|
170
|
-
* (config.mjs loadConfig already warns + strips invalid values; this guard covers direct callers
|
|
171
|
-
* and keeps the function total). specForModel stays a pure table lookup — callers without a
|
|
172
|
-
* provider keep using it.
|
|
173
|
-
*/
|
|
174
|
-
export function providerSpec(provider) {
|
|
175
|
-
const spec = specForModel(provider?.model ?? "")
|
|
176
|
-
const k = Number(provider?.context) // Number() 接受数字字符串("128")——两端语义统一(code review #1)
|
|
177
|
-
if (Number.isInteger(k) && k > 0) return { ...spec, context: k * 1024 }
|
|
178
|
-
return spec
|
|
179
|
-
}
|
package/src/peer-domains.mjs
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* peer-domains.mjs — R10 多实例协作 L3 文件写域登记 + 冲突检测(MULTI-INSTANCE-COLLAB
|
|
3
|
-
* §2a.5——评审修正 #8:独立文件,防 300 行超限)。
|
|
4
|
-
*
|
|
5
|
-
* 登记存储(D-L3a):~/.thincoder/peers/{sessionId}.json——每实例单文件、单写者(本
|
|
6
|
-
* 进程——end marker 同型模式);内容 {sessionId, pid, end, cwd, domains[绝对路径],
|
|
7
|
-
* updatedAt}。写点 = 回合级:写工具钩子累积"本回合实际写过的文件"(D-L3b 同一钩子——
|
|
8
|
-
* 检测+记录一次完成)→ 回合结束 flush 整写一次(finalizeAgentTurn);本回合无写入则
|
|
9
|
-
* 不写(文件按自身 updatedAt 自然过期——hot 5 分钟窗口语义不被空回合提前清掉)。
|
|
10
|
-
*
|
|
11
|
-
* 冲突检测(D-L3b):结构化写工具(PEER_WRITE_TOOLS)执行前查 conflicts——命中他实例
|
|
12
|
-
* hot 域 → 工具结果附软提示(决策⑥ A:不阻止)。缓存(评审修正 #2):聚合结果按 peers
|
|
13
|
-
* 目录 mtime 惰性缓存——目录未变零扫描;单次写工具调用新增开销上限 = 目录 stat 一次
|
|
14
|
-
* (N3 度量)。崩溃残留:聚合时惰性清理(死 pid 文件删除——batchAlive 一次批量);
|
|
15
|
-
* 损坏文件按缺失降级(end marker NF2 同型——不删不炸,属主下次 flush 覆盖)。
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { statSync, readdirSync, readFileSync, unlinkSync } from "node:fs"
|
|
19
|
-
import { join, resolve, sep } from "node:path"
|
|
20
|
-
import { configDir } from "./config.mjs"
|
|
21
|
-
import { normalizeCwd, getSessionId, END, writeSessionFile } from "./session-slots.mjs"
|
|
22
|
-
import { batchAlive } from "./peer-instances.mjs"
|
|
23
|
-
import { FILE_MUTATORS } from "./agent/helpers.mjs"
|
|
24
|
-
|
|
25
|
-
/** hot 窗口:登记(回合足迹 flush)后 5 分钟内视为 hot(决策⑤ A——"刚写过"语义) */
|
|
26
|
-
export const HOT_WINDOW_MS = 5 * 60 * 1000
|
|
27
|
-
|
|
28
|
-
/** L3 覆盖的结构化写工具(D-L3b 清单)——既有 FILE_MUTATORS(write/edit/insert_after/
|
|
29
|
-
* hashline_edit/apply_patch/delete)∪ file_ops。insert_after/hashline_edit 亦为结构化
|
|
30
|
-
* 写工具(同一文件写入面),file_ops 在 FILE_MUTATORS 之外故显式并入。 */
|
|
31
|
-
export const PEER_WRITE_TOOLS = new Set([...FILE_MUTATORS, "file_ops"])
|
|
32
|
-
|
|
33
|
-
export function peersDir() {
|
|
34
|
-
return join(configDir, "peers")
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function peerFilePath(sessionId) {
|
|
38
|
-
return join(peersDir(), `${sessionId}.json`)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 模块级测试注入缝(default null = 生产实现;测试注入 + finally 恢复)
|
|
42
|
-
let _testAliveFn = null
|
|
43
|
-
|
|
44
|
-
/** 注入聚合判活实现(批量判活计数/去真实 exec——T-L3b 死清理用例可用真死 pid 亦可注入)。 */
|
|
45
|
-
export function _setPeerDomainsTestImpl({ aliveFn = undefined } = {}) {
|
|
46
|
-
const prev = _testAliveFn
|
|
47
|
-
_testAliveFn = aliveFn ?? null
|
|
48
|
-
peersDirCache = null // 注入即环境变更——目录缓存失效(测试间不串)
|
|
49
|
-
return prev
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function _resetPeerDomainsTestImpl() {
|
|
53
|
-
_testAliveFn = null
|
|
54
|
-
peersDirCache = null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// peers 目录 mtime 惰性缓存(评审修正 #2)——单全局项(peers 目录全局唯一)
|
|
58
|
-
let peersDirCache = null // { mtimeMs, peers: [...] }
|
|
59
|
-
|
|
60
|
-
function statDirMtimeMs(dir) {
|
|
61
|
-
try {
|
|
62
|
-
return statSync(dir).mtimeMs
|
|
63
|
-
} catch {
|
|
64
|
-
return null // 目录缺失
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** 目标路径解析(工具 touchedPaths 优先——apply_patch/edit-batch 多文件;file_ops 无
|
|
69
|
-
* touchedPaths——source/dest 双算;其余 path 单参兜底)。相对路径按 cwd 解析为绝对
|
|
70
|
-
* 路径(resolve:绝对入参原样保留)。解析失败/畸形入参跳过(零目标 = 无检测无登记)。 */
|
|
71
|
-
export function peerWriteTargets(tool, args, cwd) {
|
|
72
|
-
const raw = tool?.touchedPaths
|
|
73
|
-
? tool.touchedPaths(args ?? {})
|
|
74
|
-
: tool?.name === "file_ops"
|
|
75
|
-
? [args?.source, args?.dest]
|
|
76
|
-
: [args?.path]
|
|
77
|
-
const out = []
|
|
78
|
-
for (const p of raw) {
|
|
79
|
-
if (typeof p !== "string" || p.length === 0) continue
|
|
80
|
-
try {
|
|
81
|
-
out.push(resolve(cwd, p))
|
|
82
|
-
} catch {
|
|
83
|
-
/* 畸形路径跳过——检测/登记尽力而为 */
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return out
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** 路径重叠:a 与 b 相等或互为目录包含(file_ops 目录级操作/delete 整目录语义——
|
|
90
|
-
* 区分大小写按平台(Windows 文件系统不区分);分隔符归一(resolve 产物为原生分隔符,
|
|
91
|
-
* 入参可能混用 / 与 \)。 */
|
|
92
|
-
export function pathsOverlap(a, b) {
|
|
93
|
-
const norm = (p) => {
|
|
94
|
-
const n = process.platform === "win32" ? p.toLowerCase() : p
|
|
95
|
-
return (process.platform === "win32" ? n.replace(/\//g, "\\") : n.replace(/\\/g, "/")).replace(/[\\/]+$/, "")
|
|
96
|
-
}
|
|
97
|
-
const pa = norm(a)
|
|
98
|
-
const pb = norm(b)
|
|
99
|
-
if (pa === pb) return true
|
|
100
|
-
const sepN = process.platform === "win32" ? "\\" : "/"
|
|
101
|
-
const prefix = (p, q) => p === q || p.startsWith(q + sepN)
|
|
102
|
-
return prefix(pa, pb) || prefix(pb, pa)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** 扫描 peers 目录:解析每文件 + 一次批量判活全部 pid + 死登记惰性清理(unlink——
|
|
106
|
-
* 崩溃残留);损坏文件按缺失降级(不删——属主下次 flush 覆盖)。返回活登记
|
|
107
|
-
* [{ sessionId, pid, end, cwd, domains, updatedAt }]。目录缺失/不可读 → []。 */
|
|
108
|
-
function scanPeersDir() {
|
|
109
|
-
let files = []
|
|
110
|
-
try {
|
|
111
|
-
files = readdirSync(peersDir(), { withFileTypes: true })
|
|
112
|
-
.filter((e) => e.isFile() && e.name.endsWith(".json"))
|
|
113
|
-
.map((e) => join(peersDir(), e.name))
|
|
114
|
-
} catch {
|
|
115
|
-
return [] // 目录缺失(从未 flush)→ 无登记
|
|
116
|
-
}
|
|
117
|
-
const parsed = []
|
|
118
|
-
for (const file of files) {
|
|
119
|
-
try {
|
|
120
|
-
const rec = JSON.parse(readFileSync(file, "utf8"))
|
|
121
|
-
if (rec && typeof rec === "object" && typeof rec.sessionId === "string" && rec.sessionId.length > 0
|
|
122
|
-
&& Number.isInteger(Number(rec.pid)) && Number(rec.pid) > 0
|
|
123
|
-
&& typeof rec.cwd === "string" && Array.isArray(rec.domains)) {
|
|
124
|
-
parsed.push({ file, rec })
|
|
125
|
-
}
|
|
126
|
-
// 结构非法 = 损坏 → 按缺失降级(不删——NF2 同型)
|
|
127
|
-
} catch {
|
|
128
|
-
/* 解析失败同损坏 */
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (parsed.length === 0) return []
|
|
132
|
-
const aliveFn = _testAliveFn ?? batchAlive
|
|
133
|
-
const alive = aliveFn([...new Set(parsed.map((p) => Number(p.rec.pid)))])
|
|
134
|
-
// 判活探测失败(batchAlive → null)≠ 死:不得据此执行死清理(unlink 是破坏性副作用)
|
|
135
|
-
// ——返回 null 信号:调用方不缓存空聚合(下轮调用重试探测)
|
|
136
|
-
if (!(alive instanceof Set)) return null
|
|
137
|
-
const live = []
|
|
138
|
-
for (const { file, rec } of parsed) {
|
|
139
|
-
if (!alive.has(Number(rec.pid))) {
|
|
140
|
-
try {
|
|
141
|
-
// 死登记惰性清理(崩溃残留——聚合时一次批量判活顺带删除)
|
|
142
|
-
unlinkSync(file)
|
|
143
|
-
} catch { /* unlink 失败不影响聚合(下次再试) */ }
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
146
|
-
live.push({
|
|
147
|
-
sessionId: rec.sessionId,
|
|
148
|
-
pid: Number(rec.pid),
|
|
149
|
-
end: typeof rec.end === "string" ? rec.end : undefined,
|
|
150
|
-
cwd: rec.cwd,
|
|
151
|
-
domains: rec.domains.filter((d) => typeof d === "string"),
|
|
152
|
-
updatedAt: Number(rec.updatedAt) || 0,
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
return live
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** peers 目录 mtime 惰性缓存聚合(评审修正 #2——目录未变不重扫)。返回当前目录下全部
|
|
159
|
-
* 活登记(含他 cwd 实例——死清理跨 cwd 一次批量;cwd 过滤在 peerDomains)。 */
|
|
160
|
-
function cachedScan() {
|
|
161
|
-
const dir = peersDir()
|
|
162
|
-
const mtime = statDirMtimeMs(dir)
|
|
163
|
-
if (mtime == null) {
|
|
164
|
-
peersDirCache = null
|
|
165
|
-
return []
|
|
166
|
-
}
|
|
167
|
-
if (peersDirCache && peersDirCache.mtimeMs === mtime) return peersDirCache.peers
|
|
168
|
-
const peers = scanPeersDir()
|
|
169
|
-
if (peers === null) {
|
|
170
|
-
peersDirCache = null // 探测失败——不缓存空聚合(下轮调用重试探测——失败 ≠ 无登记)
|
|
171
|
-
return []
|
|
172
|
-
}
|
|
173
|
-
// 死清理删文件会改目录 mtime——以清理后的 mtime 缓存(下次 stat 命中即不重扫)
|
|
174
|
-
peersDirCache = { mtimeMs: statDirMtimeMs(dir) ?? mtime, peers }
|
|
175
|
-
return peers
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* 聚合(D-L3a):本 cwd(normalizeCwd 同构比较)的其他活实例登记(self 排除——
|
|
180
|
-
* sessionId === getSessionId())。返回 [{ sessionId, pid, end, cwd, domains, updatedAt }]。
|
|
181
|
-
* 纯只读;缓存命中 = 目录 stat 一次(N3)。
|
|
182
|
-
*/
|
|
183
|
-
export function peerDomains(cwd) {
|
|
184
|
-
const normCwd = normalizeCwd(cwd)
|
|
185
|
-
const myId = getSessionId()
|
|
186
|
-
return cachedScan().filter((p) => p.sessionId !== myId && normalizeCwd(p.cwd) === normCwd)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* 冲突检测(D-L3b——写前查询):targets(绝对路径)命中他实例 hot 域(updatedAt 在
|
|
191
|
-
* HOT_WINDOW_MS 内且域含 target/互为包含)→ 返回 [{ target, by: [{end, pid, sessionId}] }]。
|
|
192
|
-
* 无冲突/无目标 → []。纯只读、不抛(内部全降级——失败按零冲突)。
|
|
193
|
-
*/
|
|
194
|
-
export function conflicts(cwd, targets, { now = Date.now() } = {}) {
|
|
195
|
-
if (!Array.isArray(targets) || targets.length === 0) return []
|
|
196
|
-
let peers = []
|
|
197
|
-
try {
|
|
198
|
-
peers = peerDomains(cwd)
|
|
199
|
-
} catch {
|
|
200
|
-
return []
|
|
201
|
-
}
|
|
202
|
-
const hot = peers.filter((p) => p.domains.length > 0 && now - p.updatedAt <= HOT_WINDOW_MS)
|
|
203
|
-
if (hot.length === 0) return []
|
|
204
|
-
const hits = []
|
|
205
|
-
for (const target of targets) {
|
|
206
|
-
const by = hot.filter((p) => p.domains.some((d) => pathsOverlap(target, d)))
|
|
207
|
-
if (by.length > 0) hits.push({ target, by: by.map((p) => ({ end: p.end, pid: p.pid, sessionId: p.sessionId })) })
|
|
208
|
-
}
|
|
209
|
-
return hits
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** dispatch 写工具钩子预检:命中他实例 hot 域 → 软提示文案(null = 无冲突/无目标/
|
|
213
|
-
* 降级——调用方零附加)。一次目录 stat(缓存命中零扫描——N3 度量)。 */
|
|
214
|
-
export function peerCollabNote(cwd, tool, args) {
|
|
215
|
-
try {
|
|
216
|
-
const targets = peerWriteTargets(tool, args, cwd)
|
|
217
|
-
if (targets.length === 0) return null
|
|
218
|
-
const hits = conflicts(cwd, targets)
|
|
219
|
-
if (hits.length === 0) return null
|
|
220
|
-
return hits.map((h) => {
|
|
221
|
-
const who = h.by.map((p) => (p.end ? `${p.end} pid=${p.pid}` : `pid=${p.pid}`)).join(", ")
|
|
222
|
-
return `[peer-collab] ${h.target} — another live instance (${who}) registered writing it within the last 5 minutes; concurrent edits may overwrite each other. Write not blocked — coordinate before proceeding.`
|
|
223
|
-
}).join("\n")
|
|
224
|
-
} catch {
|
|
225
|
-
return null // 感知失败绝不打扰工具执行
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/** 记录本回合写足迹(D-L3a 累积——D-L3b 同一钩子执行后调用;仅成功写计入"实际写过")。
|
|
230
|
-
* agent._peerWritten: Set<绝对路径>(回合级——finalizeAgentTurn flush 时整写并清空)。 */
|
|
231
|
-
export function recordPeerWrites(agent, tool, args) {
|
|
232
|
-
try {
|
|
233
|
-
const targets = peerWriteTargets(tool, args, agent.cwd)
|
|
234
|
-
if (targets.length === 0) return
|
|
235
|
-
agent._peerWritten ??= new Set()
|
|
236
|
-
for (const t of targets) agent._peerWritten.add(t)
|
|
237
|
-
} catch {
|
|
238
|
-
/* 记录失败不影响工具结果 */
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 回合末登记 flush(D-L3a——finalizeAgentTurn 调用):本回合足迹整写一次本实例文件
|
|
244
|
-
* (单写者;.tmp+rename 原子——writeSessionFile)。本回合无写入 → 不写(文件按自身
|
|
245
|
-
* updatedAt 自然过期——hot 窗口不被空回合提前清掉;低频 N3)。写失败容忍(NF2——end
|
|
246
|
-
* marker 同型);成功 flush 后清空回合集合。不抛(调用方回合收尾零风险)。
|
|
247
|
-
*/
|
|
248
|
-
export function flushPeerDomains(agent) {
|
|
249
|
-
try {
|
|
250
|
-
const set = agent._peerWritten
|
|
251
|
-
if (!set || set.size === 0) return
|
|
252
|
-
const sessionId = getSessionId()
|
|
253
|
-
writeSessionFile(peerFilePath(sessionId), {
|
|
254
|
-
sessionId,
|
|
255
|
-
pid: process.pid,
|
|
256
|
-
end: END,
|
|
257
|
-
cwd: normalizeCwd(agent.cwd),
|
|
258
|
-
domains: [...set],
|
|
259
|
-
updatedAt: Date.now(),
|
|
260
|
-
})
|
|
261
|
-
set.clear()
|
|
262
|
-
} catch {
|
|
263
|
-
/* NF2:失败容忍——下次 flush 重写 */
|
|
264
|
-
}
|
|
265
|
-
}
|