thinkpool-pair 0.7.337 → 0.7.338
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 +10 -6
- package/claude-session.mjs +4 -4
- package/codex-session.mjs +65 -5
- package/package.json +1 -1
- package/thinkpool-capabilities.json +5 -5
- package/thinkpool-room-prompt.mjs +1 -1
package/bridge.mjs
CHANGED
|
@@ -2780,11 +2780,11 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2780
2780
|
// conductor mains, spawned workers, Side lanes, and Flow lanes cannot use it.
|
|
2781
2781
|
...(canOpenMainConductor ? [tool(
|
|
2782
2782
|
'open_main_terminal',
|
|
2783
|
-
'Open a separate independent MAIN terminal
|
|
2783
|
+
'Open a separate independent MAIN terminal on any supported runtime: Claude (including a connected Anthropic-compatible BYOK provider), Codex, or Hermes. Use this whenever a person asks to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal—even for ordinary work or a handoff and even without the words Cascade or conductor. If the wording includes new or separate terminal, main-terminal routing wins. This is the top-level room lifecycle, NOT Ensemble dispatch: the new terminal has no spawnedBy owner and appears as a main terminal. Never substitute spawn_terminal, which creates worker sub-terminals only. Give the main terminal its full initial task; it can use spawn_terminal for bounded workers.',
|
|
2784
2784
|
{
|
|
2785
2785
|
name: z.string().max(80).optional().describe('short label for the main conductor, e.g. "Cascade · Multi-device chaos"'),
|
|
2786
|
-
task: z.string().min(1).describe('complete initial
|
|
2787
|
-
model: z.string().optional().describe('optional
|
|
2786
|
+
task: z.string().min(1).describe('complete initial brief for the independent main terminal'),
|
|
2787
|
+
model: z.string().optional().describe('optional main-terminal model, e.g. opus / gpt-5.6-sol / nous:z-ai/glm-5.2'),
|
|
2788
2788
|
runtime: z.enum(['claude', 'codex', 'hermes']).optional().describe('agent runtime; every top-level terminal can open every supported runtime, defaulting to its own'),
|
|
2789
2789
|
provider: z.string().optional().describe(`optional registered Claude-compatible provider name or id; omit for built-in Anthropic${registeredProviderChoices() ? `. Available now: ${registeredProviderChoices()}` : ''}`),
|
|
2790
2790
|
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional().describe('permission mode; defaults to inheriting this main terminal, except plan falls back to default'),
|
|
@@ -4445,21 +4445,24 @@ channel
|
|
|
4445
4445
|
ctlLine('nothing to compact — context unchanged')
|
|
4446
4446
|
return
|
|
4447
4447
|
}
|
|
4448
|
+
const preTokens = s.lastUsage?.ctx?.used || null
|
|
4449
|
+
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'start', ts: Date.now() } })
|
|
4448
4450
|
const nativeCompacted = await s.session.compactContext?.()
|
|
4449
4451
|
if (nativeCompacted) {
|
|
4450
|
-
const ce = { kind: 'compaction', trigger: 'manual', preTokens
|
|
4452
|
+
const ce = { kind: 'compaction', trigger: 'manual', preTokens, by: payload.by, native: true }
|
|
4451
4453
|
pushLog(s, ce)
|
|
4452
4454
|
bcast('code-event', { term: payload.term, evt: ce })
|
|
4453
|
-
|
|
4455
|
+
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
4454
4456
|
s.flush?.()
|
|
4455
4457
|
return
|
|
4456
4458
|
}
|
|
4457
4459
|
if (s.session.clearContext?.() === false) {
|
|
4460
|
+
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
4458
4461
|
ctlLine('Codex context compaction unavailable right now')
|
|
4459
4462
|
return
|
|
4460
4463
|
}
|
|
4461
4464
|
s.pendingRecap = recap
|
|
4462
|
-
const ce = { kind: 'compaction', trigger: 'manual', preTokens
|
|
4465
|
+
const ce = { kind: 'compaction', trigger: 'manual', preTokens, by: payload.by }
|
|
4463
4466
|
pushLog(s, ce)
|
|
4464
4467
|
bcast('code-event', { term: payload.term, evt: ce })
|
|
4465
4468
|
// The new native thread has only the bounded recap queued for the next
|
|
@@ -4471,6 +4474,7 @@ channel
|
|
|
4471
4474
|
s.lastUsage = usage
|
|
4472
4475
|
bcast('code-event', { term: payload.term, evt: usage })
|
|
4473
4476
|
}
|
|
4477
|
+
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
4474
4478
|
s.flush?.()
|
|
4475
4479
|
return
|
|
4476
4480
|
}
|
package/claude-session.mjs
CHANGED
|
@@ -239,9 +239,9 @@ const MODES = new Set(['default', 'acceptEdits', 'plan', 'bypassPermissions'])
|
|
|
239
239
|
const TP_ROOM_REMINDER = [
|
|
240
240
|
'You are Claude in a ThinkPool Code room, driven live from a phone or browser — NOT a local terminal. Keep using the room\'s features.',
|
|
241
241
|
THINKPOOL_RUNTIME_TURN_REMINDER,
|
|
242
|
-
'TERMINAL HIERARCHY: obey your authoritative TERMINAL ROLE.
|
|
242
|
+
'TERMINAL HIERARCHY: obey your authoritative TERMINAL ROLE. A person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal — NEVER spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. If open_main_terminal is unavailable, say so; never substitute an Ensemble child. Leaf, worker, Side, and managed Flow lanes work directly. Never use built-in invisible Task/Agent subagents or hijack a busy sibling.',
|
|
243
243
|
'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.',
|
|
244
|
-
'BUILD WORKFLOW (default, no magic word): right-size within your TERMINAL ROLE — a trivial ask or delegated slice you just do; a conductor-capable role with a genuinely decomposable build FIRST writes a short plan in chat, THEN fans worker slices into visible spawn_terminal lanes and verifies them. Worker/leaf/Side/managed Flow roles do not fan out. A requested separate
|
|
244
|
+
'BUILD WORKFLOW (default, no magic word): right-size within your TERMINAL ROLE — a trivial ask or delegated slice you just do; a conductor-capable role with a genuinely decomposable build FIRST writes a short plan in chat, THEN fans worker slices into visible spawn_terminal lanes and verifies them. Worker/leaf/Side/managed Flow roles do not fan out. A person-requested new or separate terminal uses open_main_terminal. Never plan-mode/ExitPlanMode; plans live in chat and lanes in the existing list.',
|
|
245
245
|
].join(' ')
|
|
246
246
|
|
|
247
247
|
export function startClaudeSession({ cwd, model, effort: initialEffort = 'high', resume, env, mode: initialMode = 'default', onEvent, requestPermission, mcpServers, crossPostGate, crossRoomPostGate, didSpawnTarget = null, terminalRolePrompt, rolePrompt, blockSubagents = false, onSubmitPlan = null, onLaneDone = null, onReviewVerdict = null, reviewGate = null, lazy = false, roomContext = null, suggest = true, prepareCwd = null, admitStart = null }) {
|
|
@@ -749,10 +749,10 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
749
749
|
...THINKPOOL_REMOTE_DELIVERY_RULES,
|
|
750
750
|
'CROSS-TERMINAL AWARENESS: ROOM NOW is the default roster and already satisfies the room check when it has enough detail. Do not repeat it with a no-argument read_terminal call unless it is missing or truncated. Use a targeted read_terminal call only when the current task depends on a specific lane’s detailed activity; never poll. Identify a terminal by its NAME or stable ref/id, never by an on-screen number like "Terminal 2" — positional labels renumber when a terminal is closed. The tool is read-only and its optional roster lookup is budgeted separately from bounded targeted transcript reads.',
|
|
751
751
|
'CROSS-TERMINAL HAND-OFF: you also have post_to_terminal(terminal, text) to send a message or task to ANOTHER AGENT terminal in this room (not a plain shell). Use it sparingly and only when the people clearly want the lanes to coordinate — e.g. "tell the backend terminal the API is ready", or to hand a sibling agent a concrete task. Every post requires a person in the room to approve a card before it is delivered, and an agent that was itself reached via a cross-post cannot post onward — so do not rely on it for chit-chat or loops. Prefer read_terminal to understand a sibling before you ever post to it.',
|
|
752
|
-
'TERMINAL CREATION CONTRACT:
|
|
752
|
+
'TERMINAL CREATION CONTRACT: main terminals and workers use different tools. A person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal(name?, task?, model?) — even for ordinary work or a handoff and even without the words Cascade or conductor. If the wording includes new or separate terminal, main-terminal routing wins. Never use spawn_terminal for that request, and if open_main_terminal is unavailable say so instead of substituting. Use spawn_terminal(name?, task?, model?, sliceType?) only for agent-decided bounded WORKER SUB-TERMINALS or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. Workers never receive the creation tools and never conduct. Do not dump work into busy siblings. Spawned workers are always autonomous in bypassPermissions and never ask the room for an approval card; the bridge still enforces room caps, hop limits, the kill-switch, and isolated linked worktrees. Wait for ROOM NOW or a completion signal instead of polling; after a worker finishes, collect it with one targeted read_terminal call and close_terminal immediately. Main terminals are independent, keep their requested permission mode, and are not owned/closed through Ensemble.',
|
|
753
753
|
'CROSS-SESSION AWARENESS: the Ensemble reaches across your SESSIONS, not just the terminals in this room. list_sessions() lists your OTHER ThinkPool Code rooms — both your own rooms running on this machine AND your partner\'s rooms in the same pair, reachable over the per-pair bus (a room on the partner\'s machine shows its host). read_session(session, terminal?) reads recent activity inside one (omit `terminal` to list that room\'s terminals, or pass a ref/name to read that lane). Both are READ-ONLY — they never change another session, and they reach ONLY your own rooms and rooms you share with your partner, never a stranger\'s. Reach for them when work spans rooms — "what\'s the other project up to", "pick up where the other session left off", or to check a long-running task elsewhere before you act here.',
|
|
754
754
|
'CROSS-SESSION HAND-OFF: post_to_session(session, text, terminal?) sends a task or message to an agent in ANOTHER of your rooms — your own, or your partner\'s over the pair bus. Use it sparingly and only when the people clearly want the rooms to coordinate — e.g. hand the API room\'s agent a concrete follow-up once the frontend is ready. It is dual-consent: a person in YOUR room approves sending, and a person in the TARGET room approves receiving, before anything is delivered — so never rely on it for chit-chat or loops, and an agent that was itself reached via a cross-room post cannot post onward to a third room. It spends real model tokens in the other room (maybe on the other person\'s machine), so prefer read_session to understand a room before you ever post into it, and only post one concrete hand-off at a time. Outbound list/read/post tools need the ThinkPool account bridge; a standalone owner room can still receive a paired hand-off directly and will always raise its own approval card before delivery.',
|
|
755
|
-
'SUBAGENT POLICY: in this room, a main
|
|
755
|
+
'SUBAGENT POLICY: in this room, a main terminal delegates worker slices through visible spawn_terminal Ensemble lanes. A person-requested new or separate terminal is created with open_main_terminal, never Ensemble. Worker, leaf, Side, and managed Flow lanes do their assigned work directly. Do NOT reach for built-in Task/Agent subagents: an in-process subagent is invisible to the room, cannot be peered at or steered, and its work is lost to the Ensemble.',
|
|
756
756
|
'RESEARCH LANE: you have a `research` tool that runs a REAL multi-source web search + adversarial verification and returns each claim marked HELD or REJECTED with citations. Reach for it when the people would genuinely benefit from looking something external up or settling a question of current fact — pricing, "is X still maintained / deprecated", "is that benchmark real", a debate over facts you are not sure of. Do NOT run it unprompted or for things you already know: first OFFER in plain language ("want me to spawn a research lane on that and check it?"), and only call `research(question)` once they agree — it spends real budget (plan-gated Free 5 / Plus 100 runs a month) and takes ~a minute. When it returns, present the held/rejected findings clearly and invite both people to weigh the sources, flagging any held claim that rests on a source they might not trust — that shared scrutiny is the point.',
|
|
757
757
|
'WORKTREES: parallel lanes share one machine and usually one repo. Run `git worktree list` before your first code edit; if linked worktrees exist, the shared main checkout is contended (and may be guard-blocked) — do your work in your OWN worktree on your OWN branch (`git worktree add <dir> -b <branch>`), and never edit a checkout or ride a branch another lane is using.',
|
|
758
758
|
'WRITE PLANS INTO THE CHAT: whenever you form or revise a plan — because the room is in Plan mode, or because someone asked you to plan, design, or think it through first — write the actual plan out as a normal message in the room as you develop it: the approach, the concrete steps, the files you will touch, the open questions. The room does NOT surface plan files at all, and the plan-approval card does not reliably carry the plan text, so a plan that lives only in a plan file or only inside ExitPlanMode is INVISIBLE to the people you are working with — they just see "plan ready" with no content. The chat is the canonical place your plan lives; put it there so the room can read and react to it before you proceed.',
|
package/codex-session.mjs
CHANGED
|
@@ -192,6 +192,25 @@ export function readCodexThreadUsage(sessionId, {
|
|
|
192
192
|
return null
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
// App Server publishes the authoritative current-window count during native
|
|
196
|
+
// compaction (and while turns run). Its field names differ from the rollout
|
|
197
|
+
// token_count rows, so normalize both sources to the one snapshot shape used by
|
|
198
|
+
// CodexEventMapper and the bridge context meter.
|
|
199
|
+
export function codexUsageSnapshotFromAppServer(tokenUsage) {
|
|
200
|
+
const used = Number(tokenUsage?.last?.totalTokens)
|
|
201
|
+
const max = Number(tokenUsage?.modelContextWindow)
|
|
202
|
+
if (!Number.isFinite(used) || used < 0 || !Number.isFinite(max) || max <= 0) return null
|
|
203
|
+
const total = tokenUsage?.total || {}
|
|
204
|
+
return {
|
|
205
|
+
context: { used: Math.floor(used), max: Math.floor(max) },
|
|
206
|
+
total: {
|
|
207
|
+
input_tokens: Math.max(0, Number(total.inputTokens) || 0),
|
|
208
|
+
cached_input_tokens: Math.max(0, Number(total.cachedInputTokens) || 0),
|
|
209
|
+
output_tokens: Math.max(0, Number(total.outputTokens) || 0),
|
|
210
|
+
},
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
195
214
|
export function normalizeCodexSandbox(value) {
|
|
196
215
|
return SAFE_SANDBOXES.has(value) ? value : DEFAULT_SANDBOX
|
|
197
216
|
}
|
|
@@ -218,7 +237,7 @@ export function codexPublishAccess({ cwd, env = process.env, tmpdir = os.tmpdir(
|
|
|
218
237
|
}
|
|
219
238
|
|
|
220
239
|
export const CODEX_ROOM_CASCADE_REMINDER = [
|
|
221
|
-
'THINKPOOL ROOM ROLE ENFORCEMENT: obey the authoritative TERMINAL ROLE above.
|
|
240
|
+
'THINKPOOL ROOM ROLE ENFORCEMENT: obey the authoritative TERMINAL ROLE above. A person-authored request for a new, separate, independent, top-level, or main terminal uses open_main_terminal — NEVER spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. If open_main_terminal is unavailable, say so; never substitute an Ensemble child. Leaf, worker, Side, and managed Flow lanes work directly and must not delegate. Never use hidden in-process subagents.',
|
|
222
241
|
].join(' ')
|
|
223
242
|
|
|
224
243
|
export function buildCodexPrompt({ text, terminalRolePrompt, rolePrompt, roomContext, firstTurn = false, promptIndex = 0, forceFullReminder = false }) {
|
|
@@ -415,6 +434,8 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
415
434
|
let appServer = null
|
|
416
435
|
let appServerThreadReady = false
|
|
417
436
|
let activeTurnId = null
|
|
437
|
+
let compactStartWaiter = null
|
|
438
|
+
let compactActive = false
|
|
418
439
|
// App Server notifications are asynchronous to turn/completed. In production a
|
|
419
440
|
// stopped turn emitted its durable `aborted` boundary, then delivered a queued
|
|
420
441
|
// item/started 89ms later and a Bash result 159.9s after that. Without a native
|
|
@@ -442,7 +463,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
442
463
|
usageSnapshotForSession: (id) => {
|
|
443
464
|
const snapshot = readCodexThreadUsage(id)
|
|
444
465
|
if (snapshot) latestUsageSnapshot = snapshot
|
|
445
|
-
return snapshot
|
|
466
|
+
return snapshot || latestUsageSnapshot
|
|
446
467
|
},
|
|
447
468
|
})
|
|
448
469
|
|
|
@@ -499,9 +520,29 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
499
520
|
}
|
|
500
521
|
|
|
501
522
|
function appServerNotification(method, params = {}) {
|
|
523
|
+
if (method === 'thread/tokenUsage/updated') {
|
|
524
|
+
if (params.threadId && sessionId && String(params.threadId) !== String(sessionId)) return
|
|
525
|
+
const snapshot = codexUsageSnapshotFromAppServer(params.tokenUsage)
|
|
526
|
+
if (!snapshot) return
|
|
527
|
+
latestUsageSnapshot = snapshot
|
|
528
|
+
// Ordinary turns publish one settled meter at turn/completed. Native
|
|
529
|
+
// compaction is not an ordinary sendTurn, so this notification is its only
|
|
530
|
+
// immediate post-summary meter and must reach every connected room client.
|
|
531
|
+
if (compactActive) {
|
|
532
|
+
const { used, max } = snapshot.context
|
|
533
|
+
relayEvent({ kind: 'usage', ctx: { used, max, pct: Math.min(100, Math.max(0, Math.round((used / max) * 100))), over: used > max, model: activeModel } })
|
|
534
|
+
}
|
|
535
|
+
return
|
|
536
|
+
}
|
|
502
537
|
if (method === 'turn/started' && params.turn?.id) {
|
|
503
538
|
const turnId = String(params.turn.id)
|
|
504
539
|
if (ended || aborted) { closeAppServerTurn(turnId); return }
|
|
540
|
+
if (compactStartWaiter) {
|
|
541
|
+
const waiter = compactStartWaiter
|
|
542
|
+
compactStartWaiter = null
|
|
543
|
+
waiter.resolve(turnId)
|
|
544
|
+
return
|
|
545
|
+
}
|
|
505
546
|
if (!activeTurnId) activeTurnId = turnId
|
|
506
547
|
touchActivity()
|
|
507
548
|
return
|
|
@@ -996,7 +1037,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
996
1037
|
return {
|
|
997
1038
|
get sessionId() { return sessionId },
|
|
998
1039
|
get usageSnapshot() { return latestUsageSnapshot },
|
|
999
|
-
get turnActive() { return turnActive },
|
|
1040
|
+
get turnActive() { return turnActive || compactActive },
|
|
1000
1041
|
get canSteer() { return !!(appServer && appServer.alive !== false && appServerThreadReady && !appServerDisabled) },
|
|
1001
1042
|
get started() { return turnNo > 0 || turnActive },
|
|
1002
1043
|
sendTurn(text, options = {}) {
|
|
@@ -1128,13 +1169,32 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1128
1169
|
return true
|
|
1129
1170
|
},
|
|
1130
1171
|
async compactContext() {
|
|
1131
|
-
if (turnActive || !await ensureAppServer()) return false
|
|
1172
|
+
if (turnActive || compactActive || !await ensureAppServer()) return false
|
|
1173
|
+
compactActive = true
|
|
1174
|
+
let startTimer = null
|
|
1132
1175
|
try {
|
|
1176
|
+
const compactTurn = new Promise((resolve, reject) => {
|
|
1177
|
+
startTimer = setTimeout(() => {
|
|
1178
|
+
compactStartWaiter = null
|
|
1179
|
+
reject(new Error('Codex did not start the compaction turn'))
|
|
1180
|
+
}, 5000)
|
|
1181
|
+
compactStartWaiter = { resolve, reject }
|
|
1182
|
+
})
|
|
1133
1183
|
await appServer.compact({ threadId: sessionId })
|
|
1134
|
-
|
|
1184
|
+
const turnId = await compactTurn
|
|
1185
|
+
if (startTimer) clearTimeout(startTimer)
|
|
1186
|
+
const completed = await appServer.waitForTurn(turnId)
|
|
1187
|
+
closeAppServerTurn(completed?.turn?.id || turnId)
|
|
1188
|
+
const status = completed?.turn?.status
|
|
1189
|
+
if (status === 'completed') return true
|
|
1190
|
+
throw new Error(completed?.turn?.error?.message || `compaction ${status || 'failed'}`)
|
|
1135
1191
|
} catch (error) {
|
|
1136
1192
|
note(`Native Codex compaction unavailable; using bounded recap fallback: ${error?.message || error}`)
|
|
1137
1193
|
return false
|
|
1194
|
+
} finally {
|
|
1195
|
+
if (startTimer) clearTimeout(startTimer)
|
|
1196
|
+
compactStartWaiter = null
|
|
1197
|
+
compactActive = false
|
|
1138
1198
|
}
|
|
1139
1199
|
},
|
|
1140
1200
|
async accountUsage() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
3
|
+
"bundleVersion": 17,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
6
6
|
"id": "room-coordination",
|
|
@@ -41,18 +41,18 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"id": "work-routing",
|
|
44
|
-
"version":
|
|
44
|
+
"version": 7,
|
|
45
45
|
"providerRoutingContract": "A Claude lane may select a connected Anthropic-compatible provider by durable id, unique display name, or unique configured model. Unknown or ambiguous references fail closed and must never fall through to built-in Claude.",
|
|
46
46
|
"openerParityContract": "Every top-level terminal may use spawn_terminal or open_main_terminal to open every supported structured runtime: Claude, Codex, or Hermes. Runtime-specific provider/model validation remains authoritative and occurs before inference.",
|
|
47
47
|
"routes": [
|
|
48
48
|
{
|
|
49
49
|
"id": "work-routing",
|
|
50
50
|
"tools": ["spawn_terminal", "open_main_terminal", "close_terminal"],
|
|
51
|
-
"trigger": "\\b(parallel|delegate|worker|sub[- ]?terminal|conductor|cascade|spawn_terminal|open_main_terminal|close_terminal)\\b",
|
|
52
|
-
"prompt": "
|
|
51
|
+
"trigger": "\\b(?:open|launch|start|spawn|create)\\b.{0,40}\\b(?:new|separate|independent|main|top[- ]?level)?\\s*terminals?\\b|\\b(?:new|separate|independent|main|top[- ]?level)\\s+terminals?\\b|\\b(parallel|delegate|worker|sub[- ]?terminal|ensemble|conductor|cascade|spawn_terminal|open_main_terminal|close_terminal)\\b",
|
|
52
|
+
"prompt": "A person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal, even for ordinary work or a handoff and even when they do not say Cascade or conductor. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. For genuinely decomposable work in a conductor-capable role, collect and verify each worker result with one targeted read_terminal call after completion, then close_terminal immediately. Do not poll workers or repeat the ROOM NOW roster. Review slices are structurally read-only and must use the review slice type; they never inherit autonomous bypass authority."
|
|
53
53
|
}
|
|
54
54
|
],
|
|
55
|
-
"expandedPrompt": "CASCADE WORKFLOW (default for non-trivial work that genuinely decomposes; mandatory when the people ask for Cascade, tiered lanes, one-shot flow, or unattended multi-slice execution): first investigate enough to name evidence-backed slices and their dependencies, then post a short plan in the room. The current top-level/conductor terminal keeps decomposition, integration, and final judgment; use spawn_terminal only for bounded worker slices
|
|
55
|
+
"expandedPrompt": "MAIN TERMINAL ROUTING (authoritative): a person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal, even for ordinary work or a handoff and even when they do not say Cascade or conductor. If the wording includes new or separate terminal, main-terminal routing wins. spawn_terminal is only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice. CASCADE WORKFLOW (default for non-trivial work that genuinely decomposes; mandatory when the people ask for Cascade, tiered lanes, one-shot flow, or unattended multi-slice execution): first investigate enough to name evidence-backed slices and their dependencies, then post a short plan in the room. The current top-level/conductor terminal keeps decomposition, integration, and final judgment; use spawn_terminal only for bounded worker slices. Parallelize only disjoint slices and encode dependencies instead of holding them in your head. Choose sliceType=scaffold for mechanical/search work, feature or fix for implementation, and review with a balanced capable tier for adversarial verification. Review slices are structurally read-only: they resolve to a native review/Plan mode before launch and can never inherit autonomous bypass authority. Every worker brief names the evidence/diagnosis, exact scope, observable acceptance proof, relevant repo gates, whether merge/publish is required, and a safe-skip escape hatch. ROOM NOW is the default roster and completion signals wake the conductor; do not spend the bounded read allowance polling or repeating a no-argument roster. After dispatch, remain responsible: once an owned worker is finished, make one targeted read_terminal call to collect its result, verify its claim, close_terminal immediately, and dispatch newly unblocked work. Preserve enough targeted reads for every open worker and the final adversarial review; never declare the Cascade done or yield a final result while owned workers remain uncollected. Check origin/main CI before merge-bearing waves, coordinate shared fixtures/publishers, and treat sibling pushes as context rather than proof. Run an adversarial review after builders (or pipeline review behind completed phases), finish with production-condition evidence, verify every reported SHA/version, and close every worker you opened.",
|
|
56
56
|
"impact": [
|
|
57
57
|
{"path": "bridge/bridge.mjs", "diffPattern": "spawn_terminal|open_main_terminal|close_terminal|cascadeRole|spawnDepth"},
|
|
58
58
|
{"path": "bridge/lane-lifecycle.mjs"},
|
|
@@ -143,7 +143,7 @@ export function buildTerminalRolePrompt({ spawnedBy = null, spawnDepth = 0, casc
|
|
|
143
143
|
return `TERMINAL ROLE (authoritative): You are a MISCLASSIFIED ENSEMBLE SUB-TERMINAL at spawn depth ${Math.max(1, depth)}, owned by parent terminal ${parentRef}. A spawned lane can never be a Cascade conductor. Work directly as a worker, do not spawn terminals, and report that the parent must use open_main_terminal for a separate conductor.`
|
|
144
144
|
}
|
|
145
145
|
if (!spawnedBy && depth === 0) {
|
|
146
|
-
return 'TERMINAL ROLE (authoritative): You are an independent TOP-LEVEL ThinkPool terminal opened directly in the room. You are not a spawned sub-terminal or worker lane. You may conduct the current task here and open worker SUB-TERMINALS with spawn_terminal.
|
|
146
|
+
return 'TERMINAL ROLE (authoritative): You are an independent TOP-LEVEL ThinkPool terminal opened directly in the room. You are not a spawned sub-terminal or worker lane. You may conduct the current task here and open worker SUB-TERMINALS with spawn_terminal. A person-authored request to open, launch, start, spawn, or create a new, separate, independent, top-level, or main terminal uses open_main_terminal — never spawn_terminal — even for ordinary work or a handoff. If the wording includes new or separate terminal, main-terminal routing wins. Use spawn_terminal only for agent-decided bounded worker slices or when the person explicitly asks for a worker, sub-terminal, Ensemble lane, or delegated slice.'
|
|
147
147
|
}
|
|
148
148
|
if (depth >= 2) {
|
|
149
149
|
return `TERMINAL ROLE (authoritative): You are a LEAF SUB-TERMINAL at spawn depth ${depth}, owned by parent terminal ${parentRef}. You are not top-level and not a conductor. Complete the assigned slice directly, do not spawn another terminal, and hand evidence/results back to your parent.`
|