thinkpool-pair 0.7.332 → 0.7.333
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 +8 -3
- package/cross-terminal.mjs +30 -2
- package/package.json +1 -1
- package/review-check.mjs +15 -0
package/bridge.mjs
CHANGED
|
@@ -92,7 +92,7 @@ function stopFlowPreviews (flowId, laneId = null) {
|
|
|
92
92
|
}
|
|
93
93
|
import { FLOW_REVIEWER_PROMPT, FLOW_CODEX_REVIEWER_PROMPT, revertLane, parseReviewVerdict, reviewVerdictToReflection } from './flow-review.mjs'
|
|
94
94
|
import { reviewGateDecision } from './flow-review-gate.mjs'
|
|
95
|
-
import { readReviewFile, runReviewCheck } from './review-check.mjs'
|
|
95
|
+
import { IMMUTABLE_REVIEW_TOOL_EXTRAS, readReviewFile, runReviewCheck } from './review-check.mjs'
|
|
96
96
|
import { pairAdjudicationPrompt, reviewReflectionDecision, REVIEW_DEFAULTS } from './flow-review-reflect.mjs'
|
|
97
97
|
import { mergeWorktrees, inlineSingleHtml, initRepo } from './flow-assembly.mjs'
|
|
98
98
|
import { canDispatch, FLOW_LIMITS, makeBudget, recordSpend, killSwitchEnv } from './flow-budget.mjs'
|
|
@@ -117,7 +117,7 @@ const flowRedispatch = new Map()
|
|
|
117
117
|
// wave BEFORE overrun. Lives bridge-side because waves dispatch across separate
|
|
118
118
|
// broadcasts; without persistent state the cap can never bite.
|
|
119
119
|
const flowBudgets = new Map()
|
|
120
|
-
import { formatPeek, PEEK,
|
|
120
|
+
import { formatPeek, PEEK, readTerminalTurnBudgetDecision, siblingsOf, resolveSibling, crossPostDecision, CROSSPOST, spawnDecision, SPAWN, CROSSROOM, CROSSROOM_BUS, formatPairRoster, crossRoomPostDecision, formatRoomNow, formatClosableHint, closeLaneDecision, laneBusyOf, laneStatusOf, settleLaneBusy, nativeClaudeFallbackHint, buildDispatchPreview, authorizeDirectDispatch } from './cross-terminal.mjs'
|
|
121
121
|
import { createStandalonePairResponder, standalonePairIdentity } from './direct-pair-room.mjs'
|
|
122
122
|
import { dispatchPendingRecap, recoverInterruptedTurn, recoverMissingResumeOnce, sendInterruptedContinue, shouldAttemptNativeResume, supersedeInterruptedResume } from './interrupted-resume.mjs'
|
|
123
123
|
import { supersedeDispatchLease } from './dispatch-lease.mjs'
|
|
@@ -2596,13 +2596,16 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2596
2596
|
if (args?.terminal && !target) {
|
|
2597
2597
|
return { content: [{ type: 'text', text: formatPeek({ selfId: id, sessions, terms, names: termNames, terminal: args.terminal, lines: args?.lines }) }] }
|
|
2598
2598
|
}
|
|
2599
|
-
const budget =
|
|
2599
|
+
const budget = readTerminalTurnBudgetDecision({
|
|
2600
2600
|
targeted: !!args?.terminal,
|
|
2601
2601
|
targetedCount: entry.peekCount,
|
|
2602
2602
|
rosterCount: entry.peekRosterCount,
|
|
2603
|
+
budgetTurnRev: entry.peekBudgetTurnRev,
|
|
2604
|
+
currentTurnRev: entry._turnRev,
|
|
2603
2605
|
})
|
|
2604
2606
|
entry.peekCount = budget.targetedCount
|
|
2605
2607
|
entry.peekRosterCount = budget.rosterCount
|
|
2608
|
+
entry.peekBudgetTurnRev = budget.budgetTurnRev
|
|
2606
2609
|
if (!budget.ok) return { content: [{ type: 'text', text: budget.reason }] }
|
|
2607
2610
|
const targetEntry = target?.kind === 'agent' ? sessions.get(target.id) : null
|
|
2608
2611
|
const text = formatPeek({ selfId: id, sessions, terms, names: termNames, terminal: args?.terminal, lines: args?.lines })
|
|
@@ -3029,11 +3032,13 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
3029
3032
|
'Read one regular file from the declared immutable review target. This always reads the target’s pinned Git object, never a live worktree; target and path are validated and the response is byte-bounded.',
|
|
3030
3033
|
{ target: z.string(), path: z.string() },
|
|
3031
3034
|
async (args) => ({ content: [{ type: 'text', text: JSON.stringify(await readReviewFile({ target: args?.target, filePath: args?.path, snapshots: entry.flowReviewSnapshots })) }] }),
|
|
3035
|
+
IMMUTABLE_REVIEW_TOOL_EXTRAS,
|
|
3032
3036
|
), tool(
|
|
3033
3037
|
'run_review_check',
|
|
3034
3038
|
'Run one fixed verification check against an immutable git-archived review target. This accepts only the declared target and a fixed check id; it never accepts a shell command, cwd, environment, executable, or arguments. The check runs in a scratch archive, never in a builder worktree.',
|
|
3035
3039
|
{ target: z.string(), checkId: z.string() },
|
|
3036
3040
|
async (args) => ({ content: [{ type: 'text', text: JSON.stringify(await runReviewCheck({ target: args?.target, checkId: args?.checkId, snapshots: entry.flowReviewSnapshots })) }] }),
|
|
3041
|
+
IMMUTABLE_REVIEW_TOOL_EXTRAS,
|
|
3037
3042
|
)] : []),
|
|
3038
3043
|
],
|
|
3039
3044
|
})
|
package/cross-terminal.mjs
CHANGED
|
@@ -18,7 +18,7 @@ export const PEEK = {
|
|
|
18
18
|
// explicitly when a diagnosis genuinely needs deeper sibling history.
|
|
19
19
|
defaultLines: 20,
|
|
20
20
|
maxLines: 200,
|
|
21
|
-
perTurnCap: 10, // targeted transcript reads allowed per
|
|
21
|
+
perTurnCap: 10, // targeted transcript reads allowed per accepted room turn
|
|
22
22
|
rosterPerTurnCap: 1, // no-argument roster reads use a separate allowance; ROOM NOW is default
|
|
23
23
|
lineCap: 200, // per-line truncation
|
|
24
24
|
rosterPreview: 100, // last-line preview length in the roster listing
|
|
@@ -37,7 +37,7 @@ export const readTerminalBudgetDecision = ({ targeted = false, targetedCount = 0
|
|
|
37
37
|
ok: false,
|
|
38
38
|
targetedCount: nextTargeted,
|
|
39
39
|
rosterCount: nextRoster,
|
|
40
|
-
reason: `Cross-terminal transcript read limit reached for this turn (${limits.perTurnCap}). Stop polling and continue with the worker results already collected; a
|
|
40
|
+
reason: `Cross-terminal transcript read limit reached for this turn (${limits.perTurnCap}). Stop polling and continue with the worker results already collected; a fresh accepted room turn resets the allowance.`,
|
|
41
41
|
}
|
|
42
42
|
return { ok: true, targetedCount: nextTargeted + 1, rosterCount: nextRoster }
|
|
43
43
|
}
|
|
@@ -50,6 +50,34 @@ export const readTerminalBudgetDecision = ({ targeted = false, targetedCount = 0
|
|
|
50
50
|
return { ok: true, targetedCount: nextTargeted, rosterCount: nextRoster + 1 }
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Provider/model reconstruction can deliver a fresh accepted agent turn without
|
|
54
|
+
// another browser `code-turn` frame (context-carry and interrupted-resume paths).
|
|
55
|
+
// Key read allowances to the bridge-owned turn revision as a second reset seam,
|
|
56
|
+
// so a reconstructed runtime never inherits ten reads spent by the old model.
|
|
57
|
+
// This intentionally resets only read allowances; hop/post/spawn loop breakers
|
|
58
|
+
// remain tied to real person ingress and cannot be escaped by reconstruction.
|
|
59
|
+
export const readTerminalTurnBudgetDecision = ({
|
|
60
|
+
targeted = false,
|
|
61
|
+
targetedCount = 0,
|
|
62
|
+
rosterCount = 0,
|
|
63
|
+
budgetTurnRev = null,
|
|
64
|
+
currentTurnRev = 0,
|
|
65
|
+
} = {}, limits = PEEK) => {
|
|
66
|
+
const current = Math.max(0, Number(currentTurnRev) || 0)
|
|
67
|
+
const previous = budgetTurnRev == null
|
|
68
|
+
? null
|
|
69
|
+
: Number.isFinite(Number(budgetTurnRev)) ? Number(budgetTurnRev) : null
|
|
70
|
+
const sameTurn = previous === current
|
|
71
|
+
return {
|
|
72
|
+
...readTerminalBudgetDecision({
|
|
73
|
+
targeted,
|
|
74
|
+
targetedCount: sameTurn ? targetedCount : 0,
|
|
75
|
+
rosterCount: sameTurn ? rosterCount : 0,
|
|
76
|
+
}, limits),
|
|
77
|
+
budgetTurnRev: current,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
53
81
|
// One shared lane-status classifier for every roster consumer: the bridge wire,
|
|
54
82
|
// read_terminal, and ROOM NOW. Timestamps stay absolute on the wire so clients can
|
|
55
83
|
// keep the displayed age current without a broadcast every second.
|
package/package.json
CHANGED
package/review-check.mjs
CHANGED
|
@@ -17,6 +17,21 @@ const CHECKS = Object.freeze({
|
|
|
17
17
|
})
|
|
18
18
|
const OUTPUT_CAP = 512 * 1024
|
|
19
19
|
|
|
20
|
+
// Codex's native read-only/review mode never raises approval cards. MCP tools
|
|
21
|
+
// without annotations are conservatively treated as approval-requiring, which
|
|
22
|
+
// makes `approvalPolicy: never` auto-decline even our immutable readers before
|
|
23
|
+
// their handlers run. Keep the policy next to the implementation and reuse it
|
|
24
|
+
// for both source reads and fixed scratch checks: neither mutates the reviewed
|
|
25
|
+
// target, neither reaches the open world, and repeating either is safe.
|
|
26
|
+
export const IMMUTABLE_REVIEW_TOOL_EXTRAS = Object.freeze({
|
|
27
|
+
annotations: Object.freeze({
|
|
28
|
+
readOnlyHint: true,
|
|
29
|
+
destructiveHint: false,
|
|
30
|
+
idempotentHint: true,
|
|
31
|
+
openWorldHint: false,
|
|
32
|
+
}),
|
|
33
|
+
})
|
|
34
|
+
|
|
20
35
|
export function reviewCheckCommand(checkId) {
|
|
21
36
|
if (!Object.hasOwn(CHECKS, checkId)) throw new Error('unapproved review check')
|
|
22
37
|
const [command, args] = CHECKS[checkId]
|