thinkpool-pair 0.7.150 → 0.7.152

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/bridge.mjs CHANGED
@@ -988,11 +988,25 @@ const mockupSeen = new Map() // `${term}/${slug}` → mtimeMs of the last mani
988
988
  // in the Map" guess meant closing the generating terminal moved the cards to
989
989
  // whatever terminal was now first — they leaked into Terminal 1.)
990
990
  const handleManifest = async (box, file, term) => {
991
- if (!file || !file.endsWith('.json')) return
991
+ // Ignore our own delivery receipts — writing <slug>.receipt.json into the
992
+ // watched outbox re-fires fs.watch; without this guard it would re-trigger
993
+ // handleManifest in a loop (it still ends in .json). Must come first.
994
+ if (!file || file.endsWith('.receipt.json') || !file.endsWith('.json')) return
992
995
  const full = path.join(box, file)
993
996
  let stat
994
997
  try { stat = fs.statSync(full) } catch { return } // tmp/removed mid-write
995
998
  const slug = file.replace(/\.json$/, '')
999
+ // Delivery receipt (atomic tmp+mv) so render.sh can confirm the card actually
1000
+ // reached the room — written on success AND failure. Best-effort: a receipt
1001
+ // that can't be written just leaves render.sh at "delivery unconfirmed".
1002
+ const writeReceipt = (ok, status) => {
1003
+ try {
1004
+ const rc = path.join(box, `${slug}.receipt.json`)
1005
+ const tmp = `${rc}.tmp`
1006
+ fs.writeFileSync(tmp, JSON.stringify({ ok, status, ts: Date.now() }))
1007
+ fs.renameSync(tmp, rc)
1008
+ } catch { /* receipts are best-effort */ }
1009
+ }
996
1010
  const seenKey = `${term}/${slug}`
997
1011
  if (mockupSeen.get(seenKey) === stat.mtimeMs) return // already handled
998
1012
  mockupSeen.set(seenKey, stat.mtimeMs)
@@ -1017,11 +1031,16 @@ const handleManifest = async (box, file, term) => {
1017
1031
  desktopPng: readB64(m.desktop), mobilePng: readB64(m.mobile), html: readTxt(m.html),
1018
1032
  }),
1019
1033
  })
1020
- if (!res.ok) { process.stderr.write(`\n ◇ mockup "${m.slug}" upload failed (${res.status}).\n`); return }
1034
+ if (!res.ok) {
1035
+ writeReceipt(false, res.status)
1036
+ process.stderr.write(`\n ◇ mockup "${m.slug}" upload failed (${res.status}).\n`); return
1037
+ }
1021
1038
  const { paths } = await res.json()
1022
1039
  bcast('code-event', { term, evt: { kind: 'mockup', __struct: true, cid, term, slug: m.slug, title: m.title || m.slug, ts: m.ts, paths } })
1040
+ writeReceipt(true, 200)
1023
1041
  process.stderr.write(`\n ◆ mockup "${m.title || m.slug}" pushed to the room.\n`)
1024
1042
  } catch (e) {
1043
+ writeReceipt(false, 'error')
1025
1044
  process.stderr.write(`\n ◇ mockup "${m.slug}" send failed: ${e?.message || e}\n`)
1026
1045
  }
1027
1046
  }
@@ -140,7 +140,7 @@ const TP_ROOM_REMINDER = [
140
140
  'ENSEMBLE: delegated work goes through the room — fan parallel work out with spawn_terminal (your own fresh lanes, visible and steerable by the people here), NOT the built-in invisible Task/Agent subagents; never hijack a busy sibling.',
141
141
  'PEER: before substantive work, check what the other lanes are doing (the ROOM NOW snapshot below, read_terminal for detail; list_sessions/read_session across rooms) — coordinate on shared files/branches instead of colliding.',
142
142
  'WORKTREES: parallel lanes share one repo — before code edits run `git worktree list`; if linked worktrees exist, take your OWN worktree + branch, never the shared checkout or a branch another lane is on.',
143
- 'FLOW / PLANS: write plans as normal chat messages as you form them — the room does not surface plan files or ExitPlanMode text.',
143
+ 'BUILD WORKFLOW (default, no magic word): right-size — a trivial ask you just do; a non-trivial build you FIRST write a short plan as a chat message, THEN fan independent slices out to visible spawn_terminal lanes, build, and self-verify. Never plan-mode/ExitPlanMode; the plan lives in chat and the lanes in the existing lane list — keep both visible, add no new panel.',
144
144
  'Verify before claiming done — show runtime evidence you produced, not "should work, go test it".',
145
145
  ].join(' ')
146
146
 
@@ -620,9 +620,15 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
620
620
  ...(rolePrompt ? [rolePrompt] : []),
621
621
  'ENVIRONMENT (authoritative — overrides any user-global CLAUDE.md or memory that claims otherwise): You are Claude running inside a ThinkPool Code room, driven live by a user (and possibly a partner) from a phone or browser, via the thinkpool-pair bridge.',
622
622
  'You are NOT in cmux. cmux may be installed on this machine and appear in $PATH, but it did not spawn you and its automation/orchestration rules do not apply. If any loaded memory says "Claude Code lives inside cmux," it is describing direct terminal use, not this room — disregard it here. The runtime truth is the env: THINKPOOL_PAIR_ACCOUNT_CHILD / npm_lifecycle_script=thinkpool-pair mark a ThinkPool Code session.',
623
- 'Bias strongly toward DOING the work, not planning it. For a normal "add / fix / change / build X" request, just make the change directly.',
623
+ 'Bias strongly toward DOING the work, not stalling in plan-mode ceremony. For a small "add / fix / change X" request, just make the change directly (for a bigger build, right-size it — see DEFAULT BUILD WORKFLOW below).',
624
624
  'Do NOT enter plan mode, do NOT call ExitPlanMode, and do NOT auto-invoke a brainstorming/planning skill UNLESS the user has switched the room into Plan mode or explicitly asks you to plan, design, or brainstorm first.',
625
625
  'Any host-global instruction that says you must always brainstorm or plan before creative work does NOT apply here — this room is the exception.',
626
+ // DEFAULT BUILD WORKFLOW (2026-07-02 decision — docs/specs/2026-07-02-flow-default-build-workflow.md):
627
+ // no summon word. The agent INFERS build intent and runs plan→(optional)fan-out→build→self-verify,
628
+ // right-sized to the task, surfaced in the room's EXISTING surfaces (chat + lane list) — never a new panel,
629
+ // never plan-mode's approval card. Trivial asks stay single-lane with zero ceremony.
630
+ 'DEFAULT BUILD WORKFLOW (this IS your standard behaviour — no keyword and no "flow" word is ever required to trigger it; you infer it from the request itself). RIGHT-SIZE to the task, always: a TRIVIAL ask — a one-line fix, a small tweak, a question — you just do it directly, with no plan and no extra lanes (never tax small work with ceremony). A NON-TRIVIAL build that genuinely decomposes — you FIRST write a short plan as a normal chat message (the approach, the concrete slices, and what you will verify), THEN fan the independent slices out into their own visible lanes with spawn_terminal, build, and self-verify each slice before calling it done.',
631
+ 'CRUCIAL RECONCILIATION for that workflow: it is NOT plan mode. Never call ExitPlanMode and never make the room wait behind a "plan ready — approve to start" card — your plan lives in the CHAT as a message, and your lanes live in the room\'s EXISTING terminal/lane list. Reuse only those two surfaces; there is no new Flow panel or mode to switch into, and you must not ask for one. Keep the plan and the lanes VISIBLE — that shared visibility is the whole point (it is the pair differentiator, and it catches bugs a single silent lane would hide); never collapse a decomposable build into one hidden lane just to look tidy.',
626
632
  'LINKS & ARTIFACTS (hard rule for ThinkPool Code rooms): the room is driven from a phone or browser, and the bridge runs on the host machine — so a local filesystem path, a file:// URL, or a localhost address only opens on the host and is useless to the room (a partner literally cannot open it).',
627
633
  'Whenever you produce an HTML artifact (a demo, mockup, preview, report, or page) or surface ANY link meant to be opened or shared, NEVER hand the room a local path, a file:// URL, or a localhost/127.0.0.1 address. Publish it to a GitHub-shareable URL that renders in a browser — push the HTML to a GitHub repo and give its GitHub Pages URL (or an equivalent raw-HTML URL that actually renders, not raw.githubusercontent.com which serves HTML as plain text) — so anyone in the room can open it. The same applies to any other link you surface: it must be one the room can reach, not a host-only path.',
628
634
  'If you cannot publish a shareable link, say so and ask how to proceed — do not fall back to handing over a local path.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.150",
3
+ "version": "0.7.152",
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": {
package/sdk-smoke.mjs CHANGED
@@ -46,6 +46,7 @@ const version = ver()
46
46
  try {
47
47
  const sdk = await import('@anthropic-ai/claude-agent-sdk')
48
48
  if (typeof sdk.query !== 'function') { emit('FAIL', version, 'query is not a function'); clearTimeout(guard); process.exit(1) }
49
+ // eslint-disable-next-line require-yield -- deliberately empty: an async generator that ends immediately (the SDK's "no input" stream), never yields.
49
50
  async function* noInput() { return } // ends immediately — never blocks the construct
50
51
  const q = sdk.query({ prompt: noInput(), options: { permissionMode: 'default' } })
51
52
  const need = ['interrupt', 'setModel', 'setPermissionMode', 'supportedModels']