thinkpool-pair 0.7.179 → 0.7.180

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/README.md CHANGED
@@ -120,10 +120,17 @@ ThinkPool Code runs **any model you choose** — not just Anthropic. The agent
120
120
  talks the Anthropic Messages API, so you point it at any endpoint that speaks
121
121
  that format:
122
122
 
123
- - **Anthropic-compatible endpoints directly** — Z.ai GLM, OpenRouter, your own proxy.
124
- - **Any other model via a gateway** — put a translator like **LiteLLM** or
125
- OpenRouter in front and run **GPT, Gemini, Llama, DeepSeek, or a local model**;
126
- the gateway presents the Anthropic Messages API while calling whatever you pick.
123
+ - **Anthropic-compatible endpoints directly** — Z.ai GLM, Moonshot Kimi, OpenRouter, your own proxy.
124
+ - **Any other model via a translating gateway** — put **LiteLLM** (or a proxy of
125
+ your own) in front and run **GPT, Gemini, Llama, DeepSeek, or a local model**;
126
+ it presents the Anthropic Messages API while calling whatever you pick.
127
+
128
+ > OpenRouter is the exception worth knowing: its `/v1/messages` surface is a
129
+ > pass-through for **Anthropic models only**. Per OpenRouter's own docs, "Claude
130
+ > Code expects Anthropic request semantics, so non-Anthropic models aren't
131
+ > supported through the native endpoint" — and this bridge spawns the `claude`
132
+ > CLI. To reach a non-Anthropic model, use a translating gateway (LiteLLM),
133
+ > not OpenRouter's Anthropic endpoint.
127
134
 
128
135
  ```bash
129
136
  npx thinkpool-pair provider custom --base <url> --token <key> [--model <name>]
@@ -134,7 +141,8 @@ Common base urls (the agent appends `/v1/messages`):
134
141
  | Provider / gateway | Base url | Models |
135
142
  |--------------------|-----------------------------------|------------------------------|
136
143
  | Z.ai GLM | `https://api.z.ai/api/anthropic` | GLM |
137
- | OpenRouter | `https://openrouter.ai/api` | many, incl. non-Anthropic |
144
+ | Moonshot Kimi | `https://api.moonshot.ai/anthropic` | Kimi |
145
+ | OpenRouter | `https://openrouter.ai/api` | Anthropic models only |
138
146
  | LiteLLM (your own) | `http://localhost:4000` | GPT, Gemini, Llama, local, … |
139
147
 
140
148
  > The endpoint must serve the Anthropic Messages API (`/v1/messages`). A raw
package/bridge.mjs CHANGED
@@ -1566,7 +1566,7 @@ function worktreeSnapshot(cwd) {
1566
1566
  // relay STRUCTURED events. onEvent → broadcast `code-event` + print locally +
1567
1567
  // persist to the host file; tool calls round-trip through the perm card; the
1568
1568
  // rolling log replays to joiners and survives bridge restarts (session-store).
1569
- function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rolePrompt, flowSessionId, flowTaskKey, cwd, reviewSliceRoots, openedAt, defer, provider, carryRecap }) {
1569
+ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rolePrompt, flowSessionId, flowTaskKey, cwd, reviewSliceRoots, openedAt, defer, provider, carryRecap, lastUsage }) {
1570
1570
  if (sessions.has(id)) return
1571
1571
  // The lane's EFFECTIVE model — computed ONCE and used for BOTH the truthful display
1572
1572
  // label (entry.model) and the SDK's `model` option below. These used to be computed
@@ -1608,6 +1608,12 @@ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rol
1608
1608
  // event-id.mjs + src/pages/code/seqDedup.js). pushLog() is the single stamp point.
1609
1609
  entry.seq = makeSeqCounter(maxSeq(entry.log))
1610
1610
  entry.rolePrompt = rolePrompt || null // FL-M6 — persisted so a bridge restart restores the flow role prompt
1611
+ // lastUsage — the terminal's most recent usage/ctx meter. Persisted (sessionData) +
1612
+ // restored so a bridge restart can re-emit it on replay: usage is chrome (kept out of
1613
+ // the replayed transcript log), so without this a (re)joiner sees "—" for context until
1614
+ // the terminal's next turn. correctContext nulls ctx for models whose true window we
1615
+ // can't determine; those restore a null ctx and replay nothing (no misleading %).
1616
+ entry.lastUsage = lastUsage || null
1611
1617
  // S5 (slice 1b) — a REVIEW lane's write-block scope: the worktree root(s) of the slice(s)
1612
1618
  // it reviews (its deps). Persisted so a bridge restart re-arms the structural gate. Empty/
1613
1619
  // absent on builder lanes → reviewGate stays null → builder toolset UNCHANGED.
@@ -1775,7 +1781,7 @@ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rol
1775
1781
  // restart. Without this, sessionData omitted it → on restart the resumed session
1776
1782
  // re-launched on the host default (Opus) regardless of the last switch, and the
1777
1783
  // switch looked like it "never changed the model" (Max 2026-07-02). Restored below.
1778
- const sessionData = () => ({ sessionId: entry.session?.sessionId || resume || null, log: entry.log, commands: entry.commands, mode: entry.mode, model: entry.model || null, provider: entry.provider || null, spawnedBy: entry.spawnedBy, flowSessionId: entry.flowSessionId, flowTaskKey: entry.flowTaskKey, cwd: entry.cwd, rolePrompt: entry.rolePrompt, flowReviewTarget: entry.flowReviewTarget || null, revertTarget: entry.revertTarget || null, reviewSliceRoots: entry.reviewSliceRoots || [], openedAt: entry.openedAt || null })
1784
+ const sessionData = () => ({ sessionId: entry.session?.sessionId || resume || null, log: entry.log, commands: entry.commands, mode: entry.mode, model: entry.model || null, provider: entry.provider || null, spawnedBy: entry.spawnedBy, flowSessionId: entry.flowSessionId, flowTaskKey: entry.flowTaskKey, cwd: entry.cwd, rolePrompt: entry.rolePrompt, flowReviewTarget: entry.flowReviewTarget || null, revertTarget: entry.revertTarget || null, reviewSliceRoots: entry.reviewSliceRoots || [], openedAt: entry.openedAt || null, lastUsage: entry.lastUsage || null })
1779
1785
  const persist = () => saveSession(room, id, sessionData())
1780
1786
  // Synchronous flush of this session's record. Used on open (so a brand-new session
1781
1787
  // has a file under its id BEFORE its first event — surviving a restart inside the
@@ -2227,6 +2233,10 @@ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rol
2227
2233
  const m = onBuiltin ? (evt.model || evt.ctx?.model) : null
2228
2234
  if (m && m !== entry.model) { entry.model = m; announce() }
2229
2235
  if (evt.kind === 'models' && Array.isArray(evt.models) && evt.models.length) { roomModels = evt.models; announce() } }
2236
+ // Cache the last usage/ctx meter on the entry (persisted + replayed — see
2237
+ // openStructured). Chrome events bypass pushLog/persist in emitTail below, so
2238
+ // usage needs an explicit persist() here to survive a bridge restart.
2239
+ if (evt.kind === 'usage') { entry.lastUsage = evt; persist() }
2230
2240
  // FL-B2 — fold this flow lane's completed-turn output tokens into its budget so the
2231
2241
  // autopilot cap can halt the next wave before overrun (output_tokens = the billed
2232
2242
  // reasoning+output spend the indicator already tracks; conservative enough for a guard).
@@ -2712,8 +2722,16 @@ channel
2712
2722
  // the terminal. chunkReplayEvents splits it so nothing in the recent window is lost;
2713
2723
  // classifyReplay applies the chunks in arrival order. Older history beyond the chunk
2714
2724
  // budget is covered by the client's DB transcript snapshot.
2725
+ // Attach the terminal's last usage/ctx meter to the FIRST replay chunk. usage is
2726
+ // chrome (filtered out of `events` by both bridge + client), so without this a
2727
+ // (re)joiner's ctx meter is empty ("—") until the terminal's next turn. The client
2728
+ // applies `payload.lastUsage` in its code-replay handler (which is `to`-targeted, so
2729
+ // only the joiner gets it). Gated on `.ctx` — a suppressed meter (correctContext
2730
+ // nulled it for an unknown non-Claude model) replays nothing, never a misleading %.
2731
+ let firstChunk = true
2715
2732
  for (const events of chunkReplayEvents(tail)) {
2716
- bcast('code-replay', { to: payload?.to ?? null, term: id, events, ...(ahead ? { reset: true } : {}) })
2733
+ bcast('code-replay', { to: payload?.to ?? null, term: id, events, ...(ahead ? { reset: true } : {}), ...(firstChunk && s.lastUsage?.ctx ? { lastUsage: s.lastUsage } : {}) })
2734
+ firstChunk = false
2717
2735
  }
2718
2736
  }
2719
2737
  // Re-send any still-pending permission/question cards. They ride a one-shot
@@ -3060,7 +3078,7 @@ channel
3060
3078
  // FL-M6 — restore the flow context (id/role/cwd) so an in-flight flow survives a
3061
3079
  // bridge restart: the conductor keeps its subagent-block + plan interception, and
3062
3080
  // lanes keep their worktree cwd + the ability to mark done.
3063
- openStructured({ id: rec.id, model: rec.model || undefined, provider: rec.provider || undefined, resume: canResume(rec) ? rec.sessionId : undefined, log: rec.log, commands: rec.commands, mode: rec.mode, spawnedBy: rec.spawnedBy, flowSessionId: rec.flowSessionId, flowTaskKey: rec.flowTaskKey, cwd: rec.cwd, rolePrompt: rec.rolePrompt, reviewSliceRoots: rec.reviewSliceRoots, openedAt: rec.openedAt,
3081
+ openStructured({ id: rec.id, model: rec.model || undefined, provider: rec.provider || undefined, resume: canResume(rec) ? rec.sessionId : undefined, log: rec.log, commands: rec.commands, mode: rec.mode, spawnedBy: rec.spawnedBy, flowSessionId: rec.flowSessionId, flowTaskKey: rec.flowTaskKey, cwd: rec.cwd, rolePrompt: rec.rolePrompt, reviewSliceRoots: rec.reviewSliceRoots, openedAt: rec.openedAt, lastUsage: rec.lastUsage,
3064
3082
  // Lazy-boot restored terminals that were IDLE + not part of a flow: their transcript
3065
3083
  // shows immediately; the query boots on first turn. Mid-turn + flow terminals boot now
3066
3084
  // (mid-turn needs auto-resume; flow needs its lane live).
@@ -86,7 +86,13 @@ export function correctContext(raw, env = process.env) {
86
86
  const correctedMax = override ?? mapped
87
87
 
88
88
  if (correctedMax == null) {
89
- // No correction available — but never ship a >100% pct.
89
+ // No correction available. For a NON-Claude model the SDK's max is computed for the
90
+ // wrong model id (it believes it's talking to Claude over ANTHROPIC_BASE_URL) → a %
91
+ // off that wrong max misleads (the GLM-ran-past-100 bug, 2026-07-04). Rather than ship
92
+ // a wrong number, SUPPRESS the meter: return null so the mode row + Session info show
93
+ // nothing / "—" instead. We only display ctx when we know the true window — a Claude
94
+ // id (SDK max trusted), a known-map model (corrected above), or TP_CONTEXT_MAX (override).
95
+ if (!isClaudeModel(raw.model)) return null
90
96
  const pct = Number(raw.pct)
91
97
  if (Number.isFinite(pct) && pct > 100) return { ...raw, pct: 100, over: true }
92
98
  return raw
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.179",
3
+ "version": "0.7.180",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {