botinabox 0.1.1 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to `botinabox` are documented here.
4
+
5
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
6
+
7
+ ---
8
+
9
+ ## [0.2.0] — 2026-04-03
10
+
11
+ ### Added
12
+
13
+ - **Users primitive** — `users` and `user_identities` core tables. Users are protected objects (never auto-rendered into other entities' context). `UserRegistry` class: `register()`, `getById()`, `getByEmail()`, `resolveByIdentity()`, `resolveOrCreate()`, `addIdentity()`.
14
+ - **Secrets primitive** — `secrets` core table for encrypted credential storage. Protected by default. `SecretStore` class: `set()`, `get()`, `getMeta()`, `list()`, `rotate()`, `delete()`.
15
+ - **Message pipeline user resolution** — `MessagePipeline` accepts optional `UserRegistry`. When provided, resolves `InboundMessage.from` to a user ID via `resolveOrCreate()` before task creation. `InboundMessage.userId` field added.
16
+ - **`user_id` on messages table** — Tracks resolved user alongside raw `peer_id`.
17
+ - **Protected/encrypted passthrough** — `EntityContextDef` now supports `protected` and `encrypted` fields, passed through to Lattice's entity context system.
18
+
19
+ ### Changed
20
+
21
+ - Core table count: 15 → 18 (added `users`, `user_identities`, `secrets`).
22
+ - `messages` table gains `user_id` column.
23
+
24
+ ## [0.1.1] — 2026-03-28
25
+
26
+ ### Fixed
27
+
28
+ - Initial release bug fixes and stability improvements.
29
+
30
+ ## [0.1.0] — 2026-03-25
31
+
32
+ ### Added
33
+
34
+ - Initial release: DataStore, HookBus, AgentRegistry, TaskQueue, RunManager, WakeupQueue, BudgetController, WorkflowEngine, SessionManager, ChannelRegistry, MessagePipeline.
35
+ - 15 core tables, LLM provider routing, channel adapters (Slack, Discord, Webhook).
package/README.md CHANGED
@@ -11,6 +11,7 @@ A modular TypeScript framework for building multi-agent bots with LLM orchestrat
11
11
  - **SQLite data layer** — Schema-driven tables, migrations, entity context rendering, and query builder. WAL mode for concurrent reads.
12
12
  - **Event-driven hooks** — Priority-ordered, filter-based event bus for decoupled inter-layer communication.
13
13
  - **Budget controls** — Per-agent and global cost tracking with warning thresholds and hard stops.
14
+ - **Protected primitives** — Users and secrets are first-class, privacy-isolated objects. User identity resolution across channels. Secrets with optional AES-256-GCM at-rest encryption.
14
15
  - **Security** — Input sanitization, field length enforcement, audit logging, and HMAC webhook verification.
15
16
  - **Self-updating** — Built-in update checker with configurable policies and maintenance windows.
16
17
 
@@ -21,6 +21,7 @@ interface InboundMessage {
21
21
  channel: string;
22
22
  account?: string;
23
23
  from: string;
24
+ userId?: string;
24
25
  body: string;
25
26
  threadId?: string;
26
27
  replyToId?: string;
@@ -1,4 +1,4 @@
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-m9f7MFD7.js';
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
2
 
3
3
  /**
4
4
  * DiscordAdapter — ChannelAdapter implementation for Discord.
@@ -1,4 +1,4 @@
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-m9f7MFD7.js';
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
2
 
3
3
  /**
4
4
  * SlackAdapter — ChannelAdapter implementation for Slack.
@@ -1,4 +1,4 @@
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-m9f7MFD7.js';
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
2
 
3
3
  /**
4
4
  * WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ChannelAdapter, H as HealthStatus, I as InboundMessage } from './channel-m9f7MFD7.js';
2
- export { A as Attachment, a as ChannelCapabilities, b as ChannelConfig, c as ChannelMeta, d as ChatType, F as FormattingMode, O as OutboundPayload, S as SendResult } from './channel-m9f7MFD7.js';
1
+ import { C as ChannelAdapter, H as HealthStatus, I as InboundMessage } from './channel-06G0vbIn.js';
2
+ export { A as Attachment, a as ChannelCapabilities, b as ChannelConfig, c as ChannelMeta, d as ChatType, F as FormattingMode, O as OutboundPayload, S as SendResult } from './channel-06G0vbIn.js';
3
3
  import { T as TokenUsage, L as LLMProvider, M as ModelInfo, R as ResolvedModel, C as ChatMessage } from './provider-qqJYv9nv.js';
4
4
  export { a as ChatParams, b as ChatResult, c as ContentBlock, d as ToolDefinition, e as ToolUse } from './provider-qqJYv9nv.js';
5
5
  import * as better_sqlite3 from 'better-sqlite3';
@@ -518,6 +518,12 @@ interface EntityContextDef {
518
518
  files: Record<string, EntityFileSpec>;
519
519
  indexFile?: string;
520
520
  protectedFiles?: string[];
521
+ /** When true, this entity's data is never rendered into other entities' context files. */
522
+ protected?: boolean;
523
+ /** Enable at-rest encryption. Requires encryptionKey in Lattice options. */
524
+ encrypted?: boolean | {
525
+ columns: string[];
526
+ };
521
527
  }
522
528
  interface EntityFileSpec {
523
529
  source: EntitySource;
@@ -741,6 +747,52 @@ declare class TaskQueue {
741
747
  private poll;
742
748
  }
743
749
 
750
+ interface UserInput {
751
+ id?: string;
752
+ org_id?: string;
753
+ name: string;
754
+ email?: string;
755
+ role?: string;
756
+ title?: string;
757
+ external_id?: string;
758
+ channel?: string;
759
+ timezone?: string;
760
+ preferences?: string;
761
+ notes?: string;
762
+ }
763
+ interface User {
764
+ id: string;
765
+ org_id: string | null;
766
+ name: string;
767
+ email: string | null;
768
+ role: string | null;
769
+ title: string | null;
770
+ external_id: string | null;
771
+ channel: string | null;
772
+ timezone: string | null;
773
+ preferences: string;
774
+ notes: string | null;
775
+ created_at: string;
776
+ updated_at: string;
777
+ deleted_at: string | null;
778
+ }
779
+ declare class UserRegistry {
780
+ private readonly db;
781
+ private readonly hooks;
782
+ constructor(db: DataStore, hooks: HookBus);
783
+ register(input: UserInput): Promise<User>;
784
+ getById(id: string): Promise<User | null>;
785
+ getByEmail(email: string): Promise<User | null>;
786
+ resolveByIdentity(channel: string, externalId: string): Promise<User | null>;
787
+ resolveOrCreate(externalId: string, channel: string, defaults?: Partial<UserInput>): Promise<User>;
788
+ list(filter?: {
789
+ role?: string;
790
+ org_id?: string;
791
+ }): Promise<User[]>;
792
+ update(id: string, changes: Partial<UserInput>): Promise<void>;
793
+ addIdentity(userId: string, channel: string, externalId: string, displayName?: string): Promise<void>;
794
+ }
795
+
744
796
  /**
745
797
  * MessagePipeline — routes inbound messages to the task queue.
746
798
  * Story 4.2
@@ -752,7 +804,8 @@ declare class MessagePipeline {
752
804
  private readonly taskQueue;
753
805
  private readonly config;
754
806
  private readonly agentBindings;
755
- constructor(hooks: HookBus, agentRegistry: AgentRegistry, taskQueue: TaskQueue, config: BotConfig);
807
+ private readonly userRegistry?;
808
+ constructor(hooks: HookBus, agentRegistry: AgentRegistry, taskQueue: TaskQueue, config: BotConfig, userRegistry?: UserRegistry);
756
809
  /**
757
810
  * Process an inbound message end-to-end.
758
811
  * 1. Emit 'message.inbound'
@@ -906,7 +959,7 @@ declare function chunkText(text: string, maxLen: number): string[];
906
959
  declare function formatText(text: string, mode: "mrkdwn" | "html" | "plain"): string;
907
960
 
908
961
  /**
909
- * Define all 15 core tables on a DataStore instance.
962
+ * Define all 18 core tables on a DataStore instance.
910
963
  * Call before db.init().
911
964
  */
912
965
  declare function defineCoreTables(db: DataStore): void;
@@ -1265,4 +1318,43 @@ declare class CliExecutionAdapter {
1265
1318
  }>;
1266
1319
  }
1267
1320
 
1268
- export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, ChatMessage, ChatSessionManager, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, DataStoreError, EVENTS, type EntityColumnDef, type EntityConfig, type EntityContextDef, type EntityFileSpec, type EntitySource, type ExecutionAdapter, type Filter, HealthStatus, HeartbeatScheduler, HookBus, type HookHandler, type HookOptions, type HookRegistration, InboundMessage, LLMProvider, MAX_CHAIN_DEPTH, MessagePipeline, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type PkLookup, ProviderRegistry, type QueryOptions, RUN_STATUSES, type RelationDef, type RenderConfig, ResolvedModel, type RetryPolicy, type Row, type RunContext, RunManager, type RunResult, type RunStatus, type SanitizerOptions, type SchemaError, type SecurityConfig, type SeedItem, SessionKey, SessionManager, type SqliteAdapter, type StepRef, TASK_STATUSES, type TableDefinition, type TableInfoRow, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, type Unsubscribe, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, areDependenciesMet, buildAgentBindings, buildChainOrigin, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, createConfigRevision, defineCoreTables, detectCycle, discoverChannels, discoverProviders, formatText, getConfig, initConfig, interpolate, interpolateEnv, loadConfig, parseVersion, runPackageMigrations, sanitize, topologicalSort, validateConfig };
1321
+ interface SecretInput {
1322
+ name: string;
1323
+ type?: string;
1324
+ environment?: string;
1325
+ value?: string;
1326
+ location?: string;
1327
+ description?: string;
1328
+ rotation_schedule?: string;
1329
+ expires_at?: string;
1330
+ notes?: string;
1331
+ org_id?: string;
1332
+ }
1333
+ interface SecretMeta {
1334
+ id: string;
1335
+ org_id: string | null;
1336
+ name: string;
1337
+ type: string;
1338
+ environment: string;
1339
+ location: string | null;
1340
+ description: string | null;
1341
+ rotation_schedule: string | null;
1342
+ expires_at: string | null;
1343
+ notes: string | null;
1344
+ created_at: string;
1345
+ updated_at: string;
1346
+ }
1347
+ declare class SecretStore {
1348
+ private readonly db;
1349
+ private readonly hooks;
1350
+ constructor(db: DataStore, hooks: HookBus);
1351
+ set(input: SecretInput): Promise<SecretMeta>;
1352
+ get(name: string, environment?: string): Promise<string | null>;
1353
+ getMeta(name: string, environment?: string): Promise<SecretMeta | null>;
1354
+ list(): Promise<SecretMeta[]>;
1355
+ rotate(name: string, newValue: string, environment?: string): Promise<void>;
1356
+ delete(name: string, environment?: string): Promise<void>;
1357
+ private _toMeta;
1358
+ }
1359
+
1360
+ export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, ChatMessage, ChatSessionManager, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, DataStoreError, EVENTS, type EntityColumnDef, type EntityConfig, type EntityContextDef, type EntityFileSpec, type EntitySource, type ExecutionAdapter, type Filter, HealthStatus, HeartbeatScheduler, HookBus, type HookHandler, type HookOptions, type HookRegistration, InboundMessage, LLMProvider, MAX_CHAIN_DEPTH, MessagePipeline, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type PkLookup, ProviderRegistry, type QueryOptions, RUN_STATUSES, type RelationDef, type RenderConfig, ResolvedModel, type RetryPolicy, type Row, type RunContext, RunManager, type RunResult, type RunStatus, type SanitizerOptions, type SchemaError, type SecretInput, type SecretMeta, SecretStore, type SecurityConfig, type SeedItem, SessionKey, SessionManager, type SqliteAdapter, type StepRef, TASK_STATUSES, type TableDefinition, type TableInfoRow, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, type Unsubscribe, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, 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, areDependenciesMet, buildAgentBindings, buildChainOrigin, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, createConfigRevision, defineCoreTables, detectCycle, discoverChannels, discoverProviders, formatText, getConfig, initConfig, interpolate, interpolateEnv, loadConfig, parseVersion, runPackageMigrations, sanitize, topologicalSort, validateConfig };
package/dist/index.js CHANGED
@@ -686,18 +686,20 @@ function checkMentionGate(msg, botId) {
686
686
 
687
687
  // src/core/chat/pipeline.ts
688
688
  var MessagePipeline = class {
689
- constructor(hooks, agentRegistry, taskQueue, config) {
689
+ constructor(hooks, agentRegistry, taskQueue, config, userRegistry) {
690
690
  this.hooks = hooks;
691
691
  this.agentRegistry = agentRegistry;
692
692
  this.taskQueue = taskQueue;
693
693
  this.config = config;
694
694
  this.agentBindings = buildAgentBindings(config.agents);
695
+ this.userRegistry = userRegistry;
695
696
  }
696
697
  hooks;
697
698
  agentRegistry;
698
699
  taskQueue;
699
700
  config;
700
701
  agentBindings;
702
+ userRegistry;
701
703
  /**
702
704
  * Process an inbound message end-to-end.
703
705
  * 1. Emit 'message.inbound'
@@ -708,6 +710,10 @@ var MessagePipeline = class {
708
710
  */
709
711
  async processInbound(msg) {
710
712
  await this.hooks.emit("message.inbound", { message: msg, channel: msg.channel });
713
+ if (this.userRegistry && !msg.userId) {
714
+ const user = await this.userRegistry.resolveOrCreate(msg.from, msg.channel);
715
+ msg.userId = user.id;
716
+ }
711
717
  const agentId = this.resolveAgent(msg);
712
718
  if (agentId !== void 0) {
713
719
  const allowed = this.evaluatePolicy(msg, agentId);
@@ -716,14 +722,15 @@ var MessagePipeline = class {
716
722
  title: `Message from ${msg.from} on ${msg.channel}`,
717
723
  description: msg.body,
718
724
  assignee_id: agentId,
719
- context: JSON.stringify({ message: msg })
725
+ context: JSON.stringify({ message: msg, userId: msg.userId })
720
726
  });
721
727
  }
722
728
  }
723
729
  await this.hooks.emit("message.processed", {
724
730
  message: msg,
725
731
  channel: msg.channel,
726
- agentId: agentId ?? null
732
+ agentId: agentId ?? null,
733
+ userId: msg.userId ?? null
727
734
  });
728
735
  }
729
736
  /**
@@ -1123,10 +1130,16 @@ var DataStore = class {
1123
1130
  */
1124
1131
  defineEntityContext(name, def) {
1125
1132
  this.lattice.defineEntityContext(name, {
1126
- slug: (row) => row[def.slugColumn],
1133
+ slug: (row) => {
1134
+ const val = row[def.slugColumn];
1135
+ if (val == null) return String(row.id ?? row.name ?? "unknown");
1136
+ return String(val);
1137
+ },
1127
1138
  directoryRoot: def.directory,
1128
1139
  files: def.files,
1129
1140
  protectedFiles: def.protectedFiles,
1141
+ protected: def.protected,
1142
+ encrypted: def.encrypted,
1130
1143
  index: def.indexFile ? { outputFile: def.indexFile, render: (rows) => "" } : void 0
1131
1144
  });
1132
1145
  }
@@ -1338,6 +1351,7 @@ function defineCoreTables(db) {
1338
1351
  agent_id: "TEXT NOT NULL",
1339
1352
  channel: "TEXT NOT NULL",
1340
1353
  peer_id: "TEXT NOT NULL",
1354
+ user_id: "TEXT",
1341
1355
  last_message_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1342
1356
  message_count: "INTEGER NOT NULL DEFAULT 0",
1343
1357
  context: "TEXT NOT NULL DEFAULT '{}'",
@@ -1478,6 +1492,64 @@ function defineCoreTables(db) {
1478
1492
  rolled_back_at: "TEXT"
1479
1493
  }
1480
1494
  });
1495
+ db.define("users", {
1496
+ columns: {
1497
+ id: "TEXT PRIMARY KEY",
1498
+ org_id: "TEXT",
1499
+ name: "TEXT NOT NULL",
1500
+ email: "TEXT",
1501
+ role: "TEXT",
1502
+ title: "TEXT",
1503
+ external_id: "TEXT",
1504
+ channel: "TEXT",
1505
+ timezone: "TEXT",
1506
+ preferences: "TEXT NOT NULL DEFAULT '{}'",
1507
+ notes: "TEXT",
1508
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1509
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1510
+ deleted_at: "TEXT"
1511
+ },
1512
+ tableConstraints: [
1513
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users(email) WHERE email IS NOT NULL AND deleted_at IS NULL",
1514
+ "CREATE INDEX IF NOT EXISTS idx_users_external_id ON users(external_id) WHERE deleted_at IS NULL"
1515
+ ]
1516
+ });
1517
+ db.define("user_identities", {
1518
+ columns: {
1519
+ id: "TEXT PRIMARY KEY",
1520
+ user_id: "TEXT NOT NULL",
1521
+ channel: "TEXT NOT NULL",
1522
+ external_id: "TEXT NOT NULL",
1523
+ display_name: "TEXT",
1524
+ verified: "INTEGER NOT NULL DEFAULT 0",
1525
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
1526
+ },
1527
+ tableConstraints: [
1528
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_identities_channel_ext ON user_identities(channel, external_id)",
1529
+ "FOREIGN KEY (user_id) REFERENCES users(id)"
1530
+ ]
1531
+ });
1532
+ db.define("secrets", {
1533
+ columns: {
1534
+ id: "TEXT PRIMARY KEY",
1535
+ org_id: "TEXT",
1536
+ name: "TEXT NOT NULL",
1537
+ type: "TEXT NOT NULL DEFAULT 'api_key'",
1538
+ environment: "TEXT NOT NULL DEFAULT 'production'",
1539
+ value: "TEXT",
1540
+ location: "TEXT",
1541
+ description: "TEXT",
1542
+ rotation_schedule: "TEXT",
1543
+ expires_at: "TEXT",
1544
+ notes: "TEXT",
1545
+ created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1546
+ updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
1547
+ deleted_at: "TEXT"
1548
+ },
1549
+ tableConstraints: [
1550
+ "CREATE UNIQUE INDEX IF NOT EXISTS idx_secrets_name_env ON secrets(name, environment, org_id) WHERE deleted_at IS NULL"
1551
+ ]
1552
+ });
1481
1553
  }
1482
1554
 
1483
1555
  // src/core/data/core-migrations.ts
@@ -2900,6 +2972,164 @@ var CliExecutionAdapter = class {
2900
2972
  return { output, exitCode };
2901
2973
  }
2902
2974
  };
2975
+
2976
+ // src/core/orchestrator/user-registry.ts
2977
+ import { v4 as uuidv4 } from "uuid";
2978
+ var UserRegistry = class {
2979
+ db;
2980
+ hooks;
2981
+ constructor(db, hooks) {
2982
+ this.db = db;
2983
+ this.hooks = hooks;
2984
+ }
2985
+ async register(input) {
2986
+ const id = input.id ?? uuidv4();
2987
+ await this.db.insert("users", { ...input, id });
2988
+ const user = await this.db.get("users", id);
2989
+ await this.hooks.emit("user.created", { user });
2990
+ return user;
2991
+ }
2992
+ async getById(id) {
2993
+ const row = await this.db.get("users", id);
2994
+ return row ?? null;
2995
+ }
2996
+ async getByEmail(email) {
2997
+ const rows = await this.db.query("users", {
2998
+ where: { email },
2999
+ filters: [{ col: "deleted_at", op: "isNull" }],
3000
+ limit: 1
3001
+ });
3002
+ return rows.length > 0 ? rows[0] : null;
3003
+ }
3004
+ async resolveByIdentity(channel, externalId) {
3005
+ const identities = await this.db.query("user_identities", {
3006
+ where: { channel, external_id: externalId },
3007
+ limit: 1
3008
+ });
3009
+ if (identities.length === 0) return null;
3010
+ return this.getById(identities[0].user_id);
3011
+ }
3012
+ async resolveOrCreate(externalId, channel, defaults) {
3013
+ const existing = await this.resolveByIdentity(channel, externalId);
3014
+ if (existing) return existing;
3015
+ const user = await this.register({
3016
+ name: defaults?.name ?? externalId,
3017
+ external_id: externalId,
3018
+ channel,
3019
+ ...defaults
3020
+ });
3021
+ await this.addIdentity(user.id, channel, externalId);
3022
+ return user;
3023
+ }
3024
+ async list(filter) {
3025
+ const where = {};
3026
+ const filters = [{ col: "deleted_at", op: "isNull" }];
3027
+ if (filter?.role) where.role = filter.role;
3028
+ if (filter?.org_id) where.org_id = filter.org_id;
3029
+ const rows = await this.db.query("users", { where, filters });
3030
+ return rows;
3031
+ }
3032
+ async update(id, changes) {
3033
+ await this.db.update("users", id, {
3034
+ ...changes,
3035
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3036
+ });
3037
+ }
3038
+ async addIdentity(userId, channel, externalId, displayName) {
3039
+ await this.db.insert("user_identities", {
3040
+ id: uuidv4(),
3041
+ user_id: userId,
3042
+ channel,
3043
+ external_id: externalId,
3044
+ display_name: displayName ?? null,
3045
+ verified: 0
3046
+ });
3047
+ }
3048
+ };
3049
+
3050
+ // src/core/orchestrator/secret-store.ts
3051
+ import { v4 as uuidv42 } from "uuid";
3052
+ var SecretStore = class {
3053
+ db;
3054
+ hooks;
3055
+ constructor(db, hooks) {
3056
+ this.db = db;
3057
+ this.hooks = hooks;
3058
+ }
3059
+ async set(input) {
3060
+ const id = uuidv42();
3061
+ await this.db.insert("secrets", { ...input, id });
3062
+ await this.hooks.emit("secret.created", { name: input.name });
3063
+ const row = await this.db.get("secrets", id);
3064
+ return this._toMeta(row);
3065
+ }
3066
+ async get(name, environment = "production") {
3067
+ const rows = await this.db.query("secrets", {
3068
+ where: { name, environment },
3069
+ filters: [{ col: "deleted_at", op: "isNull" }],
3070
+ limit: 1
3071
+ });
3072
+ if (rows.length === 0) return null;
3073
+ await this.hooks.emit("secret.accessed", { name, environment });
3074
+ return rows[0].value ?? null;
3075
+ }
3076
+ async getMeta(name, environment = "production") {
3077
+ const rows = await this.db.query("secrets", {
3078
+ where: { name, environment },
3079
+ filters: [{ col: "deleted_at", op: "isNull" }],
3080
+ limit: 1
3081
+ });
3082
+ return rows.length > 0 ? this._toMeta(rows[0]) : null;
3083
+ }
3084
+ async list() {
3085
+ const rows = await this.db.query("secrets", {
3086
+ filters: [{ col: "deleted_at", op: "isNull" }],
3087
+ orderBy: "name"
3088
+ });
3089
+ return rows.map((r) => this._toMeta(r));
3090
+ }
3091
+ async rotate(name, newValue, environment = "production") {
3092
+ const rows = await this.db.query("secrets", {
3093
+ where: { name, environment },
3094
+ filters: [{ col: "deleted_at", op: "isNull" }],
3095
+ limit: 1
3096
+ });
3097
+ if (rows.length === 0) throw new Error(`Secret "${name}" not found`);
3098
+ await this.db.update("secrets", rows[0].id, {
3099
+ value: newValue,
3100
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3101
+ });
3102
+ await this.hooks.emit("secret.rotated", { name, environment });
3103
+ }
3104
+ async delete(name, environment = "production") {
3105
+ const rows = await this.db.query("secrets", {
3106
+ where: { name, environment },
3107
+ filters: [{ col: "deleted_at", op: "isNull" }],
3108
+ limit: 1
3109
+ });
3110
+ if (rows.length === 0) return;
3111
+ await this.db.update("secrets", rows[0].id, {
3112
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString()
3113
+ });
3114
+ await this.hooks.emit("secret.deleted", { name, environment });
3115
+ }
3116
+ _toMeta(row) {
3117
+ return {
3118
+ id: row.id,
3119
+ org_id: row.org_id ?? null,
3120
+ name: row.name,
3121
+ type: row.type,
3122
+ environment: row.environment,
3123
+ location: row.location ?? null,
3124
+ description: row.description ?? null,
3125
+ rotation_schedule: row.rotation_schedule ?? null,
3126
+ expires_at: row.expires_at ?? null,
3127
+ notes: row.notes ?? null,
3128
+ created_at: row.created_at,
3129
+ updated_at: row.updated_at
3130
+ };
3131
+ }
3132
+ };
2903
3133
  export {
2904
3134
  AGENT_STATUSES,
2905
3135
  AgentRegistry,
@@ -2928,12 +3158,14 @@ export {
2928
3158
  ProviderRegistry,
2929
3159
  RUN_STATUSES,
2930
3160
  RunManager,
3161
+ SecretStore,
2931
3162
  SessionKey,
2932
3163
  SessionManager,
2933
3164
  TASK_STATUSES,
2934
3165
  TaskQueue,
2935
3166
  UpdateChecker,
2936
3167
  UpdateManager,
3168
+ UserRegistry,
2937
3169
  WakeupQueue,
2938
3170
  WorkflowEngine,
2939
3171
  _resetConfig,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,8 +44,10 @@
44
44
  "typecheck": "tsc --noEmit"
45
45
  },
46
46
  "dependencies": {
47
+ "@types/uuid": "^10.0.0",
47
48
  "ajv": "^8.17.1",
48
- "latticesql": "^0.17.1",
49
+ "latticesql": "^0.18.0",
50
+ "uuid": "^13.0.0",
49
51
  "yaml": "^2.7.0"
50
52
  },
51
53
  "peerDependencies": {