thinkpool-pair 0.7.281 → 0.7.283
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 +18 -16
- package/cross-terminal.mjs +4 -2
- package/flow-models.mjs +32 -0
- package/package.json +1 -1
- package/thinkpool-capabilities.json +4 -3
- package/thinkpool-room-prompt.mjs +4 -5
package/bridge.mjs
CHANGED
|
@@ -68,7 +68,7 @@ import { hostMemoryAdmission } from './host-memory.mjs'
|
|
|
68
68
|
const STRUCTURED_MODES = new Set(['default', 'acceptEdits', 'plan', 'review', 'bypassPermissions'])
|
|
69
69
|
import { FLOW_CONDUCTOR_PROMPT, FLOW_LANE_PROMPT, FLOW_CODEX_CONDUCTOR_PROMPT, FLOW_CODEX_LANE_PROMPT, buildConductorEnv, assembleCrossWaveContext, buildLanePrompt } from './flow-conductor.mjs'
|
|
70
70
|
import { legacyBuilderCompletionAllowed, normalizePlanOutput, validatePlanForRuntime, validReviewTargetShape } from './flow-task-graph.mjs'
|
|
71
|
-
import { normalizeFlowRuntime, flowLaneModelFor, flowConductorModelFor, spawnedLaneModelFor, modelCatalogValues, resolveCodexModel, assertRuntimeModelCompatible } from './flow-models.mjs'
|
|
71
|
+
import { normalizeFlowRuntime, flowLaneModelFor, flowConductorModelFor, spawnedLaneModelFor, modelCatalogValues, resolveCodexModel, resolveHermesOpenModel, assertRuntimeModelCompatible } from './flow-models.mjs'
|
|
72
72
|
// S1 (context-offload) — durable digest store. mark_flow_done digests a closed slice in;
|
|
73
73
|
// the dispatch loop reads the bounded cross-wave context back out (via assembleCrossWaveContext,
|
|
74
74
|
// which enforces the CEILING). Consume the store — the internals live in flow-context-store.mjs.
|
|
@@ -2079,15 +2079,17 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2079
2079
|
const runtime = args?.runtime || entry.runtime || 'claude'
|
|
2080
2080
|
if (runtime === 'codex' && args?.provider) return { error: 'A Codex terminal uses its Codex/OpenAI login in v1; custom bridge providers are not wired to Codex yet. Omit provider or open a Claude terminal for that provider.' }
|
|
2081
2081
|
if (runtime === 'hermes' && args?.provider) return { error: 'A Hermes terminal uses its isolated Hermes profile and ACP model catalog; bridge provider IDs do not apply. Omit provider and choose an exact Hermes model ID.' }
|
|
2082
|
-
|
|
2083
|
-
|
|
2082
|
+
const hermesCatalog = entry.runtime === 'hermes' && entry.models?.length
|
|
2083
|
+
? entry.models
|
|
2084
|
+
: [...sessions.values()].flatMap((session) => session.runtime === 'hermes' ? (session.models || []) : [])
|
|
2085
|
+
const catalog = runtime === 'codex' ? readCodexModels() : runtime === 'hermes' ? hermesCatalog : []
|
|
2084
2086
|
if (runtime === 'codex' && args?.model && !modelCatalogValues(catalog).has(args.model)) {
|
|
2085
2087
|
return { error: `Could not open a Codex terminal on ${JSON.stringify(args.model)} — that model is not in this host's visible Codex catalog.` }
|
|
2086
2088
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
}
|
|
2089
|
+
const hermesModel = runtime === 'hermes'
|
|
2090
|
+
? resolveHermesOpenModel(args?.model, catalog, { strictCatalog: entry.runtime === 'hermes' })
|
|
2091
|
+
: null
|
|
2092
|
+
if (hermesModel && !hermesModel.ok) return { error: hermesModel.error }
|
|
2091
2093
|
const provider = runtime === 'claude' && args?.provider ? resolveProviderRef(args.provider) : args?.provider
|
|
2092
2094
|
if (runtime === 'claude' && args?.provider && !provider) {
|
|
2093
2095
|
const choices = registeredProviderChoices()
|
|
@@ -2098,7 +2100,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2098
2100
|
}
|
|
2099
2101
|
return {
|
|
2100
2102
|
runtime,
|
|
2101
|
-
model: args?.model || (worker && args?.sliceType
|
|
2103
|
+
model: hermesModel?.model || args?.model || (worker && args?.sliceType
|
|
2102
2104
|
? spawnedLaneModelFor({ sliceType: args.sliceType, runtime, catalog })
|
|
2103
2105
|
: undefined),
|
|
2104
2106
|
// Worker lanes are the autonomous execution unit. They never inherit a
|
|
@@ -2359,13 +2361,13 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2359
2361
|
// conductor mains, spawned workers, Side lanes, and Flow lanes cannot use it.
|
|
2360
2362
|
...(canOpenMainConductor ? [tool(
|
|
2361
2363
|
'open_main_terminal',
|
|
2362
|
-
'Open a separate independent MAIN terminal to conduct a Cascade. Use this whenever a person explicitly asks to open, launch, start, or spawn a new Cascade/conductor terminal. 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 conductor its full initial task; it will use spawn_terminal for its workers.',
|
|
2364
|
+
'Open a separate independent MAIN terminal to conduct a Cascade on any supported runtime: Claude (including a connected Anthropic-compatible BYOK provider), Codex, or Hermes. Use this whenever a person explicitly asks to open, launch, start, or spawn a new Cascade/conductor terminal. 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 conductor its full initial task; it will use spawn_terminal for its workers.',
|
|
2363
2365
|
{
|
|
2364
2366
|
name: z.string().max(80).optional().describe('short label for the main conductor, e.g. "Cascade · Multi-device chaos"'),
|
|
2365
2367
|
task: z.string().min(1).describe('complete initial Cascade brief for the conductor'),
|
|
2366
|
-
model: z.string().optional().describe('optional conductor model, e.g. opus / gpt-5.6-sol'),
|
|
2367
|
-
runtime: z.enum(['claude', 'codex']).optional().describe('agent runtime;
|
|
2368
|
-
provider: z.string().optional().describe(
|
|
2368
|
+
model: z.string().optional().describe('optional conductor model, e.g. opus / gpt-5.6-sol / nous:z-ai/glm-5.2'),
|
|
2369
|
+
runtime: z.enum(['claude', 'codex', 'hermes']).optional().describe('agent runtime; every top-level terminal can open every supported runtime, defaulting to its own'),
|
|
2370
|
+
provider: z.string().optional().describe(`optional registered Claude-compatible provider name or id; omit for built-in Anthropic${registeredProviderChoices() ? `. Available now: ${registeredProviderChoices()}` : ''}`),
|
|
2369
2371
|
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional().describe('permission mode; defaults to inheriting this main terminal, except plan falls back to default'),
|
|
2370
2372
|
},
|
|
2371
2373
|
async (args) => {
|
|
@@ -2416,13 +2418,13 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2416
2418
|
// not receive this tool, so the hierarchy is capability-enforced.
|
|
2417
2419
|
...(canSpawnWorkers ? [tool(
|
|
2418
2420
|
'spawn_terminal',
|
|
2419
|
-
'Open a visible WORKER SUB-TERMINAL (
|
|
2421
|
+
'Open a visible WORKER SUB-TERMINAL on any supported runtime—Claude (including a connected Anthropic-compatible BYOK provider), Codex, or Hermes—from any top-level terminal. spawn_terminal is worker-only: it can never create a Cascade conductor or main terminal. For ordinary workers use an appropriate non-Sol tier: gpt-5.6-luna or gpt-5.4-mini for scaffold/search, gpt-5.6-terra for feature/fix, and a balanced tier for adversarial review; never select Sol for routine workers. A Hermes sliceType=review lane is structurally reviewer-scoped before ACP startup: it can inspect files and use the bridge review-check tool, but has no terminal, write, patch, delegate, browser, skill, memory, or session-history schema. Pass sliceType=scaffold for mechanical work, feature/fix for builders, and review for adversarial verification. Give the worker a bounded initial task, collect its result with read_terminal, then ALWAYS close_terminal it.',
|
|
2420
2422
|
{
|
|
2421
2423
|
name: z.string().max(80).optional().describe('a short label for the new lane so the room (and you) can find it, e.g. "Research: topologies"'),
|
|
2422
2424
|
task: z.string().optional().describe('an initial task to hand the new lane immediately; omit to open it idle'),
|
|
2423
|
-
model: z.string().optional().describe('model for the chosen runtime; Hermes
|
|
2425
|
+
model: z.string().optional().describe('model for the chosen runtime; Hermes accepts its profile default or an ACP ID such as nous:z-ai/glm-5.2'),
|
|
2424
2426
|
sliceType: z.enum(['scaffold', 'feature', 'fix', 'review']).optional().describe('optional cascade slice tier; omitted preserves normal inheritance/default, explicit model wins'),
|
|
2425
|
-
runtime: z.enum(['claude', 'codex', 'hermes']).optional().describe('agent runtime for the new lane;
|
|
2427
|
+
runtime: z.enum(['claude', 'codex', 'hermes']).optional().describe('agent runtime for the new lane; every top-level terminal can open every supported runtime'),
|
|
2426
2428
|
provider: z.string().optional().describe(`optional registered Claude-compatible provider name or id; omit for built-in Anthropic${registeredProviderChoices() ? `. Available now: ${registeredProviderChoices()}` : ''}`),
|
|
2427
2429
|
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional().describe('accepted for compatibility; spawned workers always run autonomously in bypassPermissions'),
|
|
2428
2430
|
},
|
|
@@ -2675,7 +2677,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2675
2677
|
cascadeRole: entry.cascadeRole,
|
|
2676
2678
|
flowRole: entry.flowRole,
|
|
2677
2679
|
sideParent: entry.sideParent,
|
|
2678
|
-
}),
|
|
2680
|
+
}), canSpawnWorkers ? HERMES_VISIBLE_WORKER_FALLBACK_RULE : ''].filter(Boolean).join('\n\n')
|
|
2679
2681
|
entry.session = startStructuredSession(runtime, {
|
|
2680
2682
|
// laneModel, NOT the raw `model` param: the SDK's `model` option OVERRIDES the
|
|
2681
2683
|
// ANTHROPIC_MODEL supplied by resolveProviderEnv() in `env` below, so an inherited
|
package/cross-terminal.mjs
CHANGED
|
@@ -431,7 +431,7 @@ export const spawnDecision = ({ hop = 0, spawnTimes = [], now = 0, spawnedLive =
|
|
|
431
431
|
// host path, or mutable process object is copied into the preview.
|
|
432
432
|
const DISPATCH_ARG_KEYS = Object.freeze(['mode', 'model', 'name', 'provider', 'runtime', 'sliceType', 'task'])
|
|
433
433
|
const DISPATCH_MODES = new Set(['default', 'acceptEdits', 'bypassPermissions', 'plan'])
|
|
434
|
-
const DISPATCH_RUNTIMES = new Set(['claude', 'codex'])
|
|
434
|
+
const DISPATCH_RUNTIMES = new Set(['claude', 'codex', 'hermes'])
|
|
435
435
|
const DISPATCH_SLICES = new Set(['scaffold', 'feature', 'fix', 'review'])
|
|
436
436
|
const secretValue = /((?<![a-z0-9])sk-[a-z0-9_-]{8,}|(?<![a-z0-9])gsk_[a-z0-9_-]{8,}|(?<![a-z0-9])AIza[a-z0-9_-]{8,}|bearer\s+[a-z0-9._-]{8,})/ig
|
|
437
437
|
const hostPath = /(?:\/home\/|\/users\/|\/private\/|\/tmp\/|[a-z]:\\|\\\\)/ig
|
|
@@ -487,7 +487,9 @@ export function buildDispatchPreview ({ args, initiatorTerminalId, roomCode, bri
|
|
|
487
487
|
// durable resolution/delivery-attempt events.
|
|
488
488
|
const authority = Object.freeze({ initiatorTerminalId: String(initiatorTerminalId), roomCode: String(roomCode).toUpperCase(), bridgeAuthorityId: String(bridgeAuthorityId), localAuthorityId: String(localAuthorityId) })
|
|
489
489
|
const fingerprint = sha256({ authority, exactArgs, caps })
|
|
490
|
-
const provider = exactArgs.provider || (exactArgs.runtime === 'codex'
|
|
490
|
+
const provider = exactArgs.provider || (exactArgs.runtime === 'codex'
|
|
491
|
+
? 'OpenAI / Codex login'
|
|
492
|
+
: exactArgs.runtime === 'hermes' ? 'Hermes ACP profile' : 'Anthropic / host default')
|
|
491
493
|
const model = exactArgs.model || context.resolvedModel || 'runtime default'
|
|
492
494
|
const purpose = safeCopy(exactArgs.name || exactArgs.task?.split(/\r?\n/)[0] || 'Open one worker lane', 120)
|
|
493
495
|
return Object.freeze({
|
package/flow-models.mjs
CHANGED
|
@@ -24,6 +24,38 @@ export function modelCatalogValues(catalog = []) {
|
|
|
24
24
|
)).filter(Boolean))
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const exactHermesModelId = (value) => /^[A-Za-z0-9._-]+:[A-Za-z0-9._-]+\/[A-Za-z0-9._:/-]+$/.test(value)
|
|
28
|
+
|
|
29
|
+
const expandHermesModelAlias = (value) => {
|
|
30
|
+
if (/^glm[-_.]/i.test(value)) return `nous:z-ai/${value.toLowerCase()}`
|
|
31
|
+
const shorthand = value.match(/^([^:/]+):([^/]+)$/)
|
|
32
|
+
if (!shorthand) return value
|
|
33
|
+
const provider = shorthand[1].toLowerCase() === 'zai' ? 'z-ai' : shorthand[1].toLowerCase()
|
|
34
|
+
return `nous:${provider}/${shorthand[2]}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a Hermes model for any bridge opener. A Hermes parent has a live ACP
|
|
39
|
+
* catalog and remains strict. Other runtimes may use a full id or unambiguous
|
|
40
|
+
* shorthand; the child session acknowledges session/set_model before inference.
|
|
41
|
+
* Omitting a model intentionally selects the isolated Hermes profile default.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveHermesOpenModel(model, catalog = [], { strictCatalog = false } = {}) {
|
|
44
|
+
const requested = String(model || '').trim()
|
|
45
|
+
if (!requested) return { ok: true, model: undefined, validatedBy: 'default' }
|
|
46
|
+
const values = [...modelCatalogValues(catalog)]
|
|
47
|
+
if (values.includes(requested)) return { ok: true, model: requested, validatedBy: 'catalog' }
|
|
48
|
+
|
|
49
|
+
const expanded = expandHermesModelAlias(requested)
|
|
50
|
+
const normalized = (value) => String(value).toLowerCase().replace(/[^a-z0-9]+/g, '')
|
|
51
|
+
const requestedKey = normalized(expanded).replace(/^nous/, '')
|
|
52
|
+
const aliases = values.filter((value) => normalized(value).replace(/^nous/, '') === requestedKey)
|
|
53
|
+
if (aliases.length === 1) return { ok: true, model: aliases[0], validatedBy: 'catalog' }
|
|
54
|
+
if (strictCatalog && values.length) return { ok: false, error: `Could not open a Hermes terminal on ${JSON.stringify(requested)} — that model is not in this session's ACP catalog.` }
|
|
55
|
+
if (!exactHermesModelId(expanded)) return { ok: false, error: `Could not resolve Hermes model ${JSON.stringify(requested)}. Use a full ACP model ID such as "nous:z-ai/glm-5.2".` }
|
|
56
|
+
return { ok: true, model: expanded, validatedBy: 'child-runtime' }
|
|
57
|
+
}
|
|
58
|
+
|
|
27
59
|
function firstVisible(candidates, catalog) {
|
|
28
60
|
const visible = modelCatalogValues(catalog)
|
|
29
61
|
for (const candidate of candidates) if (visible.has(candidate)) return candidate
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
3
|
+
"bundleVersion": 8,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
6
6
|
"id": "room-coordination",
|
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"id": "work-routing",
|
|
44
|
-
"version":
|
|
45
|
-
"providerRoutingContract": "A Claude
|
|
44
|
+
"version": 4,
|
|
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
|
+
"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.",
|
|
46
47
|
"routes": [
|
|
47
48
|
{
|
|
48
49
|
"id": "work-routing",
|
|
@@ -152,8 +152,7 @@ export const CODEX_THINKPOOL_FIRST_TURN_PREAMBLE = [
|
|
|
152
152
|
...THINKPOOL_REMOTE_DELIVERY_RULES,
|
|
153
153
|
].join(' ')
|
|
154
154
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
export const HERMES_VISIBLE_WORKER_FALLBACK_RULE = 'HERMES VISIBLE-WORKER FALLBACK: spawn_terminal may open a visible Hermes worker only from a top-level Hermes terminal and only with an explicit exact model ID advertised in this session\'s ACP catalog. If a native Claude Code worker reports an account/provider authentication or subscription-access failure, treat the entire native Claude provider as unavailable for the rest of the task—do not retry Sonnet, Opus, or Haiku aliases on it. For a manual Claude-family review fallback, use runtime="hermes", the exact Nous Claude model suggested by read_terminal, mode="default", and sliceType="review". This fallback is permission-gated, not structurally read-only: instruct it not to edit, stop/deny any write request, and never call it Flow/reviewer safety. Never use hidden Hermes delegate_task.'
|
|
155
|
+
// Every top-level main can open every supported runtime. Hermes model selection
|
|
156
|
+
// is acknowledged by the child ACP session before inference, so it is safe for a
|
|
157
|
+
// Claude or Codex parent to request a visible Hermes lane.
|
|
158
|
+
export const HERMES_VISIBLE_WORKER_FALLBACK_RULE = 'UNIVERSAL OPENER ROUTING: a top-level terminal of any runtime may use spawn_terminal or open_main_terminal to open Claude (including a connected Anthropic-compatible BYOK provider), Codex, or Hermes. For Hermes, omit model to use the isolated profile default or pass an ACP ID such as model="nous:z-ai/glm-5.2"; common GLM shorthand is normalized and the child acknowledges the selected model before inference. If a native Claude Code worker reports an account/provider authentication or subscription-access failure, treat the entire native Claude provider as unavailable for the rest of the task—do not retry Sonnet, Opus, or Haiku aliases on it. For a manual Claude-family review fallback, use runtime="hermes", the exact Nous Claude model suggested by read_terminal, mode="default", and sliceType="review". This fallback is permission-gated, not structurally read-only: instruct it not to edit, stop/deny any write request, and never call it Flow/reviewer safety. Never use hidden Hermes delegate_task.'
|