thinkpool-pair 0.7.129 → 0.7.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bridge.mjs +13 -4
  2. package/package.json +1 -1
package/bridge.mjs CHANGED
@@ -1222,12 +1222,16 @@ function restoredTurnOpen(log) {
1222
1222
  // relay STRUCTURED events. onEvent → broadcast `code-event` + print locally +
1223
1223
  // persist to the host file; tool calls round-trip through the perm card; the
1224
1224
  // rolling log replays to joiners and survives bridge restarts (session-store).
1225
- function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rolePrompt, flowSessionId, flowTaskKey, cwd, reviewSliceRoots }) {
1225
+ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rolePrompt, flowSessionId, flowTaskKey, cwd, reviewSliceRoots, openedAt }) {
1226
1226
  if (sessions.has(id)) return
1227
1227
  // spawnedBy: set when this lane was Dispatched (spawn_terminal). Restored from the
1228
1228
  // session store so the Ensemble flag survives a bridge restart (else a respin
1229
1229
  // stripped it and the lane reverted to a plain tab — the t6 "no chip" bug).
1230
- const entry = { cmd: 'claude', kind: 'structured', log: Array.isArray(log) ? log.slice(-STRUCTURED_LOG_MAX) : [], pending: new Map(), session: null, recovered: false, commands: Array.isArray(commands) ? commands : undefined, mode, model: model || null, spawnedBy: spawnedBy || undefined, flowSessionId: flowSessionId || null, flowTaskKey: flowTaskKey || null, cwd: cwd || null }
1230
+ const entry = { cmd: 'claude', kind: 'structured', log: Array.isArray(log) ? log.slice(-STRUCTURED_LOG_MAX) : [], pending: new Map(), session: null, recovered: false, commands: Array.isArray(commands) ? commands : undefined, mode, model: model || null, spawnedBy: spawnedBy || undefined, flowSessionId: flowSessionId || null, flowTaskKey: flowTaskKey || null, cwd: cwd || null,
1231
+ // Stable creation order — persisted so a bridge restart restores tabs in the SAME
1232
+ // order (not readdir/filesystem order). Legacy recs (no openedAt) derive it from the
1233
+ // first transcript event ts, so even the first post-fix restart is ordered right.
1234
+ openedAt: openedAt || (Array.isArray(log) ? (log.find((e) => e?.ts)?.ts || 0) : 0) || Date.now() }
1231
1235
  // C1 (RT-2): per-term contiguous seq counter. Seeded from the restored log's max
1232
1236
  // so a bridge restart resumes ABOVE every persisted seq — fresh events never reuse
1233
1237
  // an old seq, which is what keeps the between-turns restart dup-free (see
@@ -1340,7 +1344,7 @@ function openStructured({ id, model, resume, log, commands, mode, spawnedBy, rol
1340
1344
  // subagent-block; without cwd it runs outside its worktree; without rolePrompt it loses its
1341
1345
  // Flow role. blockSubagents / onLaneDone / onReviewVerdict / onSubmitPlan all derive from
1342
1346
  // these, so restoring them restores the whole behavior.
1343
- const sessionData = () => ({ sessionId: entry.session?.sessionId || resume || null, log: entry.log, commands: entry.commands, mode: entry.mode, 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 || [] })
1347
+ const sessionData = () => ({ sessionId: entry.session?.sessionId || resume || null, log: entry.log, commands: entry.commands, mode: entry.mode, 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 })
1344
1348
  const persist = () => saveSession(room, id, sessionData())
1345
1349
  // Synchronous flush of this session's record. Used on open (so a brand-new session
1346
1350
  // has a file under its id BEFORE its first event — surviving a restart inside the
@@ -2302,11 +2306,16 @@ Output:`
2302
2306
  // Closed sessions are unlinked by deleteSession, so a file existing on disk ==
2303
2307
  // a session that was live at shutdown (never an explicitly closed one).
2304
2308
  const all = loadAll(room)
2309
+ // Restore in stable creation order (openedAt) so the tab strip comes back in the
2310
+ // SAME order across a bridge restart — NOT readdir/filesystem order, which reshuffled
2311
+ // the tabs (Max, 2026-07-02). Legacy recs fall back to their first event ts / savedAt.
2312
+ const okey = (r) => r.openedAt || (Array.isArray(r.log) ? (r.log.find((e) => e?.ts)?.ts || 0) : 0) || r.savedAt || 0
2313
+ all.sort((a, b) => okey(a) - okey(b))
2305
2314
  if (all.length) for (const rec of all) {
2306
2315
  // FL-M6 — restore the flow context (id/role/cwd) so an in-flight flow survives a
2307
2316
  // bridge restart: the conductor keeps its subagent-block + plan interception, and
2308
2317
  // lanes keep their worktree cwd + the ability to mark done.
2309
- openStructured({ id: rec.id, 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 })
2318
+ openStructured({ id: rec.id, 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 })
2310
2319
  const re = sessions.get(rec.id)
2311
2320
  if (re && rec.flowReviewTarget) re.flowReviewTarget = rec.flowReviewTarget
2312
2321
  // S4 — a resumed re-dispatch lane keeps its surviving revert target across a bridge restart.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.129",
3
+ "version": "0.7.130",
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": {