@ouro.bot/cli 0.1.0-alpha.289 → 0.1.0-alpha.290

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.
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.attachmentToolDefinitions = void 0;
37
+ const fs = __importStar(require("node:fs/promises"));
38
+ const config_1 = require("../heart/config");
39
+ const identity_1 = require("../heart/identity");
40
+ const minimax_1 = require("../heart/providers/minimax");
41
+ const minimax_vlm_1 = require("../heart/providers/minimax-vlm");
42
+ const store_1 = require("../heart/attachments/store");
43
+ const materialize_1 = require("../heart/attachments/materialize");
44
+ const runtime_1 = require("../nerves/runtime");
45
+ const tool_results_1 = require("./tool-results");
46
+ const LIST_ATTACHMENTS_DESCRIPTION = "List the most recent attachments the harness preserved across senses. Use this when you need a stable attachment_id, " +
47
+ "when a repair flow says an attachment could not be found, or when you want to inspect what artifacts are currently on hand " +
48
+ "before asking the user to resend anything.";
49
+ const MATERIALIZE_ATTACHMENT_DESCRIPTION = "Materialize an attachment into a concrete file path the harness can inspect or hand to other tools. " +
50
+ "Use variant=original when you want the raw file. Use variant=vision_safe for images when you want the harness to normalize " +
51
+ "size and format into a VLM-safe image before retrying.";
52
+ const DESCRIBE_IMAGE_DESCRIPTION = "Use this to inspect an image attachment with a targeted question, or to re-interrogate an image after a wrong first answer. " +
53
+ "The harness materializes the original attachment, normalizes it into a VLM-safe image when needed, and sends it through the vision-language model. " +
54
+ "Prefer specific prompts like 'what is the confirmation number?' over 'describe this image'. " +
55
+ "Pass attachment_id when you have it; attachment_guid is accepted as a compatibility alias.";
56
+ function trimArg(value) {
57
+ return value?.trim() ?? "";
58
+ }
59
+ function resolveAttachmentId(args) {
60
+ return trimArg(args.attachment_id) || trimArg(args.attachment_guid);
61
+ }
62
+ function parseLimit(raw) {
63
+ if (!raw)
64
+ return undefined;
65
+ const parsed = Number.parseInt(raw, 10);
66
+ if (!Number.isFinite(parsed) || parsed <= 0)
67
+ return undefined;
68
+ return parsed;
69
+ }
70
+ function normalizeThrownReason(error) {
71
+ const rendered = String(error);
72
+ return rendered.startsWith("Error: ") ? rendered.slice(7) : rendered;
73
+ }
74
+ function attachmentNotFound(tool, attachmentId) {
75
+ const friction = {
76
+ kind: "local_repair",
77
+ recoverability: "transformable",
78
+ summary: `Attachment ${attachmentId} is not in the recent attachment store anymore.`,
79
+ signature: `${tool}:attachment-not-found`,
80
+ suggested_next_actions: [
81
+ {
82
+ kind: "tool",
83
+ tool: "list_recent_attachments",
84
+ reason: "Find the current attachment_id for the artifact you want to inspect before retrying.",
85
+ },
86
+ ],
87
+ };
88
+ return (0, tool_results_1.frictionToolResult)(tool, friction);
89
+ }
90
+ function missingAttachmentId(tool) {
91
+ return (0, tool_results_1.frictionToolResult)(tool, {
92
+ kind: "input_error",
93
+ recoverability: "transformable",
94
+ summary: "An attachment id is required for this tool call.",
95
+ signature: `${tool}:missing-attachment-id`,
96
+ suggested_next_actions: [
97
+ {
98
+ kind: "tool",
99
+ tool: "list_recent_attachments",
100
+ reason: "Inspect the current recent attachments and retry with attachment_id.",
101
+ },
102
+ ],
103
+ });
104
+ }
105
+ function missingPrompt() {
106
+ return (0, tool_results_1.frictionToolResult)("describe_image", {
107
+ kind: "input_error",
108
+ recoverability: "transformable",
109
+ summary: "describe_image needs a targeted prompt before retrying.",
110
+ signature: "describe_image:missing-prompt",
111
+ suggested_next_actions: [
112
+ {
113
+ kind: "message",
114
+ message: "Retry with a specific question like 'what is the confirmation number?' or 'what date is shown?'",
115
+ },
116
+ ],
117
+ });
118
+ }
119
+ function normalizeMaterializeError(tool, attachmentId, reason) {
120
+ if (reason.startsWith("Attachment not found:")) {
121
+ return attachmentNotFound(tool, attachmentId);
122
+ }
123
+ if (reason.includes("is not an image")) {
124
+ return (0, tool_results_1.frictionToolResult)(tool, {
125
+ kind: "input_error",
126
+ recoverability: "transformable",
127
+ summary: `Attachment ${attachmentId} is not an image, so it cannot produce a vision_safe variant.`,
128
+ signature: `${tool}:not-image`,
129
+ suggested_next_actions: [
130
+ {
131
+ kind: "tool",
132
+ tool: "materialize_attachment",
133
+ reason: "Retry with variant=original if you need the raw file instead.",
134
+ args: { attachment_id: attachmentId, variant: "original" },
135
+ },
136
+ ],
137
+ });
138
+ }
139
+ return (0, tool_results_1.frictionToolResult)(tool, {
140
+ kind: "local_repair",
141
+ recoverability: "retryable",
142
+ summary: reason,
143
+ signature: `${tool}:materialize-failed`,
144
+ suggested_next_actions: [
145
+ {
146
+ kind: "tool",
147
+ tool: "materialize_attachment",
148
+ reason: "Retry materialization once more in case the file path or normalization target changed.",
149
+ args: { attachment_id: attachmentId, variant: "vision_safe" },
150
+ },
151
+ ],
152
+ });
153
+ }
154
+ async function buildImageDataUrl(filePath, mimeType) {
155
+ const buffer = await fs.readFile(filePath);
156
+ const encoded = buffer.toString("base64");
157
+ const normalizedMime = mimeType?.trim().toLowerCase() || "image/jpeg";
158
+ return `data:${normalizedMime};base64,${encoded}`;
159
+ }
160
+ exports.attachmentToolDefinitions = [
161
+ {
162
+ tool: {
163
+ type: "function",
164
+ function: {
165
+ name: "list_recent_attachments",
166
+ description: LIST_ATTACHMENTS_DESCRIPTION,
167
+ parameters: {
168
+ type: "object",
169
+ properties: {
170
+ kind: {
171
+ type: "string",
172
+ enum: ["image", "audio", "document", "binary", "unknown"],
173
+ description: "Optional attachment kind filter.",
174
+ },
175
+ limit: {
176
+ type: "string",
177
+ description: "Optional numeric limit for how many attachments to return.",
178
+ },
179
+ },
180
+ },
181
+ },
182
+ },
183
+ handler: async (args) => {
184
+ const agentName = (0, identity_1.getAgentName)();
185
+ const kind = trimArg(args.kind);
186
+ const attachments = (0, store_1.listRecentAttachments)(agentName, {
187
+ kind: kind ? kind : undefined,
188
+ limit: parseLimit(args.limit),
189
+ });
190
+ return (0, tool_results_1.okToolResult)("list_recent_attachments", attachments);
191
+ },
192
+ summaryKeys: ["kind", "limit"],
193
+ },
194
+ {
195
+ tool: {
196
+ type: "function",
197
+ function: {
198
+ name: "materialize_attachment",
199
+ description: MATERIALIZE_ATTACHMENT_DESCRIPTION,
200
+ parameters: {
201
+ type: "object",
202
+ properties: {
203
+ attachment_id: {
204
+ type: "string",
205
+ description: "Stable attachment id such as attachment:cli-local-file:... or attachment:bluebubbles:...",
206
+ },
207
+ attachment_guid: {
208
+ type: "string",
209
+ description: "Compatibility alias for older callers. Prefer attachment_id.",
210
+ },
211
+ variant: {
212
+ type: "string",
213
+ enum: ["original", "vision_safe"],
214
+ description: "Which attachment variant to materialize.",
215
+ },
216
+ },
217
+ required: ["variant"],
218
+ },
219
+ },
220
+ },
221
+ handler: async (args) => {
222
+ const attachmentId = resolveAttachmentId(args);
223
+ if (!attachmentId) {
224
+ return missingAttachmentId("materialize_attachment");
225
+ }
226
+ const agentName = (0, identity_1.getAgentName)();
227
+ const variant = trimArg(args.variant) === "vision_safe" ? "vision_safe" : "original";
228
+ try {
229
+ const materialized = await (0, materialize_1.materializeAttachment)(agentName, attachmentId, { variant });
230
+ return (0, tool_results_1.okToolResult)("materialize_attachment", materialized);
231
+ }
232
+ catch (error) {
233
+ const reason = normalizeThrownReason(error);
234
+ return normalizeMaterializeError("materialize_attachment", attachmentId, reason);
235
+ }
236
+ },
237
+ summaryKeys: ["attachment_id", "attachment_guid", "variant"],
238
+ },
239
+ {
240
+ tool: {
241
+ type: "function",
242
+ function: {
243
+ name: "describe_image",
244
+ description: DESCRIBE_IMAGE_DESCRIPTION,
245
+ parameters: {
246
+ type: "object",
247
+ properties: {
248
+ attachment_id: {
249
+ type: "string",
250
+ description: "Stable attachment id for the image you want to inspect.",
251
+ },
252
+ attachment_guid: {
253
+ type: "string",
254
+ description: "Compatibility alias for older callers. Prefer attachment_id.",
255
+ },
256
+ prompt: {
257
+ type: "string",
258
+ description: "A targeted question about the image.",
259
+ },
260
+ },
261
+ required: ["prompt"],
262
+ },
263
+ },
264
+ },
265
+ handler: async (args) => {
266
+ const attachmentId = resolveAttachmentId(args);
267
+ if (!attachmentId) {
268
+ return missingAttachmentId("describe_image");
269
+ }
270
+ const prompt = trimArg(args.prompt);
271
+ if (!prompt) {
272
+ return missingPrompt();
273
+ }
274
+ const agentName = (0, identity_1.getAgentName)();
275
+ try {
276
+ const materialized = await (0, materialize_1.materializeAttachment)(agentName, attachmentId, { variant: "vision_safe" });
277
+ const imageDataUrl = await buildImageDataUrl(materialized.path, materialized.mimeType);
278
+ const { apiKey } = (0, config_1.getMinimaxConfig)();
279
+ if (!apiKey) {
280
+ return (0, tool_results_1.frictionToolResult)("describe_image", {
281
+ kind: "external_blocker",
282
+ recoverability: "blocked",
283
+ summary: "MiniMax credentials are missing, so image understanding is unavailable until credentials are fixed.",
284
+ signature: "describe_image:minimax-missing-key",
285
+ suggested_next_actions: [
286
+ {
287
+ kind: "message",
288
+ message: "Repair the minimax credentials for this agent, then retry describe_image.",
289
+ },
290
+ ],
291
+ });
292
+ }
293
+ const description = await (0, minimax_vlm_1.minimaxVlmDescribe)({
294
+ apiKey,
295
+ prompt,
296
+ imageDataUrl,
297
+ baseURL: minimax_1.MINIMAX_PROVIDER_BASE_URL,
298
+ attachmentGuid: attachmentId,
299
+ mimeType: materialized.mimeType,
300
+ });
301
+ (0, runtime_1.emitNervesEvent)({
302
+ component: "tools",
303
+ event: "tool.describe_image_success",
304
+ message: "describe_image returned a description",
305
+ meta: { attachmentId },
306
+ });
307
+ return description;
308
+ }
309
+ catch (error) {
310
+ const reason = normalizeThrownReason(error);
311
+ return normalizeMaterializeError("describe_image", attachmentId, reason);
312
+ }
313
+ },
314
+ summaryKeys: ["attachment_id", "attachment_guid", "prompt"],
315
+ },
316
+ ];
@@ -15,6 +15,7 @@ const tools_travel_1 = require("./tools-travel");
15
15
  const tools_user_profile_1 = require("./tools-user-profile");
16
16
  const tools_stripe_1 = require("./tools-stripe");
17
17
  const tools_flight_1 = require("./tools-flight");
18
+ const tools_attachments_1 = require("./tools-attachments");
18
19
  // Re-export flow tools for consumers that import them from tools-base
19
20
  var tools_flow_1 = require("./tools-flow");
20
21
  Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_flow_1.ponderTool; } });
@@ -44,6 +45,7 @@ exports.baseToolDefinitions = [
44
45
  ...tools_user_profile_1.userProfileToolDefinitions,
45
46
  ...tools_stripe_1.stripeToolDefinitions,
46
47
  ...tools_flight_1.flightToolDefinitions,
48
+ ...tools_attachments_1.attachmentToolDefinitions,
47
49
  ];
48
50
  // Convenience array of just the tool schemas (no handler/integration metadata).
49
51
  // Used by consumers that need the OpenAI function-tool format.
@@ -2,22 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bluebubblesToolDefinitions = void 0;
4
4
  const runtime_1 = require("../nerves/runtime");
5
- const config_1 = require("../heart/config");
6
- const minimax_1 = require("../heart/providers/minimax");
7
- const minimax_vlm_1 = require("../heart/providers/minimax-vlm");
8
- const attachment_cache_1 = require("../senses/bluebubbles/attachment-cache");
9
- const media_1 = require("../senses/bluebubbles/media");
10
- // AX-1: the tool description is the only instruction manual a future agent
11
- // gets. It must teach WHEN to reach for the tool and HOW to write a good
12
- // prompt. A type-signature-style "Describes an image." is a violation.
13
- const DESCRIBE_IMAGE_DESCRIPTION = "Use this tool to look at an attachment the user sent, or to re-interrogate an image with a targeted question. " +
14
- "The current chat model can't see images natively; this tool runs the image through a vision-language model (VLM) " +
15
- "and returns a text description. " +
16
- "Prefer targeted prompts (\"what's the flight number in the bottom-right?\") over generic ones (\"describe this image\") — " +
17
- "you'll get better answers. " +
18
- "Call this when the user sent a screenshot and you need to read text from it, verify a detail, or double-check something " +
19
- "the ingestion-time auto-describe summary might have missed. " +
20
- "The attachment_guid comes from the attachment marker in the user's most recent message.";
21
5
  exports.bluebubblesToolDefinitions = [
22
6
  {
23
7
  tool: {
@@ -107,95 +91,4 @@ exports.bluebubblesToolDefinitions = [
107
91
  },
108
92
  summaryKeys: ["target", "threadOriginatorGuid"],
109
93
  },
110
- {
111
- tool: {
112
- type: "function",
113
- function: {
114
- name: "describe_image",
115
- description: DESCRIBE_IMAGE_DESCRIPTION,
116
- parameters: {
117
- type: "object",
118
- properties: {
119
- attachment_guid: {
120
- type: "string",
121
- description: "The guid of the attachment to describe. Usually pulled from the attachment marker in the user's most recent message.",
122
- },
123
- prompt: {
124
- type: "string",
125
- description: "A targeted question about the image. Prefer specific prompts over generic ones — e.g. 'what's the flight number in the bottom-right?' gives better answers than 'describe this image'.",
126
- },
127
- },
128
- required: ["attachment_guid", "prompt"],
129
- },
130
- },
131
- },
132
- handler: async (args) => {
133
- const attachmentGuid = typeof args.attachment_guid === "string" ? args.attachment_guid.trim() : "";
134
- const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
135
- if (!attachmentGuid) {
136
- (0, runtime_1.emitNervesEvent)({
137
- level: "warn",
138
- component: "tools",
139
- event: "tool.error",
140
- message: "describe_image missing attachment_guid",
141
- meta: {},
142
- });
143
- return "describe_image: attachment_guid is required — pass the guid from the inbound attachment marker and retry";
144
- }
145
- if (!prompt) {
146
- (0, runtime_1.emitNervesEvent)({
147
- level: "warn",
148
- component: "tools",
149
- event: "tool.error",
150
- message: "describe_image missing prompt",
151
- meta: {},
152
- });
153
- return "describe_image: prompt is required — supply a targeted question (e.g. 'what's the flight number in the bottom-right?') and retry";
154
- }
155
- const summary = (0, attachment_cache_1.lookupBlueBubblesAttachment)(attachmentGuid);
156
- if (!summary) {
157
- (0, runtime_1.emitNervesEvent)({
158
- level: "warn",
159
- component: "tools",
160
- event: "tool.error",
161
- message: "describe_image attachment not found in cache",
162
- meta: { attachmentGuid },
163
- });
164
- return `describe_image: no attachment with guid ${attachmentGuid} found in recent messages — ask the user to resend the image or verify the guid`;
165
- }
166
- try {
167
- const config = (0, config_1.getBlueBubblesConfig)();
168
- const channelConfig = (0, config_1.getBlueBubblesChannelConfig)();
169
- const downloaded = await (0, media_1.downloadBlueBubblesAttachment)(summary, config, channelConfig);
170
- const mimeType = downloaded.contentType ?? summary.mimeType ?? "image/png";
171
- const base64 = downloaded.buffer.toString("base64");
172
- const dataUrl = `data:${mimeType};base64,${base64}`;
173
- const { apiKey } = (0, config_1.getMinimaxConfig)();
174
- if (!apiKey) {
175
- throw new Error("minimax API key not found in secrets.json — re-run credential setup or add a minimax key");
176
- }
177
- const description = await (0, minimax_vlm_1.minimaxVlmDescribe)({
178
- apiKey,
179
- prompt,
180
- imageDataUrl: dataUrl,
181
- baseURL: minimax_1.MINIMAX_PROVIDER_BASE_URL,
182
- attachmentGuid,
183
- mimeType,
184
- });
185
- return description;
186
- }
187
- catch (error) {
188
- const reason = error instanceof Error ? error.message : String(error);
189
- (0, runtime_1.emitNervesEvent)({
190
- level: "warn",
191
- component: "tools",
192
- event: "tool.error",
193
- message: "describe_image failed",
194
- meta: { attachmentGuid, reason },
195
- });
196
- return `describe_image failed: ${reason}`;
197
- }
198
- },
199
- summaryKeys: ["attachment_guid", "prompt"],
200
- },
201
94
  ];
@@ -5,17 +5,51 @@ exports.ponderTool = {
5
5
  type: "function",
6
6
  function: {
7
7
  name: "ponder",
8
- description: "i need to sit with this. from a conversation, takes the thread inward with a thought and a parting word. from inner dialog, keeps the wheel turning for another pass. must be the only tool call in the turn. Use when a question deserves more thought than this turn allows. Don't ponder trivial questions.",
8
+ description: "create or revise a typed ponder packet so i don't lose the plot while i keep working. use this for harness friction, research, or reflection that should survive the current turn. ponder does not end the turn, does not defer the response by itself, and may be followed by more tools before i settle or rest. Don't ponder trivial questions.",
9
9
  parameters: {
10
10
  type: "object",
11
11
  properties: {
12
+ action: {
13
+ type: "string",
14
+ enum: ["create", "revise"],
15
+ description: "create a new packet or revise an existing drafting packet.",
16
+ },
17
+ kind: {
18
+ type: "string",
19
+ enum: ["harness_friction", "research", "reflection"],
20
+ description: "the packet kind. determines the SOP the inner session should follow.",
21
+ },
22
+ packet_id: {
23
+ type: "string",
24
+ description: "required for action=revise. the packet to revise in place.",
25
+ },
26
+ follows_packet_id: {
27
+ type: "string",
28
+ description: "optional follow-up linkage when a new packet grows out of an earlier one.",
29
+ },
30
+ objective: {
31
+ type: "string",
32
+ description: "the durable objective for this packet.",
33
+ },
34
+ summary: {
35
+ type: "string",
36
+ description: "brief factual summary of the work object or friction being preserved.",
37
+ },
38
+ success_criteria: {
39
+ type: "string",
40
+ description: "newline-delimited success criteria bullets.",
41
+ },
42
+ payload_json: {
43
+ type: "string",
44
+ description: "JSON object string with packet-specific structured details. use {} when empty.",
45
+ },
12
46
  thought: {
13
47
  type: "string",
14
- description: "the question or thread that needs more thought — brief framing, not analysis. required from a conversation, ignored from inner dialog.",
48
+ description: "deprecated compatibility field. legacy thought text is normalized into a reflection packet.",
15
49
  },
16
50
  say: {
17
51
  type: "string",
18
- description: "what you say before going quiet — speak to what caught your attention, not just that something did. required from a conversation, ignored from inner dialog.",
52
+ description: "deprecated compatibility field. retained for migration only; it no longer controls deferral or silence.",
19
53
  },
20
54
  },
21
55
  },
@@ -53,10 +87,19 @@ exports.restTool = {
53
87
  type: "function",
54
88
  function: {
55
89
  name: "rest",
56
- description: "put this down for now — the wheel stops until the next heartbeat. must be the only tool call in the turn.",
90
+ description: "end an inner-session turn when i'm done thinking. rest remains the explicit terminal move for the inner session and must be the only tool call in the turn. on idle heartbeat turns, use status=HEARTBEAT_OK.",
57
91
  parameters: {
58
92
  type: "object",
59
- properties: {},
93
+ properties: {
94
+ status: {
95
+ type: "string",
96
+ description: "optional rest status. use HEARTBEAT_OK when the heartbeat fires and there is nothing to do.",
97
+ },
98
+ note: {
99
+ type: "string",
100
+ description: "optional brief note about why i'm resting.",
101
+ },
102
+ },
60
103
  },
61
104
  },
62
105
  };
@@ -15,7 +15,6 @@ const tools_bundle_1 = require("./tools-bundle");
15
15
  const runtime_1 = require("../nerves/runtime");
16
16
  const guardrails_1 = require("./guardrails");
17
17
  const identity_1 = require("../heart/identity");
18
- const model_capabilities_1 = require("../heart/model-capabilities");
19
18
  const tools_surface_1 = require("./tools-surface");
20
19
  const mcp_tools_1 = require("./mcp-tools");
21
20
  function safeGetAgentRoot() {
@@ -75,17 +74,10 @@ function filterByCapability(toolList, providerCapabilities) {
75
74
  // Teams/integration tools are included only if their integration is in availableIntegrations.
76
75
  // When toolPreferences is provided, matching preferences are appended to tool descriptions.
77
76
  // When providerCapabilities is provided, tools with requiredCapability are filtered.
78
- // When chatModel is provided and has `vision: true` in ModelCapabilities, the
79
- // BlueBubbles-scoped `describe_image` tool is excluded (native image_url
80
- // pass-through makes the tool redundant). When chatModel is undefined the
81
- // tool IS included — the default errs toward more tools on an ambiguous caller.
82
- function getToolsForChannel(capabilities, toolPreferences, _context, providerCapabilities, mcpManager, chatModel) {
77
+ function getToolsForChannel(capabilities, toolPreferences, _context, providerCapabilities, mcpManager, _chatModel) {
83
78
  const baseTools = baseToolsForCapabilities();
84
- const visionCapable = chatModel ? (0, model_capabilities_1.getModelCapabilities)(chatModel).vision === true : false;
85
79
  const bluebubblesTools = capabilities?.channel === "bluebubbles"
86
- ? tools_bluebubbles_1.bluebubblesToolDefinitions
87
- .filter((d) => d.tool.function.name !== "describe_image" || !visionCapable)
88
- .map((d) => d.tool)
80
+ ? tools_bluebubbles_1.bluebubblesToolDefinitions.map((d) => d.tool)
89
81
  : [];
90
82
  let result;
91
83
  if (!capabilities || capabilities.availableIntegrations.length === 0) {
@@ -13,9 +13,22 @@ function originKey(friendId, channel, key) {
13
13
  return `${friendId}/${channel}/${key}`;
14
14
  }
15
15
  function buildAttentionQueue(input) {
16
- const { drainedPending, outstandingObligations, friendNameResolver } = input;
16
+ const { drainedPending, outstandingObligations, friendNameResolver, packetResolver } = input;
17
17
  const seen = new Set();
18
18
  const items = [];
19
+ const enrichPacket = (packetId) => {
20
+ if (!packetId || !packetResolver)
21
+ return {};
22
+ const packet = packetResolver(packetId);
23
+ if (!packet)
24
+ return { packetId };
25
+ return {
26
+ packetId,
27
+ packetKind: packet.kind,
28
+ packetObjective: packet.objective,
29
+ packetSummary: packet.summary,
30
+ };
31
+ };
19
32
  // Source 1: drained pending messages with delegatedFrom (current-turn delegations)
20
33
  for (const msg of drainedPending) {
21
34
  if (!msg.delegatedFrom)
@@ -33,6 +46,7 @@ function buildAttentionQueue(input) {
33
46
  ...(bridgeId ? { bridgeId } : {}),
34
47
  delegatedContent: msg.content,
35
48
  ...(msg.obligationId ? { obligationId: msg.obligationId } : {}),
49
+ ...enrichPacket(msg.packetId),
36
50
  source: "drained",
37
51
  timestamp: msg.timestamp,
38
52
  });
@@ -54,6 +68,7 @@ function buildAttentionQueue(input) {
54
68
  ...(bridgeId ? { bridgeId } : {}),
55
69
  delegatedContent: obligation.delegatedContent,
56
70
  obligationId: obligation.id,
71
+ ...enrichPacket(obligation.packetId),
57
72
  source: "obligation-recovery",
58
73
  timestamp: obligation.createdAt,
59
74
  });
@@ -88,6 +103,10 @@ function buildAttentionQueueSummary(queue) {
88
103
  return "";
89
104
  const lines = ["you're holding:"];
90
105
  for (const item of queue) {
106
+ if (item.packetKind && item.packetObjective) {
107
+ lines.push(`- [${item.id}] ${item.friendName} -> ${item.packetKind}: ${item.packetObjective}`);
108
+ continue;
109
+ }
91
110
  const preview = item.delegatedContent.length > CONTENT_PREVIEW_MAX
92
111
  ? `${item.delegatedContent.slice(0, CONTENT_PREVIEW_MAX - 3)}...`
93
112
  : item.delegatedContent;