repl: asm tier auto-pause uses replay mode — env reconstructs on resume

Asm tier has no in-eval poll site (the WAT interpreter doesn't read
the SAB atomic), so SAB-pause + portal-snapshot don't apply. Until
the asm interpreter grows its own poll, fall back to the same
replay pattern the manual asm portal-save already uses: stash every
successful prior input from the transcript on tab.autoPause.replayInputs.

autoResumeTab now always reboots the tier first, then:
  * replayInputs set        — re-eval each in order so env rebuilds,
    log a "; resumed asm tier — replayed N prior inputs" entry, then
    sendInput re-fires the active input.
  * blob set (c / python)   — round-trip through portal-load! as before.
  * neither (no SAB, lost)  — bare sendInput, env starts cold.

Now switching away from a tab mid-asm-eval and switching back
re-establishes every (define …) the user had before, then re-fires
the loop. Same UX shape as the C/Python path.
This commit is contained in:
russell@unturf.com 2026-06-15 11:18:26 -04:00
parent 1f2ef730a2
commit 6bcc20cb27
No known key found for this signature in database
3 changed files with 69 additions and 9 deletions

View file

@ -264,7 +264,14 @@ async function autoPauseTab(tab) {
}
if (activeInput) {
tab.autoPause = { tier, blob, inputSrc: activeInput, savedAt: Date.now() };
// Asm tier has no SAB poll site, so we can't snapshot env mid-eval.
// Fall back to replay mode: every successful prior input gets
// re-eval'd on resume to rebuild the env, then the active input
// is sent fresh. Same pattern the manual asm portal-save uses.
const replayInputs = (tier === "asm")
? tab.transcript.filter((e) => e.kind !== "error" && e.input !== activeInput).map((e) => e.input)
: null;
tab.autoPause = { tier, blob, replayInputs, inputSrc: activeInput, savedAt: Date.now() };
}
// Terminate the worker(s) so the heap is reclaimed while the tab
@ -284,8 +291,21 @@ async function autoResumeTab(tab) {
tab.tier = ap.tier;
tierSelectEl.value = ap.tier;
if (ap.blob) {
rebootTier(tab.id, ap.tier);
rebootTier(tab.id, ap.tier);
if (ap.replayInputs && ap.replayInputs.length) {
// Asm tier replay path — re-eval every successful prior input
// in order so the env is reconstructed before sendInput fires
// the active one. Errors mid-replay are swallowed; restoring
// from a transcript that defined-on-error is the user's call.
for (const src of ap.replayInputs) {
try { await evalInTier(tab.id, ap.tier, src); } catch (e) { /* keep going */ }
}
tab.transcript.push({
input: `; resumed asm tier — replayed ${ap.replayInputs.length} prior inputs`,
results: [{ tier: ap.tier, output: "#t" }], kind: "ok",
});
} else if (ap.blob) {
// Round-trip through the tier so portal-load! has the blob
// to read. The bare 'init eval forces the tier to bootstrap
// before we push the blob into MEMFS.

View file

@ -264,7 +264,14 @@ async function autoPauseTab(tab) {
}
if (activeInput) {
tab.autoPause = { tier, blob, inputSrc: activeInput, savedAt: Date.now() };
// Asm tier has no SAB poll site, so we can't snapshot env mid-eval.
// Fall back to replay mode: every successful prior input gets
// re-eval'd on resume to rebuild the env, then the active input
// is sent fresh. Same pattern the manual asm portal-save uses.
const replayInputs = (tier === "asm")
? tab.transcript.filter((e) => e.kind !== "error" && e.input !== activeInput).map((e) => e.input)
: null;
tab.autoPause = { tier, blob, replayInputs, inputSrc: activeInput, savedAt: Date.now() };
}
// Terminate the worker(s) so the heap is reclaimed while the tab
@ -284,8 +291,21 @@ async function autoResumeTab(tab) {
tab.tier = ap.tier;
tierSelectEl.value = ap.tier;
if (ap.blob) {
rebootTier(tab.id, ap.tier);
rebootTier(tab.id, ap.tier);
if (ap.replayInputs && ap.replayInputs.length) {
// Asm tier replay path — re-eval every successful prior input
// in order so the env is reconstructed before sendInput fires
// the active one. Errors mid-replay are swallowed; restoring
// from a transcript that defined-on-error is the user's call.
for (const src of ap.replayInputs) {
try { await evalInTier(tab.id, ap.tier, src); } catch (e) { /* keep going */ }
}
tab.transcript.push({
input: `; resumed asm tier — replayed ${ap.replayInputs.length} prior inputs`,
results: [{ tier: ap.tier, output: "#t" }], kind: "ok",
});
} else if (ap.blob) {
// Round-trip through the tier so portal-load! has the blob
// to read. The bare 'init eval forces the tier to bootstrap
// before we push the blob into MEMFS.

View file

@ -264,7 +264,14 @@ async function autoPauseTab(tab) {
}
if (activeInput) {
tab.autoPause = { tier, blob, inputSrc: activeInput, savedAt: Date.now() };
// Asm tier has no SAB poll site, so we can't snapshot env mid-eval.
// Fall back to replay mode: every successful prior input gets
// re-eval'd on resume to rebuild the env, then the active input
// is sent fresh. Same pattern the manual asm portal-save uses.
const replayInputs = (tier === "asm")
? tab.transcript.filter((e) => e.kind !== "error" && e.input !== activeInput).map((e) => e.input)
: null;
tab.autoPause = { tier, blob, replayInputs, inputSrc: activeInput, savedAt: Date.now() };
}
// Terminate the worker(s) so the heap is reclaimed while the tab
@ -284,8 +291,21 @@ async function autoResumeTab(tab) {
tab.tier = ap.tier;
tierSelectEl.value = ap.tier;
if (ap.blob) {
rebootTier(tab.id, ap.tier);
rebootTier(tab.id, ap.tier);
if (ap.replayInputs && ap.replayInputs.length) {
// Asm tier replay path — re-eval every successful prior input
// in order so the env is reconstructed before sendInput fires
// the active one. Errors mid-replay are swallowed; restoring
// from a transcript that defined-on-error is the user's call.
for (const src of ap.replayInputs) {
try { await evalInTier(tab.id, ap.tier, src); } catch (e) { /* keep going */ }
}
tab.transcript.push({
input: `; resumed asm tier — replayed ${ap.replayInputs.length} prior inputs`,
results: [{ tier: ap.tier, output: "#t" }], kind: "ok",
});
} else if (ap.blob) {
// Round-trip through the tier so portal-load! has the blob
// to read. The bare 'init eval forces the tier to bootstrap
// before we push the blob into MEMFS.