Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import type { SubagentEvent } from '../subagent';
|
|
import type { SubagentView } from './Panels';
|
|
|
|
/** One-way channel for out-of-band notices, e.g. an endpoint fallback. */
|
|
export type NoticeBus = {
|
|
bind: (fn: (text: string) => void) => void;
|
|
emit: (text: string) => void;
|
|
};
|
|
|
|
export function createNoticeBus(): NoticeBus {
|
|
const queued: string[] = [];
|
|
let sink: ((text: string) => void) | undefined;
|
|
return {
|
|
bind(fn) {
|
|
sink = fn;
|
|
for (const text of queued.splice(0)) fn(text);
|
|
},
|
|
emit(text) {
|
|
if (sink) sink(text);
|
|
else queued.push(text);
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Subagent progress, from the task tool to the panel. */
|
|
export type SubagentBus = {
|
|
bind: (fn: (event: SubagentEvent) => void) => void;
|
|
emit: (event: SubagentEvent) => void;
|
|
};
|
|
|
|
export function createSubagentBus(): SubagentBus {
|
|
const queued: SubagentEvent[] = [];
|
|
let sink: ((event: SubagentEvent) => void) | undefined;
|
|
return {
|
|
bind(fn) {
|
|
sink = fn;
|
|
for (const event of queued.splice(0)) fn(event);
|
|
},
|
|
emit(event) {
|
|
if (sink) sink(event);
|
|
else queued.push(event);
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Folds a subagent event into the panel's view, keeping finished agents visible. */
|
|
export function applySubagentEvent(current: SubagentView[], event: SubagentEvent): SubagentView[] {
|
|
switch (event.type) {
|
|
case 'start':
|
|
return [
|
|
...current,
|
|
{ id: event.id, kind: event.kind, description: event.description, steps: [], status: 'running' },
|
|
];
|
|
case 'step':
|
|
return current.map((a) =>
|
|
a.id === event.id ? { ...a, steps: [...a.steps, { tool: event.tool, summary: event.summary }] } : a,
|
|
);
|
|
case 'result':
|
|
// Attaches to the step it answers rather than appending, so a subagent's
|
|
// step count stays the number of calls it made.
|
|
return current.map((a) => {
|
|
if (a.id !== event.id) return a;
|
|
const last = a.steps.at(-1);
|
|
if (!last || last.tool !== event.tool || last.outcome !== undefined) return a;
|
|
return {
|
|
...a,
|
|
steps: [...a.steps.slice(0, -1), { ...last, outcome: event.summary, ok: event.ok }],
|
|
};
|
|
});
|
|
case 'end':
|
|
return current.map((a) => (a.id === event.id ? { ...a, status: event.ok ? 'done' : 'failed' } : a));
|
|
case 'error':
|
|
return current.map((a) => (a.id === event.id ? { ...a, status: 'failed', error: event.message } : a));
|
|
}
|
|
}
|