* Fold a run's step messages behind its latest message Long tool runs posted every step as its own bubble, so the answer drowned in narration and a summary written before saving a file appeared twice. Web and mobile now fold a run's text-only step messages behind one "N steps" line above the run's latest message. The thread snapshot also stops replaying live narration that was already posted, which showed steps again after a refresh. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ * Keep a run's last step visible above a card A card such as the peer-message receipt has no words of its own, so folding the narration into it hid everything the bot said. Steps now fold only into a written answer or the live row; before a card the last step stays visible. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tgvrj34NMnaqvtgvruYQZZ --------- Co-authored-by: Claude <noreply@anthropic.com>
761 lines
22 KiB
TypeScript
761 lines
22 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.
|
|
// A run that narrates two steps before its answer, as long tool runs do.
|
|
if (lower.includes("show your steps")) {
|
|
const listSchedules = { name: "schedule_list", args: {} };
|
|
return [
|
|
{ assistant: "Opening the page.", toolCalls: [listSchedules] },
|
|
{ assistant: "Reading the article.", toolCalls: [listSchedules] },
|
|
{ assistant: "Here is the summary.", complete: true },
|
|
];
|
|
}
|
|
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 autopilot's own runs: five steps, each reported, then the result.
|
|
const autopilot = /^Autopilot:.*\nOrder: (.*)$/m.exec(prompt);
|
|
if (autopilot) {
|
|
const steps = Number(/^Steps so far: (\d+)$/m.exec(prompt)?.[1] ?? 0);
|
|
if (/idle/i.test(autopilot[1] ?? "")) {
|
|
return [{ assistant: "still looking.", complete: true }];
|
|
}
|
|
const step = steps + 1;
|
|
return [
|
|
{
|
|
assistant: "",
|
|
toolCalls: [
|
|
{
|
|
name: "autopilot_checkpoint",
|
|
args: { summary: `Ticket ${step} tidied`, next: `Ticket ${step + 1}` },
|
|
},
|
|
...(step >= 5
|
|
? [
|
|
{
|
|
name: "autopilot_finish",
|
|
args: { result: "Tidied 5 tickets; nothing waits on you." },
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
complete: true,
|
|
},
|
|
];
|
|
}
|
|
if (lower.includes("work through the night")) {
|
|
return [
|
|
{
|
|
assistant: "on it. i will keep going until the tickets are tidy.",
|
|
toolCalls: [
|
|
{
|
|
name: "autopilot_start",
|
|
args: {
|
|
goal: lower.includes("idle") ? "Idle check" : "Tidy up the tickets",
|
|
focus: "Only tidy, change nothing else",
|
|
},
|
|
},
|
|
],
|
|
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,
|
|
},
|
|
];
|
|
}
|
|
// A look is offered, not applied: the card shows it and the user decides.
|
|
if (lower.includes("show me a new look")) {
|
|
return [
|
|
{
|
|
assistant: "",
|
|
toolCalls: [
|
|
{
|
|
name: "suggest",
|
|
args: {
|
|
tool: "update_bot",
|
|
args: { look: "oo1:c=F4A7B9;sp=cat;la=soft;ck=pink;ew=round;hd=chef" },
|
|
why: "Ready for recipe nights.",
|
|
},
|
|
},
|
|
],
|
|
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)}`;
|
|
}
|