evOo-local/packages/adapters/src/scripted-runtime.ts
Claude 04f2109fc7
feat(delegation): main bot hands work to specialists as tracked tasks
The main agent had no way to give a specialist a piece of work and keep
track of it. message_bot is a fire-and-forget peer message whose work stays
hidden from the user, and handoff_to_bot ends the main bot's turn and only
works between members of one group. Delegation now runs the specialist's
work where the user follows the main agent and brings the result back so
the main agent can close the task.

- delegate_task({bot, title, brief, deadline?}) is offered to the main bot
  only and refused unless the caller leads the main conversation. It
  resolves the specialist by id or name across the user's spaces, creates a
  DelegatedTask and posts a task card in the main conversation.
- A specialist from the main conversation's space joins it and works there
  in a run with the new trigger "delegation", which the peer filters do not
  hide, so its progress, asks and approvals appear in the main conversation
  with the specialist as sender. A specialist from another space works in
  its own chat; its written result is posted into the main conversation as
  its message.
- The executor mirrors the run on its task (running, waiting, done, failed,
  cancelled) after the run starts and when each attempt ends, and updates
  the card in place through thread.message.updated. Done and failed tasks
  wake the main bot in the main conversation with the result. The job
  reconciler settles tasks whose run ended without the executor (stop,
  clear, archive, deletion, crash).
- tasks.list({threadId?, status?}) returns the caller's tasks for the
  current space.
- The task card shows who, what and status with Lingui labels; the brief
  opens on demand. The message_bot card no longer shows the recipient's
  unrelated overall status or hard-coded German copy.
- The scripted runtime understands "delegate to the bot named X: ..." and
  the task wake prompt; activity rows name delegation runs by task title.

Tests: unit tests for the prompts, status mapping, card, reducer, tool
selection and scripted runtime; a Postgres suite for same-space, group,
other-space, ask and answer, failure, stop and replay; tasks.list in the
authorization suite; and an end-to-end spec for the whole flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-02 22:41:38 +00:00

499 lines
14 KiB
TypeScript

import type {
AdapterContext,
AgentRunRequest,
AgentRuntime,
AgentRuntimeEvent,
} from "@rakazo/adapter-kit";
import { abortableDelay, DELEGATED_TASK_WAKE_CUE, inferHandoffTargetName } from "@rakazo/core";
const running = new Map<string, AbortController>();
export class ScriptedAgentRuntime implements AgentRuntime {
describe() {
return {
id: "scripted",
contractVersion: "1",
adapterVersion: "0.1.0",
capabilities: { streaming: true, compaction: false, tools: true, scripted: true },
};
}
async abort(runId: string): Promise<void> {
running.get(runId)?.abort();
}
async *run(
request: AgentRunRequest,
context?: Partial<AdapterContext>,
): AsyncIterable<AgentRuntimeEvent> {
const controller = new AbortController();
running.set(request.runId, controller);
const signal = context?.signal ?? controller.signal;
try {
if (shouldFail(request.prompt)) {
throw new Error("Scripted run failure");
}
if (shouldHang(request.prompt)) {
yield { type: "progress", text: "still working…", activity: true };
while (!controller.signal.aborted && !signal.aborted) {
await abortableDelay(50, controller.signal);
if (controller.signal.aborted || signal.aborted) break;
yield { type: "progress", text: "still working…", activity: true };
}
yield { type: "done", text: "stopped" };
return;
}
const script = request.script ?? inferScript(request.prompt, request.resumeFromCheckpoint);
// Per-run call index so repeated tools (e.g. message_agent) get distinct
// executionIds — delivery keys and effect replays key off this value.
let toolCallSeq = 0;
for (const turn of script) {
if (signal.aborted || controller.signal.aborted) {
yield { type: "done", text: "stopped" };
return;
}
if (turn.assistant) {
yield { type: "progress", text: "working…", activity: true };
yield { type: "text", text: turn.assistant };
}
for (const call of turn.toolCalls ?? []) {
const executionId = `${request.runId}:${call.name}:${toolCallSeq++}`;
if (call.name === "run_subagent") {
const agentId = `${request.runId}:subagent`;
const name = String(call.args.name ?? "helper");
const task = String(call.args.task ?? request.prompt);
yield {
type: "subagent",
agentId,
name,
task,
status: "running",
progress: "working…",
};
yield {
type: "tool",
name: call.name,
args: call.args,
executionId,
};
yield {
type: "subagent",
agentId,
name,
task,
status: "completed",
result: `done. i handled: ${task.slice(0, 180)}`,
};
continue;
}
yield {
type: "tool",
name: call.name,
args: call.args,
executionId,
};
}
if (turn.ask) {
yield {
type: "ask",
text: turn.ask.text,
detail: turn.ask.detail,
actions: turn.ask.actions,
};
return;
}
if (turn.takeover) {
yield { type: "takeover", reason: turn.takeover.reason };
return;
}
if (turn.complete) {
yield {
type: "usage",
inputTokens: 12,
outputTokens: 40,
provider: "scripted",
model: "scripted",
};
yield { type: "done", text: turn.assistant };
return;
}
}
yield { type: "text", text: "done." };
yield { type: "done", text: "done." };
} finally {
controller.abort();
running.delete(request.runId);
}
}
}
export function inferScript(
prompt: string,
resumeFromCheckpoint?: string,
): NonNullable<AgentRunRequest["script"]> {
const lower = prompt.toLowerCase();
if (resumeFromCheckpoint === "takeover-skipped") {
return [
{
assistant: "login was skipped. continuing without treating sign-in as done.",
complete: true,
},
];
}
if (resumeFromCheckpoint === "takeover") {
return [
{
assistant:
"signed in. the session stays in this computer — protected input never hit the thread.",
complete: true,
},
];
}
// A finished delegated task wakes the main bot; the result inside may say anything.
if (prompt.startsWith(DELEGATED_TASK_WAKE_CUE)) {
const title = /"([^"\n]{1,120})"/.exec(prompt)?.[1] ?? "the task";
return [{ assistant: `closing the task: ${title}.`, complete: true }];
}
// Before every content-based intent so payload text cannot steal the branch.
if (lower.includes("message the bot named") || lower.includes("message bot named")) {
const name = namedBot(prompt) ?? "Peer";
const message =
/named\s+[A-Za-z0-9][A-Za-z0-9_-]{0,39}\s+(?:saying|with|:)\s*([\s\S]+)$/i
.exec(prompt)?.[1]
?.trim() ?? `Please help with: ${prompt}`;
return [
{
assistant: "messaging that bot now.",
toolCalls: [
{
name: "message_bot",
args: {
confirm_name: name,
message,
intent: "request",
},
},
],
complete: true,
},
];
}
if (lower.includes("delegate to the bot named")) {
const name = namedBot(prompt) ?? "Researcher";
const brief =
/named\s+[A-Za-z0-9][A-Za-z0-9_-]{0,39}\s*(?::|saying|with)\s*([\s\S]+)$/i
.exec(prompt)?.[1]
?.trim() ?? prompt;
return [
{
assistant: "delegating that now.",
toolCalls: [
{ name: "delegate_task", args: { bot: name, title: brief.slice(0, 80), brief } },
],
complete: true,
},
];
}
if (
lower.includes("launch a cloud agent") ||
lower.includes("start a cloud agent") ||
lower.includes("cloud coding agent")
) {
return [
{
assistant: "launching a cloud agent for that.",
toolCalls: [
{
name: "cloud_agent_launch",
args: {
prompt: "Add a README with setup instructions",
repository: "https://github.com/example/demo",
openPr: true,
},
},
],
complete: true,
},
];
}
if (
lower.includes("masked secret card") ||
lower.includes("show a secret card") ||
lower.includes("request a masked api key")
) {
return [
{
assistant: "i need that value in a protected field.",
toolCalls: [
{
name: "request_secret",
args: {
label: "API key",
purpose: "api_key",
credential: {
name: "example_api",
origin: "https://api.example.test",
auth: { type: "bearer" },
},
},
},
],
},
];
}
if (
lower.includes("tappable choices") ||
lower.includes("choice buttons") ||
lower.includes("pick from these cities")
) {
return [
{
assistant: "pick one to continue.",
ask: {
text: "Which city should I use?",
detail: "Tap one option.",
actions: [
{ id: "choice-1", label: "Berlin" },
{ id: "choice-2", label: "Seoul" },
{ id: "choice-3", label: "Toronto" },
{ id: "choice-4", label: "Lisbon" },
],
},
},
];
}
if (
lower.includes("ask me") ||
lower.includes("which city") ||
lower.includes("need a decision")
) {
return [
{
assistant: "i need a decision before i continue.",
ask: { text: "Which city should I use?", detail: "Reply with one city name." },
},
];
}
if (lower.includes("take over") || lower.includes("sign in") || lower.includes("login")) {
return [
{ assistant: "i need you on the screen for a one-time sign-in. handing you the computer." },
{ takeover: { reason: "Sign in to continue. Protected input stays off the thread." } },
];
}
if (
lower.includes("observe your screen") ||
lower.includes("look at your screen") ||
lower.includes("use your screen")
) {
const typed = /type\s+([A-Za-z0-9._-]+)/i.exec(prompt)?.[1] ?? "ready";
return [
{
assistant: "using my screen now.",
toolCalls: [
{ name: "computer_observe", args: {} },
{
name: "computer_act",
args: {
actions: [{ kind: "type", text: typed }],
observe: true,
},
},
],
complete: true,
},
];
}
if (
lower.includes("delete the bot named") ||
lower.includes("delete the child bot") ||
lower.includes("delete child")
) {
const name = namedBot(prompt) ?? "Scout";
return [
{
assistant: "archiving that bot.",
toolCalls: [{ name: "archive_bot", args: { confirm_name: name } }],
complete: true,
},
];
}
if (
lower.includes("create a space") ||
lower.includes("create space") ||
lower.includes("new space named") ||
lower.includes("new private space")
) {
const name = namedSpace(prompt) ?? "New space";
return [
{
assistant: "i can create that separate space after you confirm the boundary.",
toolCalls: [{ name: "create_space", args: { name } }],
complete: true,
},
];
}
if (
lower.includes("spawn a bot") ||
lower.includes("spawn a child") ||
lower.includes("create a bot named") ||
lower.includes("create a child bot")
) {
const name = namedBot(prompt) ?? "Helper";
return [
{
assistant: "creating a bot for that.",
toolCalls: [
{
name: "spawn_bot",
args: {
name,
title: `${name} specialist`,
instructions: `You are ${name}.`,
prompt: "Start on the assigned work.",
},
},
],
complete: true,
},
];
}
if (lower.includes("subagent") || lower.includes("delegate to a helper")) {
return [
{
assistant: "spinning up a helper for that.",
toolCalls: [{ name: "run_subagent", args: { name: "helper", task: prompt } }],
complete: true,
},
];
}
if (
lower.includes("hand this to") ||
lower.includes("hand off to") ||
lower.includes("handoff to") ||
(lower.includes("@writer") && lower.includes("draft"))
) {
const target = inferHandoffTargetName(prompt) ?? "Writer";
return [
{
assistant: "handing this off in the group thread.",
toolCalls: [
{
name: "handoff_to_bot",
args: {
confirm_name: target,
message: prompt,
},
},
],
complete: true,
},
];
}
if (lower.startsWith("run taught skill:") || lower.includes("this is a safe test")) {
return [
{
assistant:
"Running the taught skill using its saved playbook. I will follow the demonstrated steps and report the result.",
complete: true,
},
];
}
if (/^run\s+/.test(lower)) {
return [
{
assistant:
"Using the saved taught skill playbook for that request and following its steps.",
complete: true,
},
];
}
if (lower.includes("connector") || lower.includes("crm") || lower.includes("destination")) {
return [
{
assistant: "writing the record through the connected destination.",
toolCalls: [
{
name: "destination.write",
args: { collection: "notes", title: "Rakazo result", body: prompt },
},
],
complete: true,
},
];
}
if (lower.includes("attach") && (lower.includes("thread") || lower.includes("into the thread"))) {
const said = /says?\s+(.+)$/i.exec(prompt)?.[1]?.replace(/[.]+$/, "") ?? prompt;
const content = `${said.trim()}\n`;
const filePath =
/(?:called|named|path|file)\s+([A-Za-z0-9._/-]+)/i.exec(prompt)?.[1] ?? "notes/result.txt";
return [
{ assistant: "writing that into my home and attaching it to the thread." },
{ toolCalls: [{ name: "write_file", args: { path: filePath, content } }] },
{ toolCalls: [{ name: "attach_file", args: { path: filePath } }], complete: true },
];
}
if (
lower.includes("write") &&
(lower.includes("file") || lower.includes("home") || lower.includes("note"))
) {
const said = /says?\s+(.+)$/i.exec(prompt)?.[1]?.replace(/[.]+$/, "") ?? prompt;
const content = `${said.trim()}\n`;
const filePath =
/(?:called|named)\s+([A-Za-z0-9._/-]+)/i.exec(prompt)?.[1] ?? "notes/result.txt";
return [
{ assistant: "writing that into my home now." },
{
toolCalls: [{ name: "write_file", args: { path: filePath, content } }],
complete: true,
},
];
}
if (lower.includes("remember")) {
return [
{
assistant: "noted — i will keep that in memory.",
memory: [{ scope: "bot", path: "MEMORY.md", content: `# Memory\n\n- ${prompt}\n` }],
complete: true,
},
];
}
return [
{
assistant: `on it. i will work this in the background and come back with a result.\n\n${summarize(prompt)}`,
},
{
files: [{ path: "notes/last-task.md", content: `# Task\n\n${prompt}\n` }],
complete: true,
},
];
}
function shouldFail(prompt: string): boolean {
return prompt.toLowerCase().includes("fail this run");
}
function shouldHang(prompt: string): boolean {
const lower = prompt.toLowerCase();
return (
lower.includes("keep working") ||
lower.includes("work until i stop") ||
lower.includes("until i stop you") ||
lower.includes("hang until stopped")
);
}
function namedBot(prompt: string) {
return /named\s+([A-Za-z0-9][A-Za-z0-9_-]{0,39})/i.exec(prompt)?.[1];
}
function namedSpace(prompt: string) {
return /space\s+(?:named|called)\s+["“]?([^"”\n]{1,60})/i
.exec(prompt)?.[1]
?.replace(/[.!?]+$/, "")
.trim();
}
function summarize(prompt: string): string {
return `done. i handled: ${prompt.slice(0, 180)}`;
}