@muxoai/platform 0.0.0-stage → 1.2.0
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/ARCHITECTURE.md +111 -0
- package/README.md +53 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +527 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +89 -0
- package/dist/engine.d.ts +128 -0
- package/dist/engine.js +709 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/mcp.d.ts +9 -0
- package/dist/mcp.js +401 -0
- package/dist/providers/composio-instant.d.ts +14 -0
- package/dist/providers/composio-instant.js +372 -0
- package/dist/providers/composio-sdk.d.ts +41 -0
- package/dist/providers/composio-sdk.js +514 -0
- package/dist/providers/microsoft-governance.d.ts +66 -0
- package/dist/providers/microsoft-governance.js +415 -0
- package/dist/providers/stripe-billing.d.ts +31 -0
- package/dist/providers/stripe-billing.js +168 -0
- package/dist/providers/stripe-projects.d.ts +53 -0
- package/dist/providers/stripe-projects.js +607 -0
- package/dist/providers/supermemory.d.ts +64 -0
- package/dist/providers/supermemory.js +224 -0
- package/dist/sdk.d.ts +115 -0
- package/dist/sdk.js +117 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +816 -0
- package/dist/store.d.ts +200 -0
- package/dist/store.js +1260 -0
- package/dist/types.d.ts +273 -0
- package/dist/types.js +1 -0
- package/dist/ui/console-html.d.ts +2 -0
- package/dist/ui/console-html.js +2153 -0
- package/package.json +74 -4
- package/plugins/muxo/.app.json +8 -0
- package/plugins/muxo/.claude-plugin/plugin.json +16 -0
- package/plugins/muxo/.codex-plugin/plugin.json +51 -0
- package/plugins/muxo/.mcp.json +8 -0
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/assets/logo.svg +6 -0
- package/plugins/muxo/hooks/hooks.json +27 -0
- package/plugins/muxo/hooks/session-start.sh +85 -0
- package/plugins/muxo/hooks/user-prompt-submit.sh +45 -0
- package/plugins/muxo/skills/muxo/SKILL.md +25 -0
- package/plugins/muxo/skills/muxo/references/cli.md +57 -0
- package/plugins/muxo/skills/muxo/references/mcp.md +25 -0
- package/src/catalogues/stripe-projects-catalog.json +9832 -0
- package/vendor/microsoft-governance/README.md +363 -0
- package/vendor/microsoft-governance/dist/audit.d.ts +26 -0
- package/vendor/microsoft-governance/dist/audit.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/audit.js +107 -0
- package/vendor/microsoft-governance/dist/audit.js.map +1 -0
- package/vendor/microsoft-governance/dist/cascade-containment.d.ts +84 -0
- package/vendor/microsoft-governance/dist/cascade-containment.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/cascade-containment.js +387 -0
- package/vendor/microsoft-governance/dist/cascade-containment.js.map +1 -0
- package/vendor/microsoft-governance/dist/client.d.ts +34 -0
- package/vendor/microsoft-governance/dist/client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/client.js +112 -0
- package/vendor/microsoft-governance/dist/client.js.map +1 -0
- package/vendor/microsoft-governance/dist/context-poisoning.d.ts +58 -0
- package/vendor/microsoft-governance/dist/context-poisoning.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/context-poisoning.js +410 -0
- package/vendor/microsoft-governance/dist/context-poisoning.js.map +1 -0
- package/vendor/microsoft-governance/dist/credential-vault.d.ts +213 -0
- package/vendor/microsoft-governance/dist/credential-vault.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/credential-vault.js +567 -0
- package/vendor/microsoft-governance/dist/credential-vault.js.map +1 -0
- package/vendor/microsoft-governance/dist/discovery.d.ts +80 -0
- package/vendor/microsoft-governance/dist/discovery.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/discovery.js +404 -0
- package/vendor/microsoft-governance/dist/discovery.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/channel.d.ts +29 -0
- package/vendor/microsoft-governance/dist/encryption/channel.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/channel.js +78 -0
- package/vendor/microsoft-governance/dist/encryption/channel.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/index.d.ts +9 -0
- package/vendor/microsoft-governance/dist/encryption/index.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/index.js +23 -0
- package/vendor/microsoft-governance/dist/encryption/index.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts +267 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.js +1001 -0
- package/vendor/microsoft-governance/dist/encryption/mesh-client.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts +38 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.js +223 -0
- package/vendor/microsoft-governance/dist/encryption/ratchet.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts +136 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.js +369 -0
- package/vendor/microsoft-governance/dist/encryption/registry-client.js.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts +80 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.js +238 -0
- package/vendor/microsoft-governance/dist/encryption/x3dh.js.map +1 -0
- package/vendor/microsoft-governance/dist/framework-adapter.d.ts +63 -0
- package/vendor/microsoft-governance/dist/framework-adapter.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/framework-adapter.js +253 -0
- package/vendor/microsoft-governance/dist/framework-adapter.js.map +1 -0
- package/vendor/microsoft-governance/dist/identity.d.ts +124 -0
- package/vendor/microsoft-governance/dist/identity.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/identity.js +446 -0
- package/vendor/microsoft-governance/dist/identity.js.map +1 -0
- package/vendor/microsoft-governance/dist/index.d.ts +39 -0
- package/vendor/microsoft-governance/dist/index.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/index.js +95 -0
- package/vendor/microsoft-governance/dist/index.js.map +1 -0
- package/vendor/microsoft-governance/dist/kill-switch.d.ts +22 -0
- package/vendor/microsoft-governance/dist/kill-switch.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/kill-switch.js +60 -0
- package/vendor/microsoft-governance/dist/kill-switch.js.map +1 -0
- package/vendor/microsoft-governance/dist/lifecycle.d.ts +50 -0
- package/vendor/microsoft-governance/dist/lifecycle.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/lifecycle.js +113 -0
- package/vendor/microsoft-governance/dist/lifecycle.js.map +1 -0
- package/vendor/microsoft-governance/dist/mcp.d.ts +41 -0
- package/vendor/microsoft-governance/dist/mcp.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/mcp.js +233 -0
- package/vendor/microsoft-governance/dist/mcp.js.map +1 -0
- package/vendor/microsoft-governance/dist/metrics.d.ts +123 -0
- package/vendor/microsoft-governance/dist/metrics.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/metrics.js +289 -0
- package/vendor/microsoft-governance/dist/metrics.js.map +1 -0
- package/vendor/microsoft-governance/dist/oci-manifest.d.ts +49 -0
- package/vendor/microsoft-governance/dist/oci-manifest.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/oci-manifest.js +182 -0
- package/vendor/microsoft-governance/dist/oci-manifest.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts +15 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.js +88 -0
- package/vendor/microsoft-governance/dist/policy-backends/cedar.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts +17 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.js +74 -0
- package/vendor/microsoft-governance/dist/policy-backends/opa.js.map +1 -0
- package/vendor/microsoft-governance/dist/policy.d.ts +77 -0
- package/vendor/microsoft-governance/dist/policy.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/policy.js +760 -0
- package/vendor/microsoft-governance/dist/policy.js.map +1 -0
- package/vendor/microsoft-governance/dist/prompt-defense.d.ts +37 -0
- package/vendor/microsoft-governance/dist/prompt-defense.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/prompt-defense.js +229 -0
- package/vendor/microsoft-governance/dist/prompt-defense.js.map +1 -0
- package/vendor/microsoft-governance/dist/protocol-facets.d.ts +87 -0
- package/vendor/microsoft-governance/dist/protocol-facets.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/protocol-facets.js +728 -0
- package/vendor/microsoft-governance/dist/protocol-facets.js.map +1 -0
- package/vendor/microsoft-governance/dist/rings.d.ts +17 -0
- package/vendor/microsoft-governance/dist/rings.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/rings.js +61 -0
- package/vendor/microsoft-governance/dist/rings.js.map +1 -0
- package/vendor/microsoft-governance/dist/sandbox.d.ts +61 -0
- package/vendor/microsoft-governance/dist/sandbox.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/sandbox.js +199 -0
- package/vendor/microsoft-governance/dist/sandbox.js.map +1 -0
- package/vendor/microsoft-governance/dist/surface-parity.d.ts +45 -0
- package/vendor/microsoft-governance/dist/surface-parity.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/surface-parity.js +216 -0
- package/vendor/microsoft-governance/dist/surface-parity.js.map +1 -0
- package/vendor/microsoft-governance/dist/trust.d.ts +28 -0
- package/vendor/microsoft-governance/dist/trust.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/trust.js +209 -0
- package/vendor/microsoft-governance/dist/trust.js.map +1 -0
- package/vendor/microsoft-governance/dist/types.d.ts +422 -0
- package/vendor/microsoft-governance/dist/types.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/types.js +29 -0
- package/vendor/microsoft-governance/dist/types.js.map +1 -0
- package/vendor/microsoft-governance/dist/verify.d.ts +135 -0
- package/vendor/microsoft-governance/dist/verify.d.ts.map +1 -0
- package/vendor/microsoft-governance/dist/verify.js +356 -0
- package/vendor/microsoft-governance/dist/verify.js.map +1 -0
package/dist/store.js
ADDED
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
const DEFAULT_POLICY = {
|
|
4
|
+
mode: "enforce",
|
|
5
|
+
requireApprovalFor: ["admin", "financial", "provisioning", "destructive"],
|
|
6
|
+
allowInstantWallet: true,
|
|
7
|
+
allowProvisioning: true,
|
|
8
|
+
maxCreditsPerRun: 250,
|
|
9
|
+
};
|
|
10
|
+
export class MuxoStore {
|
|
11
|
+
db;
|
|
12
|
+
vaultKey;
|
|
13
|
+
constructor(dbPath, vaultKey) {
|
|
14
|
+
this.db = new DatabaseSync(dbPath);
|
|
15
|
+
this.vaultKey = vaultKey;
|
|
16
|
+
this.initSchema();
|
|
17
|
+
}
|
|
18
|
+
initSchema() {
|
|
19
|
+
this.db.exec(`
|
|
20
|
+
PRAGMA journal_mode = WAL;
|
|
21
|
+
PRAGMA foreign_keys = ON;
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
24
|
+
id TEXT PRIMARY KEY,
|
|
25
|
+
email TEXT NOT NULL UNIQUE,
|
|
26
|
+
name TEXT NOT NULL,
|
|
27
|
+
password_hash TEXT NOT NULL,
|
|
28
|
+
workspace_id TEXT NOT NULL,
|
|
29
|
+
role TEXT NOT NULL DEFAULT 'owner',
|
|
30
|
+
created_at TEXT NOT NULL
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
34
|
+
token_hash TEXT PRIMARY KEY,
|
|
35
|
+
user_id TEXT NOT NULL,
|
|
36
|
+
workspace_id TEXT NOT NULL,
|
|
37
|
+
expires_at TEXT NOT NULL,
|
|
38
|
+
created_at TEXT NOT NULL
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
slug TEXT NOT NULL UNIQUE,
|
|
45
|
+
owner_user_id TEXT NOT NULL,
|
|
46
|
+
tier TEXT NOT NULL DEFAULT 'free',
|
|
47
|
+
monthly_actions_limit INTEGER NOT NULL DEFAULT 1000,
|
|
48
|
+
monthly_actions_used INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
credits_balance INTEGER NOT NULL DEFAULT 100,
|
|
50
|
+
stripe_customer_id TEXT,
|
|
51
|
+
stripe_subscription_id TEXT,
|
|
52
|
+
projects_env_name TEXT NOT NULL,
|
|
53
|
+
policy_json TEXT NOT NULL,
|
|
54
|
+
created_at TEXT NOT NULL,
|
|
55
|
+
updated_at TEXT NOT NULL
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
59
|
+
id TEXT PRIMARY KEY,
|
|
60
|
+
workspace_id TEXT NOT NULL,
|
|
61
|
+
name TEXT NOT NULL,
|
|
62
|
+
key_prefix TEXT NOT NULL,
|
|
63
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
64
|
+
environment TEXT NOT NULL DEFAULT 'live',
|
|
65
|
+
scopes_json TEXT NOT NULL,
|
|
66
|
+
last_used_at TEXT,
|
|
67
|
+
revoked_at TEXT,
|
|
68
|
+
created_at TEXT NOT NULL
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS connections (
|
|
72
|
+
id TEXT PRIMARY KEY,
|
|
73
|
+
workspace_id TEXT NOT NULL,
|
|
74
|
+
lane TEXT NOT NULL,
|
|
75
|
+
provider_slug TEXT NOT NULL,
|
|
76
|
+
provider_name TEXT NOT NULL,
|
|
77
|
+
status TEXT NOT NULL,
|
|
78
|
+
external_account_id TEXT,
|
|
79
|
+
redirect_url TEXT,
|
|
80
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
81
|
+
updated_at TEXT NOT NULL,
|
|
82
|
+
created_at TEXT NOT NULL,
|
|
83
|
+
UNIQUE(workspace_id, lane, provider_slug)
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS resources (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
workspace_id TEXT NOT NULL,
|
|
89
|
+
resource_name TEXT NOT NULL,
|
|
90
|
+
provider_slug TEXT NOT NULL,
|
|
91
|
+
provider_name TEXT NOT NULL,
|
|
92
|
+
service_slug TEXT NOT NULL,
|
|
93
|
+
plan_slug TEXT,
|
|
94
|
+
status TEXT NOT NULL,
|
|
95
|
+
env_var_keys_json TEXT NOT NULL DEFAULT '[]',
|
|
96
|
+
encrypted_secrets_hex TEXT,
|
|
97
|
+
external_resource_id TEXT,
|
|
98
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
99
|
+
created_at TEXT NOT NULL,
|
|
100
|
+
updated_at TEXT NOT NULL,
|
|
101
|
+
UNIQUE(workspace_id, resource_name)
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE TABLE IF NOT EXISTS plans (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
workspace_id TEXT NOT NULL,
|
|
107
|
+
actor_id TEXT NOT NULL,
|
|
108
|
+
tool_slug TEXT NOT NULL,
|
|
109
|
+
upstream_slug TEXT NOT NULL,
|
|
110
|
+
lane TEXT NOT NULL,
|
|
111
|
+
risk_level TEXT NOT NULL,
|
|
112
|
+
input_json TEXT NOT NULL,
|
|
113
|
+
reason TEXT NOT NULL,
|
|
114
|
+
credit_cost INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
approval_status TEXT NOT NULL,
|
|
116
|
+
approved_by TEXT,
|
|
117
|
+
approved_at TEXT,
|
|
118
|
+
run_id TEXT,
|
|
119
|
+
decision_record_json TEXT,
|
|
120
|
+
memory_context_json TEXT,
|
|
121
|
+
created_at TEXT NOT NULL,
|
|
122
|
+
expires_at TEXT NOT NULL
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
126
|
+
id TEXT PRIMARY KEY,
|
|
127
|
+
plan_id TEXT NOT NULL,
|
|
128
|
+
workspace_id TEXT NOT NULL,
|
|
129
|
+
actor_id TEXT NOT NULL,
|
|
130
|
+
tool_slug TEXT NOT NULL,
|
|
131
|
+
lane TEXT NOT NULL,
|
|
132
|
+
risk_level TEXT NOT NULL,
|
|
133
|
+
status TEXT NOT NULL,
|
|
134
|
+
input_json TEXT NOT NULL,
|
|
135
|
+
output_json TEXT,
|
|
136
|
+
error TEXT,
|
|
137
|
+
actions_metered INTEGER NOT NULL DEFAULT 0,
|
|
138
|
+
credits_metered INTEGER NOT NULL DEFAULT 0,
|
|
139
|
+
duration_ms INTEGER,
|
|
140
|
+
receipt_id TEXT,
|
|
141
|
+
started_at TEXT NOT NULL,
|
|
142
|
+
completed_at TEXT
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
CREATE TABLE IF NOT EXISTS receipts (
|
|
146
|
+
id TEXT PRIMARY KEY,
|
|
147
|
+
run_id TEXT NOT NULL UNIQUE,
|
|
148
|
+
plan_id TEXT NOT NULL,
|
|
149
|
+
workspace_id TEXT NOT NULL,
|
|
150
|
+
actor_id TEXT NOT NULL,
|
|
151
|
+
tool_slug TEXT NOT NULL,
|
|
152
|
+
lane TEXT NOT NULL,
|
|
153
|
+
risk_level TEXT NOT NULL,
|
|
154
|
+
status TEXT NOT NULL,
|
|
155
|
+
actions_metered INTEGER NOT NULL,
|
|
156
|
+
credits_metered INTEGER NOT NULL,
|
|
157
|
+
input_hash TEXT NOT NULL,
|
|
158
|
+
output_hash TEXT NOT NULL,
|
|
159
|
+
prev_receipt_hash TEXT NOT NULL,
|
|
160
|
+
receipt_hash TEXT NOT NULL,
|
|
161
|
+
signature TEXT NOT NULL,
|
|
162
|
+
decision_record_json TEXT,
|
|
163
|
+
issued_at TEXT NOT NULL
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
167
|
+
id TEXT PRIMARY KEY,
|
|
168
|
+
workspace_id TEXT NOT NULL,
|
|
169
|
+
namespace TEXT NOT NULL,
|
|
170
|
+
document_id TEXT NOT NULL,
|
|
171
|
+
title TEXT NOT NULL,
|
|
172
|
+
content TEXT NOT NULL,
|
|
173
|
+
kind TEXT NOT NULL,
|
|
174
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
175
|
+
created_at TEXT NOT NULL
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
CREATE TABLE IF NOT EXISTS agent_claims (
|
|
179
|
+
claim_slug TEXT PRIMARY KEY,
|
|
180
|
+
workspace_id TEXT NOT NULL,
|
|
181
|
+
target_email TEXT NOT NULL,
|
|
182
|
+
status TEXT NOT NULL DEFAULT 'invited',
|
|
183
|
+
claimed_at TEXT,
|
|
184
|
+
expires_at TEXT NOT NULL,
|
|
185
|
+
created_at TEXT NOT NULL
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
CREATE TABLE IF NOT EXISTS ledger_entries (
|
|
189
|
+
id TEXT PRIMARY KEY,
|
|
190
|
+
workspace_id TEXT NOT NULL,
|
|
191
|
+
kind TEXT NOT NULL,
|
|
192
|
+
actions_delta INTEGER NOT NULL DEFAULT 0,
|
|
193
|
+
credits_delta INTEGER NOT NULL DEFAULT 0,
|
|
194
|
+
balance_after INTEGER NOT NULL DEFAULT 0,
|
|
195
|
+
reference_id TEXT,
|
|
196
|
+
description TEXT NOT NULL,
|
|
197
|
+
created_at TEXT NOT NULL
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
CREATE TABLE IF NOT EXISTS processed_stripe_events (
|
|
201
|
+
event_id TEXT PRIMARY KEY,
|
|
202
|
+
processed_at TEXT NOT NULL
|
|
203
|
+
);
|
|
204
|
+
`);
|
|
205
|
+
// Backwards-compatible column migrations for existing databases
|
|
206
|
+
for (const stmt of [
|
|
207
|
+
"ALTER TABLE plans ADD COLUMN decision_record_json TEXT",
|
|
208
|
+
"ALTER TABLE plans ADD COLUMN memory_context_json TEXT",
|
|
209
|
+
"ALTER TABLE receipts ADD COLUMN decision_record_json TEXT",
|
|
210
|
+
]) {
|
|
211
|
+
try {
|
|
212
|
+
this.db.exec(stmt);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Column already exists
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// --- Encryption Vault (AES-256-GCM) ---
|
|
220
|
+
encryptSecrets(workspaceId, secrets) {
|
|
221
|
+
const iv = crypto.randomBytes(12);
|
|
222
|
+
const aad = Buffer.from(`muxo-vault:${workspaceId}`, "utf8");
|
|
223
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", this.vaultKey, iv);
|
|
224
|
+
cipher.setAAD(aad);
|
|
225
|
+
const plaintext = Buffer.from(JSON.stringify(secrets), "utf8");
|
|
226
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
227
|
+
const tag = cipher.getAuthTag();
|
|
228
|
+
return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
229
|
+
}
|
|
230
|
+
decryptSecrets(workspaceId, payloadHex) {
|
|
231
|
+
if (!payloadHex)
|
|
232
|
+
return {};
|
|
233
|
+
const parts = payloadHex.split(":");
|
|
234
|
+
if (parts.length !== 3)
|
|
235
|
+
return {};
|
|
236
|
+
const [ivHex, tagHex, dataHex] = parts;
|
|
237
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
238
|
+
const tag = Buffer.from(tagHex, "hex");
|
|
239
|
+
const encrypted = Buffer.from(dataHex, "hex");
|
|
240
|
+
const aad = Buffer.from(`muxo-vault:${workspaceId}`, "utf8");
|
|
241
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", this.vaultKey, iv);
|
|
242
|
+
decipher.setAAD(aad);
|
|
243
|
+
decipher.setAuthTag(tag);
|
|
244
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
245
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
246
|
+
}
|
|
247
|
+
// --- Bootstrap / Auth ---
|
|
248
|
+
ensureDefaultWorkspace() {
|
|
249
|
+
const existing = this.db
|
|
250
|
+
.prepare("SELECT id FROM workspaces ORDER BY created_at ASC LIMIT 1")
|
|
251
|
+
.get();
|
|
252
|
+
if (existing) {
|
|
253
|
+
const workspace = this.getWorkspace(existing.id);
|
|
254
|
+
const user = this.getUser(workspace.ownerUserId);
|
|
255
|
+
return { user, workspace };
|
|
256
|
+
}
|
|
257
|
+
const created = this.registerUser({
|
|
258
|
+
email: "founder@muxo.ai",
|
|
259
|
+
name: "Muxo Founder",
|
|
260
|
+
password: "muxo-demo-password",
|
|
261
|
+
workspaceName: "Muxo Default Workspace",
|
|
262
|
+
});
|
|
263
|
+
const apiKeyRes = this.createApiKey({
|
|
264
|
+
workspaceId: created.workspace.id,
|
|
265
|
+
name: "Default CLI & MCP Key",
|
|
266
|
+
environment: "live",
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
user: created.user,
|
|
270
|
+
workspace: created.workspace,
|
|
271
|
+
defaultApiKey: apiKeyRes.secretKey,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
hashPassword(password) {
|
|
275
|
+
const salt = "muxo-pw-salt-v1";
|
|
276
|
+
return crypto.scryptSync(password, salt, 32).toString("hex");
|
|
277
|
+
}
|
|
278
|
+
hashToken(token) {
|
|
279
|
+
return crypto.createHash("sha256").update(token).digest("hex");
|
|
280
|
+
}
|
|
281
|
+
registerUser(params) {
|
|
282
|
+
const now = new Date().toISOString();
|
|
283
|
+
const userId = `usr_${crypto.randomBytes(8).toString("hex")}`;
|
|
284
|
+
const workspaceId = `ws_${crypto.randomBytes(8).toString("hex")}`;
|
|
285
|
+
const slugBase = (params.workspaceName || params.name || "workspace")
|
|
286
|
+
.toLowerCase()
|
|
287
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
288
|
+
.replace(/^-|-$/g, "");
|
|
289
|
+
const slug = `${slugBase || "ws"}-${crypto.randomBytes(3).toString("hex")}`;
|
|
290
|
+
const projectsEnvName = `muxo_${workspaceId.slice(3, 11)}`;
|
|
291
|
+
const starterCredits = 250; // Initial trial credits for instant tools & exploration
|
|
292
|
+
this.db
|
|
293
|
+
.prepare(`INSERT INTO users (id, email, name, password_hash, workspace_id, role, created_at)
|
|
294
|
+
VALUES (?, ?, ?, ?, ?, 'owner', ?)`)
|
|
295
|
+
.run(userId, params.email.toLowerCase().trim(), params.name.trim(), this.hashPassword(params.password), workspaceId, now);
|
|
296
|
+
this.db
|
|
297
|
+
.prepare(`INSERT INTO workspaces (
|
|
298
|
+
id, name, slug, owner_user_id, tier,
|
|
299
|
+
monthly_actions_limit, monthly_actions_used, credits_balance,
|
|
300
|
+
stripe_customer_id, stripe_subscription_id, projects_env_name,
|
|
301
|
+
policy_json, created_at, updated_at
|
|
302
|
+
) VALUES (?, ?, ?, ?, 'free', 1000, 0, ?, NULL, NULL, ?, ?, ?, ?)`)
|
|
303
|
+
.run(workspaceId, params.workspaceName?.trim() || `${params.name.trim()}'s Workspace`, slug, userId, starterCredits, projectsEnvName, JSON.stringify(DEFAULT_POLICY), now, now);
|
|
304
|
+
this.addLedgerEntry({
|
|
305
|
+
workspaceId,
|
|
306
|
+
kind: "plan_grant",
|
|
307
|
+
actionsDelta: 1000,
|
|
308
|
+
creditsDelta: starterCredits,
|
|
309
|
+
balanceAfter: starterCredits,
|
|
310
|
+
referenceId: workspaceId,
|
|
311
|
+
description: "Free Plan initial monthly allowance (1,000 actions + 250 trial credits)",
|
|
312
|
+
});
|
|
313
|
+
const sessionToken = this.createSession(userId, workspaceId);
|
|
314
|
+
return {
|
|
315
|
+
user: this.getUser(userId),
|
|
316
|
+
workspace: this.getWorkspace(workspaceId),
|
|
317
|
+
sessionToken,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
loginUser(email, password) {
|
|
321
|
+
const row = this.db
|
|
322
|
+
.prepare("SELECT * FROM users WHERE email = ?")
|
|
323
|
+
.get(email.toLowerCase().trim());
|
|
324
|
+
if (!row)
|
|
325
|
+
return null;
|
|
326
|
+
if (row.password_hash !== this.hashPassword(password))
|
|
327
|
+
return null;
|
|
328
|
+
const user = this.mapUser(row);
|
|
329
|
+
const workspace = this.getWorkspace(user.workspaceId);
|
|
330
|
+
const sessionToken = this.createSession(user.id, workspace.id);
|
|
331
|
+
return { user, workspace, sessionToken };
|
|
332
|
+
}
|
|
333
|
+
createSession(userId, workspaceId) {
|
|
334
|
+
const rawToken = `mxs_${crypto.randomBytes(24).toString("hex")}`;
|
|
335
|
+
const now = new Date();
|
|
336
|
+
const expires = new Date(now.getTime() + 30 * 24 * 3600 * 1000);
|
|
337
|
+
this.db
|
|
338
|
+
.prepare(`INSERT INTO sessions (token_hash, user_id, workspace_id, expires_at, created_at)
|
|
339
|
+
VALUES (?, ?, ?, ?, ?)`)
|
|
340
|
+
.run(this.hashToken(rawToken), userId, workspaceId, expires.toISOString(), now.toISOString());
|
|
341
|
+
return rawToken;
|
|
342
|
+
}
|
|
343
|
+
resolveAuthToken(token) {
|
|
344
|
+
const trimmed = token.trim();
|
|
345
|
+
if (!trimmed)
|
|
346
|
+
return null;
|
|
347
|
+
if (trimmed.startsWith("mk_live_") || trimmed.startsWith("mk_test_")) {
|
|
348
|
+
const hash = this.hashToken(trimmed);
|
|
349
|
+
const row = this.db
|
|
350
|
+
.prepare("SELECT * FROM api_keys WHERE key_hash = ? AND revoked_at IS NULL")
|
|
351
|
+
.get(hash);
|
|
352
|
+
if (!row)
|
|
353
|
+
return null;
|
|
354
|
+
const now = new Date().toISOString();
|
|
355
|
+
this.db
|
|
356
|
+
.prepare("UPDATE api_keys SET last_used_at = ? WHERE id = ?")
|
|
357
|
+
.run(now, String(row.id));
|
|
358
|
+
const workspace = this.getWorkspace(String(row.workspace_id));
|
|
359
|
+
if (!workspace)
|
|
360
|
+
return null;
|
|
361
|
+
const user = this.getUser(workspace.ownerUserId);
|
|
362
|
+
if (!user)
|
|
363
|
+
return null;
|
|
364
|
+
return {
|
|
365
|
+
user,
|
|
366
|
+
workspace,
|
|
367
|
+
authType: "api_key",
|
|
368
|
+
actorId: `key:${String(row.id)}`,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
const hash = this.hashToken(trimmed);
|
|
372
|
+
const sessionRow = this.db
|
|
373
|
+
.prepare("SELECT * FROM sessions WHERE token_hash = ?")
|
|
374
|
+
.get(hash);
|
|
375
|
+
if (!sessionRow)
|
|
376
|
+
return null;
|
|
377
|
+
if (new Date(String(sessionRow.expires_at)).getTime() < Date.now()) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
const user = this.getUser(String(sessionRow.user_id));
|
|
381
|
+
const workspace = this.getWorkspace(String(sessionRow.workspace_id));
|
|
382
|
+
if (!user || !workspace)
|
|
383
|
+
return null;
|
|
384
|
+
return {
|
|
385
|
+
user,
|
|
386
|
+
workspace,
|
|
387
|
+
authType: "session",
|
|
388
|
+
actorId: user.id,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
getUser(userId) {
|
|
392
|
+
const row = this.db
|
|
393
|
+
.prepare("SELECT * FROM users WHERE id = ?")
|
|
394
|
+
.get(userId);
|
|
395
|
+
return row ? this.mapUser(row) : null;
|
|
396
|
+
}
|
|
397
|
+
getWorkspace(workspaceId) {
|
|
398
|
+
const row = this.db
|
|
399
|
+
.prepare("SELECT * FROM workspaces WHERE id = ?")
|
|
400
|
+
.get(workspaceId);
|
|
401
|
+
return row ? this.mapWorkspace(row) : null;
|
|
402
|
+
}
|
|
403
|
+
updateWorkspacePolicy(workspaceId, policy) {
|
|
404
|
+
const ws = this.getWorkspace(workspaceId);
|
|
405
|
+
if (!ws)
|
|
406
|
+
throw new Error(`Workspace ${workspaceId} not found`);
|
|
407
|
+
const merged = {
|
|
408
|
+
...ws.policy,
|
|
409
|
+
...policy,
|
|
410
|
+
};
|
|
411
|
+
const now = new Date().toISOString();
|
|
412
|
+
this.db
|
|
413
|
+
.prepare("UPDATE workspaces SET policy_json = ?, updated_at = ? WHERE id = ?")
|
|
414
|
+
.run(JSON.stringify(merged), now, workspaceId);
|
|
415
|
+
return this.getWorkspace(workspaceId);
|
|
416
|
+
}
|
|
417
|
+
// --- API Keys ---
|
|
418
|
+
createApiKey(params) {
|
|
419
|
+
const env = params.environment || "live";
|
|
420
|
+
const id = `key_${crypto.randomBytes(6).toString("hex")}`;
|
|
421
|
+
const rawSecret = `mk_${env}_${id.slice(4)}_${crypto.randomBytes(16).toString("hex")}`;
|
|
422
|
+
const keyPrefix = `${rawSecret.slice(0, 16)}...${rawSecret.slice(-4)}`;
|
|
423
|
+
const scopes = params.scopes || ["tools:read", "tools:execute", "resources:provision"];
|
|
424
|
+
const now = new Date().toISOString();
|
|
425
|
+
this.db
|
|
426
|
+
.prepare(`INSERT INTO api_keys (
|
|
427
|
+
id, workspace_id, name, key_prefix, key_hash, environment, scopes_json, created_at
|
|
428
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
429
|
+
.run(id, params.workspaceId, params.name, keyPrefix, this.hashToken(rawSecret), env, JSON.stringify(scopes), now);
|
|
430
|
+
return {
|
|
431
|
+
record: {
|
|
432
|
+
id,
|
|
433
|
+
workspaceId: params.workspaceId,
|
|
434
|
+
name: params.name,
|
|
435
|
+
keyPrefix,
|
|
436
|
+
environment: env,
|
|
437
|
+
scopes,
|
|
438
|
+
lastUsedAt: null,
|
|
439
|
+
revokedAt: null,
|
|
440
|
+
createdAt: now,
|
|
441
|
+
},
|
|
442
|
+
secretKey: rawSecret,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
listApiKeys(workspaceId) {
|
|
446
|
+
const rows = this.db
|
|
447
|
+
.prepare("SELECT * FROM api_keys WHERE workspace_id = ? ORDER BY created_at DESC")
|
|
448
|
+
.all(workspaceId);
|
|
449
|
+
return rows.map((r) => ({
|
|
450
|
+
id: String(r.id),
|
|
451
|
+
workspaceId: String(r.workspace_id),
|
|
452
|
+
name: String(r.name),
|
|
453
|
+
keyPrefix: String(r.key_prefix),
|
|
454
|
+
environment: String(r.environment),
|
|
455
|
+
scopes: JSON.parse(String(r.scopes_json || "[]")),
|
|
456
|
+
lastUsedAt: r.last_used_at ? String(r.last_used_at) : null,
|
|
457
|
+
revokedAt: r.revoked_at ? String(r.revoked_at) : null,
|
|
458
|
+
createdAt: String(r.created_at),
|
|
459
|
+
}));
|
|
460
|
+
}
|
|
461
|
+
revokeApiKey(workspaceId, keyId) {
|
|
462
|
+
const now = new Date().toISOString();
|
|
463
|
+
const res = this.db
|
|
464
|
+
.prepare("UPDATE api_keys SET revoked_at = ? WHERE id = ? AND workspace_id = ?")
|
|
465
|
+
.run(now, keyId, workspaceId);
|
|
466
|
+
return res.changes > 0;
|
|
467
|
+
}
|
|
468
|
+
// --- Connections ---
|
|
469
|
+
upsertConnection(params) {
|
|
470
|
+
const now = new Date().toISOString();
|
|
471
|
+
const existing = this.db
|
|
472
|
+
.prepare("SELECT id FROM connections WHERE workspace_id = ? AND lane = ? AND provider_slug = ?")
|
|
473
|
+
.get(params.workspaceId, params.lane, params.providerSlug);
|
|
474
|
+
const id = existing?.id || `conn_${crypto.randomBytes(8).toString("hex")}`;
|
|
475
|
+
this.db
|
|
476
|
+
.prepare(`INSERT INTO connections (
|
|
477
|
+
id, workspace_id, lane, provider_slug, provider_name, status,
|
|
478
|
+
external_account_id, redirect_url, metadata_json, updated_at, created_at
|
|
479
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
480
|
+
ON CONFLICT(workspace_id, lane, provider_slug) DO UPDATE SET
|
|
481
|
+
provider_name = excluded.provider_name,
|
|
482
|
+
status = excluded.status,
|
|
483
|
+
external_account_id = COALESCE(excluded.external_account_id, connections.external_account_id),
|
|
484
|
+
redirect_url = CASE
|
|
485
|
+
WHEN excluded.status = 'active' THEN NULL
|
|
486
|
+
ELSE COALESCE(excluded.redirect_url, connections.redirect_url)
|
|
487
|
+
END,
|
|
488
|
+
metadata_json = excluded.metadata_json,
|
|
489
|
+
updated_at = excluded.updated_at`)
|
|
490
|
+
.run(id, params.workspaceId, params.lane, params.providerSlug, params.providerName, params.status, params.externalAccountId ?? null, params.redirectUrl ?? null, JSON.stringify(params.metadata || {}), now, now);
|
|
491
|
+
return this.getConnection(id);
|
|
492
|
+
}
|
|
493
|
+
getConnection(id) {
|
|
494
|
+
const row = this.db
|
|
495
|
+
.prepare("SELECT * FROM connections WHERE id = ?")
|
|
496
|
+
.get(id);
|
|
497
|
+
return row ? this.mapConnection(row) : null;
|
|
498
|
+
}
|
|
499
|
+
listConnections(workspaceId) {
|
|
500
|
+
const rows = this.db
|
|
501
|
+
.prepare("SELECT * FROM connections WHERE workspace_id = ? ORDER BY updated_at DESC")
|
|
502
|
+
.all(workspaceId);
|
|
503
|
+
return rows.map((r) => this.mapConnection(r));
|
|
504
|
+
}
|
|
505
|
+
// --- Provisioned Resources (Stripe Projects Vault) ---
|
|
506
|
+
upsertResource(params) {
|
|
507
|
+
const now = new Date().toISOString();
|
|
508
|
+
const existing = this.db
|
|
509
|
+
.prepare("SELECT id, env_var_keys_json, encrypted_secrets_hex FROM resources WHERE workspace_id = ? AND resource_name = ?")
|
|
510
|
+
.get(params.workspaceId, params.resourceName);
|
|
511
|
+
const id = existing?.id || `res_${crypto.randomBytes(8).toString("hex")}`;
|
|
512
|
+
let envVarKeys = existing
|
|
513
|
+
? JSON.parse(existing.env_var_keys_json || "[]")
|
|
514
|
+
: [];
|
|
515
|
+
let encryptedHex = existing?.encrypted_secrets_hex || null;
|
|
516
|
+
if (params.secrets && Object.keys(params.secrets).length > 0) {
|
|
517
|
+
const mergedSecrets = {
|
|
518
|
+
...this.decryptSecrets(params.workspaceId, encryptedHex),
|
|
519
|
+
...params.secrets,
|
|
520
|
+
};
|
|
521
|
+
envVarKeys = Object.keys(mergedSecrets).sort();
|
|
522
|
+
encryptedHex = this.encryptSecrets(params.workspaceId, mergedSecrets);
|
|
523
|
+
}
|
|
524
|
+
this.db
|
|
525
|
+
.prepare(`INSERT INTO resources (
|
|
526
|
+
id, workspace_id, resource_name, provider_slug, provider_name,
|
|
527
|
+
service_slug, plan_slug, status, env_var_keys_json,
|
|
528
|
+
encrypted_secrets_hex, external_resource_id, metadata_json,
|
|
529
|
+
created_at, updated_at
|
|
530
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
531
|
+
ON CONFLICT(workspace_id, resource_name) DO UPDATE SET
|
|
532
|
+
status = excluded.status,
|
|
533
|
+
service_slug = excluded.service_slug,
|
|
534
|
+
plan_slug = COALESCE(excluded.plan_slug, resources.plan_slug),
|
|
535
|
+
env_var_keys_json = excluded.env_var_keys_json,
|
|
536
|
+
encrypted_secrets_hex = COALESCE(excluded.encrypted_secrets_hex, resources.encrypted_secrets_hex),
|
|
537
|
+
external_resource_id = COALESCE(excluded.external_resource_id, resources.external_resource_id),
|
|
538
|
+
metadata_json = excluded.metadata_json,
|
|
539
|
+
updated_at = excluded.updated_at`)
|
|
540
|
+
.run(id, params.workspaceId, params.resourceName, params.providerSlug, params.providerName, params.serviceSlug, params.planSlug ?? null, params.status, JSON.stringify(envVarKeys), encryptedHex, params.externalResourceId ?? null, JSON.stringify(params.metadata || {}), now, now);
|
|
541
|
+
return this.getResource(params.workspaceId, params.resourceName);
|
|
542
|
+
}
|
|
543
|
+
getResource(workspaceId, resourceNameOrId) {
|
|
544
|
+
const row = this.db
|
|
545
|
+
.prepare("SELECT * FROM resources WHERE workspace_id = ? AND (resource_name = ? OR id = ?)")
|
|
546
|
+
.get(workspaceId, resourceNameOrId, resourceNameOrId);
|
|
547
|
+
return row ? this.mapResource(row) : null;
|
|
548
|
+
}
|
|
549
|
+
listResources(workspaceId) {
|
|
550
|
+
const rows = this.db
|
|
551
|
+
.prepare("SELECT * FROM resources WHERE workspace_id = ? ORDER BY updated_at DESC")
|
|
552
|
+
.all(workspaceId);
|
|
553
|
+
return rows.map((r) => this.mapResource(r));
|
|
554
|
+
}
|
|
555
|
+
getDecryptedWorkspaceEnv(workspaceId) {
|
|
556
|
+
const rows = this.db
|
|
557
|
+
.prepare("SELECT encrypted_secrets_hex FROM resources WHERE workspace_id = ? AND status = 'active'")
|
|
558
|
+
.all(workspaceId);
|
|
559
|
+
const merged = {};
|
|
560
|
+
for (const row of rows) {
|
|
561
|
+
Object.assign(merged, this.decryptSecrets(workspaceId, row.encrypted_secrets_hex));
|
|
562
|
+
}
|
|
563
|
+
return merged;
|
|
564
|
+
}
|
|
565
|
+
// --- Plans ---
|
|
566
|
+
createPlan(params) {
|
|
567
|
+
const id = `plan_${crypto.randomBytes(8).toString("hex")}`;
|
|
568
|
+
const now = new Date();
|
|
569
|
+
const expires = new Date(now.getTime() + 60 * 60 * 1000);
|
|
570
|
+
this.db
|
|
571
|
+
.prepare(`INSERT INTO plans (
|
|
572
|
+
id, workspace_id, actor_id, tool_slug, upstream_slug, lane, risk_level,
|
|
573
|
+
input_json, reason, credit_cost, approval_status, approved_by, approved_at,
|
|
574
|
+
run_id, decision_record_json, memory_context_json, created_at, expires_at
|
|
575
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?)`)
|
|
576
|
+
.run(id, params.workspaceId, params.actorId, params.toolSlug, params.upstreamSlug, params.lane, params.riskLevel, JSON.stringify(params.input || {}), params.reason, params.creditCost, params.approvalStatus, params.approvalStatus === "auto_approved" ? "policy:auto" : null, params.approvalStatus === "auto_approved" ? now.toISOString() : null, params.decisionRecord ? JSON.stringify(params.decisionRecord) : null, params.memoryContext ? JSON.stringify(params.memoryContext) : null, now.toISOString(), expires.toISOString());
|
|
577
|
+
return this.getPlan(params.workspaceId, id);
|
|
578
|
+
}
|
|
579
|
+
getPlan(workspaceId, planId) {
|
|
580
|
+
const row = this.db
|
|
581
|
+
.prepare("SELECT * FROM plans WHERE id = ? AND workspace_id = ?")
|
|
582
|
+
.get(planId, workspaceId);
|
|
583
|
+
return row ? this.mapPlan(row) : null;
|
|
584
|
+
}
|
|
585
|
+
listPlans(workspaceId, limit = 50) {
|
|
586
|
+
const rows = this.db
|
|
587
|
+
.prepare("SELECT * FROM plans WHERE workspace_id = ? ORDER BY created_at DESC LIMIT ?")
|
|
588
|
+
.all(workspaceId, limit);
|
|
589
|
+
return rows.map((r) => this.mapPlan(r));
|
|
590
|
+
}
|
|
591
|
+
updatePlanApproval(params) {
|
|
592
|
+
const now = new Date().toISOString();
|
|
593
|
+
this.db
|
|
594
|
+
.prepare(`UPDATE plans
|
|
595
|
+
SET approval_status = ?, approved_by = ?, approved_at = ?
|
|
596
|
+
WHERE id = ? AND workspace_id = ?`)
|
|
597
|
+
.run(params.approvalStatus, params.approvedBy, now, params.planId, params.workspaceId);
|
|
598
|
+
return this.getPlan(params.workspaceId, params.planId);
|
|
599
|
+
}
|
|
600
|
+
// --- Runs & Cryptographic Receipts ---
|
|
601
|
+
createRun(plan, actorId) {
|
|
602
|
+
const id = `run_${crypto.randomBytes(8).toString("hex")}`;
|
|
603
|
+
const now = new Date().toISOString();
|
|
604
|
+
this.db
|
|
605
|
+
.prepare(`INSERT INTO runs (
|
|
606
|
+
id, plan_id, workspace_id, actor_id, tool_slug, lane, risk_level,
|
|
607
|
+
status, input_json, output_json, error, actions_metered, credits_metered,
|
|
608
|
+
duration_ms, receipt_id, started_at, completed_at
|
|
609
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, 'running', ?, NULL, NULL, 0, 0, NULL, NULL, ?, NULL)`)
|
|
610
|
+
.run(id, plan.id, plan.workspaceId, actorId, plan.toolSlug, plan.lane, plan.riskLevel, JSON.stringify(plan.input), now);
|
|
611
|
+
this.db
|
|
612
|
+
.prepare("UPDATE plans SET run_id = ?, approval_status = 'executed' WHERE id = ?")
|
|
613
|
+
.run(id, plan.id);
|
|
614
|
+
return this.getRun(plan.workspaceId, id);
|
|
615
|
+
}
|
|
616
|
+
finalizeRun(params) {
|
|
617
|
+
const run = this.getRun(params.workspaceId, params.runId);
|
|
618
|
+
if (!run)
|
|
619
|
+
throw new Error(`Run ${params.runId} not found`);
|
|
620
|
+
const now = new Date().toISOString();
|
|
621
|
+
if (params.actionsMetered > 0 || params.creditsMetered > 0) {
|
|
622
|
+
const ws = this.getWorkspace(params.workspaceId);
|
|
623
|
+
const newActionsUsed = ws.monthlyActionsUsed + params.actionsMetered;
|
|
624
|
+
const newCreditsBalance = Math.max(0, ws.creditsBalance - params.creditsMetered);
|
|
625
|
+
this.db
|
|
626
|
+
.prepare(`UPDATE workspaces
|
|
627
|
+
SET monthly_actions_used = ?, credits_balance = ?, updated_at = ?
|
|
628
|
+
WHERE id = ?`)
|
|
629
|
+
.run(newActionsUsed, newCreditsBalance, now, params.workspaceId);
|
|
630
|
+
const settlement = this.computeSupplierSettlement(run.lane, run.toolSlug, params.creditsMetered, "settled_by_muxo_master_wallet");
|
|
631
|
+
this.addLedgerEntry({
|
|
632
|
+
workspaceId: params.workspaceId,
|
|
633
|
+
kind: "run_deduction",
|
|
634
|
+
actionsDelta: -params.actionsMetered,
|
|
635
|
+
creditsDelta: -params.creditsMetered,
|
|
636
|
+
balanceAfter: newCreditsBalance,
|
|
637
|
+
referenceId: run.id,
|
|
638
|
+
description: `Executed ${run.toolSlug} (${run.lane}) — User paid Muxo ${params.creditsMetered} credits ($${(settlement.userRetailChargeCents / 100).toFixed(2)}) -> Muxo paid ${settlement.supplierName} ($${(settlement.supplierWholesalePayableCents / 100).toFixed(2)})`,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const receipt = this.issueReceipt({
|
|
642
|
+
run,
|
|
643
|
+
status: params.status,
|
|
644
|
+
output: params.output ?? null,
|
|
645
|
+
actionsMetered: params.actionsMetered,
|
|
646
|
+
creditsMetered: params.creditsMetered,
|
|
647
|
+
decisionRecord: params.decisionRecord ?? null,
|
|
648
|
+
issuedAt: now,
|
|
649
|
+
});
|
|
650
|
+
this.db
|
|
651
|
+
.prepare(`UPDATE runs
|
|
652
|
+
SET status = ?, output_json = ?, error = ?, actions_metered = ?,
|
|
653
|
+
credits_metered = ?, duration_ms = ?, receipt_id = ?, completed_at = ?
|
|
654
|
+
WHERE id = ? AND workspace_id = ?`)
|
|
655
|
+
.run(params.status, params.output ? JSON.stringify(params.output) : null, params.error ?? null, params.actionsMetered, params.creditsMetered, params.durationMs, receipt.id, now, params.runId, params.workspaceId);
|
|
656
|
+
return {
|
|
657
|
+
run: this.getRun(params.workspaceId, params.runId),
|
|
658
|
+
receipt,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
issueReceipt(params) {
|
|
662
|
+
const id = `rcpt_${crypto.randomBytes(8).toString("hex")}`;
|
|
663
|
+
const lastReceipt = this.db
|
|
664
|
+
.prepare("SELECT receipt_hash FROM receipts WHERE workspace_id = ? ORDER BY issued_at DESC LIMIT 1")
|
|
665
|
+
.get(params.run.workspaceId);
|
|
666
|
+
const prevReceiptHash = lastReceipt?.receipt_hash || "genesis_0000000000000000";
|
|
667
|
+
const inputHash = crypto
|
|
668
|
+
.createHash("sha256")
|
|
669
|
+
.update(JSON.stringify(params.run.input || {}))
|
|
670
|
+
.digest("hex");
|
|
671
|
+
const outputHash = crypto
|
|
672
|
+
.createHash("sha256")
|
|
673
|
+
.update(JSON.stringify(params.output || {}))
|
|
674
|
+
.digest("hex");
|
|
675
|
+
const canonicalPayload = [
|
|
676
|
+
id,
|
|
677
|
+
params.run.id,
|
|
678
|
+
params.run.planId,
|
|
679
|
+
params.run.workspaceId,
|
|
680
|
+
params.run.actorId,
|
|
681
|
+
params.run.toolSlug,
|
|
682
|
+
params.run.lane,
|
|
683
|
+
params.run.riskLevel,
|
|
684
|
+
params.status,
|
|
685
|
+
String(params.actionsMetered),
|
|
686
|
+
String(params.creditsMetered),
|
|
687
|
+
inputHash,
|
|
688
|
+
outputHash,
|
|
689
|
+
prevReceiptHash,
|
|
690
|
+
params.issuedAt,
|
|
691
|
+
].join("|");
|
|
692
|
+
const receiptHash = crypto.createHash("sha256").update(canonicalPayload).digest("hex");
|
|
693
|
+
const signature = crypto
|
|
694
|
+
.createHmac("sha256", this.vaultKey)
|
|
695
|
+
.update(receiptHash)
|
|
696
|
+
.digest("hex");
|
|
697
|
+
this.db
|
|
698
|
+
.prepare(`INSERT INTO receipts (
|
|
699
|
+
id, run_id, plan_id, workspace_id, actor_id, tool_slug, lane, risk_level,
|
|
700
|
+
status, actions_metered, credits_metered, input_hash, output_hash,
|
|
701
|
+
prev_receipt_hash, receipt_hash, signature, decision_record_json, issued_at
|
|
702
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
703
|
+
.run(id, params.run.id, params.run.planId, params.run.workspaceId, params.run.actorId, params.run.toolSlug, params.run.lane, params.run.riskLevel, params.status, params.actionsMetered, params.creditsMetered, inputHash, outputHash, prevReceiptHash, receiptHash, signature, params.decisionRecord ? JSON.stringify(params.decisionRecord) : null, params.issuedAt);
|
|
704
|
+
return this.getReceipt(params.run.workspaceId, id);
|
|
705
|
+
}
|
|
706
|
+
verifyReceipt(receipt) {
|
|
707
|
+
const canonicalPayload = [
|
|
708
|
+
receipt.id,
|
|
709
|
+
receipt.runId,
|
|
710
|
+
receipt.planId,
|
|
711
|
+
receipt.workspaceId,
|
|
712
|
+
receipt.actorId,
|
|
713
|
+
receipt.toolSlug,
|
|
714
|
+
receipt.lane,
|
|
715
|
+
receipt.riskLevel,
|
|
716
|
+
receipt.status,
|
|
717
|
+
String(receipt.actionsMetered),
|
|
718
|
+
String(receipt.creditsMetered),
|
|
719
|
+
receipt.inputHash,
|
|
720
|
+
receipt.outputHash,
|
|
721
|
+
receipt.prevReceiptHash,
|
|
722
|
+
receipt.issuedAt,
|
|
723
|
+
].join("|");
|
|
724
|
+
const expectedHash = crypto.createHash("sha256").update(canonicalPayload).digest("hex");
|
|
725
|
+
if (expectedHash !== receipt.receiptHash)
|
|
726
|
+
return false;
|
|
727
|
+
const expectedSig = crypto
|
|
728
|
+
.createHmac("sha256", this.vaultKey)
|
|
729
|
+
.update(expectedHash)
|
|
730
|
+
.digest("hex");
|
|
731
|
+
return expectedSig === receipt.signature;
|
|
732
|
+
}
|
|
733
|
+
getRun(workspaceId, runId) {
|
|
734
|
+
const row = this.db
|
|
735
|
+
.prepare("SELECT * FROM runs WHERE id = ? AND workspace_id = ?")
|
|
736
|
+
.get(runId, workspaceId);
|
|
737
|
+
return row ? this.mapRun(row) : null;
|
|
738
|
+
}
|
|
739
|
+
listRuns(workspaceId, limit = 50) {
|
|
740
|
+
const rows = this.db
|
|
741
|
+
.prepare("SELECT * FROM runs WHERE workspace_id = ? ORDER BY started_at DESC LIMIT ?")
|
|
742
|
+
.all(workspaceId, limit);
|
|
743
|
+
return rows.map((r) => this.mapRun(r));
|
|
744
|
+
}
|
|
745
|
+
getReceipt(workspaceId, receiptIdOrRunId) {
|
|
746
|
+
const row = this.db
|
|
747
|
+
.prepare("SELECT * FROM receipts WHERE workspace_id = ? AND (id = ? OR run_id = ?)")
|
|
748
|
+
.get(workspaceId, receiptIdOrRunId, receiptIdOrRunId);
|
|
749
|
+
return row ? this.mapReceipt(row) : null;
|
|
750
|
+
}
|
|
751
|
+
listReceipts(workspaceId, limit = 50) {
|
|
752
|
+
const rows = this.db
|
|
753
|
+
.prepare("SELECT * FROM receipts WHERE workspace_id = ? ORDER BY issued_at DESC LIMIT ?")
|
|
754
|
+
.all(workspaceId, limit);
|
|
755
|
+
return rows.map((r) => this.mapReceipt(r));
|
|
756
|
+
}
|
|
757
|
+
// --- Billing & Credit Ledger ---
|
|
758
|
+
markStripeEventProcessed(eventId) {
|
|
759
|
+
const existing = this.db
|
|
760
|
+
.prepare("SELECT event_id FROM processed_stripe_events WHERE event_id = ?")
|
|
761
|
+
.get(eventId);
|
|
762
|
+
if (existing)
|
|
763
|
+
return false;
|
|
764
|
+
this.db
|
|
765
|
+
.prepare("INSERT INTO processed_stripe_events (event_id, processed_at) VALUES (?, ?)")
|
|
766
|
+
.run(eventId, new Date().toISOString());
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
upgradeWorkspaceToStarter(params) {
|
|
770
|
+
if (!this.markStripeEventProcessed(`upgrade:${params.referenceId}`)) {
|
|
771
|
+
return this.getWorkspace(params.workspaceId);
|
|
772
|
+
}
|
|
773
|
+
const ws = this.getWorkspace(params.workspaceId);
|
|
774
|
+
if (!ws)
|
|
775
|
+
throw new Error(`Workspace ${params.workspaceId} not found`);
|
|
776
|
+
const now = new Date().toISOString();
|
|
777
|
+
const newCredits = ws.creditsBalance + 1000;
|
|
778
|
+
this.db
|
|
779
|
+
.prepare(`UPDATE workspaces
|
|
780
|
+
SET tier = 'starter',
|
|
781
|
+
monthly_actions_limit = 5000,
|
|
782
|
+
credits_balance = ?,
|
|
783
|
+
stripe_customer_id = COALESCE(?, stripe_customer_id),
|
|
784
|
+
stripe_subscription_id = COALESCE(?, stripe_subscription_id),
|
|
785
|
+
updated_at = ?
|
|
786
|
+
WHERE id = ?`)
|
|
787
|
+
.run(newCredits, params.stripeCustomerId ?? null, params.stripeSubscriptionId ?? null, now, params.workspaceId);
|
|
788
|
+
this.addLedgerEntry({
|
|
789
|
+
workspaceId: params.workspaceId,
|
|
790
|
+
kind: "subscription_upgrade",
|
|
791
|
+
actionsDelta: 4000,
|
|
792
|
+
creditsDelta: 1000,
|
|
793
|
+
balanceAfter: newCredits,
|
|
794
|
+
referenceId: params.referenceId,
|
|
795
|
+
description: "Upgraded to Starter Plan ($29/mo — 5,000 actions/mo + 1,000 credits)",
|
|
796
|
+
});
|
|
797
|
+
return this.getWorkspace(params.workspaceId);
|
|
798
|
+
}
|
|
799
|
+
addWorkspaceCredits(params) {
|
|
800
|
+
if (!this.markStripeEventProcessed(`credits:${params.referenceId}`)) {
|
|
801
|
+
return this.getWorkspace(params.workspaceId);
|
|
802
|
+
}
|
|
803
|
+
const ws = this.getWorkspace(params.workspaceId);
|
|
804
|
+
if (!ws)
|
|
805
|
+
throw new Error(`Workspace ${params.workspaceId} not found`);
|
|
806
|
+
const now = new Date().toISOString();
|
|
807
|
+
const newCredits = ws.creditsBalance + params.credits;
|
|
808
|
+
this.db
|
|
809
|
+
.prepare("UPDATE workspaces SET credits_balance = ?, updated_at = ? WHERE id = ?")
|
|
810
|
+
.run(newCredits, now, params.workspaceId);
|
|
811
|
+
this.addLedgerEntry({
|
|
812
|
+
workspaceId: params.workspaceId,
|
|
813
|
+
kind: "credit_topup",
|
|
814
|
+
actionsDelta: 0,
|
|
815
|
+
creditsDelta: params.credits,
|
|
816
|
+
balanceAfter: newCredits,
|
|
817
|
+
referenceId: params.referenceId,
|
|
818
|
+
description: params.description,
|
|
819
|
+
});
|
|
820
|
+
return this.getWorkspace(params.workspaceId);
|
|
821
|
+
}
|
|
822
|
+
addLedgerEntry(params) {
|
|
823
|
+
const id = `led_${crypto.randomBytes(8).toString("hex")}`;
|
|
824
|
+
const now = new Date().toISOString();
|
|
825
|
+
this.db
|
|
826
|
+
.prepare(`INSERT INTO ledger_entries (
|
|
827
|
+
id, workspace_id, kind, actions_delta, credits_delta,
|
|
828
|
+
balance_after, reference_id, description, created_at
|
|
829
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
830
|
+
.run(id, params.workspaceId, params.kind, params.actionsDelta, params.creditsDelta, params.balanceAfter, params.referenceId, params.description, now);
|
|
831
|
+
return {
|
|
832
|
+
id,
|
|
833
|
+
workspaceId: params.workspaceId,
|
|
834
|
+
kind: params.kind,
|
|
835
|
+
actionsDelta: params.actionsDelta,
|
|
836
|
+
creditsDelta: params.creditsDelta,
|
|
837
|
+
balanceAfter: params.balanceAfter,
|
|
838
|
+
referenceId: params.referenceId,
|
|
839
|
+
description: params.description,
|
|
840
|
+
createdAt: now,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
computeSupplierSettlement(lane, toolSlug, creditCost, settlementStatus = "settled_by_muxo_master_wallet") {
|
|
844
|
+
const userChargeCredits = Math.max(0, Number(creditCost || 0));
|
|
845
|
+
const userRetailChargeCents = Math.round(userChargeCredits * 100) / 100;
|
|
846
|
+
const supplierWholesalePayableCents = Math.round(userRetailChargeCents * 0.6 * 100) / 100;
|
|
847
|
+
const muxoNetMarginCents = Math.round((userRetailChargeCents - supplierWholesalePayableCents) * 100) / 100;
|
|
848
|
+
const upstreamProvider = toolSlug.split(".")[1] || lane;
|
|
849
|
+
const supplierMeta = {
|
|
850
|
+
composio_instant: {
|
|
851
|
+
supplierName: "Composio Instant Wholesale Wallet",
|
|
852
|
+
masterAccountRef: "muxo_master_composio_wallet",
|
|
853
|
+
},
|
|
854
|
+
composio_sdk: {
|
|
855
|
+
supplierName: "Composio Managed OAuth Gateway",
|
|
856
|
+
masterAccountRef: "muxo_master_composio_sdk_account",
|
|
857
|
+
},
|
|
858
|
+
stripe_projects: {
|
|
859
|
+
supplierName: "Stripe Projects Master Infrastructure Account",
|
|
860
|
+
masterAccountRef: "muxo_master_stripe_projects_account",
|
|
861
|
+
},
|
|
862
|
+
};
|
|
863
|
+
const meta = supplierMeta[lane] || supplierMeta.composio_instant;
|
|
864
|
+
return {
|
|
865
|
+
fundingMode: "muxo_funded",
|
|
866
|
+
supplier: lane,
|
|
867
|
+
supplierName: meta.supplierName,
|
|
868
|
+
upstreamProvider,
|
|
869
|
+
userChargeCredits,
|
|
870
|
+
userRetailChargeCents,
|
|
871
|
+
supplierWholesalePayableCents,
|
|
872
|
+
muxoNetMarginCents,
|
|
873
|
+
settlementStatus,
|
|
874
|
+
masterAccountRef: meta.masterAccountRef,
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
getSupplierSettlementSummary(workspaceId) {
|
|
878
|
+
const ws = this.getWorkspace(workspaceId);
|
|
879
|
+
const ledger = this.listLedgerEntries(workspaceId, 500);
|
|
880
|
+
const runs = this.listRuns(workspaceId, 500).filter((r) => r.status === "succeeded");
|
|
881
|
+
const totalCreditsPurchasedOrGranted = ledger
|
|
882
|
+
.filter((e) => e.creditsDelta > 0)
|
|
883
|
+
.reduce((acc, e) => acc + e.creditsDelta, 0);
|
|
884
|
+
const bySupplier = {
|
|
885
|
+
composio_instant: {
|
|
886
|
+
runsCount: 0,
|
|
887
|
+
creditsConsumed: 0,
|
|
888
|
+
userRetailSpendCents: 0,
|
|
889
|
+
supplierWholesalePaidCents: 0,
|
|
890
|
+
muxoNetMarginCents: 0,
|
|
891
|
+
},
|
|
892
|
+
composio_sdk: {
|
|
893
|
+
runsCount: 0,
|
|
894
|
+
creditsConsumed: 0,
|
|
895
|
+
userRetailSpendCents: 0,
|
|
896
|
+
supplierWholesalePaidCents: 0,
|
|
897
|
+
muxoNetMarginCents: 0,
|
|
898
|
+
},
|
|
899
|
+
stripe_projects: {
|
|
900
|
+
runsCount: 0,
|
|
901
|
+
creditsConsumed: 0,
|
|
902
|
+
userRetailSpendCents: 0,
|
|
903
|
+
supplierWholesalePaidCents: 0,
|
|
904
|
+
muxoNetMarginCents: 0,
|
|
905
|
+
},
|
|
906
|
+
};
|
|
907
|
+
let totalCreditsConsumed = 0;
|
|
908
|
+
let totalUserRetailSpendCents = 0;
|
|
909
|
+
let totalSupplierWholesalePaidCents = 0;
|
|
910
|
+
let totalMuxoNetMarginCents = 0;
|
|
911
|
+
for (const run of runs) {
|
|
912
|
+
const s = this.computeSupplierSettlement(run.lane, run.toolSlug, run.creditsMetered, "settled_by_muxo_master_wallet");
|
|
913
|
+
const bucket = bySupplier[run.lane] || bySupplier.composio_instant;
|
|
914
|
+
bucket.runsCount += 1;
|
|
915
|
+
bucket.creditsConsumed += s.userChargeCredits;
|
|
916
|
+
bucket.userRetailSpendCents =
|
|
917
|
+
Math.round((bucket.userRetailSpendCents + s.userRetailChargeCents) * 100) / 100;
|
|
918
|
+
bucket.supplierWholesalePaidCents =
|
|
919
|
+
Math.round((bucket.supplierWholesalePaidCents + s.supplierWholesalePayableCents) * 100) / 100;
|
|
920
|
+
bucket.muxoNetMarginCents =
|
|
921
|
+
Math.round((bucket.muxoNetMarginCents + s.muxoNetMarginCents) * 100) / 100;
|
|
922
|
+
totalCreditsConsumed += s.userChargeCredits;
|
|
923
|
+
totalUserRetailSpendCents =
|
|
924
|
+
Math.round((totalUserRetailSpendCents + s.userRetailChargeCents) * 100) / 100;
|
|
925
|
+
totalSupplierWholesalePaidCents =
|
|
926
|
+
Math.round((totalSupplierWholesalePaidCents + s.supplierWholesalePayableCents) * 100) / 100;
|
|
927
|
+
totalMuxoNetMarginCents =
|
|
928
|
+
Math.round((totalMuxoNetMarginCents + s.muxoNetMarginCents) * 100) / 100;
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
workspaceId,
|
|
932
|
+
fundingModel: "user_pays_muxo_muxo_pays_suppliers",
|
|
933
|
+
creditPriceCents: 1,
|
|
934
|
+
totalCreditsPurchasedOrGranted,
|
|
935
|
+
totalCreditsConsumed,
|
|
936
|
+
currentCreditsBalance: ws?.creditsBalance ?? 0,
|
|
937
|
+
totalUserRetailSpendCents,
|
|
938
|
+
totalSupplierWholesalePaidCents,
|
|
939
|
+
totalMuxoNetMarginCents,
|
|
940
|
+
bySupplier,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
listLedgerEntries(workspaceId, limit = 50) {
|
|
944
|
+
const rows = this.db
|
|
945
|
+
.prepare("SELECT * FROM ledger_entries WHERE workspace_id = ? ORDER BY created_at DESC LIMIT ?")
|
|
946
|
+
.all(workspaceId, limit);
|
|
947
|
+
return rows.map((r) => {
|
|
948
|
+
const kind = String(r.kind);
|
|
949
|
+
const refId = r.reference_id ? String(r.reference_id) : null;
|
|
950
|
+
const creditsDelta = Number(r.credits_delta);
|
|
951
|
+
let supplierSettlement = null;
|
|
952
|
+
if (kind === "run_deduction" && refId) {
|
|
953
|
+
const run = this.getRun(workspaceId, refId);
|
|
954
|
+
if (run) {
|
|
955
|
+
supplierSettlement = this.computeSupplierSettlement(run.lane, run.toolSlug, Math.abs(creditsDelta), "settled_by_muxo_master_wallet");
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
id: String(r.id),
|
|
960
|
+
workspaceId: String(r.workspace_id),
|
|
961
|
+
kind,
|
|
962
|
+
actionsDelta: Number(r.actions_delta),
|
|
963
|
+
creditsDelta,
|
|
964
|
+
balanceAfter: Number(r.balance_after),
|
|
965
|
+
referenceId: refId,
|
|
966
|
+
description: String(r.description),
|
|
967
|
+
supplierSettlement,
|
|
968
|
+
createdAt: String(r.created_at),
|
|
969
|
+
};
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
// --- Supermemory Local Index ---
|
|
973
|
+
saveMemoryRecord(params) {
|
|
974
|
+
const id = `mem_${crypto.randomBytes(8).toString("hex")}`;
|
|
975
|
+
const now = new Date().toISOString();
|
|
976
|
+
this.db
|
|
977
|
+
.prepare(`INSERT INTO memories (
|
|
978
|
+
id, workspace_id, namespace, document_id, title, content, kind, metadata_json, created_at
|
|
979
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
980
|
+
.run(id, params.workspaceId, params.namespace, params.documentId, params.title, params.content, params.kind, JSON.stringify(params.metadata || {}), now);
|
|
981
|
+
return {
|
|
982
|
+
id,
|
|
983
|
+
workspaceId: params.workspaceId,
|
|
984
|
+
namespace: params.namespace,
|
|
985
|
+
documentId: params.documentId,
|
|
986
|
+
title: params.title,
|
|
987
|
+
content: params.content,
|
|
988
|
+
kind: params.kind,
|
|
989
|
+
metadata: params.metadata || {},
|
|
990
|
+
createdAt: now,
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
listMemoryRecords(workspaceId, limit = 30) {
|
|
994
|
+
const rows = this.db
|
|
995
|
+
.prepare("SELECT * FROM memories WHERE workspace_id = ? ORDER BY created_at DESC LIMIT ?")
|
|
996
|
+
.all(workspaceId, limit);
|
|
997
|
+
return rows.map((r) => ({
|
|
998
|
+
id: String(r.id),
|
|
999
|
+
workspaceId: String(r.workspace_id),
|
|
1000
|
+
namespace: String(r.namespace),
|
|
1001
|
+
documentId: String(r.document_id),
|
|
1002
|
+
title: String(r.title),
|
|
1003
|
+
content: String(r.content),
|
|
1004
|
+
kind: String(r.kind),
|
|
1005
|
+
metadata: JSON.parse(String(r.metadata_json || "{}")),
|
|
1006
|
+
createdAt: String(r.created_at),
|
|
1007
|
+
}));
|
|
1008
|
+
}
|
|
1009
|
+
deleteMemoryRecord(workspaceId, idOrDocId) {
|
|
1010
|
+
const row = this.db
|
|
1011
|
+
.prepare("SELECT * FROM memories WHERE workspace_id = ? AND (id = ? OR document_id = ?)")
|
|
1012
|
+
.get(workspaceId, idOrDocId, idOrDocId);
|
|
1013
|
+
if (!row)
|
|
1014
|
+
return null;
|
|
1015
|
+
this.db
|
|
1016
|
+
.prepare("DELETE FROM memories WHERE id = ? AND workspace_id = ?")
|
|
1017
|
+
.run(String(row.id), workspaceId);
|
|
1018
|
+
return {
|
|
1019
|
+
id: String(row.id),
|
|
1020
|
+
workspaceId: String(row.workspace_id),
|
|
1021
|
+
namespace: String(row.namespace),
|
|
1022
|
+
documentId: String(row.document_id),
|
|
1023
|
+
title: String(row.title),
|
|
1024
|
+
content: String(row.content),
|
|
1025
|
+
kind: String(row.kind),
|
|
1026
|
+
metadata: JSON.parse(String(row.metadata_json || "{}")),
|
|
1027
|
+
createdAt: String(row.created_at),
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
// --- Autonomous Agent Onboarding (agents.composio.dev pattern) ---
|
|
1031
|
+
createAutonomousAgentSignup(params) {
|
|
1032
|
+
const suffix = crypto.randomBytes(4).toString("hex");
|
|
1033
|
+
const agentName = (params?.agentName || `Autonomous Agent ${suffix}`).trim();
|
|
1034
|
+
const email = `agent+${suffix}@agents.muxo.ai`;
|
|
1035
|
+
const password = crypto.randomBytes(16).toString("hex");
|
|
1036
|
+
const created = this.registerUser({
|
|
1037
|
+
email,
|
|
1038
|
+
name: agentName,
|
|
1039
|
+
password,
|
|
1040
|
+
workspaceName: `${agentName} Workspace`,
|
|
1041
|
+
});
|
|
1042
|
+
const keyRes = this.createApiKey({
|
|
1043
|
+
workspaceId: created.workspace.id,
|
|
1044
|
+
name: params?.harness
|
|
1045
|
+
? `${params.harness} Autonomous Key`
|
|
1046
|
+
: "Autonomous Agent Key",
|
|
1047
|
+
environment: "live",
|
|
1048
|
+
});
|
|
1049
|
+
return {
|
|
1050
|
+
user: created.user,
|
|
1051
|
+
workspace: created.workspace,
|
|
1052
|
+
apiKey: keyRes.secretKey,
|
|
1053
|
+
apiKeyRecord: keyRes.record,
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
getLatestClaimForWorkspace(workspaceId) {
|
|
1057
|
+
const row = this.db
|
|
1058
|
+
.prepare(`SELECT * FROM agent_claims
|
|
1059
|
+
WHERE workspace_id = ?
|
|
1060
|
+
ORDER BY created_at DESC
|
|
1061
|
+
LIMIT 1`)
|
|
1062
|
+
.get(workspaceId);
|
|
1063
|
+
if (!row)
|
|
1064
|
+
return null;
|
|
1065
|
+
return {
|
|
1066
|
+
claimSlug: String(row.claim_slug),
|
|
1067
|
+
targetEmail: String(row.target_email),
|
|
1068
|
+
status: String(row.status),
|
|
1069
|
+
claimedAt: row.claimed_at ? String(row.claimed_at) : null,
|
|
1070
|
+
expiresAt: String(row.expires_at),
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
createAgentClaim(params) {
|
|
1074
|
+
const claimSlug = crypto.randomBytes(9).toString("base64url");
|
|
1075
|
+
const now = new Date();
|
|
1076
|
+
const expires = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
|
|
1077
|
+
this.db
|
|
1078
|
+
.prepare(`INSERT INTO agent_claims (
|
|
1079
|
+
claim_slug, workspace_id, target_email, status, claimed_at, expires_at, created_at
|
|
1080
|
+
) VALUES (?, ?, ?, 'invited', NULL, ?, ?)`)
|
|
1081
|
+
.run(claimSlug, params.workspaceId, params.email.trim().toLowerCase(), expires.toISOString(), now.toISOString());
|
|
1082
|
+
return {
|
|
1083
|
+
claimSlug,
|
|
1084
|
+
claimUrl: `${params.baseUrl.replace(/\/$/, "")}/claim/${claimSlug}`,
|
|
1085
|
+
targetEmail: params.email.trim().toLowerCase(),
|
|
1086
|
+
status: "invited",
|
|
1087
|
+
expiresAt: expires.toISOString(),
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
redeemAgentClaim(claimSlug) {
|
|
1091
|
+
const row = this.db
|
|
1092
|
+
.prepare("SELECT * FROM agent_claims WHERE claim_slug = ?")
|
|
1093
|
+
.get(claimSlug);
|
|
1094
|
+
if (!row)
|
|
1095
|
+
return null;
|
|
1096
|
+
const now = new Date().toISOString();
|
|
1097
|
+
const workspaceId = String(row.workspace_id);
|
|
1098
|
+
const targetEmail = String(row.target_email);
|
|
1099
|
+
this.db
|
|
1100
|
+
.prepare("UPDATE agent_claims SET status = 'claimed', claimed_at = ? WHERE claim_slug = ?")
|
|
1101
|
+
.run(now, claimSlug);
|
|
1102
|
+
const workspace = this.getWorkspace(workspaceId);
|
|
1103
|
+
if (!workspace)
|
|
1104
|
+
return null;
|
|
1105
|
+
return {
|
|
1106
|
+
claimSlug,
|
|
1107
|
+
workspace,
|
|
1108
|
+
targetEmail,
|
|
1109
|
+
status: "claimed",
|
|
1110
|
+
claimedAt: now,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
// --- Row Mappers ---
|
|
1114
|
+
mapUser(row) {
|
|
1115
|
+
return {
|
|
1116
|
+
id: String(row.id),
|
|
1117
|
+
email: String(row.email),
|
|
1118
|
+
name: String(row.name),
|
|
1119
|
+
workspaceId: String(row.workspace_id),
|
|
1120
|
+
role: String(row.role),
|
|
1121
|
+
createdAt: String(row.created_at),
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
mapWorkspace(row) {
|
|
1125
|
+
return {
|
|
1126
|
+
id: String(row.id),
|
|
1127
|
+
name: String(row.name),
|
|
1128
|
+
slug: String(row.slug),
|
|
1129
|
+
ownerUserId: String(row.owner_user_id),
|
|
1130
|
+
tier: String(row.tier),
|
|
1131
|
+
monthlyActionsLimit: Number(row.monthly_actions_limit),
|
|
1132
|
+
monthlyActionsUsed: Number(row.monthly_actions_used),
|
|
1133
|
+
creditsBalance: Number(row.credits_balance),
|
|
1134
|
+
stripeCustomerId: row.stripe_customer_id ? String(row.stripe_customer_id) : null,
|
|
1135
|
+
stripeSubscriptionId: row.stripe_subscription_id
|
|
1136
|
+
? String(row.stripe_subscription_id)
|
|
1137
|
+
: null,
|
|
1138
|
+
projectsEnvName: String(row.projects_env_name),
|
|
1139
|
+
policy: JSON.parse(String(row.policy_json || "{}")),
|
|
1140
|
+
createdAt: String(row.created_at),
|
|
1141
|
+
updatedAt: String(row.updated_at),
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
mapConnection(row) {
|
|
1145
|
+
return {
|
|
1146
|
+
id: String(row.id),
|
|
1147
|
+
workspaceId: String(row.workspace_id),
|
|
1148
|
+
lane: String(row.lane),
|
|
1149
|
+
providerSlug: String(row.provider_slug),
|
|
1150
|
+
providerName: String(row.provider_name),
|
|
1151
|
+
status: String(row.status),
|
|
1152
|
+
externalAccountId: row.external_account_id ? String(row.external_account_id) : null,
|
|
1153
|
+
redirectUrl: row.redirect_url ? String(row.redirect_url) : null,
|
|
1154
|
+
metadata: JSON.parse(String(row.metadata_json || "{}")),
|
|
1155
|
+
updatedAt: String(row.updated_at),
|
|
1156
|
+
createdAt: String(row.created_at),
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
mapResource(row) {
|
|
1160
|
+
return {
|
|
1161
|
+
id: String(row.id),
|
|
1162
|
+
workspaceId: String(row.workspace_id),
|
|
1163
|
+
resourceName: String(row.resource_name),
|
|
1164
|
+
providerSlug: String(row.provider_slug),
|
|
1165
|
+
providerName: String(row.provider_name),
|
|
1166
|
+
serviceSlug: String(row.service_slug),
|
|
1167
|
+
planSlug: row.plan_slug ? String(row.plan_slug) : null,
|
|
1168
|
+
status: String(row.status),
|
|
1169
|
+
envVarKeys: JSON.parse(String(row.env_var_keys_json || "[]")),
|
|
1170
|
+
externalResourceId: row.external_resource_id
|
|
1171
|
+
? String(row.external_resource_id)
|
|
1172
|
+
: null,
|
|
1173
|
+
metadata: JSON.parse(String(row.metadata_json || "{}")),
|
|
1174
|
+
createdAt: String(row.created_at),
|
|
1175
|
+
updatedAt: String(row.updated_at),
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
mapPlan(row) {
|
|
1179
|
+
const lane = String(row.lane);
|
|
1180
|
+
const toolSlug = String(row.tool_slug);
|
|
1181
|
+
const creditCost = Number(row.credit_cost);
|
|
1182
|
+
const approvalStatus = String(row.approval_status);
|
|
1183
|
+
return {
|
|
1184
|
+
id: String(row.id),
|
|
1185
|
+
workspaceId: String(row.workspace_id),
|
|
1186
|
+
actorId: String(row.actor_id),
|
|
1187
|
+
toolSlug,
|
|
1188
|
+
upstreamSlug: String(row.upstream_slug),
|
|
1189
|
+
lane,
|
|
1190
|
+
riskLevel: String(row.risk_level),
|
|
1191
|
+
input: JSON.parse(String(row.input_json || "{}")),
|
|
1192
|
+
reason: String(row.reason),
|
|
1193
|
+
creditCost,
|
|
1194
|
+
approvalStatus,
|
|
1195
|
+
approvedBy: row.approved_by ? String(row.approved_by) : null,
|
|
1196
|
+
approvedAt: row.approved_at ? String(row.approved_at) : null,
|
|
1197
|
+
runId: row.run_id ? String(row.run_id) : null,
|
|
1198
|
+
decisionRecord: row.decision_record_json
|
|
1199
|
+
? JSON.parse(String(row.decision_record_json))
|
|
1200
|
+
: null,
|
|
1201
|
+
memoryContext: row.memory_context_json
|
|
1202
|
+
? JSON.parse(String(row.memory_context_json))
|
|
1203
|
+
: null,
|
|
1204
|
+
supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditCost, approvalStatus === "executed"
|
|
1205
|
+
? "settled_by_muxo_master_wallet"
|
|
1206
|
+
: "planned_muxo_master_wallet"),
|
|
1207
|
+
createdAt: String(row.created_at),
|
|
1208
|
+
expiresAt: String(row.expires_at),
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
mapRun(row) {
|
|
1212
|
+
return {
|
|
1213
|
+
id: String(row.id),
|
|
1214
|
+
planId: String(row.plan_id),
|
|
1215
|
+
workspaceId: String(row.workspace_id),
|
|
1216
|
+
actorId: String(row.actor_id),
|
|
1217
|
+
toolSlug: String(row.tool_slug),
|
|
1218
|
+
lane: String(row.lane),
|
|
1219
|
+
riskLevel: String(row.risk_level),
|
|
1220
|
+
status: String(row.status),
|
|
1221
|
+
input: JSON.parse(String(row.input_json || "{}")),
|
|
1222
|
+
output: row.output_json ? JSON.parse(String(row.output_json)) : null,
|
|
1223
|
+
error: row.error ? String(row.error) : null,
|
|
1224
|
+
actionsMetered: Number(row.actions_metered),
|
|
1225
|
+
creditsMetered: Number(row.credits_metered),
|
|
1226
|
+
durationMs: row.duration_ms !== null ? Number(row.duration_ms) : null,
|
|
1227
|
+
receiptId: row.receipt_id ? String(row.receipt_id) : null,
|
|
1228
|
+
startedAt: String(row.started_at),
|
|
1229
|
+
completedAt: row.completed_at ? String(row.completed_at) : null,
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
mapReceipt(row) {
|
|
1233
|
+
const lane = String(row.lane);
|
|
1234
|
+
const toolSlug = String(row.tool_slug);
|
|
1235
|
+
const creditsMetered = Number(row.credits_metered);
|
|
1236
|
+
return {
|
|
1237
|
+
id: String(row.id),
|
|
1238
|
+
runId: String(row.run_id),
|
|
1239
|
+
planId: String(row.plan_id),
|
|
1240
|
+
workspaceId: String(row.workspace_id),
|
|
1241
|
+
actorId: String(row.actor_id),
|
|
1242
|
+
toolSlug,
|
|
1243
|
+
lane,
|
|
1244
|
+
riskLevel: String(row.risk_level),
|
|
1245
|
+
status: String(row.status),
|
|
1246
|
+
actionsMetered: Number(row.actions_metered),
|
|
1247
|
+
creditsMetered,
|
|
1248
|
+
inputHash: String(row.input_hash),
|
|
1249
|
+
outputHash: String(row.output_hash),
|
|
1250
|
+
prevReceiptHash: String(row.prev_receipt_hash),
|
|
1251
|
+
receiptHash: String(row.receipt_hash),
|
|
1252
|
+
signature: String(row.signature),
|
|
1253
|
+
decisionRecord: row.decision_record_json
|
|
1254
|
+
? JSON.parse(String(row.decision_record_json))
|
|
1255
|
+
: null,
|
|
1256
|
+
supplierSettlement: this.computeSupplierSettlement(lane, toolSlug, creditsMetered, "settled_by_muxo_master_wallet"),
|
|
1257
|
+
issuedAt: String(row.issued_at),
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
}
|