openbot 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +8 -0
- package/AGENTS.md +68 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE +21 -0
- package/README.md +117 -14
- package/dist/agents/system.js +106 -0
- package/dist/app/cli.js +27 -0
- package/dist/app/config.js +64 -0
- package/dist/app/server.js +237 -0
- package/dist/app/utils.js +35 -0
- package/dist/harness/agent-harness.js +45 -0
- package/dist/harness/mcp.js +61 -0
- package/dist/harness/orchestrator.js +273 -0
- package/dist/harness/process.js +7 -0
- package/dist/plugins/ai-sdk.js +141 -0
- package/dist/plugins/delegation.js +52 -0
- package/dist/plugins/mcp.js +140 -0
- package/dist/plugins/storage.js +502 -0
- package/dist/plugins/ui.js +47 -0
- package/dist/registry/plugins.js +73 -0
- package/dist/services/storage.js +724 -0
- package/docs/README.md +7 -0
- package/docs/agents.md +83 -0
- package/docs/architecture.md +34 -0
- package/docs/plugins.md +77 -0
- package/logo-black.png +0 -0
- package/{dist/assets/logo.js → logo-black.svg} +24 -24
- package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
- package/package.json +10 -9
- package/src/agents/system.ts +112 -0
- package/src/app/cli.ts +38 -0
- package/src/app/config.ts +104 -0
- package/src/app/server.ts +284 -0
- package/src/app/types.ts +476 -0
- package/src/app/utils.ts +43 -0
- package/src/assets/icon.svg +1 -0
- package/src/harness/agent-harness.ts +58 -0
- package/src/harness/mcp.ts +78 -0
- package/src/harness/orchestrator.ts +342 -0
- package/src/harness/process.ts +9 -0
- package/src/harness/types.ts +34 -0
- package/src/plugins/ai-sdk.ts +197 -0
- package/src/plugins/delegation.ts +60 -0
- package/src/plugins/mcp.ts +154 -0
- package/src/plugins/storage.ts +725 -0
- package/src/plugins/ui.ts +57 -0
- package/src/registry/plugins.ts +85 -0
- package/src/services/storage.ts +957 -0
- package/tsconfig.json +18 -0
- package/dist/agents/agent-creator.js +0 -74
- package/dist/agents/browser-agent.js +0 -31
- package/dist/agents/os-agent.js +0 -32
- package/dist/agents/planner-agent.js +0 -32
- package/dist/agents/topic-agent.js +0 -46
- package/dist/architecture/execution-engine.js +0 -151
- package/dist/architecture/intent-classifier.js +0 -26
- package/dist/architecture/planner.js +0 -106
- package/dist/automation-worker.js +0 -121
- package/dist/automations.js +0 -52
- package/dist/cli.js +0 -275
- package/dist/config.js +0 -53
- package/dist/core/agents.js +0 -41
- package/dist/core/delegation.js +0 -230
- package/dist/core/manager.js +0 -96
- package/dist/core/plugins.js +0 -74
- package/dist/core/router.js +0 -191
- package/dist/handlers/init.js +0 -29
- package/dist/handlers/session-change.js +0 -21
- package/dist/handlers/settings.js +0 -47
- package/dist/handlers/tab-change.js +0 -14
- package/dist/installers.js +0 -156
- package/dist/marketplace.js +0 -80
- package/dist/model-catalog.js +0 -132
- package/dist/model-defaults.js +0 -25
- package/dist/models.js +0 -47
- package/dist/open-bot.js +0 -51
- package/dist/orchestrator/direct-invocation.js +0 -13
- package/dist/orchestrator/events.js +0 -36
- package/dist/orchestrator/state.js +0 -54
- package/dist/orchestrator.js +0 -422
- package/dist/plugins/agent/index.js +0 -81
- package/dist/plugins/approval/index.js +0 -100
- package/dist/plugins/brain/identity.js +0 -77
- package/dist/plugins/brain/index.js +0 -204
- package/dist/plugins/brain/memory.js +0 -120
- package/dist/plugins/brain/prompt.js +0 -46
- package/dist/plugins/brain/types.js +0 -45
- package/dist/plugins/brain/ui.js +0 -7
- package/dist/plugins/browser/index.js +0 -629
- package/dist/plugins/browser/ui.js +0 -13
- package/dist/plugins/file-system/index.js +0 -171
- package/dist/plugins/file-system/ui.js +0 -6
- package/dist/plugins/llm/context-budget.js +0 -139
- package/dist/plugins/llm/context-shaping.js +0 -177
- package/dist/plugins/llm/index.js +0 -380
- package/dist/plugins/memory/index.js +0 -220
- package/dist/plugins/memory/memory.js +0 -122
- package/dist/plugins/memory/prompt.js +0 -55
- package/dist/plugins/memory/types.js +0 -45
- package/dist/plugins/meta-agent/index.js +0 -570
- package/dist/plugins/meta-agent/ui.js +0 -11
- package/dist/plugins/shell/index.js +0 -100
- package/dist/plugins/shell/ui.js +0 -6
- package/dist/plugins/skills/index.js +0 -286
- package/dist/plugins/skills/types.js +0 -50
- package/dist/plugins/skills/ui.js +0 -12
- package/dist/registry/agent-registry.js +0 -35
- package/dist/registry/index.js +0 -2
- package/dist/registry/plugin-loader.js +0 -499
- package/dist/registry/plugin-registry.js +0 -44
- package/dist/registry/ts-agent-loader.js +0 -82
- package/dist/registry/yaml-agent-loader.js +0 -246
- package/dist/runtime/execution-trace.js +0 -41
- package/dist/runtime/intent-routing.js +0 -26
- package/dist/runtime/openbot-runtime.js +0 -354
- package/dist/server.js +0 -890
- package/dist/session.js +0 -179
- package/dist/ui/block.js +0 -12
- package/dist/ui/header.js +0 -52
- package/dist/ui/layout.js +0 -26
- package/dist/ui/navigation.js +0 -15
- package/dist/ui/settings.js +0 -106
- package/dist/ui/skills.js +0 -7
- package/dist/ui/thread.js +0 -16
- package/dist/ui/widgets/action-list.js +0 -2
- package/dist/ui/widgets/approval-card.js +0 -9
- package/dist/ui/widgets/code-snippet.js +0 -2
- package/dist/ui/widgets/data-block.js +0 -2
- package/dist/ui/widgets/data-table.js +0 -2
- package/dist/ui/widgets/delegation.js +0 -29
- package/dist/ui/widgets/empty-state.js +0 -2
- package/dist/ui/widgets/index.js +0 -23
- package/dist/ui/widgets/inquiry.js +0 -7
- package/dist/ui/widgets/key-value.js +0 -2
- package/dist/ui/widgets/progress-step.js +0 -2
- package/dist/ui/widgets/resource-card.js +0 -2
- package/dist/ui/widgets/status.js +0 -2
- package/dist/ui/widgets/todo-list.js +0 -2
- package/dist/version.js +0 -62
- /package/dist/{types.js → app/types.js} +0 -0
- /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"types": ["node"]
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { llmPlugin } from "../plugins/llm/index.js";
|
|
3
|
-
import { fileSystemToolDefinitions, fileSystemPlugin } from "../plugins/file-system/index.js";
|
|
4
|
-
import { DEFAULT_BASE_DIR, resolvePath } from "../config.js";
|
|
5
|
-
export const agentCreatorAgent = (options) => (builder) => {
|
|
6
|
-
const { model } = options;
|
|
7
|
-
const baseDir = resolvePath(DEFAULT_BASE_DIR);
|
|
8
|
-
const agentsDir = path.join(baseDir, "agents");
|
|
9
|
-
builder
|
|
10
|
-
.use(fileSystemPlugin({ baseDir }))
|
|
11
|
-
.use(llmPlugin({
|
|
12
|
-
model,
|
|
13
|
-
system: `You are the OpenBot Agent Creator. Your job is to help users create AND update custom OpenBot agents via natural language.
|
|
14
|
-
|
|
15
|
-
Configuration storage:
|
|
16
|
-
1. The Default Agent (the main orchestrator, usually named "OpenBot") is defined in: ${baseDir}/AGENT.md.
|
|
17
|
-
2. Custom Agents live in their own subdirectories: ${agentsDir}/<agent-name>/AGENT.md.
|
|
18
|
-
|
|
19
|
-
The AGENT.md file uses Markdown with a YAML frontmatter block at the top.
|
|
20
|
-
|
|
21
|
-
Frontmatter fields (required):
|
|
22
|
-
---
|
|
23
|
-
name: <slug-name>
|
|
24
|
-
description: <short description>
|
|
25
|
-
plugins:
|
|
26
|
-
- shell
|
|
27
|
-
- file-system
|
|
28
|
-
- name: approval
|
|
29
|
-
config:
|
|
30
|
-
rules: []
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
Optional fields:
|
|
34
|
-
- model: <provider/model-id>
|
|
35
|
-
- subscribe: [<event-type>, ...]
|
|
36
|
-
|
|
37
|
-
The Markdown body below the frontmatter contains the agent's persona and detailed behavioral instructions.
|
|
38
|
-
|
|
39
|
-
Official plugin catalog (prefer these):
|
|
40
|
-
- shell: execute shell commands
|
|
41
|
-
- file-system: read/write/list/delete files
|
|
42
|
-
- approval: require user approval before risky actions
|
|
43
|
-
- browser-tools: web automation
|
|
44
|
-
- search: search/retrieval tools
|
|
45
|
-
|
|
46
|
-
Tool Usage:
|
|
47
|
-
1. Use \`listFiles\` to explore existing agents in ${agentsDir}.
|
|
48
|
-
2. Use \`readFile\` to inspect an existing agent's AGENT.md before updating it.
|
|
49
|
-
3. Use \`writeFile\` to create or update an agent's AGENT.md.
|
|
50
|
-
|
|
51
|
-
Rules:
|
|
52
|
-
1. Do not write files until user explicitly approves the proposed changes.
|
|
53
|
-
2. For updates, ALWAYS read the current AGENT.md first using \`readFile\`, then propose changes.
|
|
54
|
-
3. For the Default Agent, ALWAYS use ${baseDir}/AGENT.md.
|
|
55
|
-
4. For Custom Agents, ALWAYS use ${agentsDir}/<name>/AGENT.md.
|
|
56
|
-
5. If you're unsure which agent to update, use \`listFiles\` on ${agentsDir} to see available agents.
|
|
57
|
-
6. Prefer official plugins.
|
|
58
|
-
7. Keep frontmatter minimal; include only meaningful fields.
|
|
59
|
-
8. If required info is missing, ask focused follow-up questions.
|
|
60
|
-
9. After writing, confirm that the correct AGENT.md was updated.
|
|
61
|
-
10. The server hot-reloads ~/.openbot changes.
|
|
62
|
-
11. ALWAYS use the consolidated AGENT.md format. Do NOT create agent.yaml files anymore.
|
|
63
|
-
|
|
64
|
-
Workflow:
|
|
65
|
-
1. Determine whether this is create or update, and if it's for the Default Agent or a Custom Agent.
|
|
66
|
-
2. If it's an update, use \`readFile\` to get the current configuration.
|
|
67
|
-
3. If the agent name is ambiguous, use \`listFiles\` to find the correct one.
|
|
68
|
-
4. Collect missing requirements from the user.
|
|
69
|
-
5. Show a proposed AGENT.md content (frontmatter + instructions) and request explicit approval.
|
|
70
|
-
6. On approval, write the appropriate AGENT.md using \`writeFile\`.
|
|
71
|
-
7. Return a short completion summary.`,
|
|
72
|
-
toolDefinitions: fileSystemToolDefinitions, // Give it access to write files
|
|
73
|
-
}));
|
|
74
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
|
-
import { browserPlugin, browserToolDefinitions, } from "../plugins/browser/index.js";
|
|
3
|
-
const DEFAULT_SYSTEM_PROMPT = `You are a Browser Agent.
|
|
4
|
-
Your job is to help the user browse the web, extract information, and perform actions.
|
|
5
|
-
|
|
6
|
-
Tools:
|
|
7
|
-
- browser_act: Give a natural language instruction to interact with the page (e.g. "click the login button").
|
|
8
|
-
- browser_observe: See what actions are available on the current page.
|
|
9
|
-
- browser_extract: Pull structured data from the page.
|
|
10
|
-
- browser_state_update: Get a fresh screenshot and URL.
|
|
11
|
-
|
|
12
|
-
Always describe what you see and what you are doing.`;
|
|
13
|
-
/**
|
|
14
|
-
* High-level Browser Agent plugin for Melony.
|
|
15
|
-
* Composes the low-level browserPlugin with an llmPlugin.
|
|
16
|
-
*/
|
|
17
|
-
export const browserAgent = (options) => (builder) => {
|
|
18
|
-
const { model, systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
|
|
19
|
-
builder
|
|
20
|
-
.use(browserPlugin({ ...options, model }))
|
|
21
|
-
.use(llmPlugin({
|
|
22
|
-
model,
|
|
23
|
-
system: systemPrompt,
|
|
24
|
-
toolDefinitions: browserToolDefinitions,
|
|
25
|
-
promptInputType: "agent:browser:input",
|
|
26
|
-
actionResultInputType: "agent:browser:result",
|
|
27
|
-
completionEventType: "agent:browser:output",
|
|
28
|
-
}));
|
|
29
|
-
// NOTE: Bridge-back to the manager is handled generically by open-bot.ts.
|
|
30
|
-
// No per-agent boilerplate needed.
|
|
31
|
-
};
|
package/dist/agents/os-agent.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
|
-
import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
|
|
3
|
-
import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
|
|
4
|
-
import approvalPlugin from "../plugins/approval/index.js";
|
|
5
|
-
const DEFAULT_SYSTEM_PROMPT = `You are an OS Agent with access to the shell and file system.
|
|
6
|
-
Your job is to help the user with file operations and command execution.
|
|
7
|
-
You can read, write, list, and delete files, as well as execute shell commands.
|
|
8
|
-
Always be careful with destructive operations.
|
|
9
|
-
When you are done with the task, summarize what you did.`;
|
|
10
|
-
export const osAgent = (options) => (builder) => {
|
|
11
|
-
const { model, cwd = process.cwd(), systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
|
|
12
|
-
builder
|
|
13
|
-
.use(shellPlugin({ cwd }))
|
|
14
|
-
.use(fileSystemPlugin({ baseDir: "/" }))
|
|
15
|
-
.use(approvalPlugin({
|
|
16
|
-
rules: [
|
|
17
|
-
{ action: "action:executeCommand", message: "The agent wants to execute a terminal command. Please review carefully." },
|
|
18
|
-
// { action: "action:writeFile", message: "The agent wants to write to a file." },
|
|
19
|
-
{ action: "action:deleteFile", message: "The agent wants to delete a file." },
|
|
20
|
-
],
|
|
21
|
-
}))
|
|
22
|
-
.use(llmPlugin({
|
|
23
|
-
model,
|
|
24
|
-
system: systemPrompt,
|
|
25
|
-
toolDefinitions: {
|
|
26
|
-
...shellToolDefinitions,
|
|
27
|
-
...fileSystemToolDefinitions
|
|
28
|
-
},
|
|
29
|
-
}));
|
|
30
|
-
// NOTE: Bridge-back to the manager is handled generically by open-bot.ts.
|
|
31
|
-
// No per-agent boilerplate needed.
|
|
32
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { llmPlugin } from "../plugins/llm/index.js";
|
|
2
|
-
const PLANNER_SYSTEM_PROMPT = `You are a planning specialist agent.
|
|
3
|
-
|
|
4
|
-
Your job:
|
|
5
|
-
- Convert a user intent into a short, executable plan.
|
|
6
|
-
- Keep plans practical and concise.
|
|
7
|
-
- Prefer 2-5 steps.
|
|
8
|
-
- Each step should specify the best agent to execute it when relevant.
|
|
9
|
-
|
|
10
|
-
Available agent naming convention:
|
|
11
|
-
- Use exact agent names when assigning (e.g. os, browser, topic, planner-agent, etc.).
|
|
12
|
-
|
|
13
|
-
Output format:
|
|
14
|
-
- Return strict JSON only.
|
|
15
|
-
- Shape:
|
|
16
|
-
{
|
|
17
|
-
"goal": "...",
|
|
18
|
-
"steps": [
|
|
19
|
-
{ "id": "step_1", "agent": "agent-name-or-manager", "task": "..." }
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
Rules:
|
|
24
|
-
- If no specialist agent is needed for a step, set "agent" to "manager".
|
|
25
|
-
- Do not include markdown fences.
|
|
26
|
-
- Do not include explanatory prose outside JSON.`;
|
|
27
|
-
export const plannerAgent = (options) => (builder) => {
|
|
28
|
-
builder.use(llmPlugin({
|
|
29
|
-
model: options.model,
|
|
30
|
-
system: PLANNER_SYSTEM_PROMPT,
|
|
31
|
-
}));
|
|
32
|
-
};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { generateText } from "ai";
|
|
2
|
-
function getTitleSourceMessages(state, event) {
|
|
3
|
-
if (Array.isArray(state.messages) && state.messages.length >= 2) {
|
|
4
|
-
return state.messages;
|
|
5
|
-
}
|
|
6
|
-
const agentName = event.meta?.agentName;
|
|
7
|
-
if (!agentName)
|
|
8
|
-
return [];
|
|
9
|
-
const agentMessages = state.agentStates?.[agentName]?.messages;
|
|
10
|
-
if (Array.isArray(agentMessages) && agentMessages.length >= 2) {
|
|
11
|
-
return agentMessages;
|
|
12
|
-
}
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
export const topicAgent = (options) => (builder) => {
|
|
16
|
-
builder.on("agent:output", async function* (event, { state }) {
|
|
17
|
-
// Only title if it doesn't have one and there's history
|
|
18
|
-
if (state.title) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
const messagesForTitle = getTitleSourceMessages(state, event);
|
|
22
|
-
// Don't title if there are too few messages
|
|
23
|
-
if (messagesForTitle.length < 2) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
const { text } = await generateText({
|
|
28
|
-
model: options.model,
|
|
29
|
-
system: "You are a Topic Agent. Create a very concise (3-5 words) title for the conversation based on the user's intent. Do not use quotes or special characters.",
|
|
30
|
-
prompt: `Analyze these messages and provide a title: ${JSON.stringify(messagesForTitle.slice(0, 6))}`,
|
|
31
|
-
});
|
|
32
|
-
const newTitle = text.replace(/["']/g, "").trim();
|
|
33
|
-
if (newTitle) {
|
|
34
|
-
state.title = newTitle;
|
|
35
|
-
// Notify the client to refresh the sessions list in the sidebar
|
|
36
|
-
yield {
|
|
37
|
-
type: "client:invalidate",
|
|
38
|
-
data: { tags: ["sessions"] }
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
console.error("[topic-agent] Failed to generate title:", error);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
};
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
const DEFAULT_POLICY = {
|
|
2
|
-
maxRetries: 1,
|
|
3
|
-
stepTimeoutMs: 45000,
|
|
4
|
-
};
|
|
5
|
-
function nowIso() {
|
|
6
|
-
return new Date().toISOString();
|
|
7
|
-
}
|
|
8
|
-
function toError(error) {
|
|
9
|
-
return error instanceof Error ? error.message : String(error);
|
|
10
|
-
}
|
|
11
|
-
function updateTrace(state, patch) {
|
|
12
|
-
const next = {
|
|
13
|
-
traceId: patch.traceId ?? state.execution?.traceId ?? `trace_${Date.now()}`,
|
|
14
|
-
state: patch.state ?? state.execution?.state ?? "RECEIVED",
|
|
15
|
-
intent: patch.intent ?? state.execution?.intent,
|
|
16
|
-
plan: patch.plan ?? state.execution?.plan,
|
|
17
|
-
currentStepId: patch.currentStepId ?? state.execution?.currentStepId,
|
|
18
|
-
error: patch.error ?? state.execution?.error,
|
|
19
|
-
updatedAt: nowIso(),
|
|
20
|
-
};
|
|
21
|
-
state.execution = next;
|
|
22
|
-
return next;
|
|
23
|
-
}
|
|
24
|
-
function toExecutionEvent(trace) {
|
|
25
|
-
return {
|
|
26
|
-
type: "execution:state",
|
|
27
|
-
data: {
|
|
28
|
-
traceId: trace.traceId,
|
|
29
|
-
state: trace.state,
|
|
30
|
-
currentStepId: trace.currentStepId,
|
|
31
|
-
error: trace.error,
|
|
32
|
-
intentType: trace.intent?.type,
|
|
33
|
-
planSteps: trace.plan?.steps.length,
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function withTimeout(promise, timeoutMs) {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
const timer = setTimeout(() => {
|
|
40
|
-
reject(new Error(`Step timed out after ${timeoutMs}ms`));
|
|
41
|
-
}, timeoutMs);
|
|
42
|
-
promise
|
|
43
|
-
.then((value) => {
|
|
44
|
-
clearTimeout(timer);
|
|
45
|
-
resolve(value);
|
|
46
|
-
})
|
|
47
|
-
.catch((error) => {
|
|
48
|
-
clearTimeout(timer);
|
|
49
|
-
reject(error);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
function hasPendingApprovals(state) {
|
|
54
|
-
const agentStates = state.agentStates || {};
|
|
55
|
-
return Object.values(agentStates).some((agentState) => !!agentState.pendingApprovals &&
|
|
56
|
-
Object.keys(agentState.pendingApprovals).length > 0);
|
|
57
|
-
}
|
|
58
|
-
async function* executeStep(step, input, policy) {
|
|
59
|
-
let lastError;
|
|
60
|
-
for (let attempt = 0; attempt <= policy.maxRetries; attempt++) {
|
|
61
|
-
try {
|
|
62
|
-
const stream = step.kind === "delegate"
|
|
63
|
-
? input.callbacks.runAgent(step.agent ?? "", step.task ?? "", step.attachments, input.state, input.runId)
|
|
64
|
-
: input.callbacks.runManager(step.content ?? "", step.attachments, input.state, input.runId);
|
|
65
|
-
while (true) {
|
|
66
|
-
const nextItem = await withTimeout(stream.next(), policy.stepTimeoutMs);
|
|
67
|
-
if (nextItem.done)
|
|
68
|
-
break;
|
|
69
|
-
const event = nextItem.value;
|
|
70
|
-
if (input.state.execution?.state === "WAITING_APPROVAL") {
|
|
71
|
-
const trace = updateTrace(input.state, {
|
|
72
|
-
state: "EXECUTING",
|
|
73
|
-
currentStepId: step.id,
|
|
74
|
-
});
|
|
75
|
-
yield toExecutionEvent(trace);
|
|
76
|
-
}
|
|
77
|
-
yield event;
|
|
78
|
-
}
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
lastError = error;
|
|
83
|
-
if (attempt < policy.maxRetries) {
|
|
84
|
-
const trace = updateTrace(input.state, {
|
|
85
|
-
state: "EXECUTING",
|
|
86
|
-
currentStepId: step.id,
|
|
87
|
-
error: `Retry ${attempt + 1}/${policy.maxRetries}: ${toError(error)}`,
|
|
88
|
-
});
|
|
89
|
-
yield toExecutionEvent(trace);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
throw lastError instanceof Error ? lastError : new Error(toError(lastError));
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Stateful execution engine for plan steps.
|
|
98
|
-
* The engine is the commit point for executing actions from a plan.
|
|
99
|
-
*/
|
|
100
|
-
export async function* executePlan(input) {
|
|
101
|
-
const policy = {
|
|
102
|
-
...DEFAULT_POLICY,
|
|
103
|
-
...(input.policy ?? {}),
|
|
104
|
-
};
|
|
105
|
-
let trace = updateTrace(input.state, {
|
|
106
|
-
traceId: input.traceId,
|
|
107
|
-
state: "EXECUTING",
|
|
108
|
-
plan: input.plan,
|
|
109
|
-
error: undefined,
|
|
110
|
-
});
|
|
111
|
-
yield toExecutionEvent(trace);
|
|
112
|
-
try {
|
|
113
|
-
for (const step of input.plan.steps) {
|
|
114
|
-
trace = updateTrace(input.state, {
|
|
115
|
-
state: "EXECUTING",
|
|
116
|
-
currentStepId: step.id,
|
|
117
|
-
error: undefined,
|
|
118
|
-
});
|
|
119
|
-
yield toExecutionEvent(trace);
|
|
120
|
-
yield* executeStep(step, input, policy);
|
|
121
|
-
if (hasPendingApprovals(input.state)) {
|
|
122
|
-
trace = updateTrace(input.state, {
|
|
123
|
-
state: "WAITING_APPROVAL",
|
|
124
|
-
currentStepId: step.id,
|
|
125
|
-
});
|
|
126
|
-
yield toExecutionEvent(trace);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
trace = updateTrace(input.state, {
|
|
131
|
-
state: "COMPLETED",
|
|
132
|
-
currentStepId: undefined,
|
|
133
|
-
error: undefined,
|
|
134
|
-
});
|
|
135
|
-
yield toExecutionEvent(trace);
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
trace = updateTrace(input.state, {
|
|
139
|
-
state: "FAILED",
|
|
140
|
-
error: toError(error),
|
|
141
|
-
});
|
|
142
|
-
yield toExecutionEvent(trace);
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
export function setExecutionState(state, patch) {
|
|
147
|
-
return updateTrace(state, patch);
|
|
148
|
-
}
|
|
149
|
-
export function executionStateEvent(trace) {
|
|
150
|
-
return toExecutionEvent(trace);
|
|
151
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Classifies user input into a routing intent.
|
|
3
|
-
* Starts with deterministic rules; can later be upgraded to hybrid LLM+rules.
|
|
4
|
-
*/
|
|
5
|
-
export function classifyIntent(input) {
|
|
6
|
-
const raw = input.content.trim();
|
|
7
|
-
if (!raw) {
|
|
8
|
-
return { type: "chat", confidence: 0.8 };
|
|
9
|
-
}
|
|
10
|
-
if (raw.startsWith("/") || raw.startsWith("@")) {
|
|
11
|
-
const firstSpace = raw.indexOf(" ");
|
|
12
|
-
const prefix = firstSpace === -1 ? raw.slice(1) : raw.slice(1, firstSpace);
|
|
13
|
-
if (input.knownAgents.has(prefix)) {
|
|
14
|
-
return {
|
|
15
|
-
type: "agent_direct",
|
|
16
|
-
confidence: 0.99,
|
|
17
|
-
targetAgent: prefix,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
const looksLikeTask = /\b(create|build|write|fix|update|implement|run|execute|open|edit|delete|list)\b/i.test(raw);
|
|
22
|
-
if (looksLikeTask) {
|
|
23
|
-
return { type: "task", confidence: 0.75 };
|
|
24
|
-
}
|
|
25
|
-
return { type: "chat", confidence: 0.7 };
|
|
26
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { generateObject } from "ai";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
function createStepId(index) {
|
|
4
|
-
return `step_${index + 1}`;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Strategic planner that emits explicit executable steps.
|
|
8
|
-
* Initial version is deterministic to keep behavior predictable.
|
|
9
|
-
*/
|
|
10
|
-
const PlanStepSchema = z.object({
|
|
11
|
-
id: z.string(),
|
|
12
|
-
kind: z.enum(["delegate", "manager"]),
|
|
13
|
-
successCriteria: z.string(),
|
|
14
|
-
agent: z.string().optional(),
|
|
15
|
-
task: z.string().optional(),
|
|
16
|
-
content: z.string().optional(),
|
|
17
|
-
});
|
|
18
|
-
const PlanSchema = z.object({
|
|
19
|
-
goal: z.string(),
|
|
20
|
-
stopCondition: z.string(),
|
|
21
|
-
steps: z.array(PlanStepSchema).min(1).max(4),
|
|
22
|
-
});
|
|
23
|
-
function sanitizePlan(raw, input) {
|
|
24
|
-
const steps = raw.steps.map((step, index) => {
|
|
25
|
-
const normalized = {
|
|
26
|
-
id: step.id?.trim() ? step.id : createStepId(index),
|
|
27
|
-
kind: step.kind,
|
|
28
|
-
successCriteria: step.successCriteria?.trim() || "Step completed successfully.",
|
|
29
|
-
};
|
|
30
|
-
if (step.kind === "delegate") {
|
|
31
|
-
const chosenAgent = step.agent && input.knownAgents.includes(step.agent)
|
|
32
|
-
? step.agent
|
|
33
|
-
: input.knownAgents[0];
|
|
34
|
-
normalized.agent = chosenAgent;
|
|
35
|
-
normalized.task = step.task?.trim() || input.content.trim();
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
normalized.content = step.content?.trim() || input.content.trim();
|
|
39
|
-
}
|
|
40
|
-
return normalized;
|
|
41
|
-
});
|
|
42
|
-
if (steps[0]) {
|
|
43
|
-
steps[0].attachments = input.attachments;
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
goal: raw.goal?.trim() || input.content || "Handle user request",
|
|
47
|
-
stopCondition: raw.stopCondition?.trim() || "All plan steps completed successfully.",
|
|
48
|
-
steps,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function buildDeterministicPlan(input) {
|
|
52
|
-
const steps = [];
|
|
53
|
-
const content = input.content.trim();
|
|
54
|
-
const attachments = input.attachments;
|
|
55
|
-
if (input.intent.type === "agent_direct" && input.intent.targetAgent) {
|
|
56
|
-
const firstSpace = content.indexOf(" ");
|
|
57
|
-
const delegatedTask = firstSpace === -1 ? "" : content.slice(firstSpace + 1).trim();
|
|
58
|
-
steps.push({
|
|
59
|
-
id: createStepId(0),
|
|
60
|
-
kind: "delegate",
|
|
61
|
-
agent: input.intent.targetAgent,
|
|
62
|
-
task: delegatedTask,
|
|
63
|
-
attachments,
|
|
64
|
-
successCriteria: "Agent returns an output event.",
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
steps.push({
|
|
69
|
-
id: createStepId(0),
|
|
70
|
-
kind: "manager",
|
|
71
|
-
content,
|
|
72
|
-
attachments,
|
|
73
|
-
successCriteria: "Manager emits a completion message.",
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
goal: content || "Handle user request",
|
|
78
|
-
steps,
|
|
79
|
-
stopCondition: "All plan steps completed successfully.",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
export async function createPlan(input, options = {}) {
|
|
83
|
-
if (input.intent.type === "agent_direct") {
|
|
84
|
-
return buildDeterministicPlan(input);
|
|
85
|
-
}
|
|
86
|
-
if (!options.model) {
|
|
87
|
-
return buildDeterministicPlan(input);
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
const { object } = await generateObject({
|
|
91
|
-
model: options.model,
|
|
92
|
-
schema: PlanSchema,
|
|
93
|
-
system: `You are a strategic planner for an AI orchestration system.
|
|
94
|
-
Output compact, executable plans.
|
|
95
|
-
Available agents: ${input.knownAgents.join(", ") || "none"}.
|
|
96
|
-
Use "delegate" only when an agent is clearly needed; otherwise use "manager".`,
|
|
97
|
-
prompt: `Intent: ${JSON.stringify(input.intent)}
|
|
98
|
-
User request: ${input.content}
|
|
99
|
-
Return a plan with 1-4 steps.`,
|
|
100
|
-
});
|
|
101
|
-
return sanitizePlan(object, input);
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return buildDeterministicPlan(input);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
function minuteKey(date) {
|
|
2
|
-
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}T${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
3
|
-
}
|
|
4
|
-
function parseNumber(value) {
|
|
5
|
-
if (!/^\d+$/.test(value))
|
|
6
|
-
return null;
|
|
7
|
-
return Number(value);
|
|
8
|
-
}
|
|
9
|
-
function matchToken(value, token, min, max) {
|
|
10
|
-
const [base, stepRaw] = token.split("/");
|
|
11
|
-
const step = stepRaw ? parseNumber(stepRaw) : null;
|
|
12
|
-
if (stepRaw && (!step || step <= 0))
|
|
13
|
-
return false;
|
|
14
|
-
const checkStep = (start) => {
|
|
15
|
-
if (!step)
|
|
16
|
-
return true;
|
|
17
|
-
return (value - start) % step === 0;
|
|
18
|
-
};
|
|
19
|
-
if (base === "*") {
|
|
20
|
-
return checkStep(min);
|
|
21
|
-
}
|
|
22
|
-
if (base.includes("-")) {
|
|
23
|
-
const [startRaw, endRaw] = base.split("-");
|
|
24
|
-
const start = parseNumber(startRaw);
|
|
25
|
-
const end = parseNumber(endRaw);
|
|
26
|
-
if (start === null || end === null || start < min || end > max || start > end)
|
|
27
|
-
return false;
|
|
28
|
-
if (value < start || value > end)
|
|
29
|
-
return false;
|
|
30
|
-
return checkStep(start);
|
|
31
|
-
}
|
|
32
|
-
const exact = parseNumber(base);
|
|
33
|
-
if (exact === null || exact < min || exact > max)
|
|
34
|
-
return false;
|
|
35
|
-
if (value !== exact)
|
|
36
|
-
return false;
|
|
37
|
-
return checkStep(exact);
|
|
38
|
-
}
|
|
39
|
-
function matchField(value, field, min, max) {
|
|
40
|
-
const tokens = field.split(",").map((t) => t.trim()).filter(Boolean);
|
|
41
|
-
if (tokens.length === 0)
|
|
42
|
-
return false;
|
|
43
|
-
return tokens.some((token) => matchToken(value, token, min, max));
|
|
44
|
-
}
|
|
45
|
-
export function isCronDue(cron, date) {
|
|
46
|
-
const fields = cron.trim().split(/\s+/);
|
|
47
|
-
if (fields.length !== 5)
|
|
48
|
-
return false;
|
|
49
|
-
const [minuteField, hourField, domField, monthField, dowField] = fields;
|
|
50
|
-
const minute = date.getMinutes();
|
|
51
|
-
const hour = date.getHours();
|
|
52
|
-
const dayOfMonth = date.getDate();
|
|
53
|
-
const month = date.getMonth() + 1;
|
|
54
|
-
const dayOfWeek = date.getDay();
|
|
55
|
-
const minuteMatch = matchField(minute, minuteField, 0, 59);
|
|
56
|
-
const hourMatch = matchField(hour, hourField, 0, 23);
|
|
57
|
-
const monthMatch = matchField(month, monthField, 1, 12);
|
|
58
|
-
const domMatch = matchField(dayOfMonth, domField, 1, 31);
|
|
59
|
-
const dowMatch = matchField(dayOfWeek, dowField, 0, 7) || (dayOfWeek === 0 && matchField(7, dowField, 0, 7));
|
|
60
|
-
const domWildcard = domField.trim() === "*";
|
|
61
|
-
const dowWildcard = dowField.trim() === "*";
|
|
62
|
-
const dayMatch = domWildcard && dowWildcard
|
|
63
|
-
? true
|
|
64
|
-
: domWildcard
|
|
65
|
-
? dowMatch
|
|
66
|
-
: dowWildcard
|
|
67
|
-
? domMatch
|
|
68
|
-
: domMatch || dowMatch;
|
|
69
|
-
return minuteMatch && hourMatch && monthMatch && dayMatch;
|
|
70
|
-
}
|
|
71
|
-
export function startAutomationWorker(options) {
|
|
72
|
-
const { listAutomations, runAutomation, pollIntervalMs = 60000, logger = console, } = options;
|
|
73
|
-
const inFlightByAutomation = new Set();
|
|
74
|
-
const seenMinuteByAutomation = new Map();
|
|
75
|
-
let polling = false;
|
|
76
|
-
const tick = async () => {
|
|
77
|
-
if (polling)
|
|
78
|
-
return;
|
|
79
|
-
polling = true;
|
|
80
|
-
try {
|
|
81
|
-
const now = new Date();
|
|
82
|
-
const currentMinute = minuteKey(now);
|
|
83
|
-
const automations = await listAutomations();
|
|
84
|
-
for (const automation of automations) {
|
|
85
|
-
if (!automation.enabled)
|
|
86
|
-
continue;
|
|
87
|
-
if (inFlightByAutomation.has(automation.id))
|
|
88
|
-
continue;
|
|
89
|
-
if (seenMinuteByAutomation.get(automation.id) === currentMinute)
|
|
90
|
-
continue;
|
|
91
|
-
const due = isCronDue(automation.cron, now);
|
|
92
|
-
if (!due)
|
|
93
|
-
continue;
|
|
94
|
-
seenMinuteByAutomation.set(automation.id, currentMinute);
|
|
95
|
-
inFlightByAutomation.add(automation.id);
|
|
96
|
-
void runAutomation(automation, new Date(now))
|
|
97
|
-
.catch((err) => {
|
|
98
|
-
logger.error(`[automations] Failed run for "${automation.name}" (${automation.id}):`, err);
|
|
99
|
-
})
|
|
100
|
-
.finally(() => {
|
|
101
|
-
inFlightByAutomation.delete(automation.id);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
logger.error("[automations] Worker tick failed:", err);
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
polling = false;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const interval = setInterval(() => {
|
|
113
|
-
void tick();
|
|
114
|
-
}, pollIntervalMs);
|
|
115
|
-
logger.info(`[automations] Worker started (poll interval ${pollIntervalMs}ms)`);
|
|
116
|
-
void tick();
|
|
117
|
-
return () => {
|
|
118
|
-
clearInterval(interval);
|
|
119
|
-
logger.info("[automations] Worker stopped");
|
|
120
|
-
};
|
|
121
|
-
}
|