mojulo 0.0.0 → 0.1.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 +54 -4
- package/lib/audit-logger-new.js +11 -0
- package/lib/auth/gate.js +25 -0
- package/lib/auth/service.js +17 -0
- package/lib/auth/session.js +63 -0
- package/lib/builder/chat-processor.js +607 -0
- package/lib/builder/composer-bridge.js +82 -0
- package/lib/builder/evaluator.js +159 -0
- package/lib/builder/executor.js +252 -0
- package/lib/builder/index.js +48 -0
- package/lib/builder/session.js +248 -0
- package/lib/builder/system-prompt.js +422 -0
- package/lib/builder/tone-presets.js +75 -0
- package/lib/builder/tool-executors.js +1527 -0
- package/lib/builder/tools.js +338 -0
- package/lib/builder/validators.js +239 -0
- package/lib/composer/composer.js +225 -0
- package/lib/composer/index.js +40 -0
- package/lib/composer/protocols/00_base.txt +19 -0
- package/lib/composer/protocols/01_knowledge.txt +9 -0
- package/lib/composer/protocols/02_form-gathering.txt +32 -0
- package/lib/composer/protocols/03_appointments.txt +16 -0
- package/lib/composer/protocols/04_triage.txt +15 -0
- package/lib/composer/protocols/05_optical-read.txt +22 -0
- package/lib/composer/response-builder.js +98 -0
- package/lib/config-builder.js +650 -0
- package/lib/db/ids.js +10 -0
- package/lib/db/index.js +179 -0
- package/lib/db/repositories/apiKeys.js +72 -0
- package/lib/db/repositories/auditLogs.js +12 -0
- package/lib/db/repositories/botSpaces.js +12 -0
- package/lib/db/repositories/builderSessions.js +312 -0
- package/lib/db/repositories/deploymentEvents.js +12 -0
- package/lib/db/repositories/deployments.js +385 -0
- package/lib/db/repositories/documents.js +68 -0
- package/lib/db/repositories/mcpJobs.js +84 -0
- package/lib/deployers/bot-fleet.js +110 -0
- package/lib/deployers/bot-proxy.js +72 -0
- package/lib/deployers/build.js +89 -0
- package/lib/deployers/cloud-deploy.js +310 -0
- package/lib/deployers/docker.js +439 -0
- package/lib/deployers/fly.js +432 -0
- package/lib/deployers/index.js +38 -0
- package/lib/deployment-auth.js +36 -0
- package/lib/document-parser.js +171 -0
- package/lib/embedder/chunker.js +93 -0
- package/lib/embedder/local.js +101 -0
- package/lib/embedder/preview-rag.js +93 -0
- package/lib/envelope-schema.js +54 -0
- package/lib/fleet/scoped-sql.js +342 -0
- package/lib/form-schema-config/base.js +135 -0
- package/lib/form-schema-config/index.js +286 -0
- package/lib/form-schema-config/locales/af-ZA.js +153 -0
- package/lib/form-schema-config/locales/ar-AE.js +142 -0
- package/lib/form-schema-config/locales/ar-SA.js +164 -0
- package/lib/form-schema-config/locales/de-DE.js +152 -0
- package/lib/form-schema-config/locales/en-AU.js +161 -0
- package/lib/form-schema-config/locales/en-CA.js +115 -0
- package/lib/form-schema-config/locales/en-GB.js +132 -0
- package/lib/form-schema-config/locales/en-IN.js +219 -0
- package/lib/form-schema-config/locales/en-MY.js +171 -0
- package/lib/form-schema-config/locales/en-NG.js +198 -0
- package/lib/form-schema-config/locales/en-PH.js +186 -0
- package/lib/form-schema-config/locales/en-SG.js +153 -0
- package/lib/form-schema-config/locales/en-US.js +138 -0
- package/lib/form-schema-config/locales/es-ES.js +171 -0
- package/lib/form-schema-config/locales/es-MX.js +193 -0
- package/lib/form-schema-config/locales/fr-CA.js +138 -0
- package/lib/form-schema-config/locales/fr-FR.js +155 -0
- package/lib/form-schema-config/locales/hi-IN.js +219 -0
- package/lib/form-schema-config/locales/it-IT.js +157 -0
- package/lib/form-schema-config/locales/ja-JP.js +169 -0
- package/lib/form-schema-config/locales/ko-KR.js +140 -0
- package/lib/form-schema-config/locales/nl-NL.js +149 -0
- package/lib/form-schema-config/locales/pt-BR.js +168 -0
- package/lib/form-schema-config/locales/zh-CN.js +172 -0
- package/lib/form-schema-config/locales/zh-HK.js +142 -0
- package/lib/form-structure-schema.js +191 -0
- package/lib/llm-providers.js +828 -0
- package/lib/markdown.js +197 -0
- package/lib/mcp/catalysts/appointment-to-calendar.md +84 -0
- package/lib/mcp/catalysts/conversations-to-channel-digest.md +104 -0
- package/lib/mcp/catalysts/document-extract-to-store.md +92 -0
- package/lib/mcp/catalysts/knowledge-gap-miner.md +96 -0
- package/lib/mcp/catalysts/loader.js +144 -0
- package/lib/mcp/catalysts/qualify-lead-to-crm.md +83 -0
- package/lib/mcp/catalysts/scan-conversations-for-signal.md +92 -0
- package/lib/mcp/catalysts/submission-to-ticket.md +83 -0
- package/lib/mcp/catalysts/submissions-to-warehouse.md +103 -0
- package/lib/mcp/catalysts/weekly-submissions-digest.md +82 -0
- package/lib/mcp/jobs.js +64 -0
- package/lib/mcp/server.js +184 -0
- package/lib/mcp/session-binding.js +130 -0
- package/lib/mcp/tools/build.js +123 -0
- package/lib/mcp/tools/catalysts.js +477 -0
- package/lib/mcp/tools/context.js +325 -0
- package/lib/mcp/tools/fleet.js +391 -0
- package/lib/mcp/tools/jobs-tools.js +240 -0
- package/lib/mcp/tools/operate.js +314 -0
- package/lib/preview/build-preview-config.js +136 -0
- package/lib/rate-limiter.js +11 -0
- package/lib/resolve-api-key.js +142 -0
- package/lib/storage/index.js +40 -0
- package/messages/de.json +2136 -0
- package/messages/en.json +2136 -0
- package/messages/es.json +2136 -0
- package/messages/fr.json +2136 -0
- package/messages/it.json +2136 -0
- package/messages/ja.json +2136 -0
- package/messages/ko.json +2136 -0
- package/messages/nl.json +2136 -0
- package/messages/pl.json +2136 -0
- package/messages/pt.json +2136 -0
- package/messages/ru.json +2136 -0
- package/messages/uk.json +2136 -0
- package/messages/zh.json +2136 -0
- package/package.json +68 -5
- package/scripts/mcp-config.mjs +162 -0
- package/scripts/mcp-stdio-loader.mjs +42 -0
- package/scripts/mcp-stdio.mjs +108 -0
- package/scripts/mojulo-paths.mjs +48 -0
package/lib/db/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
const DB_PATH = process.env.SQLITE_PATH || path.join(process.cwd(), 'data', 'mojulo-lite.db');
|
|
6
|
+
|
|
7
|
+
let _db = null;
|
|
8
|
+
|
|
9
|
+
function init(db) {
|
|
10
|
+
db.pragma('journal_mode = WAL');
|
|
11
|
+
db.pragma('foreign_keys = ON');
|
|
12
|
+
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
name TEXT NOT NULL,
|
|
17
|
+
provider TEXT NOT NULL,
|
|
18
|
+
encrypted_key TEXT NOT NULL,
|
|
19
|
+
is_default INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
created_at INTEGER NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
24
|
+
id TEXT PRIMARY KEY,
|
|
25
|
+
original_name TEXT NOT NULL,
|
|
26
|
+
mime_type TEXT NOT NULL,
|
|
27
|
+
size_bytes INTEGER NOT NULL,
|
|
28
|
+
storage_path TEXT NOT NULL,
|
|
29
|
+
parsed_text TEXT,
|
|
30
|
+
created_at INTEGER NOT NULL
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS deployments (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
bot_name TEXT NOT NULL,
|
|
36
|
+
flow_type TEXT NOT NULL,
|
|
37
|
+
status TEXT NOT NULL,
|
|
38
|
+
config TEXT NOT NULL,
|
|
39
|
+
config_hash TEXT,
|
|
40
|
+
last_built_hash TEXT,
|
|
41
|
+
artifact_path TEXT,
|
|
42
|
+
document_ids TEXT,
|
|
43
|
+
api_key TEXT NOT NULL,
|
|
44
|
+
error TEXT,
|
|
45
|
+
url TEXT,
|
|
46
|
+
last_seen_at INTEGER,
|
|
47
|
+
created_at INTEGER NOT NULL,
|
|
48
|
+
updated_at INTEGER NOT NULL
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE IF NOT EXISTS modular_sessions (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
status TEXT NOT NULL,
|
|
54
|
+
preloaded_context TEXT,
|
|
55
|
+
messages TEXT,
|
|
56
|
+
inferred_intent TEXT,
|
|
57
|
+
intent_confidence REAL,
|
|
58
|
+
recommended_protocols TEXT,
|
|
59
|
+
enabled_protocols TEXT,
|
|
60
|
+
core_config TEXT,
|
|
61
|
+
identity_config TEXT,
|
|
62
|
+
protocol_data TEXT,
|
|
63
|
+
generated_configs TEXT,
|
|
64
|
+
deployment_id TEXT,
|
|
65
|
+
created_at INTEGER NOT NULL,
|
|
66
|
+
updated_at INTEGER NOT NULL
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE TABLE IF NOT EXISTS mcp_jobs (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
tool TEXT NOT NULL,
|
|
72
|
+
status TEXT NOT NULL,
|
|
73
|
+
progress INTEGER,
|
|
74
|
+
result TEXT,
|
|
75
|
+
error TEXT,
|
|
76
|
+
mcp_session_id TEXT,
|
|
77
|
+
builder_session_id TEXT,
|
|
78
|
+
created_at INTEGER NOT NULL,
|
|
79
|
+
updated_at INTEGER NOT NULL
|
|
80
|
+
);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_jobs_created_at ON mcp_jobs(created_at);
|
|
82
|
+
`);
|
|
83
|
+
|
|
84
|
+
migrateDeploymentColumns(db);
|
|
85
|
+
reapStaleMcpJobs(db);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function reapStaleMcpJobs(db) {
|
|
89
|
+
// Job runner is in-process; a control-plane restart kills running jobs.
|
|
90
|
+
// Mark anything still in pending / running as errored on startup so
|
|
91
|
+
// clients polling stale jobIds get a clear failure instead of an
|
|
92
|
+
// indefinite "pending" state.
|
|
93
|
+
db.prepare(
|
|
94
|
+
`UPDATE mcp_jobs
|
|
95
|
+
SET status = 'error', error = COALESCE(error, 'Control plane restarted while job was in flight'), updated_at = ?
|
|
96
|
+
WHERE status IN ('pending', 'running')`
|
|
97
|
+
).run(Date.now());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function migrateDeploymentColumns(db) {
|
|
101
|
+
const cols = db.prepare('PRAGMA table_info(deployments)').all();
|
|
102
|
+
const have = new Set(cols.map((c) => c.name));
|
|
103
|
+
if (!have.has('config_hash')) {
|
|
104
|
+
db.exec('ALTER TABLE deployments ADD COLUMN config_hash TEXT');
|
|
105
|
+
}
|
|
106
|
+
if (!have.has('last_built_hash')) {
|
|
107
|
+
db.exec('ALTER TABLE deployments ADD COLUMN last_built_hash TEXT');
|
|
108
|
+
}
|
|
109
|
+
if (!have.has('url')) {
|
|
110
|
+
db.exec('ALTER TABLE deployments ADD COLUMN url TEXT');
|
|
111
|
+
}
|
|
112
|
+
if (!have.has('last_seen_at')) {
|
|
113
|
+
db.exec('ALTER TABLE deployments ADD COLUMN last_seen_at INTEGER');
|
|
114
|
+
}
|
|
115
|
+
// Vector RAG: per-deployment embeddings live alongside the row. No separate
|
|
116
|
+
// table — embeddings are 1:1 with deployments and ride the same lifecycle.
|
|
117
|
+
if (!have.has('rag_mode')) {
|
|
118
|
+
db.exec("ALTER TABLE deployments ADD COLUMN rag_mode TEXT NOT NULL DEFAULT 'keyword'");
|
|
119
|
+
}
|
|
120
|
+
if (!have.has('embedding_storage_key')) {
|
|
121
|
+
db.exec('ALTER TABLE deployments ADD COLUMN embedding_storage_key TEXT');
|
|
122
|
+
}
|
|
123
|
+
if (!have.has('embedding_model')) {
|
|
124
|
+
db.exec('ALTER TABLE deployments ADD COLUMN embedding_model TEXT');
|
|
125
|
+
}
|
|
126
|
+
if (!have.has('embedding_chunk_count')) {
|
|
127
|
+
db.exec('ALTER TABLE deployments ADD COLUMN embedding_chunk_count INTEGER');
|
|
128
|
+
}
|
|
129
|
+
// Cloud deploy state. Cloud orchestration runs the published GHCR image on
|
|
130
|
+
// a remote provider (Fly.io first) using user-supplied credentials. The
|
|
131
|
+
// existing artifact-build state above is independent: cloud deploys reuse
|
|
132
|
+
// the staged config files but don't replace the local-ZIP path.
|
|
133
|
+
if (!have.has('cloud_provider')) {
|
|
134
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_provider TEXT');
|
|
135
|
+
}
|
|
136
|
+
if (!have.has('cloud_app_name')) {
|
|
137
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_app_name TEXT');
|
|
138
|
+
}
|
|
139
|
+
if (!have.has('cloud_status')) {
|
|
140
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_status TEXT');
|
|
141
|
+
}
|
|
142
|
+
if (!have.has('cloud_url')) {
|
|
143
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_url TEXT');
|
|
144
|
+
}
|
|
145
|
+
if (!have.has('cloud_progress')) {
|
|
146
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_progress TEXT');
|
|
147
|
+
}
|
|
148
|
+
if (!have.has('cloud_options')) {
|
|
149
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_options TEXT');
|
|
150
|
+
}
|
|
151
|
+
if (!have.has('cloud_error')) {
|
|
152
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_error TEXT');
|
|
153
|
+
}
|
|
154
|
+
if (!have.has('cloud_last_deployed_at')) {
|
|
155
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_last_deployed_at INTEGER');
|
|
156
|
+
}
|
|
157
|
+
if (!have.has('cloud_machine_id')) {
|
|
158
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_machine_id TEXT');
|
|
159
|
+
}
|
|
160
|
+
if (!have.has('cloud_volume_id')) {
|
|
161
|
+
db.exec('ALTER TABLE deployments ADD COLUMN cloud_volume_id TEXT');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function getDb() {
|
|
166
|
+
if (_db) return _db;
|
|
167
|
+
const dir = path.dirname(DB_PATH);
|
|
168
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
169
|
+
_db = new Database(DB_PATH);
|
|
170
|
+
init(_db);
|
|
171
|
+
return _db;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function closeDb() {
|
|
175
|
+
if (_db) {
|
|
176
|
+
_db.close();
|
|
177
|
+
_db = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { getDb } from '../index.js';
|
|
2
|
+
import { newId } from '../ids.js';
|
|
3
|
+
|
|
4
|
+
function rowToApiKey(row) {
|
|
5
|
+
if (!row) return null;
|
|
6
|
+
return {
|
|
7
|
+
id: row.id,
|
|
8
|
+
name: row.name,
|
|
9
|
+
provider: row.provider,
|
|
10
|
+
encryptedKey: row.encrypted_key,
|
|
11
|
+
isDefault: row.is_default === 1,
|
|
12
|
+
createdAt: new Date(row.created_at),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ApiKeyRepository = {
|
|
17
|
+
async findByUserId(_userId) {
|
|
18
|
+
// Single-user mode: ignore userId and return all keys
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const rows = db.prepare('SELECT * FROM api_keys ORDER BY is_default DESC, created_at ASC').all();
|
|
21
|
+
return rows.map(rowToApiKey);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async findById(id) {
|
|
25
|
+
const db = getDb();
|
|
26
|
+
const row = db.prepare('SELECT * FROM api_keys WHERE id = ?').get(id);
|
|
27
|
+
return rowToApiKey(row);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async findDefault() {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
const row = db.prepare('SELECT * FROM api_keys WHERE is_default = 1 LIMIT 1').get();
|
|
33
|
+
return rowToApiKey(row);
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async findByProvider(provider) {
|
|
37
|
+
const db = getDb();
|
|
38
|
+
const row = db
|
|
39
|
+
.prepare('SELECT * FROM api_keys WHERE provider = ? ORDER BY is_default DESC, created_at ASC LIMIT 1')
|
|
40
|
+
.get(provider);
|
|
41
|
+
return rowToApiKey(row);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async create({ name, provider, encryptedKey, isDefault = false }) {
|
|
45
|
+
const db = getDb();
|
|
46
|
+
const id = newId('ak');
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
|
|
49
|
+
if (isDefault) {
|
|
50
|
+
db.prepare('UPDATE api_keys SET is_default = 0').run();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
db.prepare(
|
|
54
|
+
`INSERT INTO api_keys (id, name, provider, encrypted_key, is_default, created_at)
|
|
55
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
56
|
+
).run(id, name, provider, encryptedKey, isDefault ? 1 : 0, now);
|
|
57
|
+
|
|
58
|
+
return this.findById(id);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
async setDefault(id) {
|
|
62
|
+
const db = getDb();
|
|
63
|
+
db.prepare('UPDATE api_keys SET is_default = 0').run();
|
|
64
|
+
db.prepare('UPDATE api_keys SET is_default = 1 WHERE id = ?').run(id);
|
|
65
|
+
return this.findById(id);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async delete(id) {
|
|
69
|
+
const db = getDb();
|
|
70
|
+
db.prepare('DELETE FROM api_keys WHERE id = ?').run(id);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Lite has no org-level settings. The builder stream route expects these shapes;
|
|
2
|
+
// returning null/defaults causes the fallback auto-select logic to kick in.
|
|
3
|
+
|
|
4
|
+
export const OrganizationSettingsRepository = {
|
|
5
|
+
async getBuilderConfig(_orgName) {
|
|
6
|
+
return null;
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async getDefaultRegion(_orgName) {
|
|
10
|
+
return 'us-east-1';
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Lite does not have bot spaces. This stub returns null/empty results so the
|
|
2
|
+
// builder stream code can call these methods without crashing.
|
|
3
|
+
|
|
4
|
+
export const BotSpaceRepository = {
|
|
5
|
+
async findById(_id) {
|
|
6
|
+
return null;
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async list() {
|
|
10
|
+
return [];
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { getDb } from '../index.js';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
|
|
4
|
+
export const SESSION_STATUS = {
|
|
5
|
+
CREATED: 'created',
|
|
6
|
+
PROCESSING: 'processing',
|
|
7
|
+
AWAITING_CONFIRM: 'awaiting_confirm',
|
|
8
|
+
DEPLOYING: 'deploying',
|
|
9
|
+
DEPLOYED: 'deployed',
|
|
10
|
+
EDITING: 'editing',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DEFAULT_ENABLED_PROTOCOLS = {
|
|
14
|
+
knowledge: false,
|
|
15
|
+
formGathering: false,
|
|
16
|
+
appointments: false,
|
|
17
|
+
triage: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function jsonOrNull(value, fallback = null) {
|
|
21
|
+
if (value === null || value === undefined) return fallback;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(value);
|
|
24
|
+
} catch {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rowToSession(row) {
|
|
30
|
+
if (!row) return null;
|
|
31
|
+
return {
|
|
32
|
+
id: row.id,
|
|
33
|
+
userId: 'local', // Lite is single-user
|
|
34
|
+
status: row.status,
|
|
35
|
+
preloadedContext: jsonOrNull(row.preloaded_context, {}),
|
|
36
|
+
messages: jsonOrNull(row.messages, []),
|
|
37
|
+
inferredIntent: row.inferred_intent,
|
|
38
|
+
intentConfidence: row.intent_confidence,
|
|
39
|
+
recommendedProtocols: jsonOrNull(row.recommended_protocols, {}),
|
|
40
|
+
enabledProtocols: jsonOrNull(row.enabled_protocols, DEFAULT_ENABLED_PROTOCOLS),
|
|
41
|
+
coreConfig: jsonOrNull(row.core_config, {}),
|
|
42
|
+
identityConfig: jsonOrNull(row.identity_config, {}),
|
|
43
|
+
protocolData: jsonOrNull(row.protocol_data, {}),
|
|
44
|
+
generatedConfigs: jsonOrNull(row.generated_configs, {}),
|
|
45
|
+
deploymentId: row.deployment_id,
|
|
46
|
+
createdAt: new Date(row.created_at),
|
|
47
|
+
updatedAt: new Date(row.updated_at),
|
|
48
|
+
// Legacy alias for code that reads `session.botSpaceId`
|
|
49
|
+
botSpaceId: null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function newModularSessionId() {
|
|
54
|
+
return `mod_${randomUUID()}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function insertSession(fields) {
|
|
58
|
+
const db = getDb();
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const id = fields.id || newModularSessionId();
|
|
61
|
+
db.prepare(
|
|
62
|
+
`INSERT INTO modular_sessions (
|
|
63
|
+
id, status, preloaded_context, messages, inferred_intent, intent_confidence,
|
|
64
|
+
recommended_protocols, enabled_protocols, core_config, identity_config,
|
|
65
|
+
protocol_data, generated_configs, deployment_id, created_at, updated_at
|
|
66
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
67
|
+
).run(
|
|
68
|
+
id,
|
|
69
|
+
fields.status || SESSION_STATUS.CREATED,
|
|
70
|
+
JSON.stringify(fields.preloadedContext || {}),
|
|
71
|
+
JSON.stringify(fields.messages || []),
|
|
72
|
+
fields.inferredIntent || null,
|
|
73
|
+
fields.intentConfidence ?? null,
|
|
74
|
+
JSON.stringify(fields.recommendedProtocols || {}),
|
|
75
|
+
JSON.stringify(fields.enabledProtocols || DEFAULT_ENABLED_PROTOCOLS),
|
|
76
|
+
JSON.stringify(fields.coreConfig || {}),
|
|
77
|
+
JSON.stringify(fields.identityConfig || {}),
|
|
78
|
+
JSON.stringify(fields.protocolData || {}),
|
|
79
|
+
JSON.stringify(fields.generatedConfigs || {}),
|
|
80
|
+
fields.deploymentId || null,
|
|
81
|
+
now,
|
|
82
|
+
now
|
|
83
|
+
);
|
|
84
|
+
return id;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function setField(sessionId, column, value) {
|
|
88
|
+
const db = getDb();
|
|
89
|
+
db.prepare(
|
|
90
|
+
`UPDATE modular_sessions SET ${column} = ?, updated_at = ? WHERE id = ?`
|
|
91
|
+
).run(value, Date.now(), sessionId);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setJsonField(sessionId, column, value) {
|
|
95
|
+
setField(sessionId, column, JSON.stringify(value ?? null));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function fetchSession(sessionId) {
|
|
99
|
+
const db = getDb();
|
|
100
|
+
const row = db.prepare('SELECT * FROM modular_sessions WHERE id = ?').get(sessionId);
|
|
101
|
+
return rowToSession(row);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const BuilderSessionRepository = {
|
|
105
|
+
async create({ userId: _userId } = {}) {
|
|
106
|
+
const id = insertSession({});
|
|
107
|
+
return fetchSession(id);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async createWithContext({ userId: _userId, botSpaceId: _botSpaceId, preloadedContext, existingConfig }) {
|
|
111
|
+
const fields = { preloadedContext };
|
|
112
|
+
|
|
113
|
+
if (existingConfig) {
|
|
114
|
+
fields.status = SESSION_STATUS.EDITING;
|
|
115
|
+
fields.enabledProtocols = existingConfig.enabledProtocols || DEFAULT_ENABLED_PROTOCOLS;
|
|
116
|
+
fields.coreConfig = existingConfig.core || {};
|
|
117
|
+
fields.identityConfig = existingConfig.identity || {};
|
|
118
|
+
fields.protocolData = existingConfig.protocolData || {};
|
|
119
|
+
fields.generatedConfigs = existingConfig._deployment
|
|
120
|
+
? { _editingDeployment: existingConfig._deployment }
|
|
121
|
+
: {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const id = insertSession(fields);
|
|
125
|
+
return fetchSession(id);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async findById(sessionId) {
|
|
129
|
+
return fetchSession(sessionId);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
async findByIdAndUserId(sessionId, _userId) {
|
|
133
|
+
return fetchSession(sessionId);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async updateStatus(sessionId, _userId, status) {
|
|
137
|
+
setField(sessionId, 'status', status);
|
|
138
|
+
return fetchSession(sessionId);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
async appendMessage(sessionId, _userId, message) {
|
|
142
|
+
const session = await fetchSession(sessionId);
|
|
143
|
+
if (!session) return null;
|
|
144
|
+
const messages = [...(session.messages || []), { ...message, timestamp: Date.now() }];
|
|
145
|
+
setJsonField(sessionId, 'messages', messages);
|
|
146
|
+
return fetchSession(sessionId);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
async updateInference(sessionId, _userId, { intent, confidence, recommendedProtocols }) {
|
|
150
|
+
const db = getDb();
|
|
151
|
+
db.prepare(
|
|
152
|
+
`UPDATE modular_sessions
|
|
153
|
+
SET inferred_intent = ?, intent_confidence = ?, recommended_protocols = ?, updated_at = ?
|
|
154
|
+
WHERE id = ?`
|
|
155
|
+
).run(
|
|
156
|
+
intent,
|
|
157
|
+
confidence ?? null,
|
|
158
|
+
JSON.stringify(recommendedProtocols || {}),
|
|
159
|
+
Date.now(),
|
|
160
|
+
sessionId
|
|
161
|
+
);
|
|
162
|
+
return fetchSession(sessionId);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async updateProtocols(sessionId, _userId, enabledProtocols) {
|
|
166
|
+
setJsonField(sessionId, 'enabled_protocols', enabledProtocols);
|
|
167
|
+
return fetchSession(sessionId);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async updateCoreConfig(sessionId, _userId, coreConfig) {
|
|
171
|
+
setJsonField(sessionId, 'core_config', coreConfig);
|
|
172
|
+
return fetchSession(sessionId);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
async updateIdentityConfig(sessionId, _userId, identityConfig) {
|
|
176
|
+
setJsonField(sessionId, 'identity_config', identityConfig);
|
|
177
|
+
return fetchSession(sessionId);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
async updateProtocolData(sessionId, _userId, protocol, data) {
|
|
181
|
+
const session = await fetchSession(sessionId);
|
|
182
|
+
if (!session) return null;
|
|
183
|
+
const protocolData = { ...(session.protocolData || {}), [protocol]: data };
|
|
184
|
+
setJsonField(sessionId, 'protocol_data', protocolData);
|
|
185
|
+
return fetchSession(sessionId);
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
async updateGeneratedConfig(sessionId, _userId, key, data) {
|
|
189
|
+
const session = await fetchSession(sessionId);
|
|
190
|
+
if (!session) return null;
|
|
191
|
+
const generatedConfigs = { ...(session.generatedConfigs || {}), [key]: data };
|
|
192
|
+
setJsonField(sessionId, 'generated_configs', generatedConfigs);
|
|
193
|
+
return fetchSession(sessionId);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async updateStepProgress(_sessionId, _userId, _step, _status) {
|
|
197
|
+
// Lite does not track per-step progress in the wizard flow.
|
|
198
|
+
// Retained as a no-op for builder executor compatibility.
|
|
199
|
+
return null;
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
async confirmProtocols(sessionId, _userId, confirmedProtocols) {
|
|
203
|
+
// The UI's deploy button passes the recommend_protocols shape:
|
|
204
|
+
// { knowledge: { enabled: true, ... }, forms: { enabled: false, ... }, ... }
|
|
205
|
+
// The chat-builder LLM, when it calls save_modular_bot directly, often
|
|
206
|
+
// passes flat booleans:
|
|
207
|
+
// { knowledge: true, forms: false, ... }
|
|
208
|
+
// Accept either — anything truthy at either level enables the protocol.
|
|
209
|
+
// Without this, the LLM-direct path silently disables knowledge and the
|
|
210
|
+
// artifact ships without embeddings.json.
|
|
211
|
+
const flag = (v) => {
|
|
212
|
+
if (v === null || v === undefined) return false;
|
|
213
|
+
if (typeof v === 'object') return !!v.enabled;
|
|
214
|
+
return !!v;
|
|
215
|
+
};
|
|
216
|
+
const cp = confirmedProtocols || {};
|
|
217
|
+
const enabledProtocols = {
|
|
218
|
+
knowledge: flag(cp.knowledge),
|
|
219
|
+
formGathering: flag(cp.formGathering ?? cp.forms),
|
|
220
|
+
appointments: flag(cp.appointments),
|
|
221
|
+
triage: flag(cp.triage),
|
|
222
|
+
};
|
|
223
|
+
setJsonField(sessionId, 'enabled_protocols', enabledProtocols);
|
|
224
|
+
return fetchSession(sessionId);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
async cacheComposedInstructions(_sessionId, _userId, _instructions) {
|
|
228
|
+
// Lite composes instructions on-demand at deploy time; no cache needed.
|
|
229
|
+
return null;
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
async syncGeneratedConfigsToLegacy(sessionId, _userId) {
|
|
233
|
+
// Copy generatedConfigs.{core,identity,forms,appointments,triage,knowledge}
|
|
234
|
+
// into the legacy columns the deployer reads (coreConfig, identityConfig, protocolData).
|
|
235
|
+
const session = await fetchSession(sessionId);
|
|
236
|
+
if (!session) return null;
|
|
237
|
+
const g = session.generatedConfigs || {};
|
|
238
|
+
|
|
239
|
+
const coreConfig = {
|
|
240
|
+
...(session.coreConfig || {}),
|
|
241
|
+
...(g.core || {}),
|
|
242
|
+
apiKeyId: session.preloadedContext?.defaultApiKeyId || g.core?.apiKeyId,
|
|
243
|
+
_invertedFlow: true,
|
|
244
|
+
};
|
|
245
|
+
const mergedIdentity = {
|
|
246
|
+
...(session.identityConfig || {}),
|
|
247
|
+
...(g.identity || {}),
|
|
248
|
+
};
|
|
249
|
+
const identityConfig = {
|
|
250
|
+
...mergedIdentity,
|
|
251
|
+
chatDisplayName:
|
|
252
|
+
mergedIdentity.chatDisplayName ||
|
|
253
|
+
mergedIdentity.displayName ||
|
|
254
|
+
mergedIdentity.botName ||
|
|
255
|
+
'Assistant',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const protocolData = { ...(session.protocolData || {}) };
|
|
259
|
+
if (g.knowledge) {
|
|
260
|
+
protocolData.knowledge = {
|
|
261
|
+
...(protocolData.knowledge || {}),
|
|
262
|
+
domainDigest: g.knowledge.domainDigest,
|
|
263
|
+
documents: (g.knowledge.documentIds || []).map((id) => ({ id })),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (g.forms) {
|
|
267
|
+
protocolData.formGathering = {
|
|
268
|
+
...(protocolData.formGathering || {}),
|
|
269
|
+
generatedFormJson: g.forms.formSchema,
|
|
270
|
+
formCompletionWebhook: g.forms.formCompletionWebhook,
|
|
271
|
+
afterSubmitChatMessage: g.forms.afterSubmitChatMessage,
|
|
272
|
+
formSendHome: g.forms.formSendHome,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (g.appointments) {
|
|
276
|
+
protocolData.appointments = {
|
|
277
|
+
...(protocolData.appointments || {}),
|
|
278
|
+
destinations: g.appointments.destinations || [],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (g.triage) {
|
|
282
|
+
protocolData.triage = {
|
|
283
|
+
...(protocolData.triage || {}),
|
|
284
|
+
routes: g.triage.routes || [],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const db = getDb();
|
|
289
|
+
db.prepare(
|
|
290
|
+
`UPDATE modular_sessions
|
|
291
|
+
SET core_config = ?, identity_config = ?, protocol_data = ?, updated_at = ?
|
|
292
|
+
WHERE id = ?`
|
|
293
|
+
).run(
|
|
294
|
+
JSON.stringify(coreConfig),
|
|
295
|
+
JSON.stringify(identityConfig),
|
|
296
|
+
JSON.stringify(protocolData),
|
|
297
|
+
Date.now(),
|
|
298
|
+
sessionId
|
|
299
|
+
);
|
|
300
|
+
return fetchSession(sessionId);
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
async linkDeployment(sessionId, _userId, deploymentId) {
|
|
304
|
+
setField(sessionId, 'deployment_id', deploymentId);
|
|
305
|
+
return fetchSession(sessionId);
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
async delete(sessionId, _userId) {
|
|
309
|
+
const db = getDb();
|
|
310
|
+
db.prepare('DELETE FROM modular_sessions WHERE id = ?').run(sessionId);
|
|
311
|
+
},
|
|
312
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Lite does not track deployment event history separately. These stubs keep
|
|
2
|
+
// the builder executor flow intact without persisting events.
|
|
3
|
+
|
|
4
|
+
export const DeploymentEventRepository = {
|
|
5
|
+
async create(_data) {
|
|
6
|
+
return { id: null };
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async updateStatus(_id, _status) {
|
|
10
|
+
return null;
|
|
11
|
+
},
|
|
12
|
+
};
|