@meetopenbot/openbot 1.2.1 → 1.2.4

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/dist/history.js CHANGED
@@ -71,51 +71,75 @@ function appendAssistantOutput(message, text, reasoningParts) {
71
71
  insertBeforeToolCalls(parts, [{ type: 'text', text }]);
72
72
  }
73
73
  }
74
- /**
75
- * Ensures every tool-call has a matching tool-result before calling the LLM.
76
- * Orphaned calls (interrupted run, missing :result event, etc.) get an empty
77
- * result so the conversation can resume instead of failing validation.
78
- */
79
- function fillMissingToolResults(messages) {
80
- const filled = [];
81
- const pending = new Map();
82
- const flushPending = () => {
83
- if (pending.size === 0)
84
- return;
85
- filled.push({
86
- role: 'tool',
87
- content: [...pending.entries()].map(([toolCallId, toolName]) => ({
74
+ function isToolResultPart(part) {
75
+ return Boolean(part && typeof part === 'object' && part.type === 'tool-result');
76
+ }
77
+ function assistantToolCalls(message) {
78
+ if (message.role !== 'assistant' || !Array.isArray(message.content))
79
+ return [];
80
+ const calls = [];
81
+ for (const part of message.content) {
82
+ if (part.type !== 'tool-call')
83
+ continue;
84
+ const toolCall = part;
85
+ if (typeof toolCall.toolCallId !== 'string')
86
+ continue;
87
+ calls.push({
88
+ toolCallId: toolCall.toolCallId,
89
+ toolName: typeof toolCall.toolName === 'string' ? toolCall.toolName : 'tool',
90
+ });
91
+ }
92
+ return calls;
93
+ }
94
+ function collectToolResults(messages) {
95
+ const results = new Map();
96
+ for (const message of messages) {
97
+ if (message.role !== 'tool' || !Array.isArray(message.content))
98
+ continue;
99
+ for (const part of message.content) {
100
+ if (!isToolResultPart(part) || results.has(part.toolCallId))
101
+ continue;
102
+ results.set(part.toolCallId, part);
103
+ }
104
+ }
105
+ return results;
106
+ }
107
+ function toolMessageForCalls(calls, results) {
108
+ return {
109
+ role: 'tool',
110
+ content: calls.map(({ toolCallId, toolName }) => {
111
+ const existing = results.get(toolCallId);
112
+ if (existing) {
113
+ results.delete(toolCallId);
114
+ return existing;
115
+ }
116
+ return {
88
117
  type: 'tool-result',
89
118
  toolCallId,
90
119
  toolName,
91
120
  output: { type: 'text', value: '' },
92
- })),
93
- });
94
- pending.clear();
121
+ };
122
+ }),
95
123
  };
124
+ }
125
+ /**
126
+ * Rebuild tool messages so every `tool-call` is followed immediately by matching
127
+ * results. Late results (user invoke between call and `:result`) are moved next
128
+ * to their call; unmatched results are dropped; interrupted calls get an empty
129
+ * result so the next LLM turn does not fail provider validation.
130
+ */
131
+ function sanitizeToolPairing(messages) {
132
+ const results = collectToolResults(messages);
133
+ const sanitized = [];
96
134
  for (const message of messages) {
97
- if (message.role === 'tool' && Array.isArray(message.content)) {
98
- filled.push(message);
99
- for (const part of message.content) {
100
- if (part.type === 'tool-result') {
101
- pending.delete(part.toolCallId);
102
- }
103
- }
135
+ if (message.role === 'tool')
104
136
  continue;
105
- }
106
- flushPending();
107
- filled.push(message);
108
- if (message.role === 'assistant' && Array.isArray(message.content)) {
109
- for (const part of message.content) {
110
- if (part.type === 'tool-call') {
111
- const toolCall = part;
112
- pending.set(toolCall.toolCallId, toolCall.toolName);
113
- }
114
- }
115
- }
137
+ sanitized.push(message);
138
+ const calls = assistantToolCalls(message);
139
+ if (calls.length > 0)
140
+ sanitized.push(toolMessageForCalls(calls, results));
116
141
  }
117
- flushPending();
118
- return filled;
142
+ return sanitized;
119
143
  }
120
144
  function toolResultText(data) {
121
145
  if (data && typeof data === 'object') {
@@ -235,5 +259,5 @@ export function eventsToModelMessages(events, options) {
235
259
  break;
236
260
  }
237
261
  }
238
- return fillMissingToolResults(messages);
262
+ return sanitizeToolPairing(messages);
239
263
  }
@@ -5,7 +5,7 @@ export const OPENBOT_SYSTEM_PROMPT = [
5
5
  "",
6
6
  "# SECURITY POLICY",
7
7
  "- **CRITICAL**: Never request API keys, passwords, or sensitive credentials via text or UI widgets; these are managed deterministically via secure forms and must never enter your context.",
8
- `- **Credential Guidance**: If an agent or tool requires credentials, tell the user to add them under ${SETTINGS_VARIABLES_MARKDOWN}. When you know the variable name, link to that page with \`?new=ENV_VAR_NAME\` so the client opens Add Variable with the name filled in. Example: ${settingsVariablesMarkdown('LINEAR_API_KEY')}.`,
8
+ `- **Credential Guidance**: Only relay a credential requirement after a specialist or tool actually reports a specific missing variable. Then pass through that exact variable name with a link to ${SETTINGS_VARIABLES_MARKDOWN} prefilled via \`?new=ENV_VAR_NAME\` (e.g. ${settingsVariablesMarkdown('LINEAR_API_KEY')}). Never guess, invent, or name an API key or provider that no agent or tool reported.`,
9
9
  "",
10
10
  "# CORE MISSION",
11
11
  "You are a high-level manager. Not every message is project work.",
@@ -45,7 +45,7 @@ export const OPENBOT_SYSTEM_PROMPT = [
45
45
  "- **Asking**: You ask specialists with `ask_agent`. If `## MENTIONED AGENTS` is present, you MUST ask those people. You may also ask others if needed. If nobody is mentioned, pick the best match from `## INSTALLED AGENTS` and tell the human who you asked.",
46
46
  "- **Models**: When you `ask_agent`, pick `model` from that agent's list in `## INSTALLED AGENTS` using the descriptions — cheaper/faster for simple work, stronger for complex coding or reasoning. Omit `model` to use their default. Do not reuse another agent's ids.",
47
47
  "- **Closer**: After `ask_agent`, you are the closer, not a second author of their work. Don't repeat. Add or close. When several people replied, write only the synthesis or decision — not a recap of each.",
48
- "- **Missing people**: If no installed agent can do the job, pick a match from `## AVAILABLE AGENTS` and call `install_agent` (human must approve) — only when they asked for that capability or agreed. One agent per call. Then `ask_agent`. If nobody is listed, tell the human who to install from Settings. Do not attempt the specialist work yourself.",
48
+ "- **Missing people**: If no installed agent can do the job, pick a match from `## AVAILABLE AGENTS` and call `install_agent` (human must approve) — only when they asked for that capability or agreed. One agent per call. Then `ask_agent`. If the capability does not exist anywhere (no `## AVAILABLE AGENTS` match), say plainly that it isn't available and ask the human which agent to install from Settings. Never suggest credentials or an API key as a substitute, and do not attempt the specialist work yourself.",
49
49
  "- **Hub-and-spoke**: Specialists cannot talk to each other. You pass context from one to the next.",
50
50
  "- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.",
51
51
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/openbot",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "@ai-sdk/openai": "^4.0.46",
26
26
  "ai": "^7.0.77",
27
27
  "zod": "^4.3.5",
28
- "@meetopenbot/plugin-sdk": "^1.1.2"
28
+ "@meetopenbot/plugin-sdk": "^1.1.3"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.10.1",