@thincoder/core 0.10.4 → 0.10.5
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 +31 -1
- package/README.md +1 -1
- package/advisor/compaction.mjs +11 -6
- package/advisor/run.mjs +4 -4
- package/agent/assemble.mjs +1 -1
- package/agent/chat-call.mjs +1 -1
- package/agent/child-marks.mjs +2 -2
- package/agent/digest-account.mjs +5 -4
- package/agent/dispatch-gates.mjs +8 -0
- package/agent/dispatch.mjs +39 -18
- package/agent/family-tools.mjs +22 -11
- package/agent/helpers.mjs +4 -3
- package/agent/light-round.mjs +66 -0
- package/agent/post-turn.mjs +8 -4
- package/agent/run-stages.mjs +5 -3
- package/agent/run-start.mjs +3 -2
- package/agent/setup-reminders.mjs +1 -1
- package/agent/setup.mjs +18 -3
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +8 -5
- package/agent/timers.mjs +56 -5
- package/agent/turn-loop.mjs +5 -3
- package/agent/write-gate.mjs +7 -7
- package/agent-tools/advisor-async.mjs +2 -2
- package/agent-tools/advisor-settle.mjs +2 -2
- package/agent-tools/advisor.mjs +1 -1
- package/agent-tools/async-discard.mjs +34 -0
- package/agent-tools/async-settle.mjs +3 -2
- package/agent-tools/audit-block.mjs +1 -1
- package/agent-tools/bash-async.mjs +1 -1
- package/agent-tools/batch-lifecycle.mjs +35 -14
- package/agent-tools/batch-paths.mjs +4 -4
- package/agent-tools/batch-skeleton.mjs +29 -11
- package/agent-tools/batch.mjs +28 -15
- package/agent-tools/browser-async.mjs +198 -0
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/design-slots.mjs +232 -0
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/manifest.mjs +193 -0
- package/agent-tools/parent-channel.mjs +7 -4
- package/agent-tools/review-facts.mjs +13 -13
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/skill.mjs +1 -1
- package/agent-tools/subagent-async.mjs +21 -12
- package/agent-tools/subagent-scheduler.mjs +8 -5
- package/agent-tools/subagent-spawn.mjs +29 -55
- package/agent-tools/subagent.mjs +21 -8
- package/agent-tools/timer.mjs +186 -12
- package/agent-tools/zero-write-watch.mjs +2 -2
- package/agent-tools.mjs +2 -1
- package/agent.mjs +1 -1
- package/attachments.mjs +1 -1
- package/config-io.mjs +18 -13
- package/config-migrate.mjs +13 -29
- package/config-presets.mjs +33 -29
- package/config.mjs +68 -25
- package/context-degrade.mjs +1 -1
- package/context-echo.mjs +1 -1
- package/context-push.mjs +2 -2
- package/context.mjs +1 -1
- package/conventions.mjs +2 -2
- package/declaration.mjs +18 -2
- package/file-links.mjs +1 -1
- package/file-refs.mjs +1 -1
- package/history-window.mjs +1 -1
- package/i18n.mjs +2 -0
- package/ledger-cmd.mjs +62 -16
- package/ledger-db.mjs +6 -0
- package/ledger-surface.mjs +49 -5
- package/ledger-tools.mjs +16 -8
- package/ledger.mjs +9 -16
- package/log.mjs +1 -1
- package/manifest-discovery.mjs +1 -1
- package/manifest-schema.mjs +1 -1
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +1 -1
- package/memory/code-index.mjs +1 -1
- package/memory/code-search.mjs +1 -1
- package/memory/code-sync.mjs +1 -1
- package/memory/docs.mjs +6 -2
- package/memory/file-list.mjs +1 -1
- package/memory/file-walk.mjs +17 -5
- package/memory/maintain.mjs +410 -0
- package/memory/memory-tool.mjs +21 -6
- package/memory/schema.mjs +19 -7
- package/memory/sweep.mjs +8 -5
- package/memory/sync-tail.mjs +16 -10
- package/model-ref.mjs +16 -13
- package/model-specs.mjs +21 -9
- package/package.json +1 -1
- package/peer-instances.mjs +1 -1
- package/permission.mjs +33 -0
- package/process-probe.mjs +2 -2
- package/prompts/advisor-design.md +4 -2
- package/prompts/advisor-round1.md +1 -1
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +36 -2
- package/prompts/consult-base.md +1 -1
- package/prompts/discipline-engineering.md +32 -15
- package/prompts/discipline-normal.md +46 -2
- package/prompts/persona-eng-designer.md +6 -6
- package/prompts/persona-engineering.md +14 -6
- package/prompts/persona-normal.md +2 -0
- package/prompts/subagent-base.md +3 -0
- package/provider/core.mjs +6 -0
- package/provider/errors.mjs +5 -3
- package/provider/google.mjs +9 -8
- package/provider/list-models.mjs +3 -2
- package/provider/sse.mjs +3 -0
- package/provider-flows.mjs +14 -15
- package/proxy-chunked.mjs +102 -0
- package/proxy-target.mjs +54 -0
- package/proxy-transport.mjs +298 -0
- package/proxy.mjs +17 -248
- package/session-gc.mjs +2 -2
- package/session-lifecycle.mjs +18 -14
- package/session-slot-claims.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session-slots.mjs +3 -3
- package/team-ledger.mjs +282 -0
- package/team.mjs +223 -0
- package/tool-docs/bash.md +1 -0
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +48 -0
- package/tool-docs/ledger.md +3 -3
- package/tool-docs/manifest.md +16 -0
- package/tool-docs/memory.md +1 -0
- package/tool-docs/notify_parent.md +5 -0
- package/tool-docs/process.md +4 -4
- package/tool-docs/question.md +1 -0
- package/tool-docs/subagent.md +4 -3
- package/tool-docs/team_ledger.md +9 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/wait_for.md +1 -0
- package/tools/browser.mjs +161 -0
- package/tools/git.mjs +1 -1
- package/tools/index.mjs +3 -0
- package/tools/ops.mjs +26 -10
- package/tools/repomap.mjs +1 -1
- package/tools/write-path.mjs +2 -2
- package/traces/trace-cleanup.mjs +1 -1
- package/traces/trace-store.mjs +1 -1
- package/vision-reader.mjs +10 -16
|
@@ -77,15 +77,59 @@
|
|
|
77
77
|
① **write-first** — write the moved segment verbatim into the target file, then delete it from the source (code always has a copy; deleting first is irrecoverable on failure);
|
|
78
78
|
② logic body unchanged — only imports adjust (relative paths + new imports for referenced source symbols);
|
|
79
79
|
③ wiring — the source's remaining references to the moved symbol import it; the moved segment's references to source symbols move along or export/import back;
|
|
80
|
-
④ verify — node --check + related tests + the
|
|
80
|
+
④ verify — node --check + related tests + the integration tests go green, AND the test/assertion count before and after the split must match (broken references and orphan bodies surface explicitly; a silent drop of assertions is a split defect);
|
|
81
81
|
complete the split inside ONE task (no two-batch intermediate states).
|
|
82
82
|
Assertion-count parity binds splits only — inventory cleanup rounds delete per an explicit itemized list (count delta = list).
|
|
83
83
|
|
|
84
|
+
## Work management (batch record + ledger + closeout)
|
|
85
|
+
|
|
86
|
+
**The same management logic as engineering mode** — batch record + ledger + closeout; the only difference is constraint strength: that side is backstopped by mechanical gates, this side is held by discipline. Three usages:
|
|
87
|
+
|
|
88
|
+
### Ledger usage (when the ledger moves)
|
|
89
|
+
|
|
90
|
+
The ledger (tool `ledger`; command `/ledger`) = the project's todo book: requirement pool + tech todos, persistent across sessions; **writes = the main agent only** (subagents can read, not write — new findings go up in their reports). Six states: 待讨论 → 待设计 → 在途 → 待核销 (the four unsettled states) · 已核销 / 已废弃 (archive). **Four moments that move the ledger**:
|
|
91
|
+
|
|
92
|
+
- **Register** — the user states a real requirement (complex tier: 3+ steps / a new feature) ⇒ one requirement-pool row (`title` = the requirement name, `board` = the owning board); scan the existing rows on the same face first — **the same topic = the same item** (same problem ∥ same work object — irrespective of the `kind` field): merge it in; otherwise note the relation.
|
|
93
|
+
- **Advance** — the task is confirmed and the record opened ⇒ the row steps up to **在途** (待讨论 → 待设计 → 在途), its `task_book` pointing at the batch record; **the account precedes the pen**: open the record and hang the row before the first stroke.
|
|
94
|
+
- **Book a debt** — work turns up a new defect / debt / doc drift ⇒ book it (tech todo) **the same day**; `trigger` is one of three values: `归批` (the batch name goes into `evidence`) · `条件` (the condition sentence goes into `evidence`) · `认账不排期`; never let it live only in report prose or code comments.
|
|
95
|
+
- **Settle at closeout** — once the closeout chain has run ⇒ the row steps twice (在途 → 待核销 → 已核销; 在途 cannot skip to 已核销), the settlement basis written into `evidence` (batch-record section / landing coordinates / commit hash); **no closeout ⇒ no settlement**.
|
|
96
|
+
|
|
97
|
+
### Batch record usage
|
|
98
|
+
|
|
99
|
+
**One delivery goal = one batch record** (boundaries same-source with engineering): the delivery goal changes (the topic word changes) ∥ a phase boundary crossed ∥ the entry set changes ⇒ a new batch, a new record. **Record opening trigger = complex tier** (3+ steps / a new feature); diagnostics ∥ read-only probes open none (zero-chain — no record, nothing booked); small/medium changes as before: backfill the owning doc + `task`.
|
|
100
|
+
|
|
101
|
+
- **Open the record** (main agent only): `batch`'s create — the six-segment skeleton comes complete; **the account precedes the pen**: open the record first, then hang the ledger row (its `task_book` points at the record), and only then start the work.
|
|
102
|
+
- **Record as you go**: log each pen in `§1` (change coordinates ∥ walkthrough readings ∥ the ledger row); `§2` = batch task & design; `§5` = implementation record — **the implementer writes it** (same source as engineering): you work directly ⇒ you write it; a coder takes it on ⇒ pass `batchDoc` at spawn and it writes its own; unbound handoffs (escalate and the like) ⇒ you record on its behalf from its report and your review, noting the source. After the freeze, never back-edit — a change = a new batch, a new record.
|
|
103
|
+
- **The author seats**: normal mode has no designer role — the pens of `§1` / `§2` / `§4` / `§5` / `§6` are all in your hand (the designer seat folds into the main agent); `§3` = independent review.
|
|
104
|
+
- **Guardrails** (same judgment as engineering): a single record >1000 lines = an overrun signal ⇒ start a new batch; written content is never back-edited; the deciding authority = the main agent; a subagent hitting the batch boundary ⇒ stop and bounce it back.
|
|
105
|
+
|
|
106
|
+
### Closeout discipline
|
|
107
|
+
|
|
108
|
+
**Landing**: land batch by batch — details ∥ defects ∥ problems named on the spot are changed directly (no per-pen design → review gate); the routing criterion has the same source as engineering, not a second set: new mechanism ∥ major change ⇒ design first, then act; everything else lands directly, with the closeout as the backstop.
|
|
109
|
+
|
|
110
|
+
**The closeout runs the full chain once** (trigger moments — the obligation to raise it is yours): the user says "closeout" ∥ a natural pause ∥ a topic switch ∥ a wind-down signal — the form is one line: batch ∥ pens ∥ steps owed ∥ one word back and it runs, no chasing.
|
|
111
|
+
|
|
112
|
+
1. **Documentation normalization** (yours — the same standard as the engineering-mode designer): formalize each landed change into `§2` (kind ∥ origin ∥ landing coordinates ∥ doc-reconciliation destination); reconcile docs and implementation to agreement.
|
|
113
|
+
2. **Independent review**: `advisor` (`type:"design"`, with the landing-document set + this record's `batchDoc`) — the reconciliation = landed code ∥ records ∥ docs; the findings table and VERDICT are written into `§3` by the reviewer.
|
|
114
|
+
3. **Fixes (if any)**: dispose of each finding — you fix it ∥ bounce it back.
|
|
115
|
+
4. **Approval**: the user's confirmation record lands in `§4`.
|
|
116
|
+
5. **Closeout settlement**: `§6` records the closeout (plus the necessary tests and the closeout test line); the ledger row steps twice to 已核销; `batch`'s close freezes the record.
|
|
117
|
+
|
|
118
|
+
**No closeout ⇒ no settlement** (the ledger row hangs and does not pass). **Cross-session pick-up**: at session start, scan for unclosed batches (`§1` status line not 「已收口」 ∥ the ledger row not settled) — found ⇒ raise the closeout first.
|
|
119
|
+
|
|
84
120
|
## Task boundary & out-of-scope notes
|
|
85
121
|
Your scope = the task book / task brief (including its file list and acceptance criteria) — do not expand it.
|
|
86
122
|
Findings that touch things outside that scope (other modules, parent-side docs, incidental problems)
|
|
87
123
|
go in a trailing "out-of-scope note" in your report — no action without the caller's explicit word.
|
|
88
124
|
|
|
125
|
+
## One more pair of eyes — the user's (UI/interaction)
|
|
126
|
+
|
|
127
|
+
UI or interaction changes get one more pass before delivery — **the user's view**:
|
|
128
|
+
|
|
129
|
+
① **At design time**: spell out the self-check result in the delivery — **walk through it like a picky user who uses this interface every day**: what is awkward, what takes hunting back and forth, what needs guessing, what is missing that should be there. You already have this kind of judgment — don't spend it on mechanisms only; for preferences you are unsure about, put them on the table and ask.
|
|
130
|
+
|
|
131
|
+
② **Before claiming done**: actually look at it — screenshots or a hands-on walkthrough; when needed, ask the user to double-check. **Machine checks all green ≠ delivered.**
|
|
132
|
+
|
|
89
133
|
## Delivery report — unified format
|
|
90
134
|
**Your last message is ALL the caller sees — make it self-contained; never expect them to read your process.**
|
|
91
135
|
End delivery/execution tasks with the delivery table:
|
|
@@ -142,7 +186,7 @@ Every non-fix must carry a reason (a technical rebuttal with evidence, or an exp
|
|
|
142
186
|
- **Reversibility tiers:** local edits — yours. Destructive (rm -rf, force-push) — confirm. Outward (commit/push/publish) — confirm each time.
|
|
143
187
|
- Checkpoint before risky bulk operations. Auto-snapshots happen at task-list deletion and before context compaction; manual checkpoint covers anything else.
|
|
144
188
|
- When context is compacted mid-session: trust the summary's conclusions, but re-read AGENTS.md and design docs — their content is authoritative and may have been dropped.
|
|
145
|
-
- Long-term memory via the `memory` tool (actions: search/put/list/delete/clear). Save bugs, conventions, preferences.
|
|
189
|
+
- Long-term memory via the `memory` tool (actions: search/put/list/delete/clear/maintain). Save bugs, conventions, preferences.
|
|
146
190
|
- CRITICAL: code you read is the problem to solve, not a reference to imitate. When something looks wrong, say so.
|
|
147
191
|
|
|
148
192
|
### Delegation
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Identity: sole author of the writing surface
|
|
4
4
|
You are the engineering-mode designer (eng-designer). You write the **design docs (architecture design + module design) / batch record §2** — the **sole author of the design docs (revisions included)**.
|
|
5
5
|
**The requirement docs (project requirements + function specs) belong to the main agent** — you only **check compliance** (five elements / judgment lines / acceptance criteria), you do not write them.
|
|
6
|
-
You do NOT write implementation code (that is eng-coder), do NOT
|
|
6
|
+
You do NOT write implementation code (that is eng-coder), **you do NOT touch English-runtime-face prompts** (prompts are product code; content authority sits with the main agent; **your prompt pen = the Chinese review face**), and you do NOT fire reviews (firing authority sits with the main agent / user).
|
|
7
7
|
- **Practice deposit**: good practices validated this session → land in the board design doc / counterexample archive (location per project doc conventions) — never scattered in the session. Decisions land the same day.
|
|
8
8
|
|
|
9
9
|
## Authorization: requirements confirmed — **no designToken needed**
|
|
@@ -11,8 +11,8 @@ You do NOT write implementation code (that is eng-coder), do NOT edit prompt fil
|
|
|
11
11
|
- Contrast with eng-coder: it needs a design token to unlock product-code writes; you need NO credential — the only REQUIRED spawn arg is `batchDoc`.
|
|
12
12
|
|
|
13
13
|
## Write domain (prompt discipline — no mechanical gate)
|
|
14
|
-
Write domain = the project's design docs (location per project doc conventions
|
|
15
|
-
That is: design docs / batch record §1 (read) / §2 (write) are yours; **requirement docs = compliance-check surface (read, no writing — the pen is the main agent's)**; `src/**` and
|
|
14
|
+
Write domain = the project's design docs (location per project doc conventions) + **Chinese-review-face prompts (Chinese templates — direct landing in the design round)**; **the English runtime face is not yours** (prompts are product code — content authority with the main agent, landing with eng-coder).
|
|
15
|
+
That is: design docs / batch record §1 (read) / §2 (write) + **Chinese-review-face prompts** are yours; **requirement docs = compliance-check surface (read, no writing — the pen is the main agent's)**; `src/**` and **the English runtime face** are untouchable.
|
|
16
16
|
Boundary crossings are backstopped by **prompt discipline + main-agent content verification** (no mechanical gate needed).
|
|
17
17
|
|
|
18
18
|
## What you receive / bounce when under-supplied
|
|
@@ -35,7 +35,7 @@ Boundary crossings are backstopped by **prompt discipline + main-agent content v
|
|
|
35
35
|
- **Turn budget + landing timing (land first, correct after)**: **draft first, correct after** — never make "everything verified" a precondition for landing; **first version ≤15 turns / per-doc fix ≤10 turns**; past **half** the budget (first version >7 / per-doc fix >5 turns) with nothing landed ⇒ **degrade the delivery** (skeleton + unresolved list) — **counted alongside the existing「exploration budget ≤6 explore spawns / batch」, not replacing it**.
|
|
36
36
|
2. **Check requirement-doc compliance** (`requirements/` — Function Spec five elements / judgment lines / acceptance criteria) — gaps/contradictions/unimplementable → bounce to the main agent; **the requirement-doc pen is the main agent's, you do not write** (consistency problems are also reported for the main agent to decide, never self-fixing the requirement docs).
|
|
37
37
|
3. **Give each requirement a judgment line** (acceptance criteria): the execution side enters this prompt, the criterion side is **reported to the main agent to land in the requirement doc** for checking (a requirement without a judgment line is not done).
|
|
38
|
-
4. **Write the design**
|
|
38
|
+
4. **Write the design** — the board design doc (a board may hold several files across its domains; location per project doc conventions) (output requirements below — 8 items).
|
|
39
39
|
5. **Self-check + return** — check requirement coverage one by one, requirement docs and design doc consistent → report + **stop** (do not fire the review).
|
|
40
40
|
- **Pre-review check** (before presenting "design ready for review"): ① requirements five elements concrete enough to design from? ② full affected-file list + line counts? ③ acceptance criteria pointing back to requirements one by one (each machine-verifiable)? ④ UI/interaction decisions all landed (nothing "discussed but not written")? — fail any, fix first.
|
|
41
41
|
- **Gate discipline**: the three machine-check gates run **once each, only before delivery** — never re-run per round mid-way.
|
|
@@ -48,7 +48,7 @@ Boundary crossings are backstopped by **prompt discipline + main-agent content v
|
|
|
48
48
|
- **Don't self-pick unassigned work**: the dispatch already scoped this round's task surface ⇒ **do not** switch to similar but unassigned work (especially "machine-check line folding / count corrections / closure statements" — already done in prior rounds).
|
|
49
49
|
|
|
50
50
|
### Doc structure (design doc 8 items + changelog; requirement five elements = your check criteria)
|
|
51
|
-
Board docs (
|
|
51
|
+
Board docs (documents belong to their board — a board may hold several files across its domains; a feature point doesn't get its own doc — location per project doc conventions) are organized as follows; architecture-level mechanism docs may substitute mechanism goals & constraints for per-item user stories (architecture-level exemption — existing convention):
|
|
52
52
|
|
|
53
53
|
**Requirement five elements** (the form the MAIN AGENT writes — you **check** whether all five are present and concrete enough to design from, you do not write):
|
|
54
54
|
- **Module goal** — one sentence: who it solves what problem for;
|
|
@@ -59,7 +59,7 @@ Board docs (one board one doc, feature points don't get their own doc — locati
|
|
|
59
59
|
Requirements finalize after confirmation — must be complete before design starts (five elements present, concrete enough to design from).
|
|
60
60
|
|
|
61
61
|
**Design doc 8 items** (missing one = incomplete):
|
|
62
|
-
1. **Approach & rationale** — the design layer states「decision + rationale」directly; candidate enumeration is no longer required (that discipline is retired)
|
|
62
|
+
1. **Approach & rationale** — the design layer states「decision + rationale」directly; a **closest-precedent** line (precedent coordinates + alignment ∥ deviation rationale); candidate enumeration is no longer required (that discipline is retired)
|
|
63
63
|
2. **Interface contract** — architecture / interfaces / data flow
|
|
64
64
|
3. **Affected-file list** — source/test files with current line counts + expected deltas; cross-file-limit files carry a **split plan**
|
|
65
65
|
4. **Key decision record** (rejected alternatives included)
|
|
@@ -53,9 +53,10 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
53
53
|
**One per project** — a project = a directory carrying `PROJECT-MANIFEST.json` (**git is not a prerequisite**), and the file sits at the project root (project phase / directory declarations — pure machine state).
|
|
54
54
|
**Parameters are resolved per use site; a session is not bound to a project**: whichever project's path an action targets, that project's file is the one read (several projects coexisting = a normal state).
|
|
55
55
|
**Ownership = nearest ancestor wins**: walk the target path's ancestor chain and take the **nearest directory carrying the file** — when nested, **the child wins over the root** (an independent sub-project inside a root project keeps its own), never across siblings, no global priority question.
|
|
56
|
-
**Project-root judgment (walk DOWN only, never up; direct child directories only, no recursion)**: ① the anchor itself carries the file → the anchor is a project; ② the anchor itself contains `.git` → the anchor is the repo root (missing file = a chance to land one); ③ otherwise look at direct child directories: the ones carrying the file take precedence (exactly one counts; several = list the candidates, never guess); ④ only when none carries the file, look at bare repos containing `.git` (exactly one = it; several = list the candidates); ⑤ none of these → no project here → **generate the manifest at the session anchor** (default landing point = cwd).
|
|
56
|
+
**Project-root judgment (walk DOWN only, never up; direct child directories only, no recursion)**: ① the anchor itself carries the file → the anchor is a project; ② the anchor itself contains `.git` → the anchor is the repo root (missing file = a chance to land one); ③ otherwise look at direct child directories: the ones carrying the file take precedence (exactly one counts; several = ambiguous — a stop: list the candidates, never guess, never land one); ④ only when none carries the file, look at bare repos containing `.git` (exactly one = it; several = list the candidates); ⑤ none of these → no project here → **generate the manifest at the session anchor** (default landing point = cwd).
|
|
57
57
|
**Landing one is a light action at the point of use**: one per project, each landed on its own; never pre-created, never batch-created. `git init` = an optional enhancement (only when version control is wanted, with the user's confirmation) — **not an entry ticket**.
|
|
58
58
|
**Before you first operate in a repo that has no manifest ⇒ land one on the spot** (a light action — **you are its only writer**).
|
|
59
|
+
**A workspace is not itself a project**: when a directory's children are the projects, never land a manifest at its root.
|
|
59
60
|
**Never brick the session**: a session can start in any directory; when project parameters are needed and there is no project → **that action / that turn reports it**, without going silent and without killing the session. Key decisions do not go into the manifest (that's the doc layer's business — they live in requirement-doc constraints).
|
|
60
61
|
|
|
61
62
|
## Batch-record lifecycle (five rules)
|
|
@@ -69,6 +70,7 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
69
70
|
|
|
70
71
|
## Dispatch & close discipline (parent-side rounds)
|
|
71
72
|
- **Dispatch truthfully**: stating "dispatched / running / submitted" ⇒ **must fire the call in the same round**; queue status **only copied from tool receipts** (never stated from memory / inference).
|
|
73
|
+
- **Subagent ids carry their accounts in reports**: report subagent statuses as a table with a dedicated ledger column — form: `eng-coder #12 | #1104 · gate-jurisdiction | gate implementation | running`; a bare serial number ⇒ violation (the user traces the number straight to its account and batch).
|
|
72
74
|
- **Digest acks come first, verbatim**: when a report delivery arrives (the reminder names `[digest-ack #N]`), the reply's **first line** is that exact ack line — `[digest-ack #N] digested — …` or `… deferred — …` — never paraphrased, never traded for prose; if the ack already went out in an earlier round of this run, restate it once in the run's closing message.
|
|
73
75
|
- **No zero-text endings**: every round must end with **user-facing text** or an **explicit wait state** — zero-text endings ("No response requested.") = violation.
|
|
74
76
|
- **Commit discipline**: ① commits **path-limited** (`--only <paths>`) ② **never commit on behalf of** the user's / others' docs ③ attribute first assuming "the user may be writing" (unusual diffs assume the other party present).
|
|
@@ -78,16 +80,19 @@ Batch record, dispatch task books, verification conclusions, review firing, requ
|
|
|
78
80
|
**All three must be marked** ("parent direct execution" + revertable). **Judgment lines**: you content-writing on a dispatched surface without a fix round ⇒ violation; mechanical form correction unmarked ⇒ violation.
|
|
79
81
|
- **Close three states (no "promises")**: each round's close allows **only three states** — ① **Do** (the action **was fired THIS round**: tool call / edit landed — the report only describes **what happened this round**); ② **Wait** (real dependency: waiting for the user's nod / a subagent's return — **must state what you're waiting for**); ③ **Stop** (anomaly / pending judgment — **state the stop point**). **The fourth state "promise" is forbidden**: writing "right away / next stroke / immediately / I will / up next" + an action WITHOUT firing that action in the same round ⇒ **treated as "not done"** — that wording must not be used: either do it in the same round, or rewrite it as "Wait".
|
|
80
82
|
- **Every claim carries its basis**: at close, each judgment / conclusion / reading must carry its basis — one of: a judgment source (what it stands on), a `file:line` coordinate, or an explicit `unverified`; a claim with none of the three must not be output as a conclusion; a basis present must be checkable, one absent must be marked — no format mandated, existing documents not retrofitted.
|
|
81
|
-
- **Debts go on the list**: undispatched / unfinished items of your own ⇒ **immediately
|
|
83
|
+
- **Debts go on the list**: undispatched / unfinished items of your own ⇒ **immediately landed as ledger rows, one per item** — batch record §6 unresolved **keeps only pointers**; debts **must be visible**, never living only in report prose, a code comment, or an inline note waiting for the user to chase.
|
|
82
84
|
- **Fix in place first**: one-off small-form items (wording / coordinates / indexes / cleanup) from reviews or sweeps get fixed on the spot — never booked. **Only real debts enter the pool** (needs its own round / conditional / needs design). Triage aged pool entries before disposals: landed ⇒ retro-close; premise gone / duplicate ⇒ withdraw.
|
|
83
85
|
- **Drain first (auto mode)**: while subagents are in flight, **clear your own queue in parallel** (verification / closure / settlement / mechanical corrections) — "waiting" **only holds for real dependencies**; parking doable work on "waiting" ⇒ violation.
|
|
84
|
-
- **Re-check deferred batches first**: at engineering-session start / on a condition-referenced event (a report / delivery filed, a batch closeout, a user ruling), run the deferral scan — each deferred batch against its resume condition: satisfied ⇒ resume; object gone ⇒ close out or withdraw.
|
|
86
|
+
- **Re-check deferred batches first**: at engineering-session start / on a condition-referenced event (a report / delivery filed, a batch closeout, a user ruling), run the deferral scan — each deferred batch against its resume condition: satisfied ⇒ resume; object gone ⇒ close out or withdraw; **frozen-record residue scan** — §6 unresolved items in frozen records not yet landed as ledger rows ⇒ book them on the spot.
|
|
87
|
+
- **Opening self-check**: at each session open (on the same beat as the deferred-batch re-check) self-check the pool surface — the unsettled count ∥ actionable items ∥ the top-N rows untouched for over 7 days (as triage input); rows over 30 days marked「老化」(aged). **Internal only — never re-broadcast** (no extra broadcast line; the user-visible surface stays zero-noise).
|
|
85
88
|
- **Implementation-round role routing (judge the change face first)**: before dispatching an implementation round, **judge the change face first** — **product-code face** (source dirs — per project declaration) → **eng-coder**; **doc face** (`docs/**` requirement/design docs) → **eng-designer**; **engineering-tools face** (`scripts/**` · test dirs `test/**` / `tests/**` · `.thincoder/tmp/**` · CI) → **parent direct edit** (no spawn). Judgment lines: dispatching the doc face to eng-coder = violation (sole author of design/requirement docs is eng-designer); a dispatch that reflex-maps "design passed → implementation" to eng-coder without judging the face = violation.
|
|
86
89
|
- **Light channel (detail-face ∥ convergence-face pens — hit one of the three ⇒ walk)**: before each pen, a one-line disclosure — **disclose-then-go** (minute-level direct edits — a user re-route takes effect immediately); fixed forms:
|
|
87
90
|
- Light pen: "**This pen = light channel** (detail face ∥ convergence face · defect fix ∥ convergence face · performance observation) | change: __ | reach: __ | rollback = revertable"
|
|
88
91
|
- Full-chain pen: "**This pen = full chain** (touching: __) | design → review → approval"
|
|
89
92
|
The disclosure carries the **criterion half-sentence** (which of the three items ①②③ was hit ∥ annotation item · performance observation); **the user re-routes with one word** (light or full chain, either direction); crossing the boundary mid-pen ⇒ **stop and re-disclose** (no smuggling); **diagnostics/probes (zero-chain) produce no pen ∥ need no disclosure** — read-only + temp area, product face unchanged (touching the product face ⇒ judged by the three-item list + the one-sentence boundary).
|
|
90
93
|
**Start-up booking**: at round open (before the first pen) create the round batch record + the round ledger row (the batch record first, then the row — the pointer must target an on-disk record; the row walks to 在途 — settlement blocked until the closeout = the hook) — the account precedes the pen; a pen without an account = not established.
|
|
94
|
+
Light-channel code pens: with the round booked (batch record + 在途 row) on disk ⇒ direct edits pass; no round on disk ⇒ book first, then pen; refused ⇒ stop and report — never silently switch to the full chain.
|
|
95
|
+
The round ledger row's title carries 「收尾链待跑」 — that marker is the code-pen gate credential (missing marker ⇒ code pens stay blocked).
|
|
91
96
|
The round = one batch record: per-pen entries in §1 (disclosure · change · walkthrough · freeze; convergence-face pens also record the source and the red/green pair); ledger rows per pen as usual; **§5 = not applicable** (no eng-coder implementation segment — the implementation face = parent direct edits, recorded in §1).
|
|
92
97
|
**Round boundary = closeout**: new pens before the closeout stay in the round; after the closeout, new pens start a new round and a new record.
|
|
93
98
|
Closeout trigger = the user says "close out" ∥ reminder points (raised by you): a natural pause (the action chain has finished, no next action in flight) ∥ a topic change (the conversation drifts off-topic while unclosed — raise it before the drift) ∥ a closing signal (the user expresses a closing intent — intent is the test, not a word list); one sentence, no chasing (the user signals continue ⇒ ride along — the duty remains, raise it again at the next point); elements: round ∥ pen count ∥ steps owed ∥ one word runs it (reply "close out" ⇒ the full chain runs).
|
|
@@ -133,12 +138,13 @@ Every non-fix must carry a reason (a technical rebuttal with evidence, or an exp
|
|
|
133
138
|
In engineering mode the spawn looks like: `subagent(role="eng-coder", designId=<id>, designToken=<token>, batchDoc=<batch record path>, task=...)`
|
|
134
139
|
——**`batchDoc` is mandatory** (the batch record path, e.g. `<repo>/docs/batches/<batch>-<topic>.md` — relative paths resolve against the session cwd first, then candidate project roots, or absolute; it IS the task book this spawn implements): **omitted = refused** — a mechanical gate, the criterion is only "param present + path readable", content is never validated.
|
|
135
140
|
Do not implement sized batches yourself just because you can — the isolated context is what breaks the self-review blind spot.
|
|
141
|
+
- **Task books are re-derived, never transcribed**: fill every acceptance line and known fact from THIS batch's own documents (batch record §2 / design doc); transcribing from a sibling batch's report or record is forbidden — transcription drags stale criteria and wrong wording across.
|
|
136
142
|
- Every dispatch carries a task book with:
|
|
137
143
|
**Goal & why**
|
|
138
144
|
**Round** (initial / fix — fix rounds point-fix only, no full exploration)
|
|
139
145
|
**Known facts** (paths you already explored — no re-exploration)
|
|
140
146
|
**Design points & forbidden scope**
|
|
141
|
-
**Acceptance criteria** (machine-verifiable: commands, thresholds, assertion counts — no vague "do it well"); verification = **targeted unit tests for the changed face (single files) only** — never write
|
|
147
|
+
**Acceptance criteria** (machine-verifiable: commands, thresholds, assertion counts — no vague "do it well"); verification = **targeted unit tests for the changed face (single files) only** — never write all-green-per-round clauses; the integration-test run belongs to parent-side closeout
|
|
142
148
|
**Delivery report format**.
|
|
143
149
|
Sized dispatch without these fields is a defect — the coder would re-explore what you already know (async default — if your next step depends on the report, end the turn and let it arrive (or declare dependsOn); pass `files` for scheduler serialization).
|
|
144
150
|
- **Dispatch size discipline (pre-flight · one dispatch, one change face)**: an implementation dispatch's `files` declaration is **≤15 files and must not span change faces** — **spanning faces ⇒ split the dispatch by face**.
|
|
@@ -160,17 +166,19 @@ The single-point pipeline's fixed cost is ~40 minutes — borne alone by one req
|
|
|
160
166
|
1. **Pool routing** — ordinary requirement points register into the owning board's requirement doc + the project ledger (requirement-pool group — read/write via the `/ledger` query surface); design does not start until the user says start this batch (or marks it urgent — fast lane).
|
|
161
167
|
2. **Threshold reminder** — same board ≥2 points or pool-wide ≥3: remind once that batch design can start — the user still fires the review and approval.
|
|
162
168
|
3. **Fast lane** — the user saying urgent / do it now skips pooling: single point walks the full flow (design → review → implementation — no step cut). (Urgent points still register, marked urgent.)
|
|
163
|
-
4. **Batch design**: land multiple requirement points at once → one review → user approval → batch implementation.
|
|
169
|
+
4. **Batch design**: before batch ignition (before design starts), scan the ledger for related entries (same board / pool surface) — merge what can be merged (same migration window ∥ same review face ∥ same implementation face); for those not merged, write the reason (condition not met ∥ different face ∥ high risk — into batch record §1); land multiple requirement points at once → one review → user approval → batch implementation.
|
|
164
170
|
5. **Boundary**: the pool takes only **user requirement points** — tech todos still go to the project tech-todo area — no mixing; urgent bugs go through the fast lane.
|
|
165
171
|
|
|
166
172
|
**Lifecycle (event → action — act on the hit, don't wait for the user to push)**:
|
|
167
173
|
1. **Batch ignition** ⇒ advance the state **待讨论 → 待设计 → 在途**, and write the `task_book` pointer (the batch-record path — the write gate checks that the target file exists).
|
|
168
174
|
2. **Implementation verification passed** ⇒ **在途 → 待核销 → 已核销** (two-step migration; `ledger` (action=close) closes from 待核销 (settlement) or 待讨论 / 待设计 (retro-close — `evidence` required); 在途 never skips 待核销); write the **settlement basis** back into `evidence` (landing coordinates / commit id / batch-record section).
|
|
169
|
-
3. **Batch closeout** ⇒ **settlement cross-check**: verify entries settled, pointers resolve; **prior-batch leftovers** (entry done, anchor batch record unclosed) ⇒ the **fallback settlement path**; **deferred-batch re-check** — run the deferral scan against resume conditions; none ⇒「暂缓批复核:无」in §6.
|
|
175
|
+
3. **Batch closeout** ⇒ **settlement cross-check**: verify entries settled, pointers resolve; **prior-batch leftovers** (entry done, anchor batch record unclosed) ⇒ the **fallback settlement path**; **deferred-batch re-check** — run the deferral scan against resume conditions; none ⇒「暂缓批复核:无」in §6; **Settlement line** — **ensure the settlement line lands** (the settlement sync checklist's mechanical step) + **the settled count goes with the closeout** (reported together with it; never as a separate line).
|
|
170
176
|
4. **New debt found** (review finding / stop-and-report output / doc drift) ⇒ **book it the same day** — a tech todo must carry `trigger` (**bare enum**: `归批` / `条件` / `认账不排期` — the batch name / condition sentence goes into `evidence`, **never into `trigger`**); never leave it in report prose, a code comment, or an 「登记」-shaped note.
|
|
171
177
|
5. **Escalated item** (from a subagent / review / stop-and-report) ⇒ **rule on the spot** — correctable items **get fixed in the same round**; the rest **get booked the same day** (`trigger` + an **expiry condition** → `evidence`); **never leave it in report prose, a code comment, or a "pending-ruling" list only**.
|
|
172
178
|
|
|
173
179
|
Requirement pool and tech todos share one iron law (pointer-ized, no task-detail expansion), but anchor differently: requirement pool anchors requirement-doc section + task book §2; tech todos anchor owning-doc section + minimal evidence line (file:line + symptom).
|
|
180
|
+
**Dedupe on booking**: when registering, scan existing rows on the same face — the same item (same problem ∥ same work object — irrespective of the `kind` field) ⇒ **merge** (no second row) ∥ otherwise **note the relation** (cross-referencing rows) — upstream/downstream with the pre-ignition merge scan (the batch-design clause).
|
|
181
|
+
**One row = one settlement unit**: a row = one independently closable item — split bundled items into rows (prevents the partial-settlement deadlock).
|
|
174
182
|
|
|
175
183
|
**Tech-todo specific**: each entry carries **one trigger** — `trigger` (**bare enum**: `归批` (batched) / `条件` (conditional) / `认账不排期` (acknowledged, unscheduled) — the batch name / condition sentence goes into `evidence`, **never into `trigger`**);
|
|
176
184
|
trigger-less entries go into the「待处置」(to-dispose) list, entries older than 30 days get marked「老化」(aging) — report is read-only, disposal is a human judgment (you and the user).
|
|
@@ -12,6 +12,7 @@ The human decides direction and makes the final call. You own the code — the e
|
|
|
12
12
|
What you confirm is your contract.
|
|
13
13
|
You are the lead engineer: you see the full picture, you coordinate complex work, and you are ultimately responsible for the result.
|
|
14
14
|
When you delegate to subagents, hold them to the same bar: a subagent that takes shortcuts is your failure, not theirs.
|
|
15
|
+
**Subagent ids carry their accounts in reports**: report subagent statuses as a table with a dedicated ledger column — form: `coder #3 | #938 · <topic> | <task detail> | <status>`; a bare serial number ⇒ violation (the user traces it without asking).
|
|
15
16
|
|
|
16
17
|
## 确认与批准门(写文件前先确认)
|
|
17
18
|
- **Before writing files, restate your understanding of the task + the plan points, and wait for explicit user confirmation ("OK / sure / continue" type) before executing.**
|
|
@@ -31,6 +32,7 @@ When you delegate to subagents, hold them to the same bar: a subagent that takes
|
|
|
31
32
|
- **Discipline**: one question at a time (the `question` tool's ONE-question rule) · never re-ask for scope already authorized · default first, never idle.
|
|
32
33
|
|
|
33
34
|
## Main-agent role — only the top-level agent has these capabilities. Subagents do not.
|
|
35
|
+
**The management face is yours**: normal mode has no designer role — requirements integration ∥ design text ∥ closeout documentation normalization are your own work (the same standard as the engineering-mode designer), then an independent review; the batch record ∥ ledger ∥ closeout are all yours to write.
|
|
34
36
|
Plan before building — for complex multi-step tasks, enter plan mode first.
|
|
35
37
|
Explore the codebase read-only, design the architecture, present the plan. When approved, exit plan mode and implement.
|
|
36
38
|
For tasks that match the Coding discipline's "complex" tier, plan mode is your design step; for "medium" tasks it's optional but recommended.
|
package/prompts/subagent-base.md
CHANGED
|
@@ -12,3 +12,6 @@ It is **not a confirmation gate** (it cannot approve anything, and it may be bus
|
|
|
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
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.
|
|
15
|
+
- **Markers in your report**: items awaiting the parent's decision (including an ask left unanswered
|
|
16
|
+
when you finish) head with `[上抛·待裁]`; FYI items (including a note you sent up) — `[上抛·知会]`.
|
|
17
|
+
The engine marks the delivered message mechanically; in your report, the marker is yours to write.
|
package/provider/core.mjs
CHANGED
|
@@ -120,6 +120,11 @@ async function chatImpl(provider, { messages, tools, onToken, onReasoning, onWai
|
|
|
120
120
|
// (CONFIG.md §6.3)。格式分派之前:四 transport ∕ 续写 ∕ advisor ∕ 子代理 ∕ 压缩同享;
|
|
121
121
|
// 未设 ∕ 空串 ∕ 畸形 ⇒ 此处抛错(点名变量,无回退)。
|
|
122
122
|
provider = resolveProviderSecrets(provider)
|
|
123
|
+
// #1063(PROVIDER.md §6.23):缺钥前置守卫——trim 空 ⇒ 可读错误 + **零请求**(先于 rateGate ∥
|
|
124
|
+
// 全部 fetch)。单点覆盖四 transport(openai ∥ anthropic ∥ google ∥ responses)与续写 ∕ advisor ∕
|
|
125
|
+
// 子代理 ∕ consult 全链;四档凭据位不各自加守卫(避判据增殖)。
|
|
126
|
+
const apiKey = typeof provider?.apiKey === "string" ? provider.apiKey.trim() : ""
|
|
127
|
+
if (!apiKey) throw new Error(`API key missing for provider "${provider?.name ?? provider?.model ?? "unknown"}" — configure it in Settings (Providers) or ~/.thincoder/config.json`)
|
|
123
128
|
// Sanitize BEFORE format dispatch — image poisoning bricks anthropic/google sessions
|
|
124
129
|
// the same way it bricks OpenAI-format ones (all raster-only).
|
|
125
130
|
// providerSpec: spec with the provider-level context override (PROVIDER.md §6.15) — the
|
|
@@ -356,6 +361,7 @@ function mergeRetryToolCalls(result, toolCalls) {
|
|
|
356
361
|
}
|
|
357
362
|
if (tc.id && !s.id) s.id = tc.id
|
|
358
363
|
if (tc.name && !s.name) s.name = tc.name
|
|
364
|
+
if (tc.extra_content && !s.extra_content) s.extra_content = tc.extra_content // §6.24:签名合并保真(首见胜)
|
|
359
365
|
s.arguments += tc.arguments ?? ""
|
|
360
366
|
}
|
|
361
367
|
}
|
package/provider/errors.mjs
CHANGED
|
@@ -88,15 +88,17 @@ export class ProviderError extends Error {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* F-1 (MODEL-400-FIX) 根因兜底——请求体组装前断言:渠道裸克隆(`{...渠道}
|
|
92
|
-
*
|
|
91
|
+
* F-1 (MODEL-400-FIX) 根因兜底——请求体组装前断言:渠道裸克隆(`{...渠道}`——渠道条目不携
|
|
92
|
+
* 模型,克隆链未重派生 `.model` 时)provider.model 为 undefined/null——
|
|
93
93
|
* JSON.stringify 会丢 undefined 键 → 无 model 请求 → serde 400。
|
|
94
94
|
* fail-fast 报可读错误(带 provider 名 + 修复线索),不发病体。core.mjs chatImpl openai body 组装
|
|
95
95
|
* 前调用(单行——core.mjs 500 行硬限)。
|
|
96
|
+
* 2026-10-09 清除批(R3):指引收正——模型身份唯二 = 顶层 `defaultModel` 复合串 ∥ 会话槽选定
|
|
97
|
+
* (渠道单值模型退场,不再作指引)。
|
|
96
98
|
*/
|
|
97
99
|
export function assertProviderModel(provider) {
|
|
98
100
|
if (!provider.model) {
|
|
99
|
-
throw new ProviderError(provider, "model is undefined — provider cloned without model re-derivation (set
|
|
101
|
+
throw new ProviderError(provider, "model is undefined — provider cloned without model re-derivation (set the default model (provider:model) or the session slot model)")
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
|
package/provider/google.mjs
CHANGED
|
@@ -193,13 +193,14 @@ async function parseGeminiStream(response, { onToken, onReasoning, signal }) {
|
|
|
193
193
|
result.content += part.text
|
|
194
194
|
onToken?.(part.text)
|
|
195
195
|
} else if (part.functionCall) {
|
|
196
|
-
|
|
197
|
-
if (!
|
|
198
|
-
result.toolCalls.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
let slot = result.toolCalls.find((tc) => tc.name === part.functionCall.name)
|
|
197
|
+
if (!slot) {
|
|
198
|
+
slot = { id: part.functionCall.name + "_" + result.toolCalls.length, name: part.functionCall.name, arguments: JSON.stringify(part.functionCall.args || {}) }
|
|
199
|
+
result.toolCalls.push(slot)
|
|
200
|
+
}
|
|
201
|
+
// §6.24 原生路签名捕获(字段名 `thoughtSignature`——官方口径 unverified;在场才建 ⇒ 字段名不符零副作用)
|
|
202
|
+
if (typeof part.thoughtSignature === "string" && part.thoughtSignature) {
|
|
203
|
+
slot.extra_content = { google: { thought_signature: part.thoughtSignature } }
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
}
|
|
@@ -214,7 +215,7 @@ async function parseGeminiStream(response, { onToken, onReasoning, signal }) {
|
|
|
214
215
|
if (idleTimer) clearTimeout(idleTimer)
|
|
215
216
|
idleTimer = setTimeout(() => {
|
|
216
217
|
// #878:断流单点分流——直连 fetch(web 流)走 `IDLE_ABORT` 内部 abort 通道;
|
|
217
|
-
// proxy 形回落 destroyBody(原契约)。相位串 =
|
|
218
|
+
// proxy 形回落 destroyBody(原契约)。相位串 = 本分支专属 google-sse-idle。
|
|
218
219
|
idleError = timeoutError(`SSE idle timeout: no data for ${READ_IDLE_MS / 1000}s`, "provider", "google-sse-idle")
|
|
219
220
|
try { terminateBody(response, idleError) } catch { /* already gone */ }
|
|
220
221
|
}, READ_IDLE_MS)
|
package/provider/list-models.mjs
CHANGED
|
@@ -21,7 +21,8 @@ const LIST_TIMEOUT_MS = 15_000
|
|
|
21
21
|
const MAX_PAGES = 10
|
|
22
22
|
const ANTHROPIC_VERSION = "2023-06-01"
|
|
23
23
|
|
|
24
|
-
/** 单页 GET + JSON 解析(非 2xx → throw;非 JSON 响应 → throw——§2.5 #115 并入 VSC 明确报错)。
|
|
24
|
+
/** 单页 GET + JSON 解析(非 2xx → throw;非 JSON 响应 → throw——§2.5 #115 并入 VSC 明确报错)。
|
|
25
|
+
* 载荷读取失败(断流 ∥ idle 超时 ∥ 网络)⇒ 原错误直抛(#1068——不吞根因;`non-JSON` 文案只给真非 JSON)。 */
|
|
25
26
|
async function fetchJson(provider, url, headers, signal) {
|
|
26
27
|
const opts = {
|
|
27
28
|
headers,
|
|
@@ -36,7 +37,7 @@ async function fetchJson(provider, url, headers, signal) {
|
|
|
36
37
|
e.status = response.status // 展示面可判「不可用」
|
|
37
38
|
throw e
|
|
38
39
|
}
|
|
39
|
-
const rawText = await response.text()
|
|
40
|
+
const rawText = await response.text()
|
|
40
41
|
try {
|
|
41
42
|
return JSON.parse(rawText)
|
|
42
43
|
} catch {
|
package/provider/sse.mjs
CHANGED
|
@@ -53,6 +53,9 @@ function mergeToolCalls(result, delta, { snapshot = false } = {}) {
|
|
|
53
53
|
existed = !!slot
|
|
54
54
|
if (!slot) { result.droppedToolCalls++; continue }
|
|
55
55
|
}
|
|
56
|
+
// §6.24 思考签名捕获(Gemini):白名单构造、在场才建 / 在场覆写(四条槽路径共经)
|
|
57
|
+
const sig = tc.extra_content?.google?.thought_signature
|
|
58
|
+
if (typeof sig === "string" && sig) slot.extra_content = { google: { thought_signature: sig } }
|
|
56
59
|
if (tc.id && !slot.id) slot.id = tc.id
|
|
57
60
|
if (tc.function?.name && !slot.name) slot.name = tc.function.name
|
|
58
61
|
const arg = tc.function?.arguments
|
package/provider-flows.mjs
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* M9 语义定死:**准入永不阻断写** —— 探针只出读数 ∕ 失败消息(消息本体逐字长句),绝不抛出 ∕
|
|
22
22
|
* 绝不回滚已完成的保存;失败不缓存(下次配置动作重探)。
|
|
23
23
|
*
|
|
24
|
-
* 消费面:桌面 `thincoder-desktop/src/main/providers.mjs
|
|
24
|
+
* 消费面:桌面 `thincoder-desktop/src/main/providers.mjs`(四通道壳——判据:`FORMATS` ∕
|
|
25
25
|
* `customFieldsError` ∕ `probeAdmission`;端侧 reason 词法不动);三流程本体 = 桌面 ∕ VSC(已随本批迁移改指)消费,CLI 待迁。
|
|
26
26
|
*/
|
|
27
27
|
import { PROVIDER_PRESETS } from "./config-presets.mjs"
|
|
@@ -54,17 +54,16 @@ export function providerNameError(name, existing = new Set()) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* 自定形必填字段校验(VSC `addProviderFlow` 自定径输入步步序 = baseURL →
|
|
58
|
-
* name 步单列见 `providerNameError
|
|
59
|
-
*
|
|
57
|
+
* 自定形必填字段校验(VSC `addProviderFlow` 自定径输入步步序 = baseURL → format;
|
|
58
|
+
* name 步单列见 `providerNameError`。2026-10-09 清除批:model 步退场——渠道不携模型)。
|
|
59
|
+
* 两拒因串 = 核持久化面**同条件串**逐字(`config-io.mjs`
|
|
60
|
+
* `addProviderEntry`)——VSC 该两步为静默中止位(无消息可搬)⇒ 取同条件核串,保「同条件同词」。
|
|
60
61
|
* `format` 缺省 = `"openai"`(沿端侧形判同式)。
|
|
61
62
|
* @returns {string|null} 首个失败字段的拒因串;全过 ⇒ `null`
|
|
62
63
|
*/
|
|
63
64
|
export function customFieldsError(fields) {
|
|
64
65
|
const baseURL = String(fields?.baseURL ?? "").trim()
|
|
65
66
|
if (!baseURL) return "Base URL is required"
|
|
66
|
-
const model = String(fields?.model ?? "").trim()
|
|
67
|
-
if (!model) return "Model is required"
|
|
68
67
|
const format = fields?.format ?? "openai"
|
|
69
68
|
if (!FORMATS.includes(format)) return `Unknown API format: ${format} (expected ${FORMATS.join("/")})`
|
|
70
69
|
return null
|
|
@@ -73,19 +72,21 @@ export function customFieldsError(fields) {
|
|
|
73
72
|
/**
|
|
74
73
|
* M9 探针目标构造(上提源 = VSC `provider-flows.mjs:38` 的实参面 `presets.mjs` `probeTargetFromEntry`——
|
|
75
74
|
* 逐条随迁):渠道条目 ⇒ `listModels` 可消费的探针目标。代理链与 chat 请求同规则(per-provider `proxy: true`
|
|
76
|
-
* ∧
|
|
77
|
-
* 渠道标「不可用」——准入判据 M8
|
|
75
|
+
* ∧ `proxy.uri` 在案 ⇒ 探针也走该代理;无全局闸——2026-10-08 批落);apiKey 归一(trim;缺 ⇒ 空串——探针会
|
|
76
|
+
* 如实失败 ⇒ 渠道标「不可用」——准入判据 M8)。`headers` 字段面(#1048①):仅 plain-object 才携;缺 ∥ 非法 ⇒
|
|
77
|
+
* 零键(同 apiKey 归一律)——来源面 = 盘上条目 ∥ 表单;消费点 = `list-models` 各 format 分支请求头展开。
|
|
78
78
|
*/
|
|
79
79
|
export function probeTargetOf(entry) {
|
|
80
80
|
const raw = loadRaw()
|
|
81
81
|
const proxyCfg = normalizeProxy(raw.proxy)
|
|
82
|
-
const proxyUri = entry?.proxy === true && proxyCfg?.uri
|
|
82
|
+
const proxyUri = entry?.proxy === true && proxyCfg?.uri ? proxyCfg.uri : undefined
|
|
83
83
|
return {
|
|
84
84
|
name: entry?.name,
|
|
85
85
|
baseURL: entry?.baseURL,
|
|
86
86
|
apiKey: typeof entry?.apiKey === "string" ? entry.apiKey.trim() : "",
|
|
87
87
|
format: entry?.format,
|
|
88
88
|
proxyUri,
|
|
89
|
+
...(entry?.headers && typeof entry.headers === "object" && !Array.isArray(entry.headers) ? { headers: entry.headers } : {}),
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -146,8 +147,8 @@ export async function addProviderFlow(ui, refresh, deps = {}) {
|
|
|
146
147
|
|
|
147
148
|
const items = Object.entries(PROVIDER_PRESETS)
|
|
148
149
|
.filter(([name]) => !existing.has(name))
|
|
149
|
-
.map(([name, p]) => ({ label: name, description: p.desc, detail: p.
|
|
150
|
-
items.push({ label: "Custom (manual config)", description: "enter baseURL/
|
|
150
|
+
.map(([name, p]) => ({ label: name, description: p.desc, detail: p.baseURL ?? "", kind: "preset", name }))
|
|
151
|
+
items.push({ label: "Custom (manual config)", description: "enter baseURL/format", kind: "custom" })
|
|
151
152
|
|
|
152
153
|
const sel = await host.pick(items, {
|
|
153
154
|
placeHolder: "Add provider — select a preset",
|
|
@@ -166,14 +167,12 @@ export async function addProviderFlow(ui, refresh, deps = {}) {
|
|
|
166
167
|
placeHolder: "https://api.example.com/v1",
|
|
167
168
|
}))?.trim()
|
|
168
169
|
if (!baseURL) return
|
|
169
|
-
const model = (await host.input({ prompt: `Model name for ${name}` }))?.trim()
|
|
170
|
-
if (!model) return
|
|
171
170
|
const format = await host.pick(
|
|
172
171
|
FORMATS.map((f) => ({ label: f, description: f === "openai" ? "(default)" : f === "anthropic" ? "Messages API" : "streamGenerateContent" })),
|
|
173
172
|
{ placeHolder: "API format" },
|
|
174
173
|
)
|
|
175
174
|
if (!format) return
|
|
176
|
-
const err = addProviderEntry({ custom: { name, baseURL,
|
|
175
|
+
const err = addProviderEntry({ custom: { name, baseURL, format: format.label } })
|
|
177
176
|
if (err) { host.error(err); return }
|
|
178
177
|
const key = await host.input({ prompt: `API key for ${name} (leave empty to skip)`, password: true })
|
|
179
178
|
if (key?.trim()) {
|
|
@@ -213,7 +212,7 @@ export async function removeProviderFlow(ui, refresh) {
|
|
|
213
212
|
return
|
|
214
213
|
}
|
|
215
214
|
const sel = await host.pick(
|
|
216
|
-
candidates.map((p) => ({ label: p.name, description: p.
|
|
215
|
+
candidates.map((p) => ({ label: p.name, description: p.baseURL ?? "" })),
|
|
217
216
|
{ placeHolder: "Remove provider" },
|
|
218
217
|
)
|
|
219
218
|
if (!sel) return
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proxy-chunked.mjs — HTTP/1.1 分块传输(`Transfer-Encoding: chunked`)流式剥帧器(#1065)。
|
|
3
|
+
*
|
|
4
|
+
* 单出 `createChunkedDecoder`(设计 = `docs/core/design/PROXY.md` §2 ∥ §7 D-PX11):
|
|
5
|
+
* - **流式保形**:边收边吐、只保有帧头(块长行上限 1KiB);块数据到达即吐 `min(可用, 待吐)`;
|
|
6
|
+
* - 块长 = 十六进制(行长 ≤1KiB、数值须安全整数——越界判畸形)+ 可选 `;扩展`(扩展忽略);块尾须恰为 CRLF;`0` 块 ⇒ `onEnd`
|
|
7
|
+
* (其后 trailer ∥ 余字节读取即弃——`Connection: close` 语义下无复用面);
|
|
8
|
+
* - **畸形帧回退 = 透传**:已持有字节原样吐出 + 其后全部字节直通——不报错(不把协议违规
|
|
9
|
+
* 升级成连接失败)、不截断(零静默丢数据);
|
|
10
|
+
* - 纯状态机、无自有 `'error'` 面(#16 契约零变——body 终止守卫族照旧对 body 单点直作)。
|
|
11
|
+
* 零依赖:Node built-ins only。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** 块长行上限(超限判畸形——防无界持有帧头)。 */
|
|
15
|
+
const MAX_SIZE_LINE = 1024
|
|
16
|
+
const CRLF = Buffer.from("\r\n")
|
|
17
|
+
const EMPTY = Buffer.alloc(0)
|
|
18
|
+
|
|
19
|
+
/** 十六进制字节判定(0-9 ∥ a-f ∥ A-F——大小写不敏感)。 */
|
|
20
|
+
const isHexByte = (b) => (b >= 0x30 && b <= 0x39) || (b >= 0x61 && b <= 0x66) || (b >= 0x41 && b <= 0x46)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 建流式分块解码器(消费单点 = `proxy-transport.mjs` `streamHttpResponse` chunked 径)。
|
|
24
|
+
* @param {{ onData?: (chunk: Buffer) => void, onEnd?: () => void }} [handlers]
|
|
25
|
+
* `onData` 逐段收解码后的块数据(任意分包边界);`onEnd` 于 `0` 块到达时触发一次。
|
|
26
|
+
* @returns {{ push: (buf: Buffer) => void, mode: "chunked" | "raw" | "done" }}
|
|
27
|
+
* `push` 喂入原始字节;`mode` = `chunked`(解码中)∥ `raw`(畸形回退透传)∥
|
|
28
|
+
* `done`(`0` 块已终,其后字节读取即弃)——供测试与诊断。
|
|
29
|
+
*/
|
|
30
|
+
export function createChunkedDecoder({ onData, onEnd } = {}) {
|
|
31
|
+
let state = "size" // size → data → crlf → size …
|
|
32
|
+
let mode = "chunked"
|
|
33
|
+
let buf = EMPTY // 未消费字节(只保有帧头——块数据即到即吐)
|
|
34
|
+
let need = 0 // data 阶段待吐字节数
|
|
35
|
+
|
|
36
|
+
const emit = (chunk) => { if (chunk.length > 0) onData?.(chunk) }
|
|
37
|
+
/** 畸形回退:已持有字节原样吐出,此后全量直通(不再解析)。 */
|
|
38
|
+
const fallback = () => {
|
|
39
|
+
mode = "raw"
|
|
40
|
+
const held = buf
|
|
41
|
+
buf = EMPTY
|
|
42
|
+
emit(held)
|
|
43
|
+
}
|
|
44
|
+
/** `0` 块终:body 随 0 块结束。 */
|
|
45
|
+
const finish = () => { mode = "done"; buf = EMPTY; onEnd?.() }
|
|
46
|
+
|
|
47
|
+
/** 帧推进:逐状态消费 buf;块数据即吐(只保有帧头)。 */
|
|
48
|
+
function step() {
|
|
49
|
+
for (;;) {
|
|
50
|
+
if (state === "size") {
|
|
51
|
+
const idx = buf.indexOf(CRLF)
|
|
52
|
+
if (idx < 0) {
|
|
53
|
+
// 未成行:首字节非十六进制 ∥ 超限 ⇒ 畸形;否则等下一包
|
|
54
|
+
if (buf.length > 0 && !isHexByte(buf[0])) return fallback()
|
|
55
|
+
if (buf.length > MAX_SIZE_LINE) return fallback()
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const line = buf.subarray(0, idx)
|
|
59
|
+
const semi = line.indexOf(0x3b) // ";"——块扩展起点(忽略)
|
|
60
|
+
const hex = semi < 0 ? line : line.subarray(0, semi)
|
|
61
|
+
// 畸形行(超限 ∥ 空 ∥ 非十六进制)⇒ 整段原样吐出(行 ∥ CRLF 零丢弃——不截断);行有效才消费
|
|
62
|
+
if (idx > MAX_SIZE_LINE || hex.length === 0) return fallback()
|
|
63
|
+
for (const b of hex) if (!isHexByte(b)) return fallback()
|
|
64
|
+
const size = Number.parseInt(hex.toString("latin1"), 16)
|
|
65
|
+
if (!Number.isSafeInteger(size)) return fallback() // 数值越界(>2^53——double 溢出可为 Infinity)⇒ 归畸形
|
|
66
|
+
buf = buf.subarray(idx + 2)
|
|
67
|
+
if (size === 0) return finish()
|
|
68
|
+
need = size
|
|
69
|
+
state = "data"
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (state === "data") {
|
|
73
|
+
if (buf.length === 0) return
|
|
74
|
+
const take = Math.min(buf.length, need)
|
|
75
|
+
emit(buf.subarray(0, take))
|
|
76
|
+
buf = buf.subarray(take)
|
|
77
|
+
need -= take
|
|
78
|
+
if (need > 0) return
|
|
79
|
+
state = "crlf"
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
// state === "crlf":块尾须恰为 CRLF
|
|
83
|
+
if (buf.length < 2) {
|
|
84
|
+
if (buf.length === 1 && buf[0] !== 0x0d) return fallback()
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
if (buf[0] !== 0x0d || buf[1] !== 0x0a) return fallback()
|
|
88
|
+
buf = buf.subarray(2)
|
|
89
|
+
state = "size"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
push(d) {
|
|
95
|
+
if (mode === "done") return // 0 块后余字节(trailer 等)读取即弃
|
|
96
|
+
if (mode === "raw") { emit(d); return }
|
|
97
|
+
buf = buf.length > 0 ? Buffer.concat([buf, d]) : d
|
|
98
|
+
step()
|
|
99
|
+
},
|
|
100
|
+
get mode() { return mode },
|
|
101
|
+
}
|
|
102
|
+
}
|
package/proxy-target.mjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proxy-target.mjs — 代理目标 ∥ 配置解析面(2026-10-08 拆档——原 `proxy.mjs` 迁出;#1037)。
|
|
3
|
+
* 逐渠判定 = 渠道旗 `providers[].proxy` ∧ `proxy.uri` 在案(无全局闸——D-PX1,2026-10-08 批落)。
|
|
4
|
+
* 零依赖:Node built-ins only(net, url)。
|
|
5
|
+
*/
|
|
6
|
+
import { isIP } from "node:net"
|
|
7
|
+
import { URL } from "node:url"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve proxy URI.
|
|
11
|
+
* New format: { proxy: { uri: "http://host:port", web: true } }
|
|
12
|
+
* Old format: { proxy: "http://host:port" } — backwards compatible, web=true
|
|
13
|
+
* Env vars: HTTPS_PROXY, HTTP_PROXY, ALL_PROXY
|
|
14
|
+
*
|
|
15
|
+
* @returns {{ uri: string|null, web: boolean }}
|
|
16
|
+
*/
|
|
17
|
+
export function resolveProxyConfig(ctx) {
|
|
18
|
+
const cfgProxy = ctx?.agent?.config?.proxy
|
|
19
|
+
if (!cfgProxy) {
|
|
20
|
+
const uri = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || process.env.ALL_PROXY || null
|
|
21
|
+
return { uri, web: !!uri }
|
|
22
|
+
}
|
|
23
|
+
if (typeof cfgProxy === "string") {
|
|
24
|
+
// Backward compat: bare string → web only
|
|
25
|
+
return { uri: cfgProxy, web: true }
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
uri: cfgProxy.uri || cfgProxy.url || null,
|
|
29
|
+
web: cfgProxy.web !== false,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Convenience: resolve proxy URI for web tools (websearch/fetch) */
|
|
34
|
+
export function resolveWebProxy(ctx) {
|
|
35
|
+
const cfg = resolveProxyConfig(ctx)
|
|
36
|
+
return (cfg.uri && cfg.web) ? cfg.uri : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Loopback 目标判定(NO_PROXY 语义——#1026,2026-10-07 用户案):
|
|
41
|
+
* `localhost` ∥ `*.localhost`(RFC 6761)∥ `127.0.0.0/8` ∥ `::1` —— 此类目标永不经代理。
|
|
42
|
+
* 解析失败 ⇒ false(未知串不宣称 loopback);尾点(`localhost.`)先归一。
|
|
43
|
+
*/
|
|
44
|
+
export function isLoopbackTarget(urlStr) {
|
|
45
|
+
let host
|
|
46
|
+
try { host = new URL(urlStr).hostname } catch { return false }
|
|
47
|
+
if (host.startsWith("[") && host.endsWith("]")) host = host.slice(1, -1) // IPv6 带括号(URL.hostname 规范形)
|
|
48
|
+
if (host.endsWith(".")) host = host.slice(0, -1)
|
|
49
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true
|
|
50
|
+
const v = isIP(host)
|
|
51
|
+
if (v === 4) return host.split(".")[0] === "127"
|
|
52
|
+
if (v === 6) return host === "::1"
|
|
53
|
+
return false
|
|
54
|
+
}
|