botinabox 2.5.0 → 2.5.1

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/README.md CHANGED
@@ -24,7 +24,7 @@ A modular TypeScript framework for building multi-agent bots with LLM orchestrat
24
24
  - **Event-driven hooks** -- Priority-ordered, filter-based event bus for decoupled communication.
25
25
  - **Budget controls** -- Per-agent and global cost tracking with warning thresholds and hard stops.
26
26
  - **Scheduling** -- Database-backed cron and one-time schedules.
27
- - **Connectors** -- Google Gmail and Calendar via OAuth2 and service account.
27
+ - **Connectors** -- Google Gmail, Calendar, and Drive via OAuth2 and service account.
28
28
  - **Security** -- Input sanitization, field length enforcement, audit logging, HMAC webhook verification.
29
29
 
30
30
  ## Install
@@ -110,7 +110,7 @@ tasks.startPolling();
110
110
  | `botinabox/slack` | Slack channel adapter (`SlackAdapter`) |
111
111
  | `botinabox/discord` | Discord channel adapter (`DiscordAdapter`) |
112
112
  | `botinabox/webhook` | Webhook channel adapter with HMAC verification (`WebhookAdapter`) |
113
- | `botinabox/google` | Google connectors -- Gmail and Calendar via OAuth2 |
113
+ | `botinabox/google` | Google connectors -- Gmail, Calendar, and Drive via OAuth2 |
114
114
 
115
115
  ## Architecture
116
116
 
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-DuNX5WoL.js';
2
+ import { H as HookBus, c as ChatPipeline } from '../../chat-pipeline-BWrtVqEP.js';
3
3
  import 'better-sqlite3';
4
4
  import '../../provider-DLGUfnNx.js';
5
5
 
@@ -584,8 +584,6 @@ 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;
589
587
  /** TaskQueue instance — required for task dispatch */
590
588
  tasks: {
591
589
  create(task: Record<string, unknown>): Promise<string>;
@@ -610,7 +608,6 @@ declare class ChatPipeline {
610
608
  private readonly dedupWindowMs;
611
609
  private readonly tasks;
612
610
  private readonly wakeups;
613
- private readonly skipAck;
614
611
  private readonly threadChannelMap;
615
612
  /** Last dispatch promise — exposed for testing. */
616
613
  lastDispatch: Promise<void>;
@@ -54,6 +54,29 @@ interface EmailRecord {
54
54
  labels: string[];
55
55
  isRead: boolean;
56
56
  }
57
+ interface DriveOwner {
58
+ displayName: string;
59
+ emailAddress: string;
60
+ }
61
+ interface DriveFileRecord {
62
+ driveFileId: string;
63
+ account: string;
64
+ name: string;
65
+ mimeType: string;
66
+ webViewLink: string;
67
+ webContentLink?: string;
68
+ /** ISO 8601 */
69
+ modifiedTime: string;
70
+ /** ISO 8601 */
71
+ createdTime: string;
72
+ size?: number;
73
+ parents?: string[];
74
+ description?: string;
75
+ owners: DriveOwner[];
76
+ lastModifyingUser?: DriveOwner;
77
+ starred: boolean;
78
+ trashed: boolean;
79
+ }
57
80
  interface CalendarAttendee {
58
81
  email: string;
59
82
  displayName?: string;
@@ -209,4 +232,47 @@ declare class GoogleCalendarConnector implements Connector<CalendarEventRecord>
209
232
  private mapEvent;
210
233
  }
211
234
 
212
- export { type CalendarAttendee, type CalendarConnectorOpts, type CalendarEventRecord, type EmailAddress, type EmailRecord, type GmailConnectorOpts, GoogleCalendarConnector, type GoogleConnectorConfig, GoogleGmailConnector, type GoogleOAuthConfig, type GoogleServiceAccountConfig, type GoogleTokens, createOAuth2Client, createServiceAccountClient, exchangeCode, getAuthUrl, loadTokens, refreshIfNeeded, saveTokens };
235
+ /**
236
+ * Google Drive connector — pulls file metadata from Drive.
237
+ *
238
+ * Produces `DriveFileRecord` objects. Does NOT write to any database
239
+ * table; the consuming application decides how to store records.
240
+ *
241
+ * Supports incremental sync via Drive Changes API (startPageToken)
242
+ * and full sync via files.list with optional folder/MIME filters.
243
+ */
244
+
245
+ interface DriveConnectorOpts {
246
+ /** Load persisted tokens for a given account key (OAuth2 flow only). */
247
+ tokenLoader?: (key: string) => Promise<string | null>;
248
+ /** Persist tokens for a given account key (OAuth2 flow only). */
249
+ tokenSaver?: (key: string, value: string) => Promise<void>;
250
+ }
251
+ declare class GoogleDriveConnector implements Connector<DriveFileRecord> {
252
+ readonly id = "google-drive";
253
+ readonly meta: ConnectorMeta;
254
+ private tokenLoader?;
255
+ private tokenSaver?;
256
+ private client;
257
+ private config;
258
+ private tokens;
259
+ private drive;
260
+ constructor(opts?: DriveConnectorOpts);
261
+ connect(config: GoogleConnectorConfig): Promise<void>;
262
+ disconnect(): Promise<void>;
263
+ healthCheck(): Promise<{
264
+ ok: boolean;
265
+ account?: string;
266
+ error?: string;
267
+ }>;
268
+ authenticate(codeProvider: (authUrl: string) => Promise<string>): Promise<AuthResult>;
269
+ sync(options?: SyncOptions): Promise<SyncResult<DriveFileRecord>>;
270
+ /** Incremental sync using Drive Changes API. */
271
+ private syncIncremental;
272
+ /** Full sync using files.list. */
273
+ private syncFull;
274
+ private ensureConnected;
275
+ private mapFile;
276
+ }
277
+
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 };
@@ -235,8 +235,248 @@ var GoogleCalendarConnector = class {
235
235
  function errorMessage(err) {
236
236
  return err instanceof Error ? err.message : String(err);
237
237
  }
238
+
239
+ // src/connectors/google/drive-connector.ts
240
+ var FILE_FIELDS = "id, name, mimeType, webViewLink, webContentLink, modifiedTime, createdTime, size, parents, description, owners, lastModifyingUser, starred, trashed";
241
+ var GoogleDriveConnector = class {
242
+ id = "google-drive";
243
+ meta = {
244
+ displayName: "Google Drive",
245
+ provider: "google",
246
+ dataType: "document"
247
+ };
248
+ tokenLoader;
249
+ tokenSaver;
250
+ client = null;
251
+ config = null;
252
+ tokens = null;
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
+ drive = null;
255
+ constructor(opts = {}) {
256
+ this.tokenLoader = opts.tokenLoader;
257
+ this.tokenSaver = opts.tokenSaver;
258
+ }
259
+ // ── Lifecycle ──────────────────────────────────────────────────
260
+ async connect(config) {
261
+ this.config = config;
262
+ const scopes = config.scopes ?? [
263
+ "https://www.googleapis.com/auth/drive.readonly"
264
+ ];
265
+ if (config.serviceAccount) {
266
+ this.client = await createServiceAccountClient(config.serviceAccount, scopes);
267
+ } else if (config.oauth) {
268
+ this.client = await createOAuth2Client(config.oauth);
269
+ if (!this.tokenLoader) {
270
+ throw new Error("tokenLoader required for OAuth2 flow");
271
+ }
272
+ this.tokens = await loadTokens(this.tokenLoader, config.account);
273
+ if (!this.tokens) {
274
+ throw new Error(
275
+ `No stored tokens for account ${config.account}. Complete the OAuth flow first.`
276
+ );
277
+ }
278
+ this.tokens = await refreshIfNeeded(
279
+ this.client,
280
+ this.tokens,
281
+ this.tokenSaver ? async (t) => saveTokens(this.tokenSaver, config.account, t) : void 0
282
+ );
283
+ this.client.setCredentials(this.tokens);
284
+ } else {
285
+ throw new Error("Either serviceAccount or oauth config is required");
286
+ }
287
+ const { google } = await import("googleapis");
288
+ this.drive = google.drive({ version: "v3", auth: this.client });
289
+ }
290
+ async disconnect() {
291
+ this.client = null;
292
+ this.drive = null;
293
+ this.tokens = null;
294
+ this.config = null;
295
+ }
296
+ async healthCheck() {
297
+ try {
298
+ this.ensureConnected();
299
+ const res = await this.drive.about.get({ fields: "user" });
300
+ return { ok: true, account: res.data.user?.emailAddress ?? this.config.account };
301
+ } catch (err) {
302
+ return { ok: false, error: errorMessage2(err) };
303
+ }
304
+ }
305
+ // ── Auth ───────────────────────────────────────────────────────
306
+ async authenticate(codeProvider) {
307
+ if (!this.config) {
308
+ return { success: false, error: "Call connect() first." };
309
+ }
310
+ try {
311
+ if (!this.config.oauth) {
312
+ return { success: false, error: "OAuth config required. Use serviceAccount for headless auth." };
313
+ }
314
+ if (!this.tokenSaver) {
315
+ return { success: false, error: "tokenSaver required for authenticate() flow." };
316
+ }
317
+ const client = await createOAuth2Client(this.config.oauth);
318
+ const scopes = this.config.scopes ?? [
319
+ "https://www.googleapis.com/auth/drive.readonly"
320
+ ];
321
+ const authUrl = getAuthUrl(client, scopes);
322
+ const code = await codeProvider(authUrl);
323
+ const tokens = await exchangeCode(client, code);
324
+ await saveTokens(this.tokenSaver, this.config.account, tokens);
325
+ this.tokens = tokens;
326
+ this.client = client;
327
+ this.client.setCredentials(tokens);
328
+ const { google } = await import("googleapis");
329
+ this.drive = google.drive({ version: "v3", auth: this.client });
330
+ return { success: true, account: this.config.account };
331
+ } catch (err) {
332
+ return { success: false, error: errorMessage2(err) };
333
+ }
334
+ }
335
+ // ── Sync ───────────────────────────────────────────────────────
336
+ async sync(options) {
337
+ this.ensureConnected();
338
+ if (options?.cursor) {
339
+ return this.syncIncremental(options.cursor, options);
340
+ }
341
+ return this.syncFull(options);
342
+ }
343
+ /** Incremental sync using Drive Changes API. */
344
+ async syncIncremental(startPageToken, options) {
345
+ const records = [];
346
+ const errors = [];
347
+ let pageToken = startPageToken;
348
+ let newStartPageToken;
349
+ try {
350
+ do {
351
+ const res = await this.drive.changes.list({
352
+ pageToken,
353
+ fields: `nextPageToken, newStartPageToken, changes(fileId, removed, file(${FILE_FIELDS}))`,
354
+ pageSize: options?.limit ? Math.min(options.limit - records.length, 100) : 100
355
+ });
356
+ for (const change of res.data.changes ?? []) {
357
+ try {
358
+ if (change.removed || !change.file) {
359
+ if (change.fileId) {
360
+ records.push({
361
+ driveFileId: change.fileId,
362
+ account: this.config.account,
363
+ name: "",
364
+ mimeType: "",
365
+ webViewLink: "",
366
+ modifiedTime: (/* @__PURE__ */ new Date()).toISOString(),
367
+ createdTime: "",
368
+ owners: [],
369
+ starred: false,
370
+ trashed: true
371
+ });
372
+ }
373
+ } else {
374
+ records.push(this.mapFile(change.file));
375
+ }
376
+ } catch (err) {
377
+ errors.push({ id: change.fileId ?? "unknown", error: errorMessage2(err) });
378
+ }
379
+ if (options?.limit && records.length >= options.limit) break;
380
+ }
381
+ pageToken = res.data.nextPageToken ?? void 0;
382
+ newStartPageToken = res.data.newStartPageToken ?? void 0;
383
+ } while (pageToken && (!options?.limit || records.length < options.limit));
384
+ } catch (err) {
385
+ if (err?.code === 403 || err?.code === 404) {
386
+ return this.syncFull(options);
387
+ }
388
+ throw err;
389
+ }
390
+ return {
391
+ records,
392
+ cursor: newStartPageToken,
393
+ hasMore: !!pageToken,
394
+ errors
395
+ };
396
+ }
397
+ /** Full sync using files.list. */
398
+ async syncFull(options) {
399
+ const records = [];
400
+ const errors = [];
401
+ const maxResults = options?.limit ?? 500;
402
+ const queryParts = ["trashed = false"];
403
+ const folderId = options?.filters?.folderId;
404
+ if (folderId) {
405
+ queryParts.push(`'${folderId}' in parents`);
406
+ }
407
+ const mimeType = options?.filters?.mimeType;
408
+ if (mimeType) {
409
+ queryParts.push(`mimeType = '${mimeType}'`);
410
+ }
411
+ if (options?.since) {
412
+ queryParts.push(`modifiedTime > '${new Date(options.since).toISOString()}'`);
413
+ }
414
+ const q = queryParts.join(" and ");
415
+ let pageToken;
416
+ do {
417
+ const res = await this.drive.files.list({
418
+ q,
419
+ fields: `nextPageToken, files(${FILE_FIELDS})`,
420
+ orderBy: "modifiedTime desc",
421
+ pageSize: Math.min(maxResults - records.length, 100),
422
+ ...pageToken ? { pageToken } : {}
423
+ });
424
+ for (const file of res.data.files ?? []) {
425
+ try {
426
+ records.push(this.mapFile(file));
427
+ } catch (err) {
428
+ errors.push({ id: file.id, error: errorMessage2(err) });
429
+ }
430
+ if (records.length >= maxResults) break;
431
+ }
432
+ pageToken = res.data.nextPageToken ?? void 0;
433
+ } while (pageToken && records.length < maxResults);
434
+ const tokenRes = await this.drive.changes.getStartPageToken({});
435
+ const cursor = tokenRes.data.startPageToken ?? void 0;
436
+ return {
437
+ records,
438
+ cursor,
439
+ hasMore: !!pageToken,
440
+ errors
441
+ };
442
+ }
443
+ // ── Internals ─────────────────────────────────────────────────
444
+ ensureConnected() {
445
+ if (!this.drive || !this.config) {
446
+ throw new Error("GoogleDriveConnector is not connected. Call connect() first.");
447
+ }
448
+ }
449
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
450
+ mapFile(file) {
451
+ const mapOwner = (o) => ({
452
+ displayName: o?.displayName ?? "",
453
+ emailAddress: o?.emailAddress ?? ""
454
+ });
455
+ return {
456
+ driveFileId: file.id,
457
+ account: this.config.account,
458
+ name: file.name ?? "(Untitled)",
459
+ mimeType: file.mimeType ?? "",
460
+ webViewLink: file.webViewLink ?? "",
461
+ webContentLink: file.webContentLink ?? void 0,
462
+ modifiedTime: file.modifiedTime ?? (/* @__PURE__ */ new Date()).toISOString(),
463
+ createdTime: file.createdTime ?? "",
464
+ size: file.size ? parseInt(file.size, 10) : void 0,
465
+ parents: file.parents ?? void 0,
466
+ description: file.description ?? void 0,
467
+ owners: (file.owners ?? []).map(mapOwner),
468
+ lastModifyingUser: file.lastModifyingUser ? mapOwner(file.lastModifyingUser) : void 0,
469
+ starred: file.starred ?? false,
470
+ trashed: file.trashed ?? false
471
+ };
472
+ }
473
+ };
474
+ function errorMessage2(err) {
475
+ return err instanceof Error ? err.message : String(err);
476
+ }
238
477
  export {
239
478
  GoogleCalendarConnector,
479
+ GoogleDriveConnector,
240
480
  GoogleGmailConnector,
241
481
  createOAuth2Client,
242
482
  createServiceAccountClient,
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-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';
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';
9
9
  import 'better-sqlite3';
10
10
 
11
11
  /** Execution adapter types — Story 1.5 / 3.4 / 3.5 */
@@ -1791,7 +1791,6 @@ declare class LoopDetector {
1791
1791
 
1792
1792
  interface FeedbackEntry {
1793
1793
  agentId: string;
1794
- userId?: string;
1795
1794
  taskId?: string;
1796
1795
  issue: string;
1797
1796
  rootCause?: string;
@@ -1806,7 +1805,6 @@ interface PlaybookEntry {
1806
1805
  rule: string;
1807
1806
  feedbackIds: string[];
1808
1807
  projectScoped: boolean;
1809
- clientId?: string;
1810
1808
  agentIds?: string[];
1811
1809
  }
1812
1810
  interface SkillEntry {
@@ -1841,7 +1839,6 @@ declare class LearningPipeline {
1841
1839
  */
1842
1840
  listFeedback(filter?: {
1843
1841
  agentId?: string;
1844
- userId?: string;
1845
1842
  severity?: string;
1846
1843
  repeatable?: boolean;
1847
1844
  }): Promise<Array<Record<string, unknown>>>;
package/dist/index.js CHANGED
@@ -1662,7 +1662,6 @@ 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;
1666
1665
  this.tasks = config.tasks;
1667
1666
  this.wakeups = config.wakeups;
1668
1667
  this.messageStore = new MessageStore(db, hooks);
@@ -1696,7 +1695,6 @@ var ChatPipeline = class {
1696
1695
  dedupWindowMs;
1697
1696
  tasks;
1698
1697
  wakeups;
1699
- skipAck;
1700
1698
  // In-memory thread → channel mapping for response routing
1701
1699
  // (before thread_task_map exists)
1702
1700
  threadChannelMap = /* @__PURE__ */ new Map();
@@ -1733,26 +1731,24 @@ var ChatPipeline = class {
1733
1731
  const dir = m.direction === "inbound" ? "User" : "Bot";
1734
1732
  return `${dir}: ${m.body?.slice(0, 200) ?? ""}`;
1735
1733
  }).join("\n");
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 ? `
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 ? `
1743
1740
 
1744
1741
  Recent conversation history:
1745
1742
  ${historyContext}` : void 0
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
- }
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
+ });
1756
1752
  const dispatchPromise = this.interpretAndDispatch(messageId, msg, threadTs, channelId);
1757
1753
  this.lastDispatch = dispatchPromise;
1758
1754
  void dispatchPromise;
@@ -2970,7 +2966,6 @@ function defineCoreTables(db) {
2970
2966
  columns: {
2971
2967
  id: "TEXT PRIMARY KEY",
2972
2968
  agent_id: "TEXT NOT NULL",
2973
- user_id: "TEXT",
2974
2969
  task_id: "TEXT",
2975
2970
  issue: "TEXT NOT NULL",
2976
2971
  root_cause: "TEXT",
@@ -2983,8 +2978,7 @@ function defineCoreTables(db) {
2983
2978
  },
2984
2979
  tableConstraints: [
2985
2980
  "CREATE INDEX IF NOT EXISTS idx_feedback_agent ON feedback(agent_id, created_at)",
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)"
2981
+ "CREATE INDEX IF NOT EXISTS idx_feedback_issue ON feedback(issue)"
2988
2982
  ]
2989
2983
  });
2990
2984
  db.define("playbooks", {
@@ -2994,7 +2988,6 @@ function defineCoreTables(db) {
2994
2988
  rule: "TEXT NOT NULL",
2995
2989
  feedback_ids: "TEXT NOT NULL DEFAULT '[]'",
2996
2990
  project_scoped: "INTEGER NOT NULL DEFAULT 1",
2997
- client_id: "TEXT",
2998
2991
  created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
2999
2992
  updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
3000
2993
  deleted_at: "TEXT"
@@ -3120,18 +3113,6 @@ var CORE_MIGRATIONS = [
3120
3113
  {
3121
3114
  version: "006_schedules_next_index",
3122
3115
  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`
3135
3116
  }
3136
3117
  ];
3137
3118
 
@@ -5951,7 +5932,6 @@ var LearningPipeline = class {
5951
5932
  async captureFeedback(entry) {
5952
5933
  const row = await this.db.insert("feedback", {
5953
5934
  agent_id: entry.agentId,
5954
- user_id: entry.userId,
5955
5935
  task_id: entry.taskId,
5956
5936
  issue: entry.issue,
5957
5937
  root_cause: entry.rootCause,
@@ -5965,7 +5945,6 @@ var LearningPipeline = class {
5965
5945
  await this.hooks.emit("learning.feedback_captured", {
5966
5946
  feedbackId,
5967
5947
  agentId: entry.agentId,
5968
- userId: entry.userId,
5969
5948
  issue: entry.issue,
5970
5949
  severity: entry.severity
5971
5950
  });
@@ -5980,7 +5959,6 @@ var LearningPipeline = class {
5980
5959
  async listFeedback(filter) {
5981
5960
  const where = {};
5982
5961
  if (filter?.agentId) where["agent_id"] = filter.agentId;
5983
- if (filter?.userId) where["user_id"] = filter.userId;
5984
5962
  if (filter?.severity) where["severity"] = filter.severity;
5985
5963
  if (filter?.repeatable !== void 0) where["repeatable"] = filter.repeatable ? 1 : 0;
5986
5964
  return this.db.query("feedback", Object.keys(where).length ? { where } : void 0);
@@ -6022,8 +6000,7 @@ var LearningPipeline = class {
6022
6000
  pattern: entry.pattern,
6023
6001
  rule: entry.rule,
6024
6002
  feedback_ids: JSON.stringify(entry.feedbackIds),
6025
- project_scoped: entry.projectScoped ? 1 : 0,
6026
- client_id: entry.clientId
6003
+ project_scoped: entry.projectScoped ? 1 : 0
6027
6004
  });
6028
6005
  const playbookId = row["id"];
6029
6006
  if (entry.agentIds) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",