Spaces:
Running on Zero
Running on Zero
File size: 2,746 Bytes
7146e75 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | // THIS MODULE IS TRANSPORT-AGNOSTIC AND IS THE ONLY IMPLEMENTATION OF THE TURN LOOP.
// Both avatar.js (inline gr.HTML) and avatar-iframe.js (postMessage) import it.
// Do NOT add turn behaviour to either transport file - add it here, or the iframe
// fallback silently loses the feature. tests/test_transport_seam.py enforces this.
//
// The renderer is reachable only through the six-method stagePort. The host bridge is
// reachable only through getServer(), which is a GETTER rather than a value so this
// module never holds a host object and can be constructed before the bridge exists.
/**
* A deferred method. Calling one now fails loudly and names the plan that fills it in,
* which is the difference between a placeholder and an accidental no-op. The parity
* guard is therefore meaningful from this wave rather than only after the last one.
*/
function notWiredYet(name, plan) {
return () => {
throw new Error(`Avatar.${name}() is not wired yet - plan ${plan} implements it`);
};
}
/**
* @param {object} opts
* @param {object} opts.stagePort the renderer boundary
* @param {Function} [opts.emit] the facade's event bus
* @param {Function} [opts.getServer] returns the host bridge, or null when there is none
*/
export function createTurnLoop({ stagePort, emit = () => {}, getServer = () => null } = {}) {
if (!stagePort) throw new Error('createTurnLoop: a stagePort is required');
// The turn loop's own slice of __debug. The facade merges this object; it does not
// own it, and this module does not own the facade's.
const state = {
thinking: false,
listening: false,
lastTurnId: null,
replayCount: 0,
};
return {
state,
getServer,
setThinking(value) {
const on = !!value;
state.thinking = on;
stagePort.setThinking(on);
return on;
},
setListening(value) {
const on = !!value;
state.listening = on;
stagePort.setListening(on);
return on;
},
/**
* Re-play the cached directive. There is deliberately no network access of any
* kind in here: plan 01-09's test_replay asserts zero requests with a browser
* request listener, and a cache miss must fail rather than quietly re-download.
*/
async replay() {
state.replayCount += 1;
try {
return await stagePort.replayCached();
} catch (err) {
emit('error', { message: String(err?.message ?? err), where: 'replay' });
throw err;
}
},
startListening: notWiredYet('startListening', '01-07'),
stopListening: notWiredYet('stopListening', '01-07'),
dispatchTurn: notWiredYet('dispatchTurn', '01-08'),
requestSlower: notWiredYet('requestSlower', '01-08'),
};
}
|