@dfosco/storyboard 0.6.0-beta.7 → 0.6.0-beta.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard",
3
- "version": "0.6.0-beta.7",
3
+ "version": "0.6.0-beta.9",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Storyboard prototyping framework — core engine, React integration, and canvas",
@@ -549,7 +549,9 @@ export class HotPool {
549
549
  const poll = setInterval(() => {
550
550
  try {
551
551
  const paneContent = execSync(
552
- `tmux capture-pane -t "${tmuxName}" -p`,
552
+ // H1: include scrollback so the readiness echo can still be
553
+ // matched after the agent's TUI repaints over it.
554
+ `tmux capture-pane -t "${tmuxName}" -p -S -200`,
553
555
  { encoding: 'utf8', timeout: 1000 }
554
556
  )
555
557
  // Strip ANSI escape sequences — agent CLIs use heavy formatting
@@ -3148,7 +3148,8 @@ export function Default() {
3148
3148
  const poll = setInterval(() => {
3149
3149
  if (sent) { clearInterval(poll); return }
3150
3150
  try {
3151
- const pane = execSync(`tmux capture-pane -t "${tmuxName}" -p`, { encoding: 'utf8', timeout: 1000 })
3151
+ // H1: include scrollback so the echo survives Copilot's TUI repaint.
3152
+ const pane = execSync(`tmux capture-pane -t "${tmuxName}" -p -S -200`, { encoding: 'utf8', timeout: 1000 })
3152
3153
  if (pane.includes(readinessSignal)) {
3153
3154
  sent = true
3154
3155
  clearInterval(poll)
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  import { execSync } from 'node:child_process'
26
- import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, unlinkSync } from 'node:fs'
26
+ import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, unlinkSync, rmSync } from 'node:fs'
27
27
  import { resolve, join, dirname } from 'node:path'
28
28
  import { fileURLToPath } from 'node:url'
29
29
  import { tmpdir } from 'node:os'
@@ -1164,7 +1164,15 @@ function handleConnection(ws, widgetId, canvasId, prettyName, widgetStartupComma
1164
1164
  // pool-keyed for the life of the warm process, so the user-level
1165
1165
  // hook always writes there, not to the widget-keyed file.)
1166
1166
  const postStartup = resolvedAgentCfg?.postStartup || null
1167
- const readinessSignal = resolvedAgentCfg?.readinessSignal || null
1167
+ // ── H2 fix: skip readiness re-poll on warm handoff.
1168
+ // The hot pool already verified readiness when it warmed this session.
1169
+ // Re-polling for a one-shot signal like Copilot's "Environment loaded:"
1170
+ // against an already-running TUI always misses (the echo has long
1171
+ // since scrolled off the visible pane, and `tmux capture-pane -p`
1172
+ // returns only the visible region). Falling through to the 30s
1173
+ // timeout fallback was delaying /allow-all, identity, role, and
1174
+ // bindWidget by ~30s for every warm Copilot widget.
1175
+ const readinessSignal = null
1168
1176
  setTimeout(() => {
1169
1177
  let completed = false
1170
1178
  const finalize = () => {
@@ -1244,16 +1252,26 @@ function handleConnection(ws, widgetId, canvasId, prettyName, widgetStartupComma
1244
1252
  const envScriptDir = join(cwd, '.storyboard', 'terminals')
1245
1253
  try { mkdirSync(envScriptDir, { recursive: true }) } catch { /* empty */ }
1246
1254
  const envScriptPath = join(envScriptDir, `${widgetId}.env.sh`)
1255
+ // ── H4: file marker for readiness. Terminal-state-independent —
1256
+ // `existsSync` doesn't care if Copilot's TUI repainted the pane,
1257
+ // entered alt-screen, or scrolled the echo away.
1258
+ const readyFilePath = join(envScriptDir, `${widgetId}.ready`)
1259
+ try { rmSync(readyFilePath, { force: true }) } catch { /* empty */ }
1247
1260
  try {
1248
- // Trailing echo is the readiness signal the post-startup poller
1249
- // looks for. Without it, the 30s timeout fallback fires before
1250
- // /allow-all, identity, role/broadcast bind are sent — making
1251
- // the agent feel "stuck" for the first half-minute after launch.
1252
- writeFileSync(envScriptPath, envParts.join('\n') + '\necho "Environment loaded:"\n')
1261
+ // `touch` fires before the echo so the marker is set the instant
1262
+ // the env script finishes exporting. Echo kept for backwards-
1263
+ // compatible pane-scanning fallback (H1).
1264
+ writeFileSync(
1265
+ envScriptPath,
1266
+ envParts.join('\n') +
1267
+ `\ntouch ${JSON.stringify(readyFilePath)}\necho "Environment loaded:"\n`
1268
+ )
1253
1269
  } catch { /* empty */ }
1254
- const envSourceCmd = startupCommand
1255
- ? `clear && source ${JSON.stringify(envScriptPath)} && clear`
1256
- : `source ${JSON.stringify(envScriptPath)}`
1270
+ // Source env script; the trailing readiness echo MUST remain on
1271
+ // the pane so the post-startup poller can match it. Don't append
1272
+ // a `clear` here — the welcomeCmd that runs next clears the pane
1273
+ // itself before launching the agent.
1274
+ const envSourceCmd = `source ${JSON.stringify(envScriptPath)}`
1257
1275
 
1258
1276
  setTimeout(() => {
1259
1277
  try {
@@ -1336,6 +1354,7 @@ function handleConnection(ws, widgetId, canvasId, prettyName, widgetStartupComma
1336
1354
  if (sent) return
1337
1355
  sent = true
1338
1356
  clearInterval(pollInterval)
1357
+ try { rmSync(readyFilePath, { force: true }) } catch { /* empty */ }
1339
1358
  setTimeout(() => {
1340
1359
  if (postStartup) {
1341
1360
  try {
@@ -1364,14 +1383,21 @@ function handleConnection(ws, widgetId, canvasId, prettyName, widgetStartupComma
1364
1383
  }
1365
1384
  const pollInterval = setInterval(() => {
1366
1385
  if (sent) { clearInterval(pollInterval); return }
1386
+ // ── H4: marker file is set the instant env script finishes
1387
+ // exporting, before any TUI starts. Terminal-state-independent.
1388
+ if (existsSync(readyFilePath)) { finalize('file'); return }
1367
1389
  try {
1390
+ // ── H1: include 200 lines of scrollback so the echo
1391
+ // can be matched after Copilot's full-screen TUI
1392
+ // repaints over it. `-p` alone returns only the
1393
+ // visible region.
1368
1394
  const paneContent = execSync(
1369
- `tmux capture-pane -t "${tmuxName}" -p`,
1395
+ `tmux capture-pane -t "${tmuxName}" -p -S -200`,
1370
1396
  { encoding: 'utf8', timeout: 1000 }
1371
1397
  )
1372
1398
  if (paneContent.includes(readinessSignal)) finalize('signal')
1373
1399
  } catch { /* empty */ }
1374
- }, 2000)
1400
+ }, 1000)
1375
1401
  // Fallback: if readiness signal never matches (e.g. resume mode
1376
1402
  // doesn't print "Environment loaded:", or the agent shows a
1377
1403
  // prompt we can't detect), bind anyway after 30s so the widget