framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
76 lines • 3 kB
JavaScript
/**
* The live-chat message channel (#714): the user's own turns into a running run.
*
* A run's await gates (#337) are the agent asking the user; this is the reverse —
* the user speaking to the agent unprompted. Each message continues the *same*
* agent session (`claude --resume <id>`), so the conversation keeps its full
* context. The run loop drains this once the work settles; a daemon-spawned
* session then ends itself when the queue is idle (#1390) — a follow-up message
* reopens the conversation via `--resume`, like Claude Code web — while a run
* with its own terminal dashboard keeps the old stay-open wait, since that
* surface has no daemon to resume through.
*
* Wired only when an interactive channel can deliver messages (a live dashboard /
* daemon over `control.jsonl`). A headless run gets no {@link RunMessages}, so its
* loop ends when the agent stops asking — byte-identical to before this existed.
*/
/**
* A {@link RunMessages} the control channel feeds ({@link push}) and the run loop
* drains ({@link next}). A message that arrives with a waiter parked hands off
* directly; otherwise it queues until the next `next()`. FIFO in both directions.
*/
export class RunMessageQueue {
pending = [];
waiters = [];
closed = false;
/**
* Enqueue a user message (or hand it to a parked waiter). No-op once closed. `via` names the
* surface it came through (#917); omitted, the run attributes it to its own.
*/
push(text, via) {
if (this.closed)
return;
const message = via === undefined ? { text } : { text, via };
const waiter = this.waiters.shift();
if (waiter)
waiter(message);
else
this.pending.push(message);
}
/** Stop the chat: wake every parked waiter with `undefined` so their loops end. */
close() {
this.closed = true;
let waiter;
while ((waiter = this.waiters.shift()))
waiter(undefined);
}
takeQueued() {
if (this.closed)
return undefined;
return this.pending.shift();
}
next(signal) {
const queued = this.pending.shift();
if (queued !== undefined)
return Promise.resolve(queued);
if (this.closed || signal?.aborted)
return Promise.resolve(undefined);
return new Promise(resolve => {
const waiter = (message) => {
if (signal)
signal.removeEventListener('abort', onAbort);
resolve(message);
};
const onAbort = () => {
const i = this.waiters.indexOf(waiter);
if (i >= 0)
this.waiters.splice(i, 1);
resolve(undefined);
};
this.waiters.push(waiter);
if (signal)
signal.addEventListener('abort', onAbort, { once: true });
});
}
}
//# sourceMappingURL=run-messages.js.map