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 +35 -0
- package/README.md +1 -0
- package/dist/{channel-m9f7MFD7.d.ts → channel-06G0vbIn.d.ts} +1 -0
- package/dist/channels/discord/index.d.ts +1 -1
- package/dist/channels/slack/index.d.ts +1 -1
- package/dist/channels/webhook/index.d.ts +1 -1
- package/dist/index.d.ts +97 -5
- package/dist/index.js +236 -4
- package/package.json +4 -2
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
|
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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.
|
|
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.
|
|
49
|
+
"latticesql": "^0.18.0",
|
|
50
|
+
"uuid": "^13.0.0",
|
|
49
51
|
"yaml": "^2.7.0"
|
|
50
52
|
},
|
|
51
53
|
"peerDependencies": {
|