evOo-local/packages/adapters/src/scripted-runtime.ts
cynarAI 6fd136e944
feat: suggestions the user accepts or dismisses with one tap (#142)
Bots can offer an exact tool call as a card. Nothing runs until the user
taps Accept; the call then runs as its own approved run of the bot it was
meant for, and the card shows the outcome. Open suggestions also wait in
the inbox and expire after a week.


Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-05 02:19:31 +02:00

687 lines
20 KiB
TypeScript

import type {
AdapterContext,
AgentRunRequest,
AgentRuntime,
AgentRuntimeEvent,
} from "@rakazo/adapter-kit";
import { USER_FACT_CATEGORIES } from "@rakazo/contracts";
import { abortableDelay, DELEGATED_TASK_WAKE_CUE, inferHandoffTargetName } from "@rakazo/core";
import { COACH_ROUTINES } from "./coach-rhythm.js";
const running = new Map<string, AbortController>();
/** "learn about me: [category:] statement" records one fact in the user's profile. */
const LEARN_ABOUT_ME = new RegExp(
`learn about me:\\s*(?:(${USER_FACT_CATEGORIES.join("|")})\\s*:\\s*)?([\\s\\S]+)$`,
"i",
);
/** "set a goal: …", "track a habit: …", "done today: <habit>" and "write today's brief: …". */
const COACH_COMMANDS: Array<{
pattern: RegExp;
reply: string;
call: (value: string) => { name: string; args: Record<string, unknown> };
}> = [
{
pattern: /^set a goal:\s*([\s\S]+)$/i,
reply: "goal noted.",
call: (title) => ({ name: "update_goal", args: { title } }),
},
{
pattern: /^track a habit:\s*([\s\S]+)$/i,
reply: "habit noted.",
call: (title) => ({ name: "update_habit", args: { title } }),
},
{
pattern: /^done today:\s*([\s\S]+)$/i,
reply: "checked off.",
call: (habit) => ({ name: "log_check_in", args: { habit, done: true } }),
},
{
pattern: /^write today's brief:\s*([\s\S]+)$/i,
reply: "today's brief is ready.",
call: (text) => ({ name: "write_daily_brief", args: { text } }),
},
];
/** The morning check-in writes a fixed brief so tests can follow it to the Today page. */
export const SCRIPTED_MORNING_BRIEF = "One thing at a time today.";
function inferCoachScript(prompt: string): NonNullable<AgentRunRequest["script"]> | undefined {
if (prompt.startsWith(COACH_ROUTINES.morning.prompt)) {
return [
{
assistant: "good morning. today's brief is ready.",
toolCalls: [{ name: "write_daily_brief", args: { text: SCRIPTED_MORNING_BRIEF } }],
complete: true,
},
];
}
for (const command of COACH_COMMANDS) {
const value = command.pattern.exec(prompt.trim())?.[1]?.trim();
if (value) {
return [{ assistant: command.reply, toolCalls: [command.call(value)], complete: true }];
}
}
return undefined;
}
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,
},
];
}
const learned = LEARN_ABOUT_ME.exec(prompt);
if (learned?.[2]?.trim()) {
return [
{
assistant: "noted — i will keep that in your profile.",
toolCalls: [
{
name: "update_user_profile",
args: {
facts: [
{
category: (learned[1] ?? "preferences").toLowerCase(),
value: learned[2].trim(),
},
],
},
},
],
complete: true,
},
];
}
const coach = inferCoachScript(prompt);
if (coach) return coach;
// Said in passing: the bot sets the routine up without asking first.
if (lower.includes("every sunday i plan the week")) {
return [
{
assistant: "i will remind you every sunday at 6 pm.",
toolCalls: [
{
name: "schedule_create",
args: { name: "Weekly plan", prompt: "Plan the week with me.", cron: "0 18 * * 0" },
},
],
complete: true,
},
];
}
// An idea for the user: nothing runs until they accept it.
if (lower.includes("i should move more")) {
return [
{
assistant: "",
toolCalls: [
{
name: "suggest",
args: {
tool: "schedule_create",
args: {
name: "Morning walk",
prompt: "Remind me to take a short walk.",
cron: "0 8 * * *",
},
why: "A nudge each morning makes it a habit.",
},
},
],
complete: true,
},
];
}
// Everyday answers come as a card the user taps, not a list in prose.
if (lower.includes("what should we cook tonight")) {
return [
{
assistant: "",
toolCalls: [
{
name: "show_card",
args: {
title: "Dinner tonight",
meta: ["30 min", "2 servings"],
checklist: ["Spaghetti", "Tomatoes", "Basil"],
options: ["Pasta", "Curry", "Salad"],
},
},
],
complete: true,
},
];
}
if (lower.includes("pancake recipe")) {
return [
{
assistant: "",
toolCalls: [
{
name: "show_card",
args: {
title: "Pancakes",
meta: ["25 min"],
stepper: { label: "Servings", value: 2 },
checklist: [
{ label: "Flour", amount: 200, unit: "g" },
{ label: "Egg", plural: "Eggs", amount: 1 },
{ label: "Milk", amount: 300, unit: "ml" },
],
steps: [
"Whisk flour, egg and milk until smooth.",
{ text: "Let the batter rest.", timer_minutes: 10 },
"Fry thin pancakes in a hot pan.",
],
start_label: "Cook",
actions: ["Every week"],
},
},
],
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: {
// Click into the focused field first so the visible cursor has a target.
actions: [
{ kind: "click", x: 640, y: 400 },
{ 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("present") && lower.includes("report")) {
const filePath =
/(?:called|named|path|file)\s+([A-Za-z0-9._/-]+)/i.exec(prompt)?.[1] ?? "report.md";
const content =
"# Report\n\n| Item | Status |\n| --- | --- |\n| Research | Done |\n| Draft | Done |\n";
return [
{ assistant: "writing the report and putting it next to our chat." },
{ toolCalls: [{ name: "write_file", args: { path: filePath, content } }] },
{
toolCalls: [{ name: "attach_file", args: { path: filePath, present: true } }],
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: prompt, mode: "append" }],
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)}`;
}