openbot 0.2.12 → 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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/AGENTS.md +68 -0
  3. package/CONTRIBUTING.md +74 -0
  4. package/LICENSE +21 -0
  5. package/README.md +117 -14
  6. package/dist/agents/system.js +106 -0
  7. package/dist/app/cli.js +27 -0
  8. package/dist/app/config.js +64 -0
  9. package/dist/app/server.js +237 -0
  10. package/dist/app/utils.js +35 -0
  11. package/dist/harness/agent-harness.js +45 -0
  12. package/dist/harness/mcp.js +61 -0
  13. package/dist/harness/orchestrator.js +273 -0
  14. package/dist/harness/process.js +7 -0
  15. package/dist/plugins/ai-sdk.js +141 -0
  16. package/dist/plugins/delegation.js +52 -0
  17. package/dist/plugins/mcp.js +140 -0
  18. package/dist/plugins/storage.js +502 -0
  19. package/dist/plugins/ui.js +47 -0
  20. package/dist/registry/plugins.js +73 -0
  21. package/dist/services/storage.js +724 -0
  22. package/docs/README.md +7 -0
  23. package/docs/agents.md +83 -0
  24. package/docs/architecture.md +34 -0
  25. package/docs/plugins.md +77 -0
  26. package/logo-black.png +0 -0
  27. package/{dist/assets/logo.js → logo-black.svg} +24 -24
  28. package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
  29. package/package.json +10 -9
  30. package/src/agents/system.ts +112 -0
  31. package/src/app/cli.ts +38 -0
  32. package/src/app/config.ts +104 -0
  33. package/src/app/server.ts +284 -0
  34. package/src/app/types.ts +476 -0
  35. package/src/app/utils.ts +43 -0
  36. package/src/assets/icon.svg +1 -0
  37. package/src/harness/agent-harness.ts +58 -0
  38. package/src/harness/mcp.ts +78 -0
  39. package/src/harness/orchestrator.ts +342 -0
  40. package/src/harness/process.ts +9 -0
  41. package/src/harness/types.ts +34 -0
  42. package/src/plugins/ai-sdk.ts +197 -0
  43. package/src/plugins/delegation.ts +60 -0
  44. package/src/plugins/mcp.ts +154 -0
  45. package/src/plugins/storage.ts +725 -0
  46. package/src/plugins/ui.ts +57 -0
  47. package/src/registry/plugins.ts +85 -0
  48. package/src/services/storage.ts +957 -0
  49. package/tsconfig.json +18 -0
  50. package/dist/agents/agent-creator.js +0 -74
  51. package/dist/agents/browser-agent.js +0 -31
  52. package/dist/agents/os-agent.js +0 -32
  53. package/dist/agents/planner-agent.js +0 -32
  54. package/dist/agents/topic-agent.js +0 -46
  55. package/dist/architecture/execution-engine.js +0 -151
  56. package/dist/architecture/intent-classifier.js +0 -26
  57. package/dist/architecture/planner.js +0 -106
  58. package/dist/automation-worker.js +0 -121
  59. package/dist/automations.js +0 -52
  60. package/dist/cli.js +0 -279
  61. package/dist/config.js +0 -53
  62. package/dist/core/agents.js +0 -41
  63. package/dist/core/delegation.js +0 -230
  64. package/dist/core/manager.js +0 -96
  65. package/dist/core/plugins.js +0 -74
  66. package/dist/core/router.js +0 -191
  67. package/dist/handlers/init.js +0 -29
  68. package/dist/handlers/session-change.js +0 -21
  69. package/dist/handlers/settings.js +0 -47
  70. package/dist/handlers/tab-change.js +0 -14
  71. package/dist/installers.js +0 -156
  72. package/dist/marketplace.js +0 -80
  73. package/dist/model-catalog.js +0 -132
  74. package/dist/model-defaults.js +0 -25
  75. package/dist/models.js +0 -47
  76. package/dist/open-bot.js +0 -51
  77. package/dist/orchestrator/direct-invocation.js +0 -13
  78. package/dist/orchestrator/events.js +0 -36
  79. package/dist/orchestrator/state.js +0 -54
  80. package/dist/orchestrator.js +0 -422
  81. package/dist/plugins/agent/index.js +0 -81
  82. package/dist/plugins/approval/index.js +0 -100
  83. package/dist/plugins/brain/identity.js +0 -77
  84. package/dist/plugins/brain/index.js +0 -204
  85. package/dist/plugins/brain/memory.js +0 -120
  86. package/dist/plugins/brain/prompt.js +0 -46
  87. package/dist/plugins/brain/types.js +0 -45
  88. package/dist/plugins/brain/ui.js +0 -7
  89. package/dist/plugins/browser/index.js +0 -629
  90. package/dist/plugins/browser/ui.js +0 -13
  91. package/dist/plugins/file-system/index.js +0 -171
  92. package/dist/plugins/file-system/ui.js +0 -6
  93. package/dist/plugins/llm/context-budget.js +0 -139
  94. package/dist/plugins/llm/context-shaping.js +0 -177
  95. package/dist/plugins/llm/index.js +0 -380
  96. package/dist/plugins/memory/index.js +0 -220
  97. package/dist/plugins/memory/memory.js +0 -122
  98. package/dist/plugins/memory/prompt.js +0 -55
  99. package/dist/plugins/memory/types.js +0 -45
  100. package/dist/plugins/meta-agent/index.js +0 -570
  101. package/dist/plugins/meta-agent/ui.js +0 -11
  102. package/dist/plugins/shell/index.js +0 -100
  103. package/dist/plugins/shell/ui.js +0 -6
  104. package/dist/plugins/skills/index.js +0 -286
  105. package/dist/plugins/skills/types.js +0 -50
  106. package/dist/plugins/skills/ui.js +0 -12
  107. package/dist/registry/agent-registry.js +0 -35
  108. package/dist/registry/index.js +0 -2
  109. package/dist/registry/plugin-loader.js +0 -499
  110. package/dist/registry/plugin-registry.js +0 -44
  111. package/dist/registry/ts-agent-loader.js +0 -82
  112. package/dist/registry/yaml-agent-loader.js +0 -246
  113. package/dist/runtime/execution-trace.js +0 -41
  114. package/dist/runtime/intent-routing.js +0 -26
  115. package/dist/runtime/openbot-runtime.js +0 -354
  116. package/dist/server.js +0 -890
  117. package/dist/session.js +0 -179
  118. package/dist/ui/block.js +0 -12
  119. package/dist/ui/header.js +0 -52
  120. package/dist/ui/layout.js +0 -26
  121. package/dist/ui/navigation.js +0 -15
  122. package/dist/ui/settings.js +0 -106
  123. package/dist/ui/skills.js +0 -7
  124. package/dist/ui/thread.js +0 -16
  125. package/dist/ui/widgets/action-list.js +0 -2
  126. package/dist/ui/widgets/approval-card.js +0 -9
  127. package/dist/ui/widgets/code-snippet.js +0 -2
  128. package/dist/ui/widgets/data-block.js +0 -2
  129. package/dist/ui/widgets/data-table.js +0 -2
  130. package/dist/ui/widgets/delegation.js +0 -29
  131. package/dist/ui/widgets/empty-state.js +0 -2
  132. package/dist/ui/widgets/index.js +0 -23
  133. package/dist/ui/widgets/inquiry.js +0 -7
  134. package/dist/ui/widgets/key-value.js +0 -2
  135. package/dist/ui/widgets/progress-step.js +0 -2
  136. package/dist/ui/widgets/resource-card.js +0 -2
  137. package/dist/ui/widgets/status.js +0 -2
  138. package/dist/ui/widgets/todo-list.js +0 -2
  139. package/dist/version.js +0 -62
  140. /package/dist/{types.js → app/types.js} +0 -0
  141. /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
- };
@@ -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
- }