@dfosco/storyboard 0.6.0-beta.13 → 0.6.0-beta.15
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/mascot.config.json +0 -1
- package/package.json +1 -1
- package/src/core/canvas/agent-session.js +19 -11
- package/src/core/cli/dev.js +47 -5
- package/src/core/vite/server-plugin.js +48 -0
- package/terminal.config.json +2 -0
package/mascot.config.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"$comment": "Mascot animation rendered above the Vite dev banner. Each entry in `frames` is either a string (uses frameDurationMs as the default delay) or a [filename, delayMs] tuple for per-frame timing. After loops finish, settleFrame is left on screen with the dev URL beside it.",
|
|
3
3
|
"frames": [
|
|
4
4
|
["frame-01-peek-left.txt", 600],
|
|
5
|
-
["frame-02-eyes-open.txt", 300],
|
|
6
5
|
["frame-03-peek-right.txt", 600],
|
|
7
6
|
["frame-04-eyes-open.txt", 400],
|
|
8
7
|
["frame-05-eyes-closed.txt", 200],
|
package/package.json
CHANGED
|
@@ -221,25 +221,33 @@ export function buildResumeStartupCommand({ startupCommand, sessionId, agentCfg
|
|
|
221
221
|
if (!startupCommand) return startupCommand
|
|
222
222
|
|
|
223
223
|
const notice = `printf '\\n\\033[33m[storyboard] resume failed; starting fresh session...\\033[0m\\n'`
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
const lastCmd = agentCfg?.resumeLastCommand
|
|
225
|
+
|
|
226
|
+
// Chain: stored-id resume → resumeLastCommand (if any) → fresh startupCommand.
|
|
227
|
+
// Each step's non-zero exit cascades to the next via shell `||`. Note: if a
|
|
228
|
+
// resume hangs (vs. exits non-zero), the chain won't progress — users
|
|
229
|
+
// should use the widget's restart button. We deliberately don't wrap in a
|
|
230
|
+
// timeout to avoid killing slow-starting agents on flaky networks.
|
|
231
|
+
const wrapFallback = (cmd) => {
|
|
232
|
+
if (agentCfg?.resumeFallback === false) return cmd
|
|
233
|
+
const last = lastCmd ? `${lastCmd} || { ${notice}; ${startupCommand}; }` : `${notice}; ${startupCommand}`
|
|
234
|
+
return `${cmd} || { ${last}; }`
|
|
235
|
+
}
|
|
227
236
|
|
|
228
237
|
// Primary: per-widget captured sessionId → `resumeCommand` with {id}.
|
|
229
|
-
if (sessionId &&
|
|
238
|
+
if (sessionId && UUID_RE.test(sessionId)) {
|
|
230
239
|
const template = agentCfg?.resumeCommand
|
|
231
240
|
if (template && template.includes('{id}')) {
|
|
232
241
|
return wrapFallback(template.replace('{id}', sessionId))
|
|
233
242
|
}
|
|
234
243
|
}
|
|
235
244
|
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (lastCmd) return wrapFallback(lastCmd)
|
|
245
|
+
// No id stored — try resumeLastCommand (e.g. `--continue` / `resume --last`),
|
|
246
|
+
// falling through to fresh startupCommand if it exits non-zero.
|
|
247
|
+
if (lastCmd) {
|
|
248
|
+
if (agentCfg?.resumeFallback === false) return lastCmd
|
|
249
|
+
return `${lastCmd} || { ${notice}; ${startupCommand}; }`
|
|
250
|
+
}
|
|
243
251
|
|
|
244
252
|
return startupCommand
|
|
245
253
|
}
|
package/src/core/cli/dev.js
CHANGED
|
@@ -23,6 +23,7 @@ import { compactAll } from '../canvas/compact.js'
|
|
|
23
23
|
import { parseFlags } from './flags.js'
|
|
24
24
|
import { setupNeeded, writeUserState, getInstalledStoryboardVersion } from './userState.js'
|
|
25
25
|
import { dim, magenta, bold } from './intro.js'
|
|
26
|
+
import { rmSync } from 'node:fs'
|
|
26
27
|
|
|
27
28
|
/** Find the mascot directory shipped with the storyboard package. */
|
|
28
29
|
function mascotPaths(targetCwd) {
|
|
@@ -157,6 +158,28 @@ function readConfiguredPort(cwd) {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Resolve the Vite base path for URL display. Priority:
|
|
163
|
+
* 1. `VITE_BASE_PATH` env var (matches vite.config.js convention)
|
|
164
|
+
* 2. `basePath` key in storyboard.config.json (if user wants to override)
|
|
165
|
+
* 3. `/` (Vite default)
|
|
166
|
+
*
|
|
167
|
+
* Trailing slash is normalized so the rendered URL never double-slashes.
|
|
168
|
+
*/
|
|
169
|
+
function resolveBasePath(cwd) {
|
|
170
|
+
let base = process.env.VITE_BASE_PATH || null
|
|
171
|
+
if (!base) {
|
|
172
|
+
try {
|
|
173
|
+
const cfg = JSON.parse(readFileSync(resolve(cwd, 'storyboard.config.json'), 'utf8'))
|
|
174
|
+
if (typeof cfg.basePath === 'string' && cfg.basePath) base = cfg.basePath
|
|
175
|
+
} catch { /* empty */ }
|
|
176
|
+
}
|
|
177
|
+
base = base || '/'
|
|
178
|
+
if (!base.startsWith('/')) base = '/' + base
|
|
179
|
+
if (!base.endsWith('/')) base = base + '/'
|
|
180
|
+
return base
|
|
181
|
+
}
|
|
182
|
+
|
|
160
183
|
async function main() {
|
|
161
184
|
const { flags } = parseFlags(process.argv.slice(3), flagSchema)
|
|
162
185
|
const worktreeName = detectWorktreeName()
|
|
@@ -169,6 +192,8 @@ async function main() {
|
|
|
169
192
|
const configuredPort = readConfiguredPort(targetCwd)
|
|
170
193
|
const strictPort = flags.port == null && configuredPort != null
|
|
171
194
|
const port = flags.port || configuredPort || getPort(worktreeName)
|
|
195
|
+
const basePath = resolveBasePath(targetCwd)
|
|
196
|
+
const devUrl = `http://localhost:${port}${basePath}`
|
|
172
197
|
|
|
173
198
|
const verbose = flags.verbose
|
|
174
199
|
|
|
@@ -193,6 +218,11 @@ async function main() {
|
|
|
193
218
|
? 'first run in this repo'
|
|
194
219
|
: `version changed ${need.from} → ${need.to}`
|
|
195
220
|
if (verbose) p.log.info(`Running setup (${why})…`)
|
|
221
|
+
|
|
222
|
+
// Invalidate Vite's optimize-deps cache when the storyboard version
|
|
223
|
+
// changes. Otherwise the browser hits 504 Outdated Optimize Dep
|
|
224
|
+
// because the dep graph IDs no longer match the cached chunks.
|
|
225
|
+
try { rmSync(join(targetCwd, 'node_modules', '.vite'), { recursive: true, force: true }) } catch { /* empty */ }
|
|
196
226
|
await new Promise((resolveSetup) => {
|
|
197
227
|
const setupChild = spawn(
|
|
198
228
|
process.platform === 'win32' ? 'npx.cmd' : 'npx',
|
|
@@ -274,11 +304,11 @@ async function main() {
|
|
|
274
304
|
console.log()
|
|
275
305
|
const animated = showBuddy && renderMascot(
|
|
276
306
|
mascotPaths(targetCwd),
|
|
277
|
-
bold(
|
|
307
|
+
bold(devUrl),
|
|
278
308
|
dim('Stop with Ctrl+C'),
|
|
279
309
|
)
|
|
280
310
|
if (!animated) {
|
|
281
|
-
console.log(` ${bold(
|
|
311
|
+
console.log(` ${bold(devUrl)}`)
|
|
282
312
|
console.log(` ${dim('Stop with Ctrl+C')}`)
|
|
283
313
|
}
|
|
284
314
|
// Wait for animation to settle, then flush anything Vite emitted
|
|
@@ -323,18 +353,30 @@ async function main() {
|
|
|
323
353
|
setTimeout(() => { renderOnce() }, 8000).unref?.()
|
|
324
354
|
}
|
|
325
355
|
|
|
356
|
+
let shuttingDown = false
|
|
326
357
|
function shutdown() {
|
|
358
|
+
if (shuttingDown) {
|
|
359
|
+
// Second Ctrl+C → hard exit, kill child with SIGKILL.
|
|
360
|
+
try { child.kill('SIGKILL') } catch { /* empty */ }
|
|
361
|
+
process.exit(130)
|
|
362
|
+
}
|
|
363
|
+
shuttingDown = true
|
|
327
364
|
clearInterval(compactInterval)
|
|
328
365
|
renameWatcher.close()
|
|
329
366
|
// Suppress Vite's shutdown-time esbuild noise ("Pre-transform error:
|
|
330
|
-
// The service was stopped" for every in-flight transform)
|
|
367
|
+
// The service was stopped" for every in-flight transform) AND the
|
|
368
|
+
// orphan-archive log spam from the storyboard-server plugin teardown.
|
|
331
369
|
try { child.stdout?.removeAllListeners('data') } catch { /* empty */ }
|
|
332
370
|
try { child.stderr?.removeAllListeners('data') } catch { /* empty */ }
|
|
333
371
|
try { child.stdout?.destroy() } catch { /* empty */ }
|
|
334
372
|
try { child.stderr?.destroy() } catch { /* empty */ }
|
|
335
|
-
// SIGINT
|
|
336
|
-
//
|
|
373
|
+
// SIGINT first (clean esbuild shutdown), then SIGTERM after 2s if
|
|
374
|
+
// Vite is still alive (handles plugins that loop on session teardown),
|
|
375
|
+
// then SIGKILL after 5s as last resort.
|
|
337
376
|
try { child.kill('SIGINT') } catch { /* already dead */ }
|
|
377
|
+
const term = setTimeout(() => { try { child.kill('SIGTERM') } catch { /* empty */ } }, 2000)
|
|
378
|
+
const kill = setTimeout(() => { try { child.kill('SIGKILL') } catch { /* empty */ } }, 5000)
|
|
379
|
+
term.unref?.(); kill.unref?.()
|
|
338
380
|
releasePort(worktreeName)
|
|
339
381
|
}
|
|
340
382
|
process.on('SIGINT', shutdown)
|
|
@@ -774,6 +774,54 @@ export default function storyboardServer() {
|
|
|
774
774
|
})
|
|
775
775
|
}
|
|
776
776
|
|
|
777
|
+
// Auto-reload on Vite's "outdated optimize dep" 504 errors.
|
|
778
|
+
// Happens when the dep graph IDs in cached chunks no longer match
|
|
779
|
+
// what Vite is serving (after upgrades, dep additions, etc).
|
|
780
|
+
// We catch failed fetches inside the page and trigger a full reload
|
|
781
|
+
// once — the second load will see the freshly-built optimize deps.
|
|
782
|
+
if (isDev) {
|
|
783
|
+
tags.push({
|
|
784
|
+
tag: 'script',
|
|
785
|
+
children: `
|
|
786
|
+
(function(){
|
|
787
|
+
var reloaded = false;
|
|
788
|
+
function maybeReload(reason){
|
|
789
|
+
if (reloaded) return;
|
|
790
|
+
if (sessionStorage.getItem('__sb_outdated_reload__')) return;
|
|
791
|
+
reloaded = true;
|
|
792
|
+
sessionStorage.setItem('__sb_outdated_reload__', '1');
|
|
793
|
+
console.warn('[storyboard] Reloading: ' + reason);
|
|
794
|
+
setTimeout(function(){ sessionStorage.removeItem('__sb_outdated_reload__'); }, 5000);
|
|
795
|
+
location.reload();
|
|
796
|
+
}
|
|
797
|
+
// Clear stale guard from previous successful loads.
|
|
798
|
+
if (document.readyState === 'complete') {
|
|
799
|
+
sessionStorage.removeItem('__sb_outdated_reload__');
|
|
800
|
+
} else {
|
|
801
|
+
window.addEventListener('load', function(){
|
|
802
|
+
setTimeout(function(){ sessionStorage.removeItem('__sb_outdated_reload__'); }, 2000);
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
// Catch module load failures.
|
|
806
|
+
window.addEventListener('error', function(e){
|
|
807
|
+
var msg = (e && e.message) || '';
|
|
808
|
+
if (/Outdated Optimize Dep|Failed to fetch dynamically imported module|504/i.test(msg)) {
|
|
809
|
+
maybeReload('outdated dep / dynamic import failure');
|
|
810
|
+
}
|
|
811
|
+
}, true);
|
|
812
|
+
// Catch unhandled promise rejections from dynamic imports.
|
|
813
|
+
window.addEventListener('unhandledrejection', function(e){
|
|
814
|
+
var msg = (e && e.reason && (e.reason.message || String(e.reason))) || '';
|
|
815
|
+
if (/Outdated Optimize Dep|Failed to fetch dynamically imported module|504/i.test(msg)) {
|
|
816
|
+
maybeReload('outdated dep / dynamic import failure');
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
})();
|
|
820
|
+
`.trim(),
|
|
821
|
+
injectTo: 'head',
|
|
822
|
+
})
|
|
823
|
+
}
|
|
824
|
+
|
|
777
825
|
// Inject base path so the inspector UI can resolve static assets
|
|
778
826
|
// (e.g. inspector.json) when deployed under a subpath
|
|
779
827
|
tags.push({
|
package/terminal.config.json
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"icon": "primer/copilot",
|
|
19
19
|
"startupCommand": "copilot --agent terminal-agent",
|
|
20
20
|
"resumeCommand": "copilot --resume={id} --agent terminal-agent",
|
|
21
|
+
"resumeLastCommand": "copilot --continue --agent terminal-agent",
|
|
21
22
|
"sessionIdEnv": "COPILOT_AGENT_SESSION_ID",
|
|
22
23
|
"postStartup": "/allow-all on",
|
|
23
24
|
"resizable": true
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
"icon": "claude",
|
|
28
29
|
"startupCommand": "claude --agent terminal-agent --dangerously-skip-permissions",
|
|
29
30
|
"resumeCommand": "claude --resume {id} --agent terminal-agent --dangerously-skip-permissions",
|
|
31
|
+
"resumeLastCommand": "claude --continue --agent terminal-agent --dangerously-skip-permissions",
|
|
30
32
|
"sessionIdEnv": "CLAUDE_SESSION_ID",
|
|
31
33
|
"sessionStateGlob": "~/.claude/projects/*/{id}.jsonl",
|
|
32
34
|
"readinessSignal": "bypass permissions",
|