thinkpool-pair 0.7.270 → 0.7.272
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/codex-session.mjs +34 -3
- package/package.json +1 -1
- package/thinkpool-capabilities.json +6 -6
package/codex-session.mjs
CHANGED
|
@@ -543,8 +543,11 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
543
543
|
|
|
544
544
|
async function runAppServer(prompt, options = {}) {
|
|
545
545
|
if (!await ensureAppServer()) return false
|
|
546
|
+
// Stop can land while the cold App Server / MCP bootstrap is still awaiting.
|
|
547
|
+
// The accepted turn was already closed at the room boundary; never start it
|
|
548
|
+
// after bootstrap finally resolves.
|
|
549
|
+
if (ended || aborted) return true
|
|
546
550
|
mapper.setUsageBaseline(sessionId ? readCodexThreadUsage(sessionId) : null)
|
|
547
|
-
aborted = false
|
|
548
551
|
turnActive = true
|
|
549
552
|
try {
|
|
550
553
|
activeTurnId = await appServer.startTurn({
|
|
@@ -556,6 +559,13 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
556
559
|
approvalPolicy: modeConfig.approvalPolicy,
|
|
557
560
|
collaborationMode: codexDefaultCollaborationMode(activeModel, activeEffort),
|
|
558
561
|
})
|
|
562
|
+
// Stop may land after the RPC was written but before startTurn returns its
|
|
563
|
+
// id. abort() already closed the room boundary; interrupt the now-known
|
|
564
|
+
// native turn before waiting, and never let it continue invisibly.
|
|
565
|
+
if (aborted || ended) {
|
|
566
|
+
try { await appServer.interrupt({ threadId: sessionId, turnId: activeTurnId }) } catch { /* boundary is already closed */ }
|
|
567
|
+
return true
|
|
568
|
+
}
|
|
559
569
|
const completed = await appServer.waitForTurn(activeTurnId)
|
|
560
570
|
const status = completed?.turn?.status
|
|
561
571
|
if (aborted || status === 'interrupted') {
|
|
@@ -592,13 +602,17 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
592
602
|
|
|
593
603
|
async function runAppServerReview(commandText) {
|
|
594
604
|
if (!await ensureAppServer()) return false
|
|
605
|
+
if (ended || aborted) return true
|
|
595
606
|
mapper.setUsageBaseline(sessionId ? readCodexThreadUsage(sessionId) : null)
|
|
596
|
-
aborted = false
|
|
597
607
|
turnActive = true
|
|
598
608
|
try {
|
|
599
609
|
const started = await appServer.startReview({ threadId: sessionId, target: codexReviewTarget(commandText) })
|
|
600
610
|
activeTurnId = started?.turn?.id || started?.turnId
|
|
601
611
|
if (!activeTurnId) throw new Error('Codex review/start returned no turn id')
|
|
612
|
+
if (aborted || ended) {
|
|
613
|
+
try { await appServer.interrupt({ threadId: sessionId, turnId: activeTurnId }) } catch { /* boundary is already closed */ }
|
|
614
|
+
return true
|
|
615
|
+
}
|
|
602
616
|
const completed = await appServer.waitForTurn(activeTurnId)
|
|
603
617
|
const status = completed?.turn?.status
|
|
604
618
|
if (aborted || status === 'interrupted') {
|
|
@@ -628,6 +642,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
628
642
|
}
|
|
629
643
|
|
|
630
644
|
async function runExec(prompt, options = {}) {
|
|
645
|
+
if (ended || aborted) return
|
|
631
646
|
// First turn: fresh exec. Later turns: resume THIS lane's captured thread.
|
|
632
647
|
// `--last` is process-global and can cross-wire two concurrently-active Codex
|
|
633
648
|
// lanes, so it is never safe in a multi-lane bridge.
|
|
@@ -658,7 +673,6 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
658
673
|
const opts = { cwd, env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] }
|
|
659
674
|
if (stdinFd != null) opts.stdio = [stdinFd, 'pipe', 'pipe']
|
|
660
675
|
|
|
661
|
-
aborted = false
|
|
662
676
|
turnActive = true
|
|
663
677
|
child = spawnImpl('codex', args, opts)
|
|
664
678
|
if (stdinFd != null) { try { fs.closeSync(stdinFd) } catch { /* child owns its duplicated fd */ } }
|
|
@@ -777,18 +791,35 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
777
791
|
return true
|
|
778
792
|
}
|
|
779
793
|
queue.push({ text, options, promptIndex, forceFullReminder: thisTurnForceFull })
|
|
794
|
+
// Match Claude and Hermes: accepting an idle turn claims the lane before
|
|
795
|
+
// any cold App Server, MCP, or exec bootstrap. bridge.mjs announces
|
|
796
|
+
// synchronously after sendTurn(), so this is the edge that makes Thinking,
|
|
797
|
+
// Stop, and the tab spinner appear at dispatch time rather than at the
|
|
798
|
+
// first provider event many seconds later.
|
|
799
|
+
if (!turnActive) {
|
|
800
|
+
aborted = false
|
|
801
|
+
turnActive = true
|
|
802
|
+
}
|
|
780
803
|
// start the pump if idle
|
|
781
804
|
if (queue.length === 1) pump()
|
|
782
805
|
return true
|
|
783
806
|
},
|
|
784
807
|
abort() {
|
|
808
|
+
const bootstrapOnly = turnActive && !activeTurnId && !child
|
|
785
809
|
aborted = true
|
|
786
810
|
queue.length = 0
|
|
787
811
|
if (appServer && activeTurnId) { void appServer.interrupt({ threadId: sessionId, turnId: activeTurnId }).catch(() => {}) ; return }
|
|
788
812
|
if (child) { try { child.kill('SIGTERM') } catch { /* noop */ } ; setTimeout(() => { try { child?.kill('SIGKILL') } catch { /* noop */ } }, 1500) }
|
|
813
|
+
else if (bootstrapOnly) {
|
|
814
|
+
// No native turn exists to produce a boundary. Close the optimistic
|
|
815
|
+
// accepted turn exactly once and let runAppServer's post-bootstrap
|
|
816
|
+
// aborted check discard the delayed startup.
|
|
817
|
+
emitTurnBoundary({ kind: 'result', subtype: 'aborted', sessionId, model: activeModel, costUsd: null, usage: null, numTurns: 0, durationMs: undefined, denials: 0, resultText: null })
|
|
818
|
+
}
|
|
789
819
|
},
|
|
790
820
|
end() {
|
|
791
821
|
ended = true
|
|
822
|
+
turnActive = false
|
|
792
823
|
queue.length = 0
|
|
793
824
|
if (child) { try { child.kill() } catch { /* noop */ } }
|
|
794
825
|
try { appServer?.end() } catch { /* noop */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
3
|
+
"bundleVersion": 3,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
6
6
|
"id": "room-coordination",
|
|
@@ -161,17 +161,17 @@
|
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"id": "design-workspace",
|
|
164
|
-
"version":
|
|
165
|
-
"interactionPrompt": "DESIGN EDITING MODEL: a trusted source-backed mockup card offers
|
|
166
|
-
"turnReminder": "DESIGN ROUTE: authored HTML must produce a source-backed Thinkpool Design card with verified desktop and mobile renders.
|
|
164
|
+
"version": 2,
|
|
165
|
+
"interactionPrompt": "DESIGN EDITING MODEL: a trusted source-backed mockup card offers Edit in Design. That explicit action—not ordinary Preview or unrelated artifact delivery—arms a persistent room-level Design workspace, opens the artifact directly in editable mode, and adds Design · Page name as a virtual Ensemble lane for both partners; it creates no terminal, agent runtime, or worker slot. Once armed, the Design lane reopens the active artifact at its latest verified revision without another agent turn, history search, HTML/path request, or re-selection. Design is the default canvas there and Preview is only a temporary interaction mode. Direct text, supported movement, and selected-image changes auto-stage as optimistic drafts in the bounded changes queue. Apply changes sends the ordered batch once to the producing lane, that lane edits the canonical authored HTML, and a successful desktop+mobile render advances the verified revision for the room. Never call a staged draft saved or live. A preview_capture card is visual evidence, not an editable Design artifact.",
|
|
166
|
+
"turnReminder": "DESIGN ROUTE: authored HTML must produce a source-backed Thinkpool Design card with verified desktop and mobile renders. Edit in Design explicitly arms the persistent Design workspace and adds its virtual Design · Page Ensemble lane; Preview alone does not arm it. Do not duplicate the card renders as inline PNGs. Use inline PNG evidence only when no interactive Design artifact is available.",
|
|
167
167
|
"impact": [
|
|
168
168
|
{"path": "src/pages/code/design/"},
|
|
169
|
-
{"path": "src/pages/code/structured.jsx", "diffPattern": "
|
|
169
|
+
{"path": "src/pages/code/structured.jsx", "diffPattern": "Edit in Design|openMockup|tp-mockup-view|sourceKnown"},
|
|
170
170
|
{"path": "src/pages/code/room.jsx", "diffPattern": "designWorkspace|designLane|activeDesign|DesignViewer"},
|
|
171
171
|
{"path": "bridge/design-edit.mjs"}
|
|
172
172
|
],
|
|
173
173
|
"evidence": [
|
|
174
|
-
{"path": "src/pages/code/structured.jsx", "pattern": "
|
|
174
|
+
{"path": "src/pages/code/structured.jsx", "pattern": "Edit in Design"},
|
|
175
175
|
{"path": "src/pages/code/design/workspace.js", "pattern": "armed: true"},
|
|
176
176
|
{"path": "src/pages/code/room.jsx", "pattern": "designLaneSelected"},
|
|
177
177
|
{"path": "src/pages/code/design/DesignViewer.jsx", "pattern": "createPortal\\(surface, laneHost\\)"},
|