thinkpool-pair 0.7.99 → 0.7.101
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 +4 -1
- package/flow-assembly.mjs +16 -3
- package/flow-conductor.mjs +2 -0
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -1839,7 +1839,10 @@ flowChannel
|
|
|
1839
1839
|
const laneId = randomUUID()
|
|
1840
1840
|
termNames[laneId] = `Flow · ${t.task_key}`
|
|
1841
1841
|
saveNames(room, termNames)
|
|
1842
|
-
|
|
1842
|
+
// Lanes build autonomously in their own worktree — bypassPermissions so they
|
|
1843
|
+
// don't stall on a card for every write/bash (matches the user's expectation that
|
|
1844
|
+
// a Flow summoned from a bypass terminal runs hands-off).
|
|
1845
|
+
openStructured({ id: laneId, cwd: dir, mode: 'bypassPermissions', rolePrompt: isReview ? FLOW_REVIEWER_PROMPT : FLOW_LANE_PROMPT, flowSessionId: payload.flowId, flowTaskKey: t.task_key, spawnedBy: `flow:${payload.flowId}` })
|
|
1843
1846
|
const le = sessions.get(laneId)
|
|
1844
1847
|
if (le?.session) {
|
|
1845
1848
|
const spec =
|
package/flow-assembly.mjs
CHANGED
|
@@ -42,7 +42,18 @@ export function inlineSingleHtml ({ htmlPath, root, readFile = fsReadFile }) {
|
|
|
42
42
|
let html = String(readFile(htmlPath, 'utf8'))
|
|
43
43
|
|
|
44
44
|
const isLocal = (ref) => ref && !/^[a-z]+:\/\//i.test(ref) && !ref.startsWith('//')
|
|
45
|
-
|
|
45
|
+
// CONTAIN to baseDir — a lane-authored index.html could reference `../../etc/passwd`
|
|
46
|
+
// or an absolute path; without this the bridge would read arbitrary host files and
|
|
47
|
+
// inline them into the served preview (path traversal). Mirrors resolveUnderRoot in
|
|
48
|
+
// flow-preview.mjs: resolve, then reject anything that escapes baseDir. Returns null on
|
|
49
|
+
// an out-of-root / absolute ref so callers leave the tag untouched (never read it).
|
|
50
|
+
const resolve = (ref) => {
|
|
51
|
+
if (!ref || path.isAbsolute(ref)) return null
|
|
52
|
+
const abs = path.resolve(baseDir, ref.replace(/^\.\//, ''))
|
|
53
|
+
const rel = path.relative(baseDir, abs)
|
|
54
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) return rel === '' ? abs : null
|
|
55
|
+
return abs
|
|
56
|
+
}
|
|
46
57
|
|
|
47
58
|
// Collected module-script bodies (import/export users) get merged into one block.
|
|
48
59
|
const moduleBodies = []
|
|
@@ -53,7 +64,8 @@ export function inlineSingleHtml ({ htmlPath, root, readFile = fsReadFile }) {
|
|
|
53
64
|
(tag) => {
|
|
54
65
|
const m = tag.match(/\bhref=["']([^"']+)["']/i)
|
|
55
66
|
if (!m || !isLocal(m[1])) return tag
|
|
56
|
-
const
|
|
67
|
+
const p = resolve(m[1]); if (!p) return tag // out-of-root ref → leave the tag as-is
|
|
68
|
+
const css = String(readFile(p, 'utf8'))
|
|
57
69
|
return `<style>\n${css}\n</style>`
|
|
58
70
|
}
|
|
59
71
|
)
|
|
@@ -64,9 +76,10 @@ export function inlineSingleHtml ({ htmlPath, root, readFile = fsReadFile }) {
|
|
|
64
76
|
/<script\b([^>]*)\bsrc=["']([^"']+)["']([^>]*)>\s*<\/script>/gi,
|
|
65
77
|
(tag, pre, src, post) => {
|
|
66
78
|
if (!isLocal(src)) return tag
|
|
79
|
+
const p = resolve(src); if (!p) return tag // out-of-root ref → leave the tag as-is
|
|
67
80
|
const attrs = `${pre} ${post}`
|
|
68
81
|
const isModule = /\btype=["']module["']/i.test(attrs)
|
|
69
|
-
const body = String(readFile(
|
|
82
|
+
const body = String(readFile(p, 'utf8'))
|
|
70
83
|
const usesEsm = /(^|[\s;{(])(import|export)\s/.test(body)
|
|
71
84
|
if (isModule && usesEsm) {
|
|
72
85
|
moduleBodies.push(`/* inlined: ${src} */\n${body}`)
|
package/flow-conductor.mjs
CHANGED
|
@@ -24,6 +24,8 @@ export { normalizePlanOutput }
|
|
|
24
24
|
// moat, partition by file ownership, plan-in-store-not-context, right-size the slice
|
|
25
25
|
// count (3 focused beats 7 scattered), explicit acyclic deps.
|
|
26
26
|
export const FLOW_CONDUCTOR_PROMPT = [
|
|
27
|
+
'THINKPOOL FLOW CONDUCTOR — HARD RULES (read first, no exceptions): You are in PLAN MODE. Your ONLY output action is calling the ExitPlanMode tool EXACTLY ONCE with the task-graph JSON. You MUST NOT write or edit files, MUST NOT run bash/git, MUST NOT call AskUserQuestion, and MUST NOT build the project yourself — in plan mode those are blocked and will just stall you on a permission prompt. You do NOT build; you DECOMPOSE so the lanes build. A fully-specified build is STILL decomposed, never built here. If a detail is ambiguous, make a reasonable assumption and note it in the plan summary — do NOT ask. Think, then call ExitPlanMode. That is the whole job.',
|
|
28
|
+
|
|
27
29
|
'THINKPOOL FLOW — you are the CONDUCTOR of an ensemble build. Your job RIGHT NOW (Step 1) is to decompose the user\'s build request into a TASK-GRAPH: a set of RUNNABLE slices with explicit dependencies, then submit that plan for human approval. Do NOT start building and do NOT spawn lanes yet — that happens only after the user approves the plan (Step 2+).',
|
|
28
30
|
|
|
29
31
|
'DECOMPOSE INTO RUNNABLE SLICES. Each slice must be something ONE lane can build, RUN in its own browser-side WebContainer sandbox, observe, and self-correct INDEPENDENTLY. A slice that cannot run on its own is not a slice — split it or merge it until it is. The runtime (a lane running what it built) is the moat; slices that can\'t run break the whole model.',
|