botinabox 2.5.1 → 2.5.3

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/bin/botinabox.mjs CHANGED
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import('../dist/cli.js').then(m => m.main(process.argv.slice(2)));
@@ -1,5 +1,5 @@
1
1
  import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-06G0vbIn.js';
2
- import { H as HookBus, c as ChatPipeline } from '../../chat-pipeline-BWrtVqEP.js';
2
+ import { H as HookBus, c as ChatPipeline } from '../../chat-pipeline-DuNX5WoL.js';
3
3
  import 'better-sqlite3';
4
4
  import '../../provider-DLGUfnNx.js';
5
5
 
@@ -584,6 +584,8 @@ interface ChatPipelineConfig {
584
584
  model?: string;
585
585
  /** Enable LLM fallback routing (default: false) */
586
586
  llmRouting?: boolean;
587
+ /** Skip the ack layer — no fast response before task dispatch (default: false) */
588
+ skipAck?: boolean;
587
589
  /** TaskQueue instance — required for task dispatch */
588
590
  tasks: {
589
591
  create(task: Record<string, unknown>): Promise<string>;
@@ -608,6 +610,7 @@ declare class ChatPipeline {
608
610
  private readonly dedupWindowMs;
609
611
  private readonly tasks;
610
612
  private readonly wakeups;
613
+ private readonly skipAck;
611
614
  private readonly threadChannelMap;
612
615
  /** Last dispatch promise — exposed for testing. */
613
616
  lastDispatch: Promise<void>;
@@ -0,0 +1,419 @@
1
+ // src/connectors/google/oauth.ts
2
+ var _google;
3
+ async function getGoogle() {
4
+ if (!_google) {
5
+ try {
6
+ const mod = await import("googleapis");
7
+ _google = mod.google;
8
+ } catch {
9
+ throw new Error(
10
+ "googleapis is required for Google connectors. Install it: npm install googleapis"
11
+ );
12
+ }
13
+ }
14
+ return _google;
15
+ }
16
+ async function createOAuth2Client(config) {
17
+ const google = await getGoogle();
18
+ return new google.auth.OAuth2(
19
+ config.clientId,
20
+ config.clientSecret,
21
+ config.redirectUri
22
+ );
23
+ }
24
+ function getAuthUrl(client, scopes) {
25
+ return client.generateAuthUrl({
26
+ access_type: "offline",
27
+ prompt: "consent",
28
+ scope: scopes
29
+ });
30
+ }
31
+ async function exchangeCode(client, code) {
32
+ const { tokens } = await client.getToken(code);
33
+ return tokens;
34
+ }
35
+ async function createServiceAccountClient(config, scopes) {
36
+ const google = await getGoogle();
37
+ const auth = new google.auth.GoogleAuth({
38
+ ...config.keyFile ? { keyFile: config.keyFile } : {},
39
+ ...config.credentials ? { credentials: config.credentials } : {},
40
+ scopes,
41
+ clientOptions: { subject: config.subject }
42
+ });
43
+ return auth.getClient();
44
+ }
45
+ async function loadTokens(getter, accountKey) {
46
+ const raw = await getter(`google_tokens:${accountKey}`);
47
+ if (!raw) return null;
48
+ try {
49
+ return JSON.parse(raw);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ async function saveTokens(setter, accountKey, tokens) {
55
+ await setter(`google_tokens:${accountKey}`, JSON.stringify(tokens));
56
+ }
57
+ async function refreshIfNeeded(client, tokens, saver) {
58
+ const buffer = 6e4;
59
+ const isExpired = tokens.expiry_date != null && Date.now() >= tokens.expiry_date - buffer;
60
+ if (!isExpired) return tokens;
61
+ client.setCredentials(tokens);
62
+ const { credentials } = await client.refreshAccessToken();
63
+ const refreshed = {
64
+ access_token: credentials.access_token,
65
+ refresh_token: credentials.refresh_token ?? tokens.refresh_token,
66
+ expiry_date: credentials.expiry_date ?? void 0,
67
+ token_type: credentials.token_type ?? "Bearer"
68
+ };
69
+ if (saver) {
70
+ await saver(refreshed);
71
+ }
72
+ return refreshed;
73
+ }
74
+
75
+ // src/connectors/google/gmail-connector.ts
76
+ var GoogleGmailConnector = class {
77
+ id = "google-gmail";
78
+ meta = {
79
+ displayName: "Google Gmail",
80
+ provider: "google",
81
+ dataType: "email"
82
+ };
83
+ tokenLoader;
84
+ tokenSaver;
85
+ client = null;
86
+ config = null;
87
+ tokens = null;
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ gmail = null;
90
+ constructor(opts = {}) {
91
+ this.tokenLoader = opts.tokenLoader;
92
+ this.tokenSaver = opts.tokenSaver;
93
+ }
94
+ // ── Lifecycle ──────────────────────────────────────────────────
95
+ async connect(config) {
96
+ this.config = config;
97
+ const scopes = config.scopes ?? [
98
+ "https://www.googleapis.com/auth/gmail.readonly"
99
+ ];
100
+ if (config.serviceAccount) {
101
+ this.client = await createServiceAccountClient(config.serviceAccount, scopes);
102
+ } else if (config.oauth) {
103
+ this.client = await createOAuth2Client(config.oauth);
104
+ if (!this.tokenLoader) {
105
+ throw new Error("tokenLoader required for OAuth2 flow");
106
+ }
107
+ this.tokens = await loadTokens(this.tokenLoader, config.account);
108
+ if (!this.tokens) {
109
+ throw new Error(
110
+ `No stored tokens for account ${config.account}. Complete the OAuth flow first.`
111
+ );
112
+ }
113
+ this.tokens = await refreshIfNeeded(
114
+ this.client,
115
+ this.tokens,
116
+ this.tokenSaver ? async (t) => saveTokens(this.tokenSaver, config.account, t) : void 0
117
+ );
118
+ this.client.setCredentials(this.tokens);
119
+ } else {
120
+ throw new Error("Either serviceAccount or oauth config is required");
121
+ }
122
+ const { google } = await import("googleapis");
123
+ this.gmail = google.gmail({ version: "v1", auth: this.client });
124
+ }
125
+ async disconnect() {
126
+ this.client = null;
127
+ this.gmail = null;
128
+ this.tokens = null;
129
+ this.config = null;
130
+ }
131
+ async healthCheck() {
132
+ try {
133
+ this.ensureConnected();
134
+ const res = await this.gmail.users.getProfile({ userId: "me" });
135
+ return { ok: true, account: res.data.emailAddress };
136
+ } catch (err) {
137
+ return { ok: false, error: errorMessage(err) };
138
+ }
139
+ }
140
+ // ── Auth ───────────────────────────────────────────────────────
141
+ async authenticate(codeProvider) {
142
+ if (!this.config) {
143
+ return { success: false, error: "Call connect() first to set config, or pass config and call authenticate() before connect()." };
144
+ }
145
+ try {
146
+ if (!this.config.oauth) {
147
+ return { success: false, error: "OAuth config required for browser-based authenticate(). Use serviceAccount for headless auth." };
148
+ }
149
+ if (!this.tokenSaver) {
150
+ return { success: false, error: "tokenSaver required for authenticate() flow." };
151
+ }
152
+ const client = await createOAuth2Client(this.config.oauth);
153
+ const scopes = this.config.scopes ?? [
154
+ "https://www.googleapis.com/auth/gmail.readonly",
155
+ "https://www.googleapis.com/auth/gmail.send"
156
+ ];
157
+ const authUrl = getAuthUrl(client, scopes);
158
+ const code = await codeProvider(authUrl);
159
+ const tokens = await exchangeCode(client, code);
160
+ await saveTokens(this.tokenSaver, this.config.account, tokens);
161
+ this.tokens = tokens;
162
+ this.client = client;
163
+ this.client.setCredentials(tokens);
164
+ const { google } = await import("googleapis");
165
+ this.gmail = google.gmail({ version: "v1", auth: this.client });
166
+ return { success: true, account: this.config.account };
167
+ } catch (err) {
168
+ return { success: false, error: errorMessage(err) };
169
+ }
170
+ }
171
+ // ── Sync ───────────────────────────────────────────────────────
172
+ async sync(options) {
173
+ this.ensureConnected();
174
+ if (options?.cursor) {
175
+ return this.syncIncremental(options.cursor, options.limit);
176
+ }
177
+ return this.syncFull(options);
178
+ }
179
+ /** Incremental sync using Gmail history API. */
180
+ async syncIncremental(startHistoryId, limit) {
181
+ const records = [];
182
+ const errors = [];
183
+ const seenIds = /* @__PURE__ */ new Set();
184
+ let pageToken;
185
+ let latestHistoryId = startHistoryId;
186
+ do {
187
+ const res = await this.gmail.users.history.list({
188
+ userId: "me",
189
+ startHistoryId,
190
+ historyTypes: ["messageAdded"],
191
+ ...pageToken ? { pageToken } : {}
192
+ });
193
+ latestHistoryId = res.data.historyId ?? latestHistoryId;
194
+ const histories = res.data.history ?? [];
195
+ for (const h of histories) {
196
+ for (const added of h.messagesAdded ?? []) {
197
+ const msgId = added.message?.id;
198
+ if (!msgId || seenIds.has(msgId)) continue;
199
+ seenIds.add(msgId);
200
+ try {
201
+ const record = await this.fetchMessage(msgId);
202
+ records.push(record);
203
+ } catch (err) {
204
+ errors.push({ id: msgId, error: errorMessage(err) });
205
+ }
206
+ if (limit && records.length >= limit) {
207
+ return { records, cursor: latestHistoryId, hasMore: true, errors };
208
+ }
209
+ }
210
+ }
211
+ pageToken = res.data.nextPageToken ?? void 0;
212
+ } while (pageToken);
213
+ return { records, cursor: latestHistoryId, hasMore: false, errors };
214
+ }
215
+ /** Full sync — list messages and fetch each one. */
216
+ async syncFull(options) {
217
+ const records = [];
218
+ const errors = [];
219
+ const maxResults = options?.limit ?? 100;
220
+ let query = "";
221
+ if (options?.since) {
222
+ const epoch = Math.floor(new Date(options.since).getTime() / 1e3);
223
+ query = `after:${epoch}`;
224
+ }
225
+ if (options?.filters?.q) {
226
+ query = query ? `${query} ${options.filters.q}` : String(options.filters.q);
227
+ }
228
+ let pageToken;
229
+ let collected = 0;
230
+ do {
231
+ const res = await this.gmail.users.messages.list({
232
+ userId: "me",
233
+ maxResults: Math.min(maxResults - collected, 100),
234
+ ...query ? { q: query } : {},
235
+ ...pageToken ? { pageToken } : {}
236
+ });
237
+ const messages = res.data.messages ?? [];
238
+ for (const msg of messages) {
239
+ try {
240
+ const record = await this.fetchMessage(msg.id);
241
+ records.push(record);
242
+ } catch (err) {
243
+ errors.push({ id: msg.id, error: errorMessage(err) });
244
+ }
245
+ collected++;
246
+ if (collected >= maxResults) break;
247
+ }
248
+ pageToken = res.data.nextPageToken ?? void 0;
249
+ } while (pageToken && collected < maxResults);
250
+ const profile = await this.gmail.users.getProfile({ userId: "me" });
251
+ const cursor = profile.data.historyId ?? void 0;
252
+ return {
253
+ records,
254
+ cursor,
255
+ hasMore: !!pageToken,
256
+ errors
257
+ };
258
+ }
259
+ // ── Push (send email) ─────────────────────────────────────────
260
+ async push(payload) {
261
+ this.ensureConnected();
262
+ try {
263
+ const toHeader = payload.to.map(formatAddress).join(", ");
264
+ const ccHeader = payload.cc.length ? `Cc: ${payload.cc.map(formatAddress).join(", ")}\r
265
+ ` : "";
266
+ const bccHeader = payload.bcc.length ? `Bcc: ${payload.bcc.map(formatAddress).join(", ")}\r
267
+ ` : "";
268
+ const mime = [
269
+ `To: ${toHeader}\r
270
+ `,
271
+ ccHeader,
272
+ bccHeader,
273
+ `Subject: ${payload.subject}\r
274
+ `,
275
+ `Content-Type: text/plain; charset="UTF-8"\r
276
+ `,
277
+ `\r
278
+ `,
279
+ payload.body ?? ""
280
+ ].join("");
281
+ const encoded = Buffer.from(mime).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
282
+ const res = await this.gmail.users.messages.send({
283
+ userId: "me",
284
+ requestBody: { raw: encoded }
285
+ });
286
+ return { success: true, externalId: res.data.id };
287
+ } catch (err) {
288
+ return { success: false, error: errorMessage(err) };
289
+ }
290
+ }
291
+ // ── Internals ─────────────────────────────────────────────────
292
+ ensureConnected() {
293
+ if (!this.gmail || !this.config) {
294
+ throw new Error("GoogleGmailConnector is not connected. Call connect() first.");
295
+ }
296
+ }
297
+ /** Fetch a single message by ID and parse into an EmailRecord. */
298
+ async fetchMessage(messageId) {
299
+ const res = await this.gmail.users.messages.get({
300
+ userId: "me",
301
+ id: messageId,
302
+ format: "full"
303
+ });
304
+ const msg = res.data;
305
+ const headers = msg.payload?.headers ?? [];
306
+ const getHeader = (name) => headers.find((h) => h.name.toLowerCase() === name.toLowerCase())?.value ?? "";
307
+ return {
308
+ gmailId: msg.id,
309
+ threadId: msg.threadId,
310
+ account: this.config.account,
311
+ subject: getHeader("Subject"),
312
+ from: parseAddress(getHeader("From")),
313
+ to: parseAddressList(getHeader("To")),
314
+ cc: parseAddressList(getHeader("Cc")),
315
+ bcc: parseAddressList(getHeader("Bcc")),
316
+ date: new Date(getHeader("Date")).toISOString(),
317
+ snippet: msg.snippet ?? "",
318
+ body: extractPlainTextBody(msg.payload),
319
+ labels: msg.labelIds ?? [],
320
+ isRead: !(msg.labelIds ?? []).includes("UNREAD"),
321
+ attachments: extractAttachments(msg.payload)
322
+ };
323
+ }
324
+ };
325
+ function extractAttachments(payload) {
326
+ if (!payload) return [];
327
+ const out = [];
328
+ const walk = (part) => {
329
+ if (!part) return;
330
+ const filename = part.filename ?? "";
331
+ const attachmentId = part.body?.attachmentId;
332
+ if (filename && attachmentId) {
333
+ const dispositionHeader = (part.headers ?? []).find(
334
+ (h) => h.name.toLowerCase() === "content-disposition"
335
+ );
336
+ const disposition = dispositionHeader?.value ?? "";
337
+ const isInline = disposition.toLowerCase().startsWith("inline");
338
+ if (!isInline) {
339
+ out.push({
340
+ attachmentId,
341
+ filename,
342
+ mimeType: part.mimeType ?? "application/octet-stream",
343
+ size: typeof part.body?.size === "number" ? part.body.size : 0
344
+ });
345
+ }
346
+ }
347
+ if (Array.isArray(part.parts)) {
348
+ for (const child of part.parts) walk(child);
349
+ }
350
+ };
351
+ walk(payload);
352
+ return out;
353
+ }
354
+ function extractPlainTextBody(payload) {
355
+ if (!payload) return void 0;
356
+ if (payload.mimeType === "text/plain" && payload.body?.data) {
357
+ return decodeBase64Url(payload.body.data);
358
+ }
359
+ if (payload.parts) {
360
+ for (const part of payload.parts) {
361
+ if (part.mimeType === "text/plain" && part.body?.data) {
362
+ return decodeBase64Url(part.body.data);
363
+ }
364
+ }
365
+ for (const part of payload.parts) {
366
+ if (part.mimeType?.startsWith("multipart/")) {
367
+ const result = extractPlainTextBody(part);
368
+ if (result) return result;
369
+ }
370
+ }
371
+ }
372
+ return void 0;
373
+ }
374
+ function decodeBase64Url(data) {
375
+ const base64 = data.replace(/-/g, "+").replace(/_/g, "/");
376
+ return Buffer.from(base64, "base64").toString("utf-8");
377
+ }
378
+ function parseAddress(raw) {
379
+ const match = raw.match(/^(.+?)\s*<([^>]+)>$/);
380
+ if (match) {
381
+ return { name: match[1].replace(/^["']|["']$/g, "").trim(), email: match[2] };
382
+ }
383
+ return { email: raw.trim() };
384
+ }
385
+ function parseAddressList(raw) {
386
+ if (!raw.trim()) return [];
387
+ const results = [];
388
+ let current = "";
389
+ let depth = 0;
390
+ for (const ch of raw) {
391
+ if (ch === "<") depth++;
392
+ else if (ch === ">") depth--;
393
+ else if (ch === "," && depth === 0) {
394
+ if (current.trim()) results.push(parseAddress(current.trim()));
395
+ current = "";
396
+ continue;
397
+ }
398
+ current += ch;
399
+ }
400
+ if (current.trim()) results.push(parseAddress(current.trim()));
401
+ return results;
402
+ }
403
+ function formatAddress(addr) {
404
+ return addr.name ? `${addr.name} <${addr.email}>` : addr.email;
405
+ }
406
+ function errorMessage(err) {
407
+ return err instanceof Error ? err.message : String(err);
408
+ }
409
+
410
+ export {
411
+ createOAuth2Client,
412
+ getAuthUrl,
413
+ exchangeCode,
414
+ createServiceAccountClient,
415
+ loadTokens,
416
+ saveTokens,
417
+ refreshIfNeeded,
418
+ GoogleGmailConnector
419
+ };
package/dist/cli.js CHANGED
@@ -179,7 +179,7 @@ async function authGoogle(args) {
179
179
  });
180
180
  }
181
181
  };
182
- const { GoogleGmailConnector } = await import("./gmail-connector-Z7SO6VOS.js");
182
+ const { GoogleGmailConnector } = await import("./gmail-connector-VP5FF56J.js");
183
183
  const connector = new GoogleGmailConnector({ tokenLoader, tokenSaver });
184
184
  connector.config = {
185
185
  account,
@@ -38,6 +38,14 @@ interface EmailAddress {
38
38
  name?: string;
39
39
  email: string;
40
40
  }
41
+ interface EmailAttachment {
42
+ /** The Gmail attachment ID — can be used with users.messages.attachments.get */
43
+ attachmentId: string;
44
+ filename: string;
45
+ mimeType: string;
46
+ /** Size in bytes as reported by Gmail */
47
+ size: number;
48
+ }
41
49
  interface EmailRecord {
42
50
  gmailId: string;
43
51
  threadId: string;
@@ -53,6 +61,7 @@ interface EmailRecord {
53
61
  body?: string;
54
62
  labels: string[];
55
63
  isRead: boolean;
64
+ attachments: EmailAttachment[];
56
65
  }
57
66
  interface DriveOwner {
58
67
  displayName: string;
@@ -275,4 +284,4 @@ declare class GoogleDriveConnector implements Connector<DriveFileRecord> {
275
284
  private mapFile;
276
285
  }
277
286
 
278
- export { type CalendarAttendee, type CalendarConnectorOpts, type CalendarEventRecord, type DriveConnectorOpts, type DriveFileRecord, type DriveOwner, type EmailAddress, type EmailRecord, type GmailConnectorOpts, GoogleCalendarConnector, type GoogleConnectorConfig, GoogleDriveConnector, GoogleGmailConnector, type GoogleOAuthConfig, type GoogleServiceAccountConfig, type GoogleTokens, createOAuth2Client, createServiceAccountClient, exchangeCode, getAuthUrl, loadTokens, refreshIfNeeded, saveTokens };
287
+ export { type CalendarAttendee, type CalendarConnectorOpts, type CalendarEventRecord, type DriveConnectorOpts, type DriveFileRecord, type DriveOwner, type EmailAddress, type EmailAttachment, type EmailRecord, type GmailConnectorOpts, GoogleCalendarConnector, type GoogleConnectorConfig, GoogleDriveConnector, GoogleGmailConnector, type GoogleOAuthConfig, type GoogleServiceAccountConfig, type GoogleTokens, createOAuth2Client, createServiceAccountClient, exchangeCode, getAuthUrl, loadTokens, refreshIfNeeded, saveTokens };
@@ -7,7 +7,7 @@ import {
7
7
  loadTokens,
8
8
  refreshIfNeeded,
9
9
  saveTokens
10
- } from "../../chunk-XYF5PSB2.js";
10
+ } from "../../chunk-7AGWGYZC.js";
11
11
  import "../../chunk-3RG5ZIWI.js";
12
12
 
13
13
  // src/connectors/google/calendar-connector.ts
@@ -0,0 +1,7 @@
1
+ import {
2
+ GoogleGmailConnector
3
+ } from "./chunk-7AGWGYZC.js";
4
+ import "./chunk-3RG5ZIWI.js";
5
+ export {
6
+ GoogleGmailConnector
7
+ };
package/dist/index.d.ts CHANGED
@@ -4,8 +4,8 @@ import { T as TokenUsage, L as LLMProvider, M as ModelInfo, R as ResolvedModel,
4
4
  export { a as ChatParams, b as ChatResult, c as ContentBlock, d as ToolUse } from './provider-DLGUfnNx.js';
5
5
  import { C as ConnectorConfig } from './connector-B4Mj0P1b.js';
6
6
  export { A as AuthResult, a as Connector, b as ConnectorMeta, P as PushResult, S as SyncOptions, c as SyncResult } from './connector-B4Mj0P1b.js';
7
- import { C as ChatResponderConfig, D as DataStore, H as HookBus, M as MessageStore, a as ChatResponder, b as MessageInterpreter, E as Extractor } from './chat-pipeline-BWrtVqEP.js';
8
- export { c as ChatPipeline, d as ChatPipelineConfig, e as DataStoreError, f as EntityContextDef, g as EntityFileSpec, h as EntitySource, i as ExtractedFile, j as ExtractedMemory, k as ExtractedTask, l as ExtractedUserContext, F as Filter, m as HookHandler, n as HookOptions, o as HookRegistration, I as InterpretationResult, L as LLMCallFn, p as MessageInterpreterConfig, P as PkLookup, Q as QueryOptions, R as RelationDef, q as RoutingDecision, r as RoutingRule, s as Row, S as SeedItem, t as SqliteAdapter, u as StoreResult, v as StoredAttachment, T as TableDefinition, w as TableInfoRow, x as TriageRouter, y as TriageRouterConfig, U as Unsubscribe } from './chat-pipeline-BWrtVqEP.js';
7
+ import { C as ChatResponderConfig, D as DataStore, H as HookBus, M as MessageStore, a as ChatResponder, b as MessageInterpreter, E as Extractor } from './chat-pipeline-DuNX5WoL.js';
8
+ export { c as ChatPipeline, d as ChatPipelineConfig, e as DataStoreError, f as EntityContextDef, g as EntityFileSpec, h as EntitySource, i as ExtractedFile, j as ExtractedMemory, k as ExtractedTask, l as ExtractedUserContext, F as Filter, m as HookHandler, n as HookOptions, o as HookRegistration, I as InterpretationResult, L as LLMCallFn, p as MessageInterpreterConfig, P as PkLookup, Q as QueryOptions, R as RelationDef, q as RoutingDecision, r as RoutingRule, s as Row, S as SeedItem, t as SqliteAdapter, u as StoreResult, v as StoredAttachment, T as TableDefinition, w as TableInfoRow, x as TriageRouter, y as TriageRouterConfig, U as Unsubscribe } from './chat-pipeline-DuNX5WoL.js';
9
9
  import 'better-sqlite3';
10
10
 
11
11
  /** Execution adapter types — Story 1.5 / 3.4 / 3.5 */
@@ -1791,6 +1791,7 @@ declare class LoopDetector {
1791
1791
 
1792
1792
  interface FeedbackEntry {
1793
1793
  agentId: string;
1794
+ userId?: string;
1794
1795
  taskId?: string;
1795
1796
  issue: string;
1796
1797
  rootCause?: string;
@@ -1805,6 +1806,7 @@ interface PlaybookEntry {
1805
1806
  rule: string;
1806
1807
  feedbackIds: string[];
1807
1808
  projectScoped: boolean;
1809
+ clientId?: string;
1808
1810
  agentIds?: string[];
1809
1811
  }
1810
1812
  interface SkillEntry {
@@ -1839,6 +1841,7 @@ declare class LearningPipeline {
1839
1841
  */
1840
1842
  listFeedback(filter?: {
1841
1843
  agentId?: string;
1844
+ userId?: string;
1842
1845
  severity?: string;
1843
1846
  repeatable?: boolean;
1844
1847
  }): Promise<Array<Record<string, unknown>>>;
@@ -2163,6 +2166,16 @@ declare const searchConversationTool: {
2163
2166
  handler: ToolHandler;
2164
2167
  };
2165
2168
 
2169
+ /**
2170
+ * Shared agent resolution: slug → role → name (case-insensitive).
2171
+ *
2172
+ * LLMs see list_agents output like "AgentName (role)" and naturally use
2173
+ * the role as the identifier. All agent-resolving tools must accept
2174
+ * slug, role, OR name — not just slug.
2175
+ */
2176
+
2177
+ declare function resolveAgent(db: DataStore, ref: string): Promise<Record<string, unknown> | null>;
2178
+
2166
2179
  /**
2167
2180
  * Built-in tools: Entity creation — agents, projects.
2168
2181
  */
@@ -2287,4 +2300,4 @@ declare function isLoginRequired(stdout: string): boolean;
2287
2300
  /** Rewrite local image paths to prevent CLI auto-embedding as vision content. */
2288
2301
  declare function deactivateLocalImagePaths(prompt: string): string;
2289
2302
 
2290
- export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, type ApprovalResponse, type ApprovalStatus, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, BreakerState, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, type ChatConfig, ChatMessage, ChatPipelineV2, type ChatPipelineV2Config, ChatResponder, ChatResponderConfig, ChatSessionManager, CircuitBreaker, type CircuitBreakerConfig, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, ConnectorConfig, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, type DefaultLLMCallConfig, DeterministicAdapter, type DeterministicConfig, type DomainEntityContextOptions, type DomainSchemaOptions, DriftGate, EVENTS, type EntityColumnDef, type EntityConfig, type ExecutionAdapter, type ExecutionConfig, type ExecutionEngineConfig, Extractor, type FeedbackEntry, type GateFinding, type GateInput, type GateResult, GateRunner, type GateVerdict, GovernanceGate, HealthStatus, HookBus, InboundMessage, LLMProvider, LearningPipeline, type LearningPipelineConfig, type LoopDetection, LoopDetector, type LoopDetectorConfig, LoopType, MAX_CHAIN_DEPTH, MessageInterpreter, MessagePipeline, MessageStore, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type ParsedStream, type PermissionPrompt, type PermissionProvider, PermissionRelay, type PermissionRelayConfig, type PlaybookEntry, ProviderRegistry, QAGate, QualityGate, RUN_STATUSES, type RenderConfig, ResolvedModel, type RetryPolicy, type RoutingConfig, type RunContext, RunManager, type RunResult, type RunStatus, type SafetyConfig, type SanitizerOptions, type Schedule, type ScheduleDef, Scheduler, type SchemaError, type SecretInput, type SecretMeta, SecretStore, type SecurityConfig, SessionKey, SessionManager, type SkillEntry, type StepRef, type SystemContextOptions, TASK_STATUSES, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, type ToolContext, type ToolDefinition, type ToolHandler, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, type UsageSummary, type User, type UserInput, UserRegistry, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, addTaskCommentTool, areDependenciesMet, autoUpdate, buildAgentBindings, buildChainOrigin, buildProcessEnv, buildSystemContext, cancelTaskTool, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, coordinatorTools, createAgentTool, createConfigRevision, createDefaultLLMCall, createProjectTool, deactivateLocalImagePaths, defineCoreEntityContexts, defineCoreTables, defineDomainEntityContexts, defineDomainTables, detectCycle, discoverChannels, discoverProviders, dispatchTaskTool, formatText, getActiveTasksTool, getAgentDetailTool, getAgentStatusTool, getConfig, getSystemStatusTool, getTaskStatusTool, initConfig, interpolate, interpolateEnv, isLoginRequired, isMaxTurns, listAgentsTool, listFilesTool, listProjectsTool, loadConfig, nativeTools, parseClaudeStream, parseVersion, readConversationTool, readFileTool, reassignTaskTool, registerExecutionEngine, registerFileTool, runPackageMigrations, sanitize, searchConversationTool, sendFileTool, sendMessageTool, topologicalSort, truncateAtWord, validateConfig };
2303
+ export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, type ApprovalResponse, type ApprovalStatus, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, BreakerState, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, type ChatConfig, ChatMessage, ChatPipelineV2, type ChatPipelineV2Config, ChatResponder, ChatResponderConfig, ChatSessionManager, CircuitBreaker, type CircuitBreakerConfig, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, ConnectorConfig, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, type DefaultLLMCallConfig, DeterministicAdapter, type DeterministicConfig, type DomainEntityContextOptions, type DomainSchemaOptions, DriftGate, EVENTS, type EntityColumnDef, type EntityConfig, type ExecutionAdapter, type ExecutionConfig, type ExecutionEngineConfig, Extractor, type FeedbackEntry, type GateFinding, type GateInput, type GateResult, GateRunner, type GateVerdict, GovernanceGate, HealthStatus, HookBus, InboundMessage, LLMProvider, LearningPipeline, type LearningPipelineConfig, type LoopDetection, LoopDetector, type LoopDetectorConfig, LoopType, MAX_CHAIN_DEPTH, MessageInterpreter, MessagePipeline, MessageStore, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type ParsedStream, type PermissionPrompt, type PermissionProvider, PermissionRelay, type PermissionRelayConfig, type PlaybookEntry, ProviderRegistry, QAGate, QualityGate, RUN_STATUSES, type RenderConfig, ResolvedModel, type RetryPolicy, type RoutingConfig, type RunContext, RunManager, type RunResult, type RunStatus, type SafetyConfig, type SanitizerOptions, type Schedule, type ScheduleDef, Scheduler, type SchemaError, type SecretInput, type SecretMeta, SecretStore, type SecurityConfig, SessionKey, SessionManager, type SkillEntry, type StepRef, type SystemContextOptions, TASK_STATUSES, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, type ToolContext, type ToolDefinition, type ToolHandler, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, type UsageSummary, type User, type UserInput, UserRegistry, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, addTaskCommentTool, areDependenciesMet, autoUpdate, buildAgentBindings, buildChainOrigin, buildProcessEnv, buildSystemContext, cancelTaskTool, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, coordinatorTools, createAgentTool, createConfigRevision, createDefaultLLMCall, createProjectTool, deactivateLocalImagePaths, defineCoreEntityContexts, defineCoreTables, defineDomainEntityContexts, defineDomainTables, detectCycle, discoverChannels, discoverProviders, dispatchTaskTool, formatText, getActiveTasksTool, getAgentDetailTool, getAgentStatusTool, getConfig, getSystemStatusTool, getTaskStatusTool, initConfig, interpolate, interpolateEnv, isLoginRequired, isMaxTurns, listAgentsTool, listFilesTool, listProjectsTool, loadConfig, nativeTools, parseClaudeStream, parseVersion, readConversationTool, readFileTool, reassignTaskTool, registerExecutionEngine, registerFileTool, resolveAgent, runPackageMigrations, sanitize, searchConversationTool, sendFileTool, sendMessageTool, topologicalSort, truncateAtWord, validateConfig };
package/dist/index.js CHANGED
@@ -1662,6 +1662,7 @@ var ChatPipeline = class {
1662
1662
  this.messageFilter = config.messageFilter;
1663
1663
  this.capabilities = config.capabilities;
1664
1664
  this.dedupWindowMs = config.dedupWindowMs ?? DEFAULT_DEDUP_WINDOW_MS;
1665
+ this.skipAck = config.skipAck ?? false;
1665
1666
  this.tasks = config.tasks;
1666
1667
  this.wakeups = config.wakeups;
1667
1668
  this.messageStore = new MessageStore(db, hooks);
@@ -1695,6 +1696,7 @@ var ChatPipeline = class {
1695
1696
  dedupWindowMs;
1696
1697
  tasks;
1697
1698
  wakeups;
1699
+ skipAck;
1698
1700
  // In-memory thread → channel mapping for response routing
1699
1701
  // (before thread_task_map exists)
1700
1702
  threadChannelMap = /* @__PURE__ */ new Map();
@@ -1731,24 +1733,26 @@ var ChatPipeline = class {
1731
1733
  const dir = m.direction === "inbound" ? "User" : "Bot";
1732
1734
  return `${dir}: ${m.body?.slice(0, 200) ?? ""}`;
1733
1735
  }).join("\n");
1734
- const ackResponse = await this.responder.respond({
1735
- messageBody: msg.body,
1736
- threadId: threadTs,
1737
- channel: this.channel,
1738
- capabilities: this.capabilities,
1739
- additionalContext: historyContext ? `
1736
+ if (!this.skipAck) {
1737
+ const ackResponse = await this.responder.respond({
1738
+ messageBody: msg.body,
1739
+ threadId: threadTs,
1740
+ channel: this.channel,
1741
+ capabilities: this.capabilities,
1742
+ additionalContext: historyContext ? `
1740
1743
 
1741
1744
  Recent conversation history:
1742
1745
  ${historyContext}` : void 0
1743
- });
1744
- await this.responder.sendResponse({
1745
- text: ackResponse,
1746
- channel: this.channel,
1747
- threadId: threadTs,
1748
- source: "responder",
1749
- skipFilter: true,
1750
- skipRedundancyCheck: true
1751
- });
1746
+ });
1747
+ await this.responder.sendResponse({
1748
+ text: ackResponse,
1749
+ channel: this.channel,
1750
+ threadId: threadTs,
1751
+ source: "responder",
1752
+ skipFilter: true,
1753
+ skipRedundancyCheck: true
1754
+ });
1755
+ }
1752
1756
  const dispatchPromise = this.interpretAndDispatch(messageId, msg, threadTs, channelId);
1753
1757
  this.lastDispatch = dispatchPromise;
1754
1758
  void dispatchPromise;
@@ -2966,6 +2970,7 @@ function defineCoreTables(db) {
2966
2970
  columns: {
2967
2971
  id: "TEXT PRIMARY KEY",
2968
2972
  agent_id: "TEXT NOT NULL",
2973
+ user_id: "TEXT",
2969
2974
  task_id: "TEXT",
2970
2975
  issue: "TEXT NOT NULL",
2971
2976
  root_cause: "TEXT",
@@ -2978,7 +2983,8 @@ function defineCoreTables(db) {
2978
2983
  },
2979
2984
  tableConstraints: [
2980
2985
  "CREATE INDEX IF NOT EXISTS idx_feedback_agent ON feedback(agent_id, created_at)",
2981
- "CREATE INDEX IF NOT EXISTS idx_feedback_issue ON feedback(issue)"
2986
+ "CREATE INDEX IF NOT EXISTS idx_feedback_issue ON feedback(issue)",
2987
+ "CREATE INDEX IF NOT EXISTS idx_feedback_user ON feedback(user_id)"
2982
2988
  ]
2983
2989
  });
2984
2990
  db.define("playbooks", {
@@ -2988,6 +2994,7 @@ function defineCoreTables(db) {
2988
2994
  rule: "TEXT NOT NULL",
2989
2995
  feedback_ids: "TEXT NOT NULL DEFAULT '[]'",
2990
2996
  project_scoped: "INTEGER NOT NULL DEFAULT 1",
2997
+ client_id: "TEXT",
2991
2998
  created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
2992
2999
  updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
2993
3000
  deleted_at: "TEXT"
@@ -3113,6 +3120,18 @@ var CORE_MIGRATIONS = [
3113
3120
  {
3114
3121
  version: "006_schedules_next_index",
3115
3122
  sql: `CREATE INDEX IF NOT EXISTS idx_schedules_next ON schedules(enabled, next_fire_at) WHERE deleted_at IS NULL`
3123
+ },
3124
+ {
3125
+ version: "biab:2.5.0:feedback-user-id",
3126
+ sql: `ALTER TABLE feedback ADD COLUMN user_id TEXT`
3127
+ },
3128
+ {
3129
+ version: "biab:2.5.0:feedback-user-idx",
3130
+ sql: `CREATE INDEX IF NOT EXISTS idx_feedback_user ON feedback(user_id)`
3131
+ },
3132
+ {
3133
+ version: "biab:2.5.0:playbooks-client-id",
3134
+ sql: `ALTER TABLE playbooks ADD COLUMN client_id TEXT`
3116
3135
  }
3117
3136
  ];
3118
3137
 
@@ -5932,6 +5951,7 @@ var LearningPipeline = class {
5932
5951
  async captureFeedback(entry) {
5933
5952
  const row = await this.db.insert("feedback", {
5934
5953
  agent_id: entry.agentId,
5954
+ user_id: entry.userId,
5935
5955
  task_id: entry.taskId,
5936
5956
  issue: entry.issue,
5937
5957
  root_cause: entry.rootCause,
@@ -5945,6 +5965,7 @@ var LearningPipeline = class {
5945
5965
  await this.hooks.emit("learning.feedback_captured", {
5946
5966
  feedbackId,
5947
5967
  agentId: entry.agentId,
5968
+ userId: entry.userId,
5948
5969
  issue: entry.issue,
5949
5970
  severity: entry.severity
5950
5971
  });
@@ -5959,6 +5980,7 @@ var LearningPipeline = class {
5959
5980
  async listFeedback(filter) {
5960
5981
  const where = {};
5961
5982
  if (filter?.agentId) where["agent_id"] = filter.agentId;
5983
+ if (filter?.userId) where["user_id"] = filter.userId;
5962
5984
  if (filter?.severity) where["severity"] = filter.severity;
5963
5985
  if (filter?.repeatable !== void 0) where["repeatable"] = filter.repeatable ? 1 : 0;
5964
5986
  return this.db.query("feedback", Object.keys(where).length ? { where } : void 0);
@@ -6000,7 +6022,8 @@ var LearningPipeline = class {
6000
6022
  pattern: entry.pattern,
6001
6023
  rule: entry.rule,
6002
6024
  feedback_ids: JSON.stringify(entry.feedbackIds),
6003
- project_scoped: entry.projectScoped ? 1 : 0
6025
+ project_scoped: entry.projectScoped ? 1 : 0,
6026
+ client_id: entry.clientId
6004
6027
  });
6005
6028
  const playbookId = row["id"];
6006
6029
  if (entry.agentIds) {
@@ -6662,6 +6685,25 @@ var registerFileTool = {
6662
6685
 
6663
6686
  // src/core/orchestrator/tools/task-ops.ts
6664
6687
  import { randomUUID as randomUUID2 } from "crypto";
6688
+
6689
+ // src/core/orchestrator/tools/resolve-agent.ts
6690
+ async function resolveAgent(db, ref) {
6691
+ const bySlug = await db.query("agents", { where: { slug: ref } });
6692
+ if (bySlug[0] && !bySlug[0].deleted_at) return bySlug[0];
6693
+ const byRole = await db.query("agents", { where: { role: ref } });
6694
+ const activeByRole = byRole.find((a) => !a.deleted_at);
6695
+ if (activeByRole) return activeByRole;
6696
+ const byName = await db.query("agents", { where: { name: ref } });
6697
+ const activeByName = byName.find((a) => !a.deleted_at);
6698
+ if (activeByName) return activeByName;
6699
+ const lower = ref.toLowerCase();
6700
+ const all = await db.query("agents");
6701
+ return all.find(
6702
+ (a) => !a.deleted_at && (a.slug?.toLowerCase() === lower || a.role?.toLowerCase() === lower || a.name?.toLowerCase() === lower)
6703
+ ) ?? null;
6704
+ }
6705
+
6706
+ // src/core/orchestrator/tools/task-ops.ts
6665
6707
  var dispatchTaskTool = {
6666
6708
  definition: {
6667
6709
  name: "dispatch_task",
@@ -6671,15 +6713,14 @@ var dispatchTaskTool = {
6671
6713
  properties: {
6672
6714
  title: { type: "string", description: "Short task title" },
6673
6715
  description: { type: "string", description: "Detailed task description" },
6674
- agent_slug: { type: "string", description: 'Agent slug to assign to (e.g. "engineer", "qa")' },
6716
+ agent_slug: { type: "string", description: 'Agent slug, role, or name (e.g. "eddy", "engineer", "Eddy")' },
6675
6717
  priority: { type: "number", description: "Priority 1-10 (lower = higher priority). Default: 5" }
6676
6718
  },
6677
6719
  required: ["title", "agent_slug"]
6678
6720
  }
6679
6721
  },
6680
6722
  handler: async (input, ctx) => {
6681
- const agents = await ctx.db.query("agents", { where: { slug: input.agent_slug } });
6682
- const agent = agents[0];
6723
+ const agent = await resolveAgent(ctx.db, input.agent_slug);
6683
6724
  if (!agent) return `Error: agent "${input.agent_slug}" not found.`;
6684
6725
  const row = await ctx.db.insert("tasks", {
6685
6726
  id: randomUUID2(),
@@ -6719,7 +6760,7 @@ var reassignTaskTool = {
6719
6760
  type: "object",
6720
6761
  properties: {
6721
6762
  task_id: { type: "string", description: "Task ID to reassign" },
6722
- new_agent_slug: { type: "string", description: "Agent slug to reassign to" }
6763
+ new_agent_slug: { type: "string", description: "Agent slug, role, or name to reassign to" }
6723
6764
  },
6724
6765
  required: ["task_id", "new_agent_slug"]
6725
6766
  }
@@ -6727,8 +6768,7 @@ var reassignTaskTool = {
6727
6768
  handler: async (input, ctx) => {
6728
6769
  const task = await ctx.db.get("tasks", { id: input.task_id });
6729
6770
  if (!task) return `Error: task ${input.task_id} not found.`;
6730
- const agents = await ctx.db.query("agents", { where: { slug: input.new_agent_slug } });
6731
- const newAgent = agents[0];
6771
+ const newAgent = await resolveAgent(ctx.db, input.new_agent_slug);
6732
6772
  if (!newAgent) return `Error: agent "${input.new_agent_slug}" not found.`;
6733
6773
  await ctx.db.update("tasks", { id: input.task_id }, { status: "cancelled" });
6734
6774
  const row = await ctx.db.insert("tasks", {
@@ -6776,14 +6816,13 @@ var getAgentStatusTool = {
6776
6816
  input_schema: {
6777
6817
  type: "object",
6778
6818
  properties: {
6779
- agent_slug: { type: "string", description: 'Agent slug (e.g. "engineer")' }
6819
+ agent_slug: { type: "string", description: 'Agent slug, role, or name (e.g. "eddy", "engineer", "Eddy")' }
6780
6820
  },
6781
6821
  required: ["agent_slug"]
6782
6822
  }
6783
6823
  },
6784
6824
  handler: async (input, ctx) => {
6785
- const agents = await ctx.db.query("agents", { where: { slug: input.agent_slug } });
6786
- const agent = agents[0];
6825
+ const agent = await resolveAgent(ctx.db, input.agent_slug);
6787
6826
  if (!agent) return `Agent "${input.agent_slug}" not found.`;
6788
6827
  const tasks = await ctx.db.query("tasks", { where: { assignee_id: agent.id, status: "todo" } });
6789
6828
  const runs = await ctx.db.query("runs", { where: { agent_id: agent.id }, limit: 5 });
@@ -6867,8 +6906,7 @@ var getAgentDetailTool = {
6867
6906
  }
6868
6907
  },
6869
6908
  handler: async (input, ctx) => {
6870
- const agents = await ctx.db.query("agents", { where: { slug: input.agent_slug } });
6871
- const agent = agents[0];
6909
+ const agent = await resolveAgent(ctx.db, input.agent_slug);
6872
6910
  if (!agent) return `Agent "${input.agent_slug}" not found.`;
6873
6911
  const skills = await ctx.db.query("agent_skills", { where: { agent_id: agent.id } }).catch(() => []);
6874
6912
  const skillNames = [];
@@ -7544,6 +7582,7 @@ export {
7544
7582
  reassignTaskTool,
7545
7583
  registerExecutionEngine,
7546
7584
  registerFileTool,
7585
+ resolveAgent,
7547
7586
  runPackageMigrations,
7548
7587
  sanitize,
7549
7588
  searchConversationTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "2.5.1",
3
+ "version": "2.5.3",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",