@muxoai/platform 1.4.0 → 1.4.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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +2 -2
- package/ARCHITECTURE.md +16 -6
- package/README.md +1 -1
- package/dist/cli.js +317 -10
- package/dist/composio-catalog.d.ts +58 -0
- package/dist/composio-catalog.js +474 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +16 -0
- package/dist/durable-workflow.d.ts +45 -0
- package/dist/durable-workflow.js +90 -0
- package/dist/engine.d.ts +276 -5
- package/dist/engine.js +1261 -71
- package/dist/mcp-schema.d.ts +125 -0
- package/dist/mcp-schema.js +1076 -0
- package/dist/mcp.d.ts +2 -30
- package/dist/mcp.js +505 -539
- package/dist/memory-graph.d.ts +21 -0
- package/dist/memory-graph.js +173 -0
- package/dist/nl.js +155 -0
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +134 -0
- package/dist/policy.d.ts +75 -1
- package/dist/policy.js +1119 -0
- package/dist/providers/composio-instant.js +225 -5
- package/dist/providers/composio-sdk.d.ts +68 -5
- package/dist/providers/composio-sdk.js +683 -78
- package/dist/providers/microsoft-governance.d.ts +28 -1
- package/dist/providers/microsoft-governance.js +224 -11
- package/dist/providers/stripe-billing.d.ts +42 -0
- package/dist/providers/stripe-billing.js +230 -1
- package/dist/providers/stripe-projects.d.ts +451 -3
- package/dist/providers/stripe-projects.js +2106 -87
- package/dist/providers/supermemory.d.ts +74 -1
- package/dist/providers/supermemory.js +426 -0
- package/dist/provisioning-managed.d.ts +50 -0
- package/dist/provisioning-managed.js +155 -0
- package/dist/sdk.d.ts +440 -3
- package/dist/sdk.js +305 -3
- package/dist/server.js +1841 -232
- package/dist/store.d.ts +113 -6
- package/dist/store.js +675 -52
- package/dist/types.d.ts +192 -3
- package/dist/ui/console-html.d.ts +10 -1
- package/dist/ui/console-html.js +1082 -452
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/plugins/muxo/.claude-plugin/plugin.json +1 -1
- package/plugins/muxo/.codex-plugin/plugin.json +1 -1
- package/plugins/muxo/plugin.json +2 -2
- package/plugins/muxo/skills/muxo/SKILL.md +42 -1
- package/plugins/muxo/skills/muxo/references/mcp.md +3 -2
- package/plugins/muxo/skills/setup/SKILL.md +8 -0
package/dist/engine.js
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { APPROVED_PLAN_STATUSES, approvalRequiredError, buildApprovalUrl, clampWaitSeconds, looksLikeToolSlug, unknownToolError, } from "./mcp-schema.js";
|
|
3
|
+
import { completeCloudProvisioningWorkflow, createCloudProvisioningWorkflow, normalizeWorkflowTarget, workflowNotFoundError, } from "./durable-workflow.js";
|
|
4
|
+
import { buildMemoryKnowledgeGraph } from "./memory-graph.js";
|
|
1
5
|
import { resolveNaturalLanguageIntent, } from "./nl.js";
|
|
6
|
+
import { compileNaturalLanguageGuardrailWithLlm, describePolicyInPlainEnglish, formatHumanFriendlyExplanation, parseNaturalLanguageGuardrail, } from "./policy.js";
|
|
2
7
|
import { ComposioInstantAdapter } from "./providers/composio-instant.js";
|
|
3
8
|
import { ComposioSdkAdapter } from "./providers/composio-sdk.js";
|
|
4
9
|
import { MicrosoftGovernanceAdapter } from "./providers/microsoft-governance.js";
|
|
5
10
|
import { StripeBillingAdapter } from "./providers/stripe-billing.js";
|
|
6
11
|
import { StripeProjectsAdapter, isPaidCloudProvisioning, } from "./providers/stripe-projects.js";
|
|
7
|
-
import { SupermemoryAdapter } from "./providers/supermemory.js";
|
|
12
|
+
import { SupermemoryAdapter, applyActiveMemoryToToolInput, explainMemoryContext, extractActiveMemoryDefaults, parseNaturalLanguageMemoryInstruction, rankMemoriesHybrid, } from "./providers/supermemory.js";
|
|
13
|
+
function makeControlPlaneResolution(toolSlug, prompt, humanSummary, input = {}) {
|
|
14
|
+
return {
|
|
15
|
+
resolved: true,
|
|
16
|
+
confidence: 0.98,
|
|
17
|
+
resolutionMethod: "semantic_router",
|
|
18
|
+
byokUsed: null,
|
|
19
|
+
tool: null,
|
|
20
|
+
toolSlug,
|
|
21
|
+
input,
|
|
22
|
+
extractedInput: input,
|
|
23
|
+
missingInputs: [],
|
|
24
|
+
needsConnection: false,
|
|
25
|
+
suggestedByokProvider: null,
|
|
26
|
+
reason: prompt,
|
|
27
|
+
alternatives: [],
|
|
28
|
+
humanSummary,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
8
31
|
export class MuxoEngine {
|
|
9
32
|
config;
|
|
10
33
|
store;
|
|
@@ -14,6 +37,7 @@ export class MuxoEngine {
|
|
|
14
37
|
stripeBilling;
|
|
15
38
|
governance;
|
|
16
39
|
supermemory;
|
|
40
|
+
durableWorkflows = new Map();
|
|
17
41
|
constructor(config, store) {
|
|
18
42
|
this.config = config;
|
|
19
43
|
this.store = store;
|
|
@@ -48,6 +72,7 @@ export class MuxoEngine {
|
|
|
48
72
|
const resources = this.store.listResources(ws.id);
|
|
49
73
|
const connections = this.store.listConnections(ws.id);
|
|
50
74
|
const supplierSettlementSummary = this.store.getSupplierSettlementSummary(ws.id);
|
|
75
|
+
const plainEnglishPolicy = describePolicyInPlainEnglish(ws.policy);
|
|
51
76
|
return {
|
|
52
77
|
organization: {
|
|
53
78
|
id: ws.id,
|
|
@@ -62,6 +87,8 @@ export class MuxoEngine {
|
|
|
62
87
|
},
|
|
63
88
|
supplierSettlementSummary,
|
|
64
89
|
policy: ws.policy,
|
|
90
|
+
plainEnglishPolicy,
|
|
91
|
+
explanation: plainEnglishPolicy.explanation,
|
|
65
92
|
activeConnectionsCount: connections.filter((c) => c.status === "active").length,
|
|
66
93
|
provisionedResourcesCount: resources.filter((r) => r.status === "active").length,
|
|
67
94
|
projectsEnvironment: ws.projectsEnvName,
|
|
@@ -126,13 +153,14 @@ export class MuxoEngine {
|
|
|
126
153
|
const sdk = await this.composioSdk.inspectTool(normalized);
|
|
127
154
|
if (sdk)
|
|
128
155
|
return sdk;
|
|
129
|
-
throw
|
|
156
|
+
throw unknownToolError(normalized);
|
|
130
157
|
}
|
|
131
158
|
// 5. connection_status
|
|
132
159
|
async connectionStatus(ctx, params) {
|
|
133
160
|
if (params?.syncLive !== false) {
|
|
134
161
|
// Sync live Composio connected accounts for this workspace
|
|
135
|
-
const
|
|
162
|
+
const composioByok = this.store.getByokKey(ctx.workspace.id, "composio");
|
|
163
|
+
const liveComposio = await this.composioSdk.listLiveConnections(ctx.workspace.id, composioByok);
|
|
136
164
|
for (const c of liveComposio) {
|
|
137
165
|
this.store.upsertConnection({
|
|
138
166
|
workspaceId: ctx.workspace.id,
|
|
@@ -145,7 +173,8 @@ export class MuxoEngine {
|
|
|
145
173
|
}
|
|
146
174
|
// Sync live Stripe Projects linked providers
|
|
147
175
|
try {
|
|
148
|
-
const
|
|
176
|
+
const stripeByok = this.store.getByokKey(ctx.workspace.id, "stripe");
|
|
177
|
+
const projStatus = await this.stripeProjects.getLiveStatus(stripeByok);
|
|
149
178
|
for (const p of projStatus.providers) {
|
|
150
179
|
this.store.upsertConnection({
|
|
151
180
|
workspaceId: ctx.workspace.id,
|
|
@@ -186,22 +215,30 @@ export class MuxoEngine {
|
|
|
186
215
|
const isProjectsLane = params.lane === "stripe_projects" || rawProvider.startsWith("resource.");
|
|
187
216
|
const cleanSlug = rawProvider.replace(/^(app\.|resource\.)/, "");
|
|
188
217
|
if (isProjectsLane) {
|
|
189
|
-
const
|
|
218
|
+
const stripeByok = this.store.getByokKey(ctx.workspace.id, "stripe");
|
|
219
|
+
const linked = await this.stripeProjects.linkProvider(cleanSlug, stripeByok);
|
|
190
220
|
const record = this.store.upsertConnection({
|
|
191
221
|
workspaceId: ctx.workspace.id,
|
|
192
222
|
lane: "stripe_projects",
|
|
193
223
|
providerSlug: linked.providerSlug,
|
|
194
224
|
providerName: linked.providerSlug.charAt(0).toUpperCase() +
|
|
195
225
|
linked.providerSlug.slice(1),
|
|
196
|
-
status: linked.status,
|
|
226
|
+
status: linked.status === "requires_review" ? "pending" : linked.status,
|
|
227
|
+
externalAccountId: linked.connectionRequestId || null,
|
|
197
228
|
redirectUrl: linked.redirectUrl,
|
|
198
229
|
});
|
|
199
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
...record,
|
|
232
|
+
message: linked.message,
|
|
233
|
+
...(linked.workflowStatus ? { workflowStatus: linked.workflowStatus } : {}),
|
|
234
|
+
};
|
|
200
235
|
}
|
|
236
|
+
const composioByok = this.store.getByokKey(ctx.workspace.id, "composio");
|
|
201
237
|
const started = await this.composioSdk.startConnection({
|
|
202
238
|
workspaceId: ctx.workspace.id,
|
|
203
239
|
providerSlug: cleanSlug,
|
|
204
240
|
callbackUrl: params.callbackUrl,
|
|
241
|
+
composioApiKey: composioByok,
|
|
205
242
|
});
|
|
206
243
|
const record = this.store.upsertConnection({
|
|
207
244
|
workspaceId: ctx.workspace.id,
|
|
@@ -225,9 +262,27 @@ export class MuxoEngine {
|
|
|
225
262
|
.trim()
|
|
226
263
|
.toLowerCase()
|
|
227
264
|
.replace(/^(app\.|resource\.)/, "");
|
|
228
|
-
|
|
265
|
+
if (!cleanSlug) {
|
|
266
|
+
throw new Error("provider is required to wait for a connection (e.g. 'github').");
|
|
267
|
+
}
|
|
268
|
+
const startedAt = Date.now();
|
|
269
|
+
const timeoutMs = clampWaitSeconds(params.timeoutSeconds) * 1000;
|
|
229
270
|
const pollIntervalMs = Math.min(5000, Math.max(200, Number(params.pollIntervalMs ?? 1500)));
|
|
230
|
-
const deadline =
|
|
271
|
+
const deadline = startedAt + timeoutMs;
|
|
272
|
+
let polledAttempts = 0;
|
|
273
|
+
const connectedResult = (connection, message) => ({
|
|
274
|
+
ok: true,
|
|
275
|
+
connected: true,
|
|
276
|
+
ready: true,
|
|
277
|
+
timedOut: false,
|
|
278
|
+
provider: cleanSlug,
|
|
279
|
+
status: "active",
|
|
280
|
+
connection,
|
|
281
|
+
redirectUrl: null,
|
|
282
|
+
polledAttempts,
|
|
283
|
+
elapsedMs: Date.now() - startedAt,
|
|
284
|
+
message,
|
|
285
|
+
});
|
|
231
286
|
let current = this.store
|
|
232
287
|
.listConnections(ctx.workspace.id)
|
|
233
288
|
.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
@@ -240,24 +295,19 @@ export class MuxoEngine {
|
|
|
240
295
|
}
|
|
241
296
|
}
|
|
242
297
|
while (true) {
|
|
298
|
+
polledAttempts += 1;
|
|
243
299
|
const latestLocal = this.store
|
|
244
300
|
.listConnections(ctx.workspace.id)
|
|
245
301
|
.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
246
302
|
if (latestLocal) {
|
|
247
303
|
current = latestLocal;
|
|
248
304
|
if (latestLocal.status === "active") {
|
|
249
|
-
return {
|
|
250
|
-
connected: true,
|
|
251
|
-
provider: cleanSlug,
|
|
252
|
-
status: "active",
|
|
253
|
-
connection: latestLocal,
|
|
254
|
-
redirectUrl: null,
|
|
255
|
-
message: `${latestLocal.providerName} OAuth handoff completed and connection is active.`,
|
|
256
|
-
};
|
|
305
|
+
return connectedResult(latestLocal, `${latestLocal.providerName} OAuth handoff completed and connection is active.`);
|
|
257
306
|
}
|
|
258
307
|
}
|
|
259
308
|
if (current?.externalAccountId && current.lane === "composio_sdk") {
|
|
260
|
-
const
|
|
309
|
+
const composioByok = this.store.getByokKey(ctx.workspace.id, "composio");
|
|
310
|
+
const direct = await this.composioSdk.getConnectedAccountStatus(current.externalAccountId, composioByok);
|
|
261
311
|
if (direct && direct.status === "active") {
|
|
262
312
|
current = this.store.upsertConnection({
|
|
263
313
|
workspaceId: ctx.workspace.id,
|
|
@@ -268,14 +318,7 @@ export class MuxoEngine {
|
|
|
268
318
|
externalAccountId: direct.id,
|
|
269
319
|
redirectUrl: null,
|
|
270
320
|
});
|
|
271
|
-
return {
|
|
272
|
-
connected: true,
|
|
273
|
-
provider: cleanSlug,
|
|
274
|
-
status: "active",
|
|
275
|
-
connection: current,
|
|
276
|
-
redirectUrl: null,
|
|
277
|
-
message: `${current.providerName} OAuth handoff completed and connection is active.`,
|
|
278
|
-
};
|
|
321
|
+
return connectedResult(current, `${current.providerName} OAuth handoff completed and connection is active.`);
|
|
279
322
|
}
|
|
280
323
|
}
|
|
281
324
|
const synced = await this.connectionStatus(ctx, {
|
|
@@ -287,14 +330,7 @@ export class MuxoEngine {
|
|
|
287
330
|
if (found) {
|
|
288
331
|
current = found;
|
|
289
332
|
if (found.status === "active") {
|
|
290
|
-
return {
|
|
291
|
-
connected: true,
|
|
292
|
-
provider: cleanSlug,
|
|
293
|
-
status: "active",
|
|
294
|
-
connection: found,
|
|
295
|
-
redirectUrl: null,
|
|
296
|
-
message: `${found.providerName} connection is active and ready for governed execution.`,
|
|
297
|
-
};
|
|
333
|
+
return connectedResult(found, `${found.providerName} connection is active and ready for governed execution.`);
|
|
298
334
|
}
|
|
299
335
|
}
|
|
300
336
|
if (Date.now() >= deadline) {
|
|
@@ -306,11 +342,16 @@ export class MuxoEngine {
|
|
|
306
342
|
current = await this.startConnection(ctx, { provider: cleanSlug });
|
|
307
343
|
}
|
|
308
344
|
return {
|
|
345
|
+
ok: true,
|
|
309
346
|
connected: false,
|
|
347
|
+
ready: false,
|
|
348
|
+
timedOut: true,
|
|
310
349
|
provider: cleanSlug,
|
|
311
350
|
status: current.status,
|
|
312
351
|
connection: current,
|
|
313
352
|
redirectUrl: current.redirectUrl,
|
|
353
|
+
polledAttempts,
|
|
354
|
+
elapsedMs: Date.now() - startedAt,
|
|
314
355
|
message: current.redirectUrl
|
|
315
356
|
? `Awaiting user OAuth authorization for ${current.providerName}. Open ${current.redirectUrl} in a browser, then re-run wait_for_connection.`
|
|
316
357
|
: `Connection for ${current.providerName} is currently ${current.status}.`,
|
|
@@ -332,21 +373,29 @@ export class MuxoEngine {
|
|
|
332
373
|
.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
333
374
|
const accountId = params.connectedAccountId || existing?.externalAccountId || null;
|
|
334
375
|
let resolvedStatus = "pending";
|
|
376
|
+
const composioByok = this.store.getByokKey(ws.id, "composio");
|
|
335
377
|
if (accountId) {
|
|
336
|
-
const live = await this.composioSdk.getConnectedAccountStatus(accountId);
|
|
378
|
+
const live = await this.composioSdk.getConnectedAccountStatus(accountId, composioByok);
|
|
337
379
|
if (live) {
|
|
338
380
|
resolvedStatus = live.status;
|
|
339
381
|
}
|
|
340
382
|
}
|
|
341
383
|
if (resolvedStatus !== "active") {
|
|
342
|
-
const liveList = await this.composioSdk.listLiveConnections(ws.id);
|
|
384
|
+
const liveList = await this.composioSdk.listLiveConnections(ws.id, composioByok);
|
|
343
385
|
const match = liveList.find((c) => c.providerSlug === cleanSlug);
|
|
344
386
|
if (match) {
|
|
345
387
|
resolvedStatus = match.status;
|
|
346
388
|
}
|
|
347
389
|
}
|
|
348
390
|
const rawCallbackStatus = String(params.status || "").toLowerCase();
|
|
349
|
-
if (rawCallbackStatus === "
|
|
391
|
+
if (rawCallbackStatus === "failed" ||
|
|
392
|
+
rawCallbackStatus === "error" ||
|
|
393
|
+
rawCallbackStatus === "expired" ||
|
|
394
|
+
rawCallbackStatus === "cancelled" ||
|
|
395
|
+
rawCallbackStatus === "denied") {
|
|
396
|
+
resolvedStatus = "expired";
|
|
397
|
+
}
|
|
398
|
+
else if (rawCallbackStatus === "success" ||
|
|
350
399
|
rawCallbackStatus === "active" ||
|
|
351
400
|
rawCallbackStatus === "connected") {
|
|
352
401
|
resolvedStatus = "active";
|
|
@@ -379,6 +428,9 @@ export class MuxoEngine {
|
|
|
379
428
|
tool = await this.inspectTool(params.toolSlug);
|
|
380
429
|
}
|
|
381
430
|
catch (inspectErr) {
|
|
431
|
+
if (looksLikeToolSlug(params.toolSlug)) {
|
|
432
|
+
throw inspectErr;
|
|
433
|
+
}
|
|
382
434
|
// Natural-language fallback: if an agent or human passed plain English as toolSlug/instruction, resolve it!
|
|
383
435
|
const nlSource = `${params.toolSlug} ${params.reason || ""}`.trim();
|
|
384
436
|
const allTools = await this.listAllCatalogTools();
|
|
@@ -413,7 +465,20 @@ export class MuxoEngine {
|
|
|
413
465
|
if (effectiveCreditCost > ws.policy.maxCreditsPerRun) {
|
|
414
466
|
throw new Error(`Tool credit cost (${effectiveCreditCost}) exceeds workspace policy maxCreditsPerRun (${ws.policy.maxCreditsPerRun}).`);
|
|
415
467
|
}
|
|
416
|
-
// 1.
|
|
468
|
+
// 1. Recall Supermemory v5 contextual brain first so Active Memory defaults can auto-fill tool parameters
|
|
469
|
+
const memoryQuery = `${tool.slug} ${tool.name} ${resolvedReason}`.trim();
|
|
470
|
+
const supermemoryByok = this.store.getByokKey(ws.id, "supermemory");
|
|
471
|
+
const memoryContext = await this.enrichMemoryContext(ws.id, await this.supermemory.getContextForWorkspace(ws.id, memoryQuery, supermemoryByok), memoryQuery);
|
|
472
|
+
const activeMemoryResult = applyActiveMemoryToToolInput({
|
|
473
|
+
toolSlug: tool.slug,
|
|
474
|
+
input: resolvedInput,
|
|
475
|
+
activeDefaults: memoryContext.activeDefaults,
|
|
476
|
+
});
|
|
477
|
+
resolvedInput = activeMemoryResult.mergedInput;
|
|
478
|
+
if (Object.keys(activeMemoryResult.autoFilledInput).length > 0) {
|
|
479
|
+
memoryContext.autoFilledInput = activeMemoryResult.autoFilledInput;
|
|
480
|
+
}
|
|
481
|
+
// 2. Evaluate Microsoft Agent Governance Pipeline:
|
|
417
482
|
// Agent -> McpSecurityScanner -> PolicyEngine -> AgentIdentity (Ed25519 DID) -> AuditLogger -> DecisionRecord
|
|
418
483
|
const decisionRecord = this.governance.evaluatePlan({
|
|
419
484
|
workspace: ws,
|
|
@@ -425,13 +490,7 @@ export class MuxoEngine {
|
|
|
425
490
|
if (decisionRecord.verdict === "deny") {
|
|
426
491
|
throw new Error(`Microsoft Agent Governance denied plan for ${tool.slug} (rule: ${decisionRecord.matchedRule}): ${decisionRecord.reason}`);
|
|
427
492
|
}
|
|
428
|
-
|
|
429
|
-
const memoryQuery = `${tool.slug} ${tool.name} ${resolvedReason}`.trim();
|
|
430
|
-
const supermemoryByok = this.store.getByokKey(ws.id, "supermemory");
|
|
431
|
-
const memoryContext = await this.enrichMemoryContext(ws.id, await this.supermemory.getContextForWorkspace(ws.id, memoryQuery, supermemoryByok), memoryQuery);
|
|
432
|
-
const requiresHumanApproval = decisionRecord.verdict === "escalate" ||
|
|
433
|
-
(ws.policy.mode === "enforce" &&
|
|
434
|
-
ws.policy.requireApprovalFor.includes(tool.riskLevel));
|
|
493
|
+
const requiresHumanApproval = decisionRecord.verdict === "escalate";
|
|
435
494
|
const approvalStatus = requiresHumanApproval
|
|
436
495
|
? "pending_approval"
|
|
437
496
|
: "auto_approved";
|
|
@@ -488,12 +547,20 @@ export class MuxoEngine {
|
|
|
488
547
|
if (authHandoff?.required) {
|
|
489
548
|
nextStep = `${authHandoff.instructions} Once connected, ${nextStep}`;
|
|
490
549
|
}
|
|
550
|
+
const humanHandoff = authHandoff?.required
|
|
551
|
+
? this.buildOAuthHandoff(authHandoff, tool.providerName)
|
|
552
|
+
: approvalStatus === "pending_approval"
|
|
553
|
+
? this.buildApprovalHandoff(plan)
|
|
554
|
+
: undefined;
|
|
555
|
+
const llmGuidance = this.buildPlanLlmGuidance(plan, authHandoff);
|
|
491
556
|
return {
|
|
492
557
|
plan,
|
|
493
558
|
tool,
|
|
494
559
|
decisionRecord,
|
|
495
560
|
memoryContext,
|
|
496
561
|
...(authHandoff ? { authHandoff } : {}),
|
|
562
|
+
...(humanHandoff ? { humanHandoff } : {}),
|
|
563
|
+
llmGuidance,
|
|
497
564
|
nextStep,
|
|
498
565
|
};
|
|
499
566
|
}
|
|
@@ -504,6 +571,27 @@ export class MuxoEngine {
|
|
|
504
571
|
throw new Error(`Plan '${planId}' not found in workspace.`);
|
|
505
572
|
return plan;
|
|
506
573
|
}
|
|
574
|
+
async waitForPlanApproval(ctx, params) {
|
|
575
|
+
const startedAt = Date.now();
|
|
576
|
+
const deadline = startedAt + clampWaitSeconds(params.timeoutSeconds, 15, 60) * 1000;
|
|
577
|
+
const pollIntervalMs = Math.min(2000, Math.max(100, Number(params.pollIntervalMs ?? 250)));
|
|
578
|
+
let plan = this.getPlan(ctx, params.planId);
|
|
579
|
+
while (plan.approvalStatus === "pending_approval" && Date.now() < deadline) {
|
|
580
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
581
|
+
plan = this.getPlan(ctx, params.planId);
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
ok: true,
|
|
585
|
+
approved: APPROVED_PLAN_STATUSES.has(plan.approvalStatus),
|
|
586
|
+
rejected: plan.approvalStatus === "rejected",
|
|
587
|
+
timedOut: plan.approvalStatus === "pending_approval",
|
|
588
|
+
waitedForApproval: true,
|
|
589
|
+
approvalStatus: plan.approvalStatus,
|
|
590
|
+
approvalUrl: buildApprovalUrl(this.config.baseUrl, plan.id),
|
|
591
|
+
waitedMs: Date.now() - startedAt,
|
|
592
|
+
plan,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
507
595
|
// Human owner approval / rejection
|
|
508
596
|
approvePlan(ctx, planId, decision = "approved") {
|
|
509
597
|
const plan = this.getPlan(ctx, planId);
|
|
@@ -517,6 +605,76 @@ export class MuxoEngine {
|
|
|
517
605
|
approvedBy: ctx.user.email,
|
|
518
606
|
});
|
|
519
607
|
}
|
|
608
|
+
buildApprovalHandoff(plan) {
|
|
609
|
+
const url = `${this.config.baseUrl}/approve/${encodeURIComponent(plan.id)}`;
|
|
610
|
+
return {
|
|
611
|
+
kind: "approval",
|
|
612
|
+
url,
|
|
613
|
+
ctaLabel: "Review & approve",
|
|
614
|
+
headline: `Muxo needs your approval to run ${plan.toolSlug}`,
|
|
615
|
+
instructions: `Open ${url} to review and approve this ${plan.riskLevel}-risk action with one click (or run \`muxo approve ${plan.id}\`). Nothing runs until you approve.`,
|
|
616
|
+
planId: plan.id,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
buildOAuthHandoff(handoff, providerName) {
|
|
620
|
+
return {
|
|
621
|
+
kind: "oauth",
|
|
622
|
+
url: handoff.redirectUrl,
|
|
623
|
+
ctaLabel: `Connect ${providerName}`,
|
|
624
|
+
headline: `Connect ${providerName} to continue`,
|
|
625
|
+
instructions: handoff.instructions,
|
|
626
|
+
provider: handoff.provider,
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
buildPlanLlmGuidance(plan, authHandoff) {
|
|
630
|
+
const executeTool = `execute_${plan.riskLevel}`;
|
|
631
|
+
const nextToolCalls = [];
|
|
632
|
+
if (authHandoff?.required) {
|
|
633
|
+
nextToolCalls.push(authHandoff.redirectUrl
|
|
634
|
+
? {
|
|
635
|
+
tool: "wait_for_connection",
|
|
636
|
+
input: { provider: authHandoff.provider },
|
|
637
|
+
when: `After the user confirms they completed OAuth at ${authHandoff.redirectUrl}`,
|
|
638
|
+
}
|
|
639
|
+
: {
|
|
640
|
+
tool: "start_connection",
|
|
641
|
+
input: { provider: authHandoff.provider },
|
|
642
|
+
when: "Immediately, to obtain an OAuth redirect URL for the user",
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
if (plan.approvalStatus === "pending_approval") {
|
|
646
|
+
nextToolCalls.push({
|
|
647
|
+
tool: "get_plan",
|
|
648
|
+
input: { plan_id: plan.id, wait_for_approval: true, timeout_seconds: 25 },
|
|
649
|
+
when: `After sharing ${this.config.baseUrl}/approve/${plan.id} with the user, to wait in-turn until they click Approve`,
|
|
650
|
+
});
|
|
651
|
+
nextToolCalls.push({
|
|
652
|
+
tool: executeTool,
|
|
653
|
+
input: { plan_id: plan.id },
|
|
654
|
+
when: `Only after the user explicitly approves plan ${plan.id} (via ${this.config.baseUrl}/approve/${plan.id} or \`muxo approve ${plan.id}\`)`,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
nextToolCalls.push({
|
|
659
|
+
tool: executeTool,
|
|
660
|
+
input: { plan_id: plan.id },
|
|
661
|
+
when: authHandoff?.required
|
|
662
|
+
? "After the connection becomes active"
|
|
663
|
+
: "Immediately; the plan is auto-approved",
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
nextToolCalls.push({
|
|
667
|
+
tool: "get_receipt",
|
|
668
|
+
input: { run_id: "<run_id from the execute response>" },
|
|
669
|
+
when: "After execution, to verify the tamper-proof receipt",
|
|
670
|
+
});
|
|
671
|
+
const summary = authHandoff?.required
|
|
672
|
+
? `Hand the OAuth link to the user, wait for the connection, then ${plan.approvalStatus === "pending_approval" ? "collect approval and " : ""}call ${executeTool}.`
|
|
673
|
+
: plan.approvalStatus === "pending_approval"
|
|
674
|
+
? `Stop and ask the user to approve plan ${plan.id}; do not call ${executeTool} until they confirm.`
|
|
675
|
+
: `Call ${executeTool} with plan_id="${plan.id}" to execute now.`;
|
|
676
|
+
return { summary, nextToolCalls };
|
|
677
|
+
}
|
|
520
678
|
async enrichMemoryContext(workspaceId, ctx, query) {
|
|
521
679
|
const localRecords = this.store.listMemoryRecords(workspaceId, 8);
|
|
522
680
|
const temporalFacts = this.store.listTemporalFacts(workspaceId, {
|
|
@@ -581,7 +739,33 @@ export class MuxoEngine {
|
|
|
581
739
|
.slice(0, 8)
|
|
582
740
|
.map((f) => `- **${f.subject}** \`${f.predicate}\` **${f.object}** _(validFrom: ${f.validFrom})_ — ${f.summary}`));
|
|
583
741
|
}
|
|
584
|
-
|
|
742
|
+
const allLocalMemories = this.store.listMemoryRecords(workspaceId, 25);
|
|
743
|
+
const allActiveFacts = this.store.listTemporalFacts(workspaceId, {
|
|
744
|
+
includeHistorical: false,
|
|
745
|
+
limit: 30,
|
|
746
|
+
});
|
|
747
|
+
const inferred = extractActiveMemoryDefaults({
|
|
748
|
+
memories: allLocalMemories,
|
|
749
|
+
temporalFacts: allActiveFacts,
|
|
750
|
+
preferences: ctx.profile?.preferences || [],
|
|
751
|
+
staticFacts,
|
|
752
|
+
});
|
|
753
|
+
const mergedActiveDefaults = {
|
|
754
|
+
...(ctx.activeDefaults || {}),
|
|
755
|
+
...inferred.activeDefaults,
|
|
756
|
+
};
|
|
757
|
+
const ws = this.store.getWorkspace(workspaceId);
|
|
758
|
+
const mergedGuardrails = Array.from(new Set([
|
|
759
|
+
...(ws?.policy?.naturalLanguageRules || []),
|
|
760
|
+
...(ctx.activeGuardrails || []),
|
|
761
|
+
...inferred.activeGuardrails,
|
|
762
|
+
]));
|
|
763
|
+
if (Object.keys(mergedActiveDefaults).length > 0) {
|
|
764
|
+
promptLines.push(`Active Memory Auto-Fill Defaults: ${Object.entries(mergedActiveDefaults)
|
|
765
|
+
.map(([k, v]) => `${k}=${String(v)}`)
|
|
766
|
+
.join(", ")}`);
|
|
767
|
+
}
|
|
768
|
+
const enriched = {
|
|
585
769
|
...ctx,
|
|
586
770
|
staticFacts,
|
|
587
771
|
dynamicContext,
|
|
@@ -595,17 +779,27 @@ export class MuxoEngine {
|
|
|
595
779
|
dynamic: dynamicContext,
|
|
596
780
|
},
|
|
597
781
|
temporalFacts,
|
|
782
|
+
activeDefaults: mergedActiveDefaults,
|
|
783
|
+
activeGuardrails: mergedGuardrails,
|
|
598
784
|
contextPoisoningScan: {
|
|
599
785
|
safe: poisonScan.safe,
|
|
600
786
|
riskLevel: poisonScan.riskLevel,
|
|
601
787
|
findingsCount: poisonScan.findingsCount,
|
|
602
788
|
},
|
|
603
789
|
};
|
|
790
|
+
enriched.explanation = explainMemoryContext({
|
|
791
|
+
memory: enriched,
|
|
792
|
+
localCount: allLocalMemories.length,
|
|
793
|
+
activeTemporalCount: allActiveFacts.length,
|
|
794
|
+
policy: ws?.policy,
|
|
795
|
+
});
|
|
796
|
+
return enriched;
|
|
604
797
|
}
|
|
605
798
|
// 9. get_run_context
|
|
606
799
|
async getRunContext(ctx, query) {
|
|
607
800
|
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
608
801
|
const resources = this.store.listResources(ws.id);
|
|
802
|
+
const activeResources = resources.filter((r) => r.status === "active");
|
|
609
803
|
const activeEnv = this.store.getDecryptedWorkspaceEnv(ws.id);
|
|
610
804
|
const redactedEnv = {};
|
|
611
805
|
for (const [k, v] of Object.entries(activeEnv)) {
|
|
@@ -614,24 +808,67 @@ export class MuxoEngine {
|
|
|
614
808
|
}
|
|
615
809
|
const supermemoryByok = this.store.getByokKey(ws.id, "supermemory");
|
|
616
810
|
const memoryContext = await this.enrichMemoryContext(ws.id, await this.supermemory.getContextForWorkspace(ws.id, query, supermemoryByok), query);
|
|
811
|
+
const plainEnglishPolicy = describePolicyInPlainEnglish(ws.policy);
|
|
812
|
+
const governanceStatus = this.governance.getStatus(ws);
|
|
813
|
+
const trustScore = this.governance.getWorkspaceTrustScore(ws.id);
|
|
814
|
+
const actionsRemaining = Math.max(0, ws.monthlyActionsLimit - ws.monthlyActionsUsed);
|
|
815
|
+
const defaultsList = Object.entries(memoryContext.activeDefaults || {}).map(([k, v]) => `${k}=${String(v)}`);
|
|
816
|
+
const contextHeadline = `Workspace ${ws.name} (${ws.slug}): ${ws.creditsBalance} credits ($${(ws.creditsBalance * 0.01).toFixed(2)}) available · ${plainEnglishPolicy.headline}`;
|
|
817
|
+
const contextNonTechnical = [
|
|
818
|
+
contextHeadline,
|
|
819
|
+
defaultsList.length > 0
|
|
820
|
+
? `Learned defaults ready for auto-fill: ${defaultsList.join(", ")}.`
|
|
821
|
+
: "No learned defaults set yet.",
|
|
822
|
+
activeResources.length > 0
|
|
823
|
+
? `Active cloud resources (${activeResources.length}): ${activeResources.map((r) => `${r.resourceName} (${r.providerName})`).join(", ")}.`
|
|
824
|
+
: "No cloud infrastructure resources provisioned yet.",
|
|
825
|
+
memoryContext.explanation?.nonTechnical || "",
|
|
826
|
+
]
|
|
827
|
+
.filter(Boolean)
|
|
828
|
+
.join(" ");
|
|
829
|
+
const contextTechnical = `workspace=${ws.id} (${ws.slug}, tier=${ws.tier}) | credits=${ws.creditsBalance} | actionsRemaining=${actionsRemaining}/${ws.monthlyActionsLimit} | ${plainEnglishPolicy.technical} | supermemoryNs=${memoryContext.namespace} | temporalFacts=${(memoryContext.temporalFacts || []).length} | activeDefaults=${JSON.stringify(memoryContext.activeDefaults || {})} | activeResources=${activeResources.length} | redactedEnvKeys=[${Object.keys(redactedEnv).join(", ")}] | agentDid=${governanceStatus.agentDid} | trustScore=${trustScore.overall} (${trustScore.tier})`;
|
|
830
|
+
const explanation = formatHumanFriendlyExplanation({
|
|
831
|
+
headline: contextHeadline,
|
|
832
|
+
nonTechnical: contextNonTechnical,
|
|
833
|
+
technical: contextTechnical,
|
|
834
|
+
policy: ws.policy,
|
|
835
|
+
});
|
|
836
|
+
const llmSystemPrompt = [
|
|
837
|
+
`# Muxo Governed Workspace Context (${ws.name} / ${ws.slug})`,
|
|
838
|
+
`- **Language Preference**: ${ws.policy.languageMode || "hybrid"}${ws.policy.customInstructions?.length ? ` (Custom Instructions: ${ws.policy.customInstructions.join("; ")})` : ""}`,
|
|
839
|
+
`- **Safety Guardrails**: ${plainEnglishPolicy.preferred}`,
|
|
840
|
+
`- **Learned Memory Defaults (Auto-Fill)**: ${defaultsList.length > 0 ? defaultsList.join(", ") : "none"}`,
|
|
841
|
+
`- **Credits & Quota**: ${ws.creditsBalance} credits ($${(ws.creditsBalance * 0.01).toFixed(2)}) available, ${actionsRemaining} monthly actions remaining`,
|
|
842
|
+
`- **Active Cloud Resources**: ${activeResources.length > 0 ? activeResources.map((r) => `${r.resourceName} (${r.serviceSlug})`).join(", ") : "none"}`,
|
|
843
|
+
memoryContext.formattedPromptContext,
|
|
844
|
+
].join("\n");
|
|
617
845
|
return {
|
|
618
846
|
workspaceId: ws.id,
|
|
619
847
|
workspaceSlug: ws.slug,
|
|
620
848
|
projectsEnvironment: ws.projectsEnvName,
|
|
621
849
|
policy: ws.policy,
|
|
850
|
+
plainEnglishPolicy,
|
|
851
|
+
languageMode: ws.policy.languageMode || "hybrid",
|
|
852
|
+
customInstructions: ws.policy.customInstructions || [],
|
|
853
|
+
activeDefaults: (memoryContext.activeDefaults ||
|
|
854
|
+
{}),
|
|
855
|
+
activeGuardrails: memoryContext.activeGuardrails || [],
|
|
622
856
|
creditsBalance: ws.creditsBalance,
|
|
623
|
-
monthlyActionsRemaining:
|
|
624
|
-
provisionedResources:
|
|
857
|
+
monthlyActionsRemaining: actionsRemaining,
|
|
858
|
+
provisionedResources: activeResources,
|
|
625
859
|
environmentVariablesRedacted: redactedEnv,
|
|
626
860
|
supermemoryBrain: memoryContext,
|
|
627
861
|
temporalFacts: memoryContext.temporalFacts || [],
|
|
628
|
-
governance:
|
|
629
|
-
trustScore
|
|
862
|
+
governance: governanceStatus,
|
|
863
|
+
trustScore,
|
|
864
|
+
llmSystemPrompt,
|
|
865
|
+
explanation,
|
|
630
866
|
envPullHint: "Run `muxo env pull` or call GET /api/v1/resources/env to write decrypted credentials to your local .env file.",
|
|
631
867
|
};
|
|
632
868
|
}
|
|
633
869
|
// Supermemory Workspace Brain Operations
|
|
634
870
|
async getMemoryOverview(ctx, query) {
|
|
871
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
635
872
|
const supermemoryByok = this.store.getByokKey(ctx.workspace.id, "supermemory");
|
|
636
873
|
const context = await this.enrichMemoryContext(ctx.workspace.id, await this.supermemory.getContextForWorkspace(ctx.workspace.id, query, supermemoryByok), query);
|
|
637
874
|
const recentMemories = this.store.listMemoryRecords(ctx.workspace.id, 25);
|
|
@@ -640,15 +877,27 @@ export class MuxoEngine {
|
|
|
640
877
|
query,
|
|
641
878
|
limit: 50,
|
|
642
879
|
});
|
|
880
|
+
const activeFacts = allFacts.filter((f) => f.validTo === null);
|
|
881
|
+
const historicalFacts = allFacts.filter((f) => f.validTo !== null);
|
|
882
|
+
const explanation = explainMemoryContext({
|
|
883
|
+
memory: context,
|
|
884
|
+
localCount: recentMemories.length,
|
|
885
|
+
activeTemporalCount: activeFacts.length,
|
|
886
|
+
historicalTemporalCount: historicalFacts.length,
|
|
887
|
+
policy: ws.policy,
|
|
888
|
+
});
|
|
643
889
|
return {
|
|
644
890
|
enabled: context.enabled,
|
|
645
891
|
namespace: context.namespace,
|
|
646
892
|
fundingSource: context.fundingSource || "local_fallback",
|
|
647
893
|
context,
|
|
894
|
+
activeDefaults: (context.activeDefaults || {}),
|
|
895
|
+
activeGuardrails: context.activeGuardrails || [],
|
|
896
|
+
explanation,
|
|
648
897
|
recentMemories,
|
|
649
898
|
memories: recentMemories,
|
|
650
|
-
temporalFacts:
|
|
651
|
-
historicalFacts
|
|
899
|
+
temporalFacts: activeFacts,
|
|
900
|
+
historicalFacts,
|
|
652
901
|
};
|
|
653
902
|
}
|
|
654
903
|
async addWorkspaceMemory(ctx, params) {
|
|
@@ -755,6 +1004,29 @@ export class MuxoEngine {
|
|
|
755
1004
|
invalidatedFacts: localPruned.invalidatedFacts,
|
|
756
1005
|
};
|
|
757
1006
|
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Synthesizes a Bi-Temporal Knowledge Graph (entities, active relationships, superseded historical relationships,
|
|
1009
|
+
* Mermaid diagram, and human + technical explanations) directly from SQLite temporal_facts, memories, resources, and connections.
|
|
1010
|
+
*/
|
|
1011
|
+
async getMemoryGraph(ctx, query) {
|
|
1012
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1013
|
+
const overview = await this.getMemoryOverview(ctx, query);
|
|
1014
|
+
return buildMemoryKnowledgeGraph({
|
|
1015
|
+
workspace: ws,
|
|
1016
|
+
namespace: overview.namespace,
|
|
1017
|
+
query,
|
|
1018
|
+
facts: this.store.listTemporalFacts(ws.id, {
|
|
1019
|
+
includeHistorical: true,
|
|
1020
|
+
query,
|
|
1021
|
+
limit: 100,
|
|
1022
|
+
}),
|
|
1023
|
+
resources: this.store.listResources(ws.id),
|
|
1024
|
+
activeConnections: this.store
|
|
1025
|
+
.listConnections(ws.id)
|
|
1026
|
+
.filter((c) => c.status === "active"),
|
|
1027
|
+
memoriesCount: overview.memories.length,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
758
1030
|
async activateWorkspaceKillSwitch(ctx, params) {
|
|
759
1031
|
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
760
1032
|
const result = await this.governance.activateKillSwitch(ws.id, params.reason, params.action || "quarantine");
|
|
@@ -800,7 +1072,7 @@ export class MuxoEngine {
|
|
|
800
1072
|
throw new Error(`Plan '${plan.id}' was already claimed by run '${plan.runId}' (execute-once invariant).`);
|
|
801
1073
|
}
|
|
802
1074
|
if (plan.approvalStatus === "pending_approval") {
|
|
803
|
-
throw
|
|
1075
|
+
throw approvalRequiredError(this.config.baseUrl, plan);
|
|
804
1076
|
}
|
|
805
1077
|
if (plan.approvalStatus === "rejected") {
|
|
806
1078
|
throw new Error(`Plan '${plan.id}' was rejected and cannot be executed.`);
|
|
@@ -901,10 +1173,31 @@ export class MuxoEngine {
|
|
|
901
1173
|
const stripeByok = byokActive
|
|
902
1174
|
? this.store.getByokKey(ctx.workspace.id, "stripe")
|
|
903
1175
|
: null;
|
|
1176
|
+
const enrichedInput = { ...plan.input };
|
|
1177
|
+
if (!enrichedInput.resource_id &&
|
|
1178
|
+
(plan.toolSlug === "resource.projects.rotate_credentials" ||
|
|
1179
|
+
plan.toolSlug === "resource.projects.deprovision" ||
|
|
1180
|
+
plan.toolSlug === "resource.projects.reveal_access_configuration")) {
|
|
1181
|
+
const rName = String(enrichedInput.resource_name ||
|
|
1182
|
+
enrichedInput.database_name ||
|
|
1183
|
+
enrichedInput.name ||
|
|
1184
|
+
"")
|
|
1185
|
+
.trim()
|
|
1186
|
+
.toLowerCase();
|
|
1187
|
+
if (rName) {
|
|
1188
|
+
const matchedRes = this.store
|
|
1189
|
+
.listResources(ctx.workspace.id)
|
|
1190
|
+
.find((r) => r.resourceName.toLowerCase() === rName &&
|
|
1191
|
+
r.externalResourceId);
|
|
1192
|
+
if (matchedRes?.externalResourceId) {
|
|
1193
|
+
enrichedInput.resource_id = matchedRes.externalResourceId;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
904
1197
|
const projResult = await this.stripeProjects.executeTool({
|
|
905
1198
|
workspaceId: ctx.workspace.id,
|
|
906
1199
|
tool,
|
|
907
|
-
input:
|
|
1200
|
+
input: enrichedInput,
|
|
908
1201
|
stripeApiKey: stripeByok,
|
|
909
1202
|
});
|
|
910
1203
|
output = projResult.output;
|
|
@@ -930,6 +1223,17 @@ export class MuxoEngine {
|
|
|
930
1223
|
durationMs,
|
|
931
1224
|
decisionRecord: execDecisionRecord,
|
|
932
1225
|
});
|
|
1226
|
+
finalized.receipt.agenticReceipt =
|
|
1227
|
+
this.governance.buildPortableAgenticReceipt({
|
|
1228
|
+
receipt: finalized.receipt,
|
|
1229
|
+
run: finalized.run,
|
|
1230
|
+
workspace: ws,
|
|
1231
|
+
});
|
|
1232
|
+
finalized.receipt.explanation = this.explainReceipt(finalized.receipt, {
|
|
1233
|
+
signatureValid: this.store.verifyReceipt(finalized.receipt),
|
|
1234
|
+
governanceSignatureValid: true,
|
|
1235
|
+
agenticReceiptValid: true,
|
|
1236
|
+
}, ws.policy);
|
|
933
1237
|
this.store.upsertTemporalFact({
|
|
934
1238
|
workspaceId: ctx.workspace.id,
|
|
935
1239
|
subject: `tool:${plan.toolSlug}`,
|
|
@@ -987,7 +1291,7 @@ export class MuxoEngine {
|
|
|
987
1291
|
const durationMs = Date.now() - startedMs;
|
|
988
1292
|
this.governance.recordAgentOutcome(ctx.workspace.id, false, 0.08);
|
|
989
1293
|
const errorMessage = String(err?.message || err || "Execution failed");
|
|
990
|
-
|
|
1294
|
+
const failedFinalized = this.store.finalizeRun({
|
|
991
1295
|
workspaceId: ctx.workspace.id,
|
|
992
1296
|
runId: run.id,
|
|
993
1297
|
status: "failed",
|
|
@@ -998,6 +1302,18 @@ export class MuxoEngine {
|
|
|
998
1302
|
durationMs,
|
|
999
1303
|
decisionRecord: execDecisionRecord,
|
|
1000
1304
|
});
|
|
1305
|
+
failedFinalized.receipt.agenticReceipt =
|
|
1306
|
+
this.governance.buildPortableAgenticReceipt({
|
|
1307
|
+
receipt: failedFinalized.receipt,
|
|
1308
|
+
run: failedFinalized.run,
|
|
1309
|
+
workspace: ws,
|
|
1310
|
+
});
|
|
1311
|
+
failedFinalized.receipt.explanation = this.explainReceipt(failedFinalized.receipt, {
|
|
1312
|
+
signatureValid: this.store.verifyReceipt(failedFinalized.receipt),
|
|
1313
|
+
governanceSignatureValid: true,
|
|
1314
|
+
agenticReceiptValid: true,
|
|
1315
|
+
}, ws.policy);
|
|
1316
|
+
return failedFinalized;
|
|
1001
1317
|
}
|
|
1002
1318
|
}
|
|
1003
1319
|
// 16. get_run
|
|
@@ -1009,39 +1325,691 @@ export class MuxoEngine {
|
|
|
1009
1325
|
}
|
|
1010
1326
|
// 17. cancel_run
|
|
1011
1327
|
cancelRun(ctx, runId, reason) {
|
|
1012
|
-
const
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1328
|
+
const cleanId = String(runId || "").trim();
|
|
1329
|
+
if (!cleanId)
|
|
1330
|
+
throw new Error("run_id is required to cancel a run.");
|
|
1331
|
+
const run = this.getRun(ctx, cleanId);
|
|
1332
|
+
if (run.status === "cancelled") {
|
|
1333
|
+
const existing = this.store.getReceipt(ctx.workspace.id, run.id);
|
|
1334
|
+
if (existing) {
|
|
1335
|
+
return {
|
|
1336
|
+
ok: true,
|
|
1337
|
+
cancelled: true,
|
|
1338
|
+
alreadyCancelled: true,
|
|
1339
|
+
previousStatus: "cancelled",
|
|
1340
|
+
run,
|
|
1341
|
+
receipt: existing,
|
|
1342
|
+
signatureValid: this.store.verifyReceipt(existing),
|
|
1343
|
+
message: `Run '${run.id}' was already cancelled.`,
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1018
1346
|
}
|
|
1019
|
-
const
|
|
1347
|
+
const previousStatus = run.status;
|
|
1348
|
+
const cancelled = this.store.cancelRun({
|
|
1020
1349
|
workspaceId: ctx.workspace.id,
|
|
1021
1350
|
runId: run.id,
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1351
|
+
cancelledBy: ctx.actorId,
|
|
1352
|
+
reason: reason?.trim() || undefined,
|
|
1353
|
+
});
|
|
1354
|
+
const completedEarlier = previousStatus === "succeeded" || previousStatus === "failed";
|
|
1355
|
+
return {
|
|
1356
|
+
ok: true,
|
|
1357
|
+
cancelled: true,
|
|
1358
|
+
alreadyCancelled: false,
|
|
1359
|
+
previousStatus,
|
|
1360
|
+
run: cancelled.run,
|
|
1361
|
+
receipt: cancelled.receipt,
|
|
1362
|
+
signatureValid: this.store.verifyReceipt(cancelled.receipt),
|
|
1363
|
+
message: completedEarlier
|
|
1364
|
+
? `Run '${run.id}' had already ${previousStatus}; it is now marked cancelled with a signed cancellation receipt (${cancelled.receipt.id}). Upstream side effects are not reversed.`
|
|
1365
|
+
: `Run '${run.id}' cancelled with signed cancellation receipt ${cancelled.receipt.id}.`,
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
explainReceipt(receipt, verification, policy, languageMode) {
|
|
1369
|
+
const dollars = (receipt.creditsMetered * 0.01).toFixed(2);
|
|
1370
|
+
const fundingMode = receipt.supplierSettlement?.fundingMode || "muxo_funded";
|
|
1371
|
+
const fundingPlain = fundingMode === "byok"
|
|
1372
|
+
? "0 credits charged (settled directly using your BYOK key)"
|
|
1373
|
+
: `${receipt.creditsMetered} credit${receipt.creditsMetered === 1 ? "" : "s"} ($${dollars} USD, settled via Muxo master wallet)`;
|
|
1374
|
+
const agenticOk = verification.agenticReceiptValid ?? true;
|
|
1375
|
+
const verified = verification.signatureValid &&
|
|
1376
|
+
verification.governanceSignatureValid &&
|
|
1377
|
+
agenticOk;
|
|
1378
|
+
const headline = receipt.status === "succeeded"
|
|
1379
|
+
? `Verified Receipt (${receipt.id}): ${receipt.toolSlug} succeeded and passed all safety & integrity checks.`
|
|
1380
|
+
: receipt.status === "failed"
|
|
1381
|
+
? `Verified Receipt (${receipt.id}): ${receipt.toolSlug} failed (0 credits charged).`
|
|
1382
|
+
: `Verified Receipt (${receipt.id}): ${receipt.toolSlug} was cancelled.`;
|
|
1383
|
+
const nonTechnical = [
|
|
1384
|
+
headline,
|
|
1385
|
+
`Action: ${receipt.toolSlug} (${receipt.riskLevel} risk lane) at ${receipt.issuedAt}.`,
|
|
1386
|
+
`Cost & billing: ${fundingPlain}.`,
|
|
1387
|
+
verified
|
|
1388
|
+
? "Tamper-proof check: Verified (HMAC vault signature, portable Ed25519 AgenticReceipt envelope, and governance audit trail all match)."
|
|
1389
|
+
: "Tamper-proof check: Warning (signature verification mismatch detected).",
|
|
1390
|
+
].join(" ");
|
|
1391
|
+
const technical = `receiptId=${receipt.id} | runId=${receipt.runId} | planId=${receipt.planId} | toolSlug=${receipt.toolSlug} | lane=${receipt.lane} | riskLevel=${receipt.riskLevel} | status=${receipt.status} | creditsMetered=${receipt.creditsMetered} ($${dollars}) | fundingMode=${fundingMode} | inputSha256=${receipt.inputHash.slice(0, 16)}... | outputSha256=${receipt.outputHash.slice(0, 16)}... | hashChain=(${receipt.prevReceiptHash.slice(0, 16)}... -> ${receipt.receiptHash.slice(0, 16)}...) | hmacSignatureValid=${verification.signatureValid} | ed25519GovernanceValid=${verification.governanceSignatureValid} | ed25519AgenticReceiptValid=${agenticOk}`;
|
|
1392
|
+
return formatHumanFriendlyExplanation({
|
|
1393
|
+
headline,
|
|
1394
|
+
nonTechnical,
|
|
1395
|
+
technical,
|
|
1396
|
+
policy,
|
|
1397
|
+
languageMode,
|
|
1028
1398
|
});
|
|
1029
|
-
return finalized;
|
|
1030
1399
|
}
|
|
1031
1400
|
// 18. get_receipt
|
|
1032
1401
|
getReceipt(ctx, receiptOrRunId) {
|
|
1033
|
-
const
|
|
1402
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1403
|
+
const trimmedId = (receiptOrRunId || "").trim();
|
|
1404
|
+
const receipt = trimmedId === "latest" || trimmedId === "last" || !trimmedId
|
|
1405
|
+
? this.store.listReceipts(ctx.workspace.id, 1)[0] || null
|
|
1406
|
+
: this.store.getReceipt(ctx.workspace.id, trimmedId);
|
|
1034
1407
|
if (!receipt) {
|
|
1035
1408
|
throw new Error(`Receipt '${receiptOrRunId}' not found in workspace.`);
|
|
1036
1409
|
}
|
|
1410
|
+
const run = this.store.getRun(ws.id, receipt.runId);
|
|
1411
|
+
const agenticReceipt = this.governance.buildPortableAgenticReceipt({
|
|
1412
|
+
receipt,
|
|
1413
|
+
run,
|
|
1414
|
+
workspace: ws,
|
|
1415
|
+
});
|
|
1416
|
+
receipt.agenticReceipt = agenticReceipt;
|
|
1037
1417
|
const signatureValid = this.store.verifyReceipt(receipt);
|
|
1038
1418
|
const governanceSignatureValid = receipt.decisionRecord
|
|
1039
1419
|
? this.governance.verifyDecisionRecord(receipt.decisionRecord)
|
|
1040
1420
|
: true;
|
|
1041
|
-
|
|
1421
|
+
const agenticReceiptValid = this.governance.verifyPortableAgenticReceipt(agenticReceipt, ws);
|
|
1422
|
+
const explanation = this.explainReceipt(receipt, { signatureValid, governanceSignatureValid, agenticReceiptValid }, ws.policy);
|
|
1423
|
+
receipt.explanation = explanation;
|
|
1424
|
+
return {
|
|
1425
|
+
receipt,
|
|
1426
|
+
agenticReceipt,
|
|
1427
|
+
signatureValid,
|
|
1428
|
+
governanceSignatureValid,
|
|
1429
|
+
agenticReceiptValid,
|
|
1430
|
+
explanation,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
listReceipts(ctx, limit = 25) {
|
|
1434
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1435
|
+
const rows = this.store.listReceipts(ws.id, limit);
|
|
1436
|
+
const enriched = rows.map((r) => {
|
|
1437
|
+
const run = this.store.getRun(ws.id, r.runId);
|
|
1438
|
+
const agenticReceipt = this.governance.buildPortableAgenticReceipt({
|
|
1439
|
+
receipt: r,
|
|
1440
|
+
run,
|
|
1441
|
+
workspace: ws,
|
|
1442
|
+
});
|
|
1443
|
+
r.agenticReceipt = agenticReceipt;
|
|
1444
|
+
const signatureValid = this.store.verifyReceipt(r);
|
|
1445
|
+
const governanceSignatureValid = r.decisionRecord
|
|
1446
|
+
? this.governance.verifyDecisionRecord(r.decisionRecord)
|
|
1447
|
+
: true;
|
|
1448
|
+
const agenticReceiptValid = this.governance.verifyPortableAgenticReceipt(agenticReceipt, ws);
|
|
1449
|
+
const explanation = this.explainReceipt(r, { signatureValid, governanceSignatureValid, agenticReceiptValid }, ws.policy);
|
|
1450
|
+
r.explanation = explanation;
|
|
1451
|
+
return {
|
|
1452
|
+
...r,
|
|
1453
|
+
agenticReceipt,
|
|
1454
|
+
signatureValid,
|
|
1455
|
+
governanceSignatureValid,
|
|
1456
|
+
agenticReceiptValid,
|
|
1457
|
+
explanation,
|
|
1458
|
+
};
|
|
1459
|
+
});
|
|
1460
|
+
const allValid = enriched.every((r) => r.signatureValid &&
|
|
1461
|
+
r.governanceSignatureValid &&
|
|
1462
|
+
r.agenticReceiptValid);
|
|
1463
|
+
const totalCredits = enriched.reduce((sum, r) => sum + r.creditsMetered, 0);
|
|
1464
|
+
const summaryExplanation = formatHumanFriendlyExplanation({
|
|
1465
|
+
headline: enriched.length > 0
|
|
1466
|
+
? `${enriched.length} tamper-proof execution receipt${enriched.length === 1 ? "" : "s"} (${allValid ? "all verified" : "verification warning"}).`
|
|
1467
|
+
: "No execution receipts recorded yet.",
|
|
1468
|
+
nonTechnical: enriched.length > 0
|
|
1469
|
+
? `Found ${enriched.length} recent receipt${enriched.length === 1 ? "" : "s"} totaling ${totalCredits} credits ($${(totalCredits * 0.01).toFixed(2)} USD). Latest action: ${enriched[0].toolSlug} (${enriched[0].status}). ${allValid ? "All receipts passed HMAC and portable Ed25519 AgenticReceipt verification." : "One or more receipts failed signature verification."}`
|
|
1470
|
+
: "No tools have been executed in this workspace yet.",
|
|
1471
|
+
technical: `count=${enriched.length} | allSignaturesValid=${allValid} | totalCreditsMetered=${totalCredits} | latestReceiptId=${enriched[0]?.id || "none"} | latestHash=${enriched[0]?.receiptHash?.slice(0, 16) || "GENESIS"}`,
|
|
1472
|
+
policy: ws.policy,
|
|
1473
|
+
});
|
|
1474
|
+
return {
|
|
1475
|
+
receipts: enriched,
|
|
1476
|
+
total: enriched.length,
|
|
1477
|
+
explanation: summaryExplanation,
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Verifies the entire cryptographic receipt chain (`prevReceiptHash` -> `receiptHash`,
|
|
1482
|
+
* HMAC vault signatures, Ed25519 Governance DecisionRecords, and portable Ed25519 AgenticReceipts).
|
|
1483
|
+
*/
|
|
1484
|
+
verifyReceiptChain(ctx, limit = 100) {
|
|
1485
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1486
|
+
const govStatus = this.governance.getStatus(ws);
|
|
1487
|
+
const descList = this.listReceipts(ctx, limit);
|
|
1488
|
+
const chronological = [...descList.receipts].reverse();
|
|
1489
|
+
let hashLinksValid = true;
|
|
1490
|
+
let allHmacValid = true;
|
|
1491
|
+
let allEd25519AgenticValid = true;
|
|
1492
|
+
let allGovernanceValid = true;
|
|
1493
|
+
const links = chronological.map((r, idx) => {
|
|
1494
|
+
const expectedPrev = idx === 0
|
|
1495
|
+
? r.prevReceiptHash // First receipt in window (either genesis_0000000000000000 or older link)
|
|
1496
|
+
: chronological[idx - 1].receiptHash;
|
|
1497
|
+
const hashLinkValid = idx === 0
|
|
1498
|
+
? Boolean(r.prevReceiptHash)
|
|
1499
|
+
: r.prevReceiptHash === expectedPrev;
|
|
1500
|
+
if (!hashLinkValid)
|
|
1501
|
+
hashLinksValid = false;
|
|
1502
|
+
if (!r.signatureValid)
|
|
1503
|
+
allHmacValid = false;
|
|
1504
|
+
if (!r.agenticReceiptValid)
|
|
1505
|
+
allEd25519AgenticValid = false;
|
|
1506
|
+
if (!r.governanceSignatureValid)
|
|
1507
|
+
allGovernanceValid = false;
|
|
1508
|
+
return {
|
|
1509
|
+
index: idx,
|
|
1510
|
+
receiptId: r.id,
|
|
1511
|
+
runId: r.runId,
|
|
1512
|
+
toolSlug: r.toolSlug,
|
|
1513
|
+
prevReceiptHash: r.prevReceiptHash,
|
|
1514
|
+
receiptHash: r.receiptHash,
|
|
1515
|
+
hashLinkValid,
|
|
1516
|
+
hmacValid: r.signatureValid,
|
|
1517
|
+
ed25519AgenticValid: r.agenticReceiptValid,
|
|
1518
|
+
governanceValid: r.governanceSignatureValid,
|
|
1519
|
+
issuedAt: r.issuedAt,
|
|
1520
|
+
};
|
|
1521
|
+
});
|
|
1522
|
+
const chainValid = hashLinksValid &&
|
|
1523
|
+
allHmacValid &&
|
|
1524
|
+
allEd25519AgenticValid &&
|
|
1525
|
+
allGovernanceValid;
|
|
1526
|
+
const genesisHash = chronological[0]?.prevReceiptHash || "genesis_0000000000000000";
|
|
1527
|
+
const headReceiptHash = chronological[chronological.length - 1]?.receiptHash ||
|
|
1528
|
+
"genesis_0000000000000000";
|
|
1529
|
+
const headline = chronological.length === 0
|
|
1530
|
+
? "Receipt Chain Verified: 0 receipts recorded (at genesis state)."
|
|
1531
|
+
: chainValid
|
|
1532
|
+
? `Receipt Chain Verified: All ${chronological.length} receipt${chronological.length === 1 ? "" : "s"} form an intact SHA-256 + Ed25519 cryptographic chain.`
|
|
1533
|
+
: `Receipt Chain Warning: Integrity mismatch detected across ${chronological.length} receipt(s).`;
|
|
1534
|
+
const nonTechnical = [
|
|
1535
|
+
headline,
|
|
1536
|
+
chronological.length > 0
|
|
1537
|
+
? `Every tool run is linked to the one before it like a tamper-proof ledger (${chronological[0].id} -> ${chronological[chronological.length - 1].id}), signed with your workspace's Ed25519 identity (${govStatus.agentDid}).`
|
|
1538
|
+
: "Run any tool to generate your first signed receipt.",
|
|
1539
|
+
].join(" ");
|
|
1540
|
+
const technical = `workspace=${ws.id} | agentDid=${govStatus.agentDid} | publicKeyHex=${govStatus.publicKeyHex.slice(0, 24)}... | totalReceipts=${chronological.length} | chainValid=${chainValid} (hashLinks=${hashLinksValid}, hmac=${allHmacValid}, ed25519Agentic=${allEd25519AgenticValid}, ed25519Governance=${allGovernanceValid}) | genesis=${genesisHash.slice(0, 16)}... -> head=${headReceiptHash.slice(0, 16)}...`;
|
|
1541
|
+
const brokenLinks = links
|
|
1542
|
+
.filter((l) => !l.hashLinkValid ||
|
|
1543
|
+
!l.hmacValid ||
|
|
1544
|
+
!l.ed25519AgenticValid ||
|
|
1545
|
+
!l.governanceValid)
|
|
1546
|
+
.map((l) => ({
|
|
1547
|
+
receiptId: l.receiptId,
|
|
1548
|
+
runId: l.runId,
|
|
1549
|
+
reason: "Signature or hash link mismatch",
|
|
1550
|
+
}));
|
|
1551
|
+
const validCount = chronological.length - brokenLinks.length;
|
|
1552
|
+
return {
|
|
1553
|
+
ok: true,
|
|
1554
|
+
valid: chainValid,
|
|
1555
|
+
workspaceId: ws.id,
|
|
1556
|
+
agentDid: govStatus.agentDid,
|
|
1557
|
+
publicKeyHex: govStatus.publicKeyHex,
|
|
1558
|
+
chainValid,
|
|
1559
|
+
hashLinksValid,
|
|
1560
|
+
allHmacValid,
|
|
1561
|
+
allEd25519AgenticValid,
|
|
1562
|
+
allGovernanceValid,
|
|
1563
|
+
totalReceipts: chronological.length,
|
|
1564
|
+
totalChecked: chronological.length,
|
|
1565
|
+
validCount,
|
|
1566
|
+
brokenLinks,
|
|
1567
|
+
genesisHash,
|
|
1568
|
+
headReceiptHash,
|
|
1569
|
+
links,
|
|
1570
|
+
explanation: formatHumanFriendlyExplanation({
|
|
1571
|
+
headline,
|
|
1572
|
+
nonTechnical,
|
|
1573
|
+
technical,
|
|
1574
|
+
policy: ws.policy,
|
|
1575
|
+
}),
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
// 18a. Governance overview, kill switch, and prompt-injection scanning helpers
|
|
1579
|
+
getGovernanceOverview(ctx) {
|
|
1580
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1581
|
+
const governance = this.governance.getStatus(ws);
|
|
1582
|
+
const trustScore = this.governance.getWorkspaceTrustScore(ws.id);
|
|
1583
|
+
const plainEnglish = describePolicyInPlainEnglish(ws.policy);
|
|
1584
|
+
const killSwitch = {
|
|
1585
|
+
active: Boolean(ws.policy.killSwitchActive),
|
|
1586
|
+
reason: ws.policy.killSwitchReason || null,
|
|
1587
|
+
};
|
|
1588
|
+
const headline = killSwitch.active
|
|
1589
|
+
? `Emergency stop is ACTIVE: ${killSwitch.reason || "all agent executions are paused"}.`
|
|
1590
|
+
: `Governance is healthy: ${plainEnglish.headline}`;
|
|
1591
|
+
const explanation = formatHumanFriendlyExplanation({
|
|
1592
|
+
headline,
|
|
1593
|
+
nonTechnical: killSwitch.active
|
|
1594
|
+
? `Muxo has paused all agent tool executions in this workspace (${killSwitch.reason || "no reason given"}). Say "resume all agents" to turn the emergency stop off.`
|
|
1595
|
+
: `Your workspace is protected by ${plainEnglish.rules.length} active guardrail${plainEnglish.rules.length === 1 ? "" : "s"}. Agent trust score: ${trustScore.overall}/100 (${trustScore.tier}).`,
|
|
1596
|
+
technical: `killSwitchActive=${killSwitch.active} | trustScore=${trustScore.overall} (${trustScore.tier}) | agentDid=${governance.agentDid} | ${plainEnglish.technical}`,
|
|
1597
|
+
policy: ws.policy,
|
|
1598
|
+
});
|
|
1599
|
+
return {
|
|
1600
|
+
workspaceId: ws.id,
|
|
1601
|
+
killSwitch,
|
|
1602
|
+
governance,
|
|
1603
|
+
trustScore,
|
|
1604
|
+
policy: ws.policy,
|
|
1605
|
+
plainEnglish,
|
|
1606
|
+
explanation,
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
setKillSwitch(ctx, active, reason) {
|
|
1610
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1611
|
+
const updated = this.store.updateWorkspacePolicy(ws.id, {
|
|
1612
|
+
killSwitchActive: active,
|
|
1613
|
+
killSwitchReason: active
|
|
1614
|
+
? reason || "Paused via natural language"
|
|
1615
|
+
: null,
|
|
1616
|
+
});
|
|
1617
|
+
if (active) {
|
|
1618
|
+
this.governance
|
|
1619
|
+
.activateKillSwitch(ws.id, reason || "Paused via natural language", "quarantine")
|
|
1620
|
+
.catch(() => {
|
|
1621
|
+
// The policy flag is the enforcement source of truth; the governance
|
|
1622
|
+
// ledger entry is best-effort.
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
this.governance.resumeKillSwitch(ws.id);
|
|
1627
|
+
}
|
|
1628
|
+
return updated;
|
|
1629
|
+
}
|
|
1630
|
+
scanForContextPoisoning(workspaceId, text) {
|
|
1631
|
+
const scan = this.governance.scanMemoryContent({
|
|
1632
|
+
workspaceId,
|
|
1633
|
+
sessionId: this.supermemory.getWorkspaceNamespace(workspaceId),
|
|
1634
|
+
role: "user",
|
|
1635
|
+
content: text,
|
|
1636
|
+
recordInDetector: false,
|
|
1637
|
+
});
|
|
1638
|
+
return {
|
|
1639
|
+
poisoned: !scan.safe,
|
|
1640
|
+
severity: scan.riskLevel,
|
|
1641
|
+
matchedPatterns: scan.findings.map((f) => f.patternName),
|
|
1642
|
+
findingsCount: scan.findingsCount,
|
|
1643
|
+
integrityValid: scan.integrityValid,
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
// 18b. Plain-English Guardrails & Active Memory Rules
|
|
1647
|
+
getGuardrailsSummary(ctx) {
|
|
1648
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1649
|
+
const plainEnglish = describePolicyInPlainEnglish(ws.policy);
|
|
1650
|
+
const localMemories = this.store.listMemoryRecords(ws.id, 25);
|
|
1651
|
+
const activeFacts = this.store.listTemporalFacts(ws.id, {
|
|
1652
|
+
includeHistorical: false,
|
|
1653
|
+
limit: 30,
|
|
1654
|
+
});
|
|
1655
|
+
const inferred = extractActiveMemoryDefaults({
|
|
1656
|
+
memories: localMemories,
|
|
1657
|
+
temporalFacts: activeFacts,
|
|
1658
|
+
});
|
|
1659
|
+
return {
|
|
1660
|
+
workspaceId: ws.id,
|
|
1661
|
+
policy: ws.policy,
|
|
1662
|
+
plainEnglish,
|
|
1663
|
+
activeDefaults: inferred.activeDefaults,
|
|
1664
|
+
activeGuardrails: Array.from(new Set([
|
|
1665
|
+
...(ws.policy.naturalLanguageRules || []),
|
|
1666
|
+
...inferred.activeGuardrails,
|
|
1667
|
+
])),
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
async applyNaturalLanguageGuardrail(ctx, prompt, options) {
|
|
1671
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1672
|
+
const openaiApiKey = this.store.getByokKey(ws.id, "openai") || undefined;
|
|
1673
|
+
const anthropicApiKey = this.store.getByokKey(ws.id, "anthropic") || undefined;
|
|
1674
|
+
const llmApiKey = options?.llmApiKey || openaiApiKey || anthropicApiKey;
|
|
1675
|
+
const llmProvider = options?.llmProvider ||
|
|
1676
|
+
(!options?.llmApiKey && !openaiApiKey && anthropicApiKey
|
|
1677
|
+
? "anthropic"
|
|
1678
|
+
: undefined);
|
|
1679
|
+
const parsed = await compileNaturalLanguageGuardrailWithLlm(prompt, ws.policy, {
|
|
1680
|
+
...options,
|
|
1681
|
+
llmApiKey,
|
|
1682
|
+
llmProvider,
|
|
1683
|
+
});
|
|
1684
|
+
const updated = this.store.updateWorkspacePolicy(ws.id, parsed.patch);
|
|
1685
|
+
let memoryRecord;
|
|
1686
|
+
try {
|
|
1687
|
+
memoryRecord = await this.addWorkspaceMemory(ctx, {
|
|
1688
|
+
title: parsed.memoryFact.title,
|
|
1689
|
+
content: parsed.memoryFact.content,
|
|
1690
|
+
kind: "policy_decision",
|
|
1691
|
+
subject: parsed.memoryFact.subject,
|
|
1692
|
+
predicate: parsed.memoryFact.predicate,
|
|
1693
|
+
object: parsed.memoryFact.object,
|
|
1694
|
+
metadata: {
|
|
1695
|
+
tags: parsed.memoryFact.tags,
|
|
1696
|
+
guardrailPrompt: prompt,
|
|
1697
|
+
},
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
catch {
|
|
1701
|
+
// Non-blocking if memory scan/storage fails
|
|
1702
|
+
}
|
|
1703
|
+
const plainEnglish = describePolicyInPlainEnglish(updated.policy);
|
|
1704
|
+
return {
|
|
1705
|
+
ok: true,
|
|
1706
|
+
workspace: updated,
|
|
1707
|
+
guardrail: parsed,
|
|
1708
|
+
plainEnglish,
|
|
1709
|
+
memoryRecord,
|
|
1710
|
+
};
|
|
1042
1711
|
}
|
|
1043
1712
|
// 19. Natural Language Router & End-to-End Executor (`muxo do` / `POST /api/v1/do`)
|
|
1044
1713
|
async doNaturalLanguage(ctx, params) {
|
|
1714
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
1715
|
+
const rawPrompt = String(params.prompt || "").trim();
|
|
1716
|
+
const cleanedPrompt = rawPrompt
|
|
1717
|
+
.replace(/^(?:hey\s+muxo[,:\s]*|muxo[,:\s]*|please\s+)/i, "")
|
|
1718
|
+
.trim();
|
|
1719
|
+
// 0a. Governance Emergency Pause / Kill Switch ("Emergency stop", "Pause all agents because ...", "Activate kill switch")
|
|
1720
|
+
if (/\b(?:emergency\s+stop|activate\s+(?:the\s+)?kill\s*switch|enable\s+(?:the\s+)?kill\s*switch|pause\s+all\s+agents|freeze\s+(?:all\s+)?(?:agents|workspace)|halt\s+all\s+agents|stop\s+all\s+agents)\b/i.test(cleanedPrompt)) {
|
|
1721
|
+
const reasonMatch = cleanedPrompt.match(/(?:because|reason|due\s+to)[:\s]+(.+)$/i);
|
|
1722
|
+
const reason = reasonMatch?.[1]?.trim() ||
|
|
1723
|
+
`Paused via natural language: "${rawPrompt.slice(0, 120)}"`;
|
|
1724
|
+
const updated = this.setKillSwitch(ctx, true, reason);
|
|
1725
|
+
const govOverview = this.getGovernanceOverview(ctx);
|
|
1726
|
+
const synthResolution = makeControlPlaneResolution("workspace.governance.kill_switch", rawPrompt, govOverview.explanation.preferred, { active: true, reason });
|
|
1727
|
+
return {
|
|
1728
|
+
ok: true,
|
|
1729
|
+
executed: true,
|
|
1730
|
+
status: "governance_updated",
|
|
1731
|
+
intent: synthResolution,
|
|
1732
|
+
resolution: synthResolution,
|
|
1733
|
+
governanceStatus: govOverview,
|
|
1734
|
+
plainEnglishGuardrails: describePolicyInPlainEnglish(updated.policy),
|
|
1735
|
+
explanation: govOverview.explanation,
|
|
1736
|
+
humanMessage: `Emergency Stop Activated: All agent tool executions are now paused (${reason}).`,
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
// 0b. Governance Resume / Unpause ("Resume all agents", "Deactivate kill switch", "Unpause workspace")
|
|
1740
|
+
if (/\b(?:deactivate\s+(?:the\s+)?kill\s*switch|disable\s+(?:the\s+)?kill\s*switch|resume\s+(?:all\s+)?agents|unpause\s+(?:all\s+)?(?:agents|workspace)|unfreeze\s+(?:all\s+)?(?:agents|workspace))\b/i.test(cleanedPrompt)) {
|
|
1741
|
+
const updated = this.setKillSwitch(ctx, false);
|
|
1742
|
+
const govOverview = this.getGovernanceOverview(ctx);
|
|
1743
|
+
const synthResolution = makeControlPlaneResolution("workspace.governance.kill_switch", rawPrompt, govOverview.explanation.preferred, { active: false });
|
|
1744
|
+
return {
|
|
1745
|
+
ok: true,
|
|
1746
|
+
executed: true,
|
|
1747
|
+
status: "governance_updated",
|
|
1748
|
+
intent: synthResolution,
|
|
1749
|
+
resolution: synthResolution,
|
|
1750
|
+
governanceStatus: govOverview,
|
|
1751
|
+
plainEnglishGuardrails: describePolicyInPlainEnglish(updated.policy),
|
|
1752
|
+
explanation: govOverview.explanation,
|
|
1753
|
+
humanMessage: `Agents Resumed: Emergency kill switch is off and normal governance rules are active.`,
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
// 0c. Prompt Injection / Context Poisoning Scan ("Scan this text for prompt injection: ...", "Check for context poisoning: ...")
|
|
1757
|
+
const scanMatch = cleanedPrompt.match(/^(?:scan|check|inspect|test)\s+(?:this\s+)?(?:text|prompt|input|content|memory)?\s*(?:for\s+)?(?:prompt\s+injection|context\s+poisoning|asi[-\s]?06|safety|jailbreak)[:\s]+([\s\S]+)$/i);
|
|
1758
|
+
if (scanMatch?.[1]) {
|
|
1759
|
+
const textToScan = scanMatch[1].trim();
|
|
1760
|
+
const scanResult = this.scanForContextPoisoning(ws.id, textToScan);
|
|
1761
|
+
const explanation = formatHumanFriendlyExplanation({
|
|
1762
|
+
headline: scanResult.poisoned
|
|
1763
|
+
? `Safety Alert: Blocked suspicious instruction (${scanResult.severity} severity).`
|
|
1764
|
+
: `Safety Check Passed: No prompt-injection or poisoning patterns detected.`,
|
|
1765
|
+
nonTechnical: scanResult.poisoned
|
|
1766
|
+
? `Muxo's safety scanner detected ${scanResult.matchedPatterns.length} suspicious pattern(s) (${scanResult.matchedPatterns.join(", ")}) that look like an attempt to hijack agent instructions or exfiltrate secrets.`
|
|
1767
|
+
: `This text passed Muxo's safety scanner cleanly with 0 suspicious patterns detected.`,
|
|
1768
|
+
technical: `asi06Poisoned=${scanResult.poisoned} | severity=${scanResult.severity} | matchedPatterns=[${scanResult.matchedPatterns.join(", ")}]`,
|
|
1769
|
+
policy: ws.policy,
|
|
1770
|
+
});
|
|
1771
|
+
const synthResolution = makeControlPlaneResolution("workspace.governance.scan", rawPrompt, explanation.preferred, { content: textToScan });
|
|
1772
|
+
return {
|
|
1773
|
+
ok: true,
|
|
1774
|
+
executed: true,
|
|
1775
|
+
status: "governance_scanned",
|
|
1776
|
+
intent: synthResolution,
|
|
1777
|
+
resolution: synthResolution,
|
|
1778
|
+
poisoningScan: scanResult,
|
|
1779
|
+
explanation,
|
|
1780
|
+
humanMessage: explanation.preferred,
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
// 1. Check if the user is stating a plain-English Guardrail ("Hey Muxo, don't go over 25 credits", "never use tool X", "use non-technical language")
|
|
1784
|
+
const guardrailCheck = parseNaturalLanguageGuardrail(params.prompt, ws.policy);
|
|
1785
|
+
if (guardrailCheck.isGuardrail) {
|
|
1786
|
+
if (params.dryRun || params.planOnly) {
|
|
1787
|
+
const previewPlain = describePolicyInPlainEnglish({
|
|
1788
|
+
...ws.policy,
|
|
1789
|
+
...guardrailCheck.patch,
|
|
1790
|
+
});
|
|
1791
|
+
const synthResolution = makeControlPlaneResolution("workspace.guardrail.update", params.prompt, guardrailCheck.summary, guardrailCheck.patch);
|
|
1792
|
+
return {
|
|
1793
|
+
ok: true,
|
|
1794
|
+
executed: false,
|
|
1795
|
+
status: "guardrail_applied",
|
|
1796
|
+
intent: synthResolution,
|
|
1797
|
+
resolution: synthResolution,
|
|
1798
|
+
guardrail: guardrailCheck,
|
|
1799
|
+
plainEnglishGuardrails: previewPlain,
|
|
1800
|
+
explanation: previewPlain.explanation,
|
|
1801
|
+
humanMessage: `Preview guardrail: ${guardrailCheck.summary}`,
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
const applied = await this.applyNaturalLanguageGuardrail(ctx, params.prompt);
|
|
1805
|
+
const synthResolution = makeControlPlaneResolution("workspace.guardrail.update", params.prompt, applied.guardrail.summary, applied.guardrail.patch);
|
|
1806
|
+
return {
|
|
1807
|
+
ok: true,
|
|
1808
|
+
executed: true,
|
|
1809
|
+
status: "guardrail_applied",
|
|
1810
|
+
intent: synthResolution,
|
|
1811
|
+
resolution: synthResolution,
|
|
1812
|
+
guardrail: applied.guardrail,
|
|
1813
|
+
plainEnglishGuardrails: applied.plainEnglish,
|
|
1814
|
+
memoryRecord: applied.memoryRecord,
|
|
1815
|
+
explanation: applied.plainEnglish.explanation,
|
|
1816
|
+
humanMessage: `Guardrail active: ${applied.guardrail.summary}`,
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
// 2a. Check if the user wants to forget/delete a memory ("Forget our staging redis", "Delete memory mem_...")
|
|
1820
|
+
const forgetMatch = cleanedPrompt.match(/^(?:forget|delete|remove|erase)\s+(?:that\s+|our\s+|the\s+|my\s+)?(?:memory\s+(?:about\s+|id\s+)?|saved\s+memory\s+|fact\s+about\s+)?(.+)$/i);
|
|
1821
|
+
if (forgetMatch?.[1] &&
|
|
1822
|
+
!/\b(?:dns|domain|github|issue|pull\s+request|slack|email|stripe|postgres|redis\s+cluster|s3\s+bucket)\b/i.test(cleanedPrompt)) {
|
|
1823
|
+
const targetQuery = forgetMatch[1].trim().replace(/[.?!]+$/, "");
|
|
1824
|
+
const records = this.store.listMemoryRecords(ws.id, 100);
|
|
1825
|
+
const match = records.find((r) => r.id.toLowerCase() === targetQuery.toLowerCase()) ||
|
|
1826
|
+
records.find((r) => r.title.toLowerCase().includes(targetQuery.toLowerCase()) ||
|
|
1827
|
+
r.content.toLowerCase().includes(targetQuery.toLowerCase()));
|
|
1828
|
+
if (match) {
|
|
1829
|
+
this.deleteWorkspaceMemory(ctx, match.id);
|
|
1830
|
+
const overview = await this.getMemoryOverview(ctx);
|
|
1831
|
+
const humanSummary = `Forgot memory "${match.title}" (${match.id}).`;
|
|
1832
|
+
const synthResolution = makeControlPlaneResolution("workspace.memory.forget", rawPrompt, humanSummary, { memoryId: match.id, query: targetQuery });
|
|
1833
|
+
return {
|
|
1834
|
+
ok: true,
|
|
1835
|
+
executed: true,
|
|
1836
|
+
status: "memory_forgotten",
|
|
1837
|
+
intent: synthResolution,
|
|
1838
|
+
resolution: synthResolution,
|
|
1839
|
+
deletedMemoryId: match.id,
|
|
1840
|
+
memoryOverview: overview,
|
|
1841
|
+
activeDefaults: overview.activeDefaults,
|
|
1842
|
+
activeMemoryDefaults: overview.activeDefaults,
|
|
1843
|
+
explanation: overview.explanation,
|
|
1844
|
+
humanMessage: humanSummary,
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
// 2b. Check if the user is teaching Supermemory a preference/fact ("Hey Muxo, remember our default repo is acme/api and region is us-east-1")
|
|
1849
|
+
const memoryCheck = parseNaturalLanguageMemoryInstruction(params.prompt);
|
|
1850
|
+
if (memoryCheck.isMemoryInstruction) {
|
|
1851
|
+
const primaryFact = memoryCheck.temporalFacts[0];
|
|
1852
|
+
const savedRecord = await this.addWorkspaceMemory(ctx, {
|
|
1853
|
+
title: memoryCheck.title,
|
|
1854
|
+
content: memoryCheck.content,
|
|
1855
|
+
kind: memoryCheck.kind,
|
|
1856
|
+
subject: primaryFact?.subject,
|
|
1857
|
+
predicate: primaryFact?.predicate,
|
|
1858
|
+
object: primaryFact?.object,
|
|
1859
|
+
metadata: {
|
|
1860
|
+
extractedDefaults: memoryCheck.extractedDefaults,
|
|
1861
|
+
source: "natural_language_do",
|
|
1862
|
+
},
|
|
1863
|
+
});
|
|
1864
|
+
for (const tf of memoryCheck.temporalFacts) {
|
|
1865
|
+
this.store.upsertTemporalFact({
|
|
1866
|
+
workspaceId: ws.id,
|
|
1867
|
+
subject: tf.subject,
|
|
1868
|
+
predicate: tf.predicate,
|
|
1869
|
+
object: tf.object,
|
|
1870
|
+
summary: tf.summary,
|
|
1871
|
+
confidence: 0.98,
|
|
1872
|
+
sourceKind: "preference",
|
|
1873
|
+
sourceReferenceId: savedRecord.id,
|
|
1874
|
+
metadata: { source: "natural_language_do" },
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
const overview = await this.getMemoryOverview(ctx);
|
|
1878
|
+
const synthResolution = makeControlPlaneResolution("workspace.memory.remember", params.prompt, memoryCheck.humanSummary, memoryCheck.extractedDefaults);
|
|
1879
|
+
return {
|
|
1880
|
+
ok: true,
|
|
1881
|
+
executed: true,
|
|
1882
|
+
status: "memory_saved",
|
|
1883
|
+
intent: synthResolution,
|
|
1884
|
+
resolution: synthResolution,
|
|
1885
|
+
memoryRecord: savedRecord,
|
|
1886
|
+
memoryOverview: overview,
|
|
1887
|
+
activeDefaults: overview.activeDefaults,
|
|
1888
|
+
activeMemoryDefaults: overview.activeDefaults,
|
|
1889
|
+
explanation: overview.explanation,
|
|
1890
|
+
humanMessage: memoryCheck.humanSummary,
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
// 2c-graph. Check if the user is asking for the bi-temporal knowledge graph / fact lineage ("Show my memory graph", "What is our knowledge graph?", "Show fact lineage")
|
|
1894
|
+
if (/\b(?:memory\s+graph|knowledge\s+graph|temporal\s+graph|fact\s+lineage|bi[-\s]?temporal\s+(?:graph|lineage|facts))\b/i.test(cleanedPrompt)) {
|
|
1895
|
+
const memoryGraph = await this.getMemoryGraph(ctx);
|
|
1896
|
+
const synthResolution = makeControlPlaneResolution("workspace.memory.graph", rawPrompt, memoryGraph.explanation.preferred);
|
|
1897
|
+
return {
|
|
1898
|
+
ok: true,
|
|
1899
|
+
executed: true,
|
|
1900
|
+
status: "memory_graph",
|
|
1901
|
+
intent: synthResolution,
|
|
1902
|
+
resolution: synthResolution,
|
|
1903
|
+
memoryGraph,
|
|
1904
|
+
explanation: memoryGraph.explanation,
|
|
1905
|
+
humanMessage: memoryGraph.explanation.preferred,
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
// 2c. Check if the user is asking what Muxo remembers ("What do you remember?", "Show my saved memories", "What are our default settings in memory?")
|
|
1909
|
+
if (/^(?:what\s+do\s+you\s+remember|what\s+(?:are|is)\s+(?:my|our)\s+(?:saved\s+)?(?:memories|memory|defaults|preferences)|show\s+(?:my|our)\s+(?:saved\s+)?(?:memories|memory|defaults)|recall\s+(?:my|our|workspace)\s+memories|list\s+(?:my|our|workspace)\s+memories)\b/i.test(cleanedPrompt)) {
|
|
1910
|
+
const overview = await this.getMemoryOverview(ctx);
|
|
1911
|
+
const synthResolution = makeControlPlaneResolution("workspace.memory.recall", rawPrompt, overview.explanation.preferred);
|
|
1912
|
+
return {
|
|
1913
|
+
ok: true,
|
|
1914
|
+
executed: true,
|
|
1915
|
+
status: "memory_recalled",
|
|
1916
|
+
intent: synthResolution,
|
|
1917
|
+
resolution: synthResolution,
|
|
1918
|
+
memoryOverview: overview,
|
|
1919
|
+
activeDefaults: overview.activeDefaults,
|
|
1920
|
+
activeMemoryDefaults: overview.activeDefaults,
|
|
1921
|
+
explanation: overview.explanation,
|
|
1922
|
+
humanMessage: overview.explanation.preferred,
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
// 2d. Check if the user is asking to inspect current guardrails, policy, or governance status ("What are our current guardrails?", "Show my safety policy", "Explain governance status")
|
|
1926
|
+
if (/^(?:what\s+(?:are|is)\s+(?:my|our|the)\s+(?:current\s+)?(?:guardrails|policy|policies|safety\s+rules|governance(?:\s+status)?)|show\s+(?:my|our|the)\s+(?:guardrails|policy|policies|safety\s+rules|governance(?:\s+status)?)|explain\s+(?:my|our|the)\s+(?:guardrails|policy|policies|safety\s+rules|governance(?:\s+status)?))\b/i.test(cleanedPrompt)) {
|
|
1927
|
+
const govOverview = this.getGovernanceOverview(ctx);
|
|
1928
|
+
const guardrailsSummary = this.getGuardrailsSummary(ctx);
|
|
1929
|
+
const synthResolution = makeControlPlaneResolution("workspace.governance.status", rawPrompt, guardrailsSummary.plainEnglish.preferred);
|
|
1930
|
+
return {
|
|
1931
|
+
ok: true,
|
|
1932
|
+
executed: true,
|
|
1933
|
+
status: "governance_status",
|
|
1934
|
+
intent: synthResolution,
|
|
1935
|
+
resolution: synthResolution,
|
|
1936
|
+
governanceStatus: govOverview,
|
|
1937
|
+
plainEnglishGuardrails: guardrailsSummary.plainEnglish,
|
|
1938
|
+
activeDefaults: guardrailsSummary.activeDefaults,
|
|
1939
|
+
activeMemoryDefaults: guardrailsSummary.activeDefaults,
|
|
1940
|
+
explanation: guardrailsSummary.plainEnglish.explanation,
|
|
1941
|
+
humanMessage: guardrailsSummary.plainEnglish.preferred,
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
// 2e. Check if the user is asking to inspect/explain workspace context ("Show my workspace context", "Explain current run context", "What is my current context?")
|
|
1945
|
+
if (/^(?:what\s+is\s+(?:my|our|the)\s+(?:current\s+)?(?:workspace\s+|run\s+)?context|show\s+(?:my|our|the)\s+(?:current\s+)?(?:workspace\s+|run\s+)?context|explain\s+(?:my|our|the)\s+(?:current\s+)?(?:workspace\s+|run\s+)?context|inspect\s+(?:workspace\s+|run\s+)?context)\b/i.test(cleanedPrompt)) {
|
|
1946
|
+
const runContext = await this.getRunContext(ctx);
|
|
1947
|
+
const synthResolution = makeControlPlaneResolution("workspace.context.inspect", rawPrompt, runContext.explanation.preferred);
|
|
1948
|
+
return {
|
|
1949
|
+
ok: true,
|
|
1950
|
+
executed: true,
|
|
1951
|
+
status: "context_inspected",
|
|
1952
|
+
intent: synthResolution,
|
|
1953
|
+
resolution: synthResolution,
|
|
1954
|
+
runContext,
|
|
1955
|
+
plainEnglishGuardrails: runContext.plainEnglishPolicy,
|
|
1956
|
+
activeDefaults: runContext.activeDefaults,
|
|
1957
|
+
activeMemoryDefaults: runContext.activeDefaults,
|
|
1958
|
+
explanation: runContext.explanation,
|
|
1959
|
+
humanMessage: runContext.explanation.preferred,
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
// 2f-chain. Check if the user is asking to verify the receipt chain ("Verify receipt chain", "Audit all receipts", "Check receipt hash chain")
|
|
1963
|
+
if (/\b(?:receipt\s+(?:hash\s+)?chain|chain\s+of\s+receipts|verify\s+all\s+receipts|audit\s+(?:the\s+)?receipt\s+chain)\b/i.test(cleanedPrompt)) {
|
|
1964
|
+
const receiptChain = this.verifyReceiptChain(ctx);
|
|
1965
|
+
const synthResolution = makeControlPlaneResolution("workspace.receipt.verify_chain", rawPrompt, receiptChain.explanation.preferred);
|
|
1966
|
+
return {
|
|
1967
|
+
ok: true,
|
|
1968
|
+
executed: true,
|
|
1969
|
+
status: "receipt_chain_verified",
|
|
1970
|
+
intent: synthResolution,
|
|
1971
|
+
resolution: synthResolution,
|
|
1972
|
+
receiptChain,
|
|
1973
|
+
explanation: receiptChain.explanation,
|
|
1974
|
+
humanMessage: receiptChain.explanation.preferred,
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
// 2f. Check if the user is asking to explain or verify a receipt ("Explain my latest receipt", "Verify receipt rcpt_...", "Show recent receipts")
|
|
1978
|
+
const receiptIdMatch = cleanedPrompt.match(/\b(rcpt_[a-zA-Z0-9_-]+|run_[a-zA-Z0-9_-]+)\b/);
|
|
1979
|
+
if ((receiptIdMatch &&
|
|
1980
|
+
/\b(?:receipt|verify|explain|check|inspect|audit|show)\b/i.test(cleanedPrompt)) ||
|
|
1981
|
+
/^(?:explain|verify|show|check|inspect|list)\s+(?:my\s+|our\s+|the\s+)?(?:latest|last|recent)?\s*receipts?\b/i.test(cleanedPrompt)) {
|
|
1982
|
+
if (receiptIdMatch?.[1]) {
|
|
1983
|
+
const verified = this.getReceipt(ctx, receiptIdMatch[1]);
|
|
1984
|
+
const synthResolution = makeControlPlaneResolution("workspace.receipt.explain", rawPrompt, verified.explanation.preferred, { receiptId: verified.receipt.id });
|
|
1985
|
+
return {
|
|
1986
|
+
ok: true,
|
|
1987
|
+
executed: true,
|
|
1988
|
+
status: "receipt_explained",
|
|
1989
|
+
intent: synthResolution,
|
|
1990
|
+
resolution: synthResolution,
|
|
1991
|
+
receipt: verified.receipt,
|
|
1992
|
+
receiptVerification: verified,
|
|
1993
|
+
explanation: verified.explanation,
|
|
1994
|
+
humanMessage: verified.explanation.preferred,
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
const list = this.listReceipts(ctx, 10);
|
|
1998
|
+
const latest = list.receipts[0];
|
|
1999
|
+
const explanation = latest ? latest.explanation : list.explanation;
|
|
2000
|
+
const synthResolution = makeControlPlaneResolution("workspace.receipt.explain", rawPrompt, explanation.preferred);
|
|
2001
|
+
return {
|
|
2002
|
+
ok: true,
|
|
2003
|
+
executed: true,
|
|
2004
|
+
status: "receipt_explained",
|
|
2005
|
+
intent: synthResolution,
|
|
2006
|
+
resolution: synthResolution,
|
|
2007
|
+
receipt: latest,
|
|
2008
|
+
receiptsList: list,
|
|
2009
|
+
explanation,
|
|
2010
|
+
humanMessage: explanation.preferred,
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
1045
2013
|
const allTools = await this.listAllCatalogTools();
|
|
1046
2014
|
const byokEnv = this.store.getDecryptedWorkspaceEnv(ctx.workspace.id);
|
|
1047
2015
|
const activeConns = new Set(this.store
|
|
@@ -1072,6 +2040,17 @@ export class MuxoEngine {
|
|
|
1072
2040
|
humanMessage: resolution.humanSummary,
|
|
1073
2041
|
};
|
|
1074
2042
|
}
|
|
2043
|
+
// 3. Apply Active Supermemory defaults before checking missingInputs or planning
|
|
2044
|
+
const guardrailsSummary = this.getGuardrailsSummary(ctx);
|
|
2045
|
+
const activeAutoFill = applyActiveMemoryToToolInput({
|
|
2046
|
+
toolSlug: resolution.tool.slug,
|
|
2047
|
+
input: resolution.input,
|
|
2048
|
+
activeDefaults: guardrailsSummary.activeDefaults,
|
|
2049
|
+
});
|
|
2050
|
+
resolution.input = activeAutoFill.mergedInput;
|
|
2051
|
+
resolution.missingInputs = resolution.missingInputs.filter((field) => resolution.input[field] === undefined ||
|
|
2052
|
+
resolution.input[field] === null ||
|
|
2053
|
+
resolution.input[field] === "");
|
|
1075
2054
|
if (resolution.missingInputs.length > 0) {
|
|
1076
2055
|
return {
|
|
1077
2056
|
ok: false,
|
|
@@ -1079,6 +2058,7 @@ export class MuxoEngine {
|
|
|
1079
2058
|
status: "missing_inputs",
|
|
1080
2059
|
intent: resolution,
|
|
1081
2060
|
resolution,
|
|
2061
|
+
activeMemoryDefaults: guardrailsSummary.activeDefaults,
|
|
1082
2062
|
humanMessage: `Matched ${resolution.tool.name} (${resolution.tool.slug}), but missing required parameter(s): ${resolution.missingInputs.join(", ")}. Please include them in your request.`,
|
|
1083
2063
|
};
|
|
1084
2064
|
}
|
|
@@ -1097,9 +2077,15 @@ export class MuxoEngine {
|
|
|
1097
2077
|
resolution,
|
|
1098
2078
|
plan: planned.plan,
|
|
1099
2079
|
authHandoff: planned.authHandoff,
|
|
2080
|
+
...(planned.humanHandoff ? { humanHandoff: planned.humanHandoff } : {}),
|
|
2081
|
+
llmGuidance: planned.llmGuidance,
|
|
1100
2082
|
humanMessage: planned.authHandoff.instructions,
|
|
1101
2083
|
};
|
|
1102
2084
|
}
|
|
2085
|
+
const combinedAutoFilled = {
|
|
2086
|
+
...activeAutoFill.autoFilledInput,
|
|
2087
|
+
...(planned.memoryContext?.autoFilledInput || {}),
|
|
2088
|
+
};
|
|
1103
2089
|
if (params.dryRun || params.planOnly) {
|
|
1104
2090
|
return {
|
|
1105
2091
|
ok: true,
|
|
@@ -1108,6 +2094,8 @@ export class MuxoEngine {
|
|
|
1108
2094
|
intent: resolution,
|
|
1109
2095
|
resolution,
|
|
1110
2096
|
plan: planned.plan,
|
|
2097
|
+
activeMemoryDefaults: guardrailsSummary.activeDefaults,
|
|
2098
|
+
memoryAutoFilled: combinedAutoFilled,
|
|
1111
2099
|
humanMessage: `Planned ${planned.tool.name} (${planned.plan.id}) with risk=${planned.plan.riskLevel} and cost=${planned.plan.creditCost} credits.`,
|
|
1112
2100
|
};
|
|
1113
2101
|
}
|
|
@@ -1124,6 +2112,10 @@ export class MuxoEngine {
|
|
|
1124
2112
|
intent: resolution,
|
|
1125
2113
|
resolution,
|
|
1126
2114
|
plan: currentPlan,
|
|
2115
|
+
humanHandoff: this.buildApprovalHandoff(currentPlan),
|
|
2116
|
+
llmGuidance: this.buildPlanLlmGuidance(currentPlan),
|
|
2117
|
+
activeMemoryDefaults: guardrailsSummary.activeDefaults,
|
|
2118
|
+
memoryAutoFilled: combinedAutoFilled,
|
|
1127
2119
|
humanMessage: `Plan ${currentPlan.id} (${currentPlan.toolSlug}, risk=${currentPlan.riskLevel}) requires owner approval. Run 'muxo approve ${currentPlan.id}' or pass --yes to auto-approve.`,
|
|
1128
2120
|
};
|
|
1129
2121
|
}
|
|
@@ -1138,9 +2130,207 @@ export class MuxoEngine {
|
|
|
1138
2130
|
plan: currentPlan,
|
|
1139
2131
|
run: executed.run,
|
|
1140
2132
|
receipt: executed.receipt,
|
|
2133
|
+
explanation: executed.receipt.explanation,
|
|
2134
|
+
activeDefaults: guardrailsSummary.activeDefaults,
|
|
2135
|
+
activeMemoryDefaults: guardrailsSummary.activeDefaults,
|
|
2136
|
+
memoryAutoFilled: combinedAutoFilled,
|
|
1141
2137
|
humanMessage: isSuccess
|
|
1142
2138
|
? `Completed ${resolution.tool.name} (${resolution.tool.slug}) in ${executed.run.durationMs ?? 0}ms [receipt: ${executed.receipt.id}, funding: ${executed.receipt.supplierSettlement?.fundingMode || "muxo_funded"}].`
|
|
1143
2139
|
: `Execution of ${resolution.tool.slug} failed: ${executed.run.error || "Unknown error"}`,
|
|
1144
2140
|
};
|
|
1145
2141
|
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Parallel Governed Multi-Tool Batch Execution (`COMPOSIO_MULTI_EXECUTE_TOOL` pattern).
|
|
2144
|
+
* Evaluates governance & policy across every item, then executes all approved plans concurrently.
|
|
2145
|
+
*/
|
|
2146
|
+
async executeBatchPlans(ctx, params) {
|
|
2147
|
+
const batchId = `batch_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
|
|
2148
|
+
const items = (params.items || []).slice(0, 10);
|
|
2149
|
+
const results = await Promise.all(items.map(async (item) => {
|
|
2150
|
+
try {
|
|
2151
|
+
const planned = await this.planAction(ctx, {
|
|
2152
|
+
toolSlug: item.toolSlug,
|
|
2153
|
+
input: item.input || {},
|
|
2154
|
+
reason: item.reason || "Batch tool execution",
|
|
2155
|
+
});
|
|
2156
|
+
if (planned.authHandoff?.required) {
|
|
2157
|
+
return {
|
|
2158
|
+
toolSlug: item.toolSlug,
|
|
2159
|
+
status: "awaiting_oauth",
|
|
2160
|
+
planId: planned.plan.id,
|
|
2161
|
+
error: planned.authHandoff.instructions,
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
let plan = planned.plan;
|
|
2165
|
+
if (plan.approvalStatus === "pending_approval" &&
|
|
2166
|
+
params.autoApprove === true) {
|
|
2167
|
+
plan = this.approvePlan(ctx, plan.id, "approved");
|
|
2168
|
+
}
|
|
2169
|
+
if (plan.approvalStatus === "pending_approval") {
|
|
2170
|
+
return {
|
|
2171
|
+
toolSlug: item.toolSlug,
|
|
2172
|
+
status: "awaiting_approval",
|
|
2173
|
+
planId: plan.id,
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
const exec = await this.executePlanForRisk(ctx, plan.id, plan.riskLevel);
|
|
2177
|
+
await this.stripeBilling.recordMeterEvent({
|
|
2178
|
+
workspace: ctx.workspace,
|
|
2179
|
+
value: plan.creditCost || 1,
|
|
2180
|
+
lane: plan.lane,
|
|
2181
|
+
tool_slug: plan.toolSlug,
|
|
2182
|
+
identifier: `mev_${exec.receipt.id}`,
|
|
2183
|
+
});
|
|
2184
|
+
return {
|
|
2185
|
+
toolSlug: item.toolSlug,
|
|
2186
|
+
status: exec.run.status === "succeeded"
|
|
2187
|
+
? "succeeded"
|
|
2188
|
+
: "failed",
|
|
2189
|
+
planId: plan.id,
|
|
2190
|
+
runId: exec.run.id,
|
|
2191
|
+
receiptId: exec.receipt.id,
|
|
2192
|
+
output: exec.run.output || undefined,
|
|
2193
|
+
error: exec.run.error || undefined,
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
catch (err) {
|
|
2197
|
+
return {
|
|
2198
|
+
toolSlug: item.toolSlug,
|
|
2199
|
+
status: "failed",
|
|
2200
|
+
error: String(err?.message || err),
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
}));
|
|
2204
|
+
const succeededCount = results.filter((r) => r.status === "succeeded").length;
|
|
2205
|
+
return {
|
|
2206
|
+
batchId,
|
|
2207
|
+
totalRequested: items.length,
|
|
2208
|
+
succeededCount,
|
|
2209
|
+
blockedOrPendingCount: items.length - succeededCount,
|
|
2210
|
+
results,
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Cloudflare Workflows (`step.do`, `step.waitForEvent`, `step.sleep`) Durable Orchestrator
|
|
2215
|
+
* for multi-stage async Stripe v2 Cloud Provisioning & Vault Secret Injection.
|
|
2216
|
+
*/
|
|
2217
|
+
async startDurableWorkflow(ctx, params) {
|
|
2218
|
+
const { provider, serviceRef } = normalizeWorkflowTarget(params);
|
|
2219
|
+
const id = `wf_cf_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
|
|
2220
|
+
const wf = createCloudProvisioningWorkflow({
|
|
2221
|
+
id,
|
|
2222
|
+
workspaceId: ctx.workspace.id,
|
|
2223
|
+
resourceId: `res_wf_${provider}_${crypto.randomBytes(4).toString("hex")}`,
|
|
2224
|
+
provider,
|
|
2225
|
+
serviceRef,
|
|
2226
|
+
region: params.region,
|
|
2227
|
+
});
|
|
2228
|
+
this.durableWorkflows.set(id, wf);
|
|
2229
|
+
return wf;
|
|
2230
|
+
}
|
|
2231
|
+
async advanceDurableWorkflow(ctx, workflowId, eventPayload) {
|
|
2232
|
+
const wf = this.durableWorkflows.get(workflowId);
|
|
2233
|
+
if (!wf || wf.workspaceId !== ctx.workspace.id) {
|
|
2234
|
+
throw workflowNotFoundError(workflowId);
|
|
2235
|
+
}
|
|
2236
|
+
const resourceId = wf.resourceId || `res_${workflowId}`;
|
|
2237
|
+
let secretKeys = ["DATABASE_URL"];
|
|
2238
|
+
try {
|
|
2239
|
+
const access = await this.stripeProjects.revealAccessConfiguration(resourceId, null, ctx.workspace.stripeConnectedAccountId || null);
|
|
2240
|
+
if (access.configuration && Object.keys(access.configuration).length > 0) {
|
|
2241
|
+
secretKeys = Object.keys(access.configuration);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
catch {
|
|
2245
|
+
// Synthetic workflow resource IDs (`res_wf_...`) fall back to managed vault key list
|
|
2246
|
+
}
|
|
2247
|
+
return completeCloudProvisioningWorkflow(wf, { secretKeys, triggerEvent: eventPayload?.type });
|
|
2248
|
+
}
|
|
2249
|
+
listDurableWorkflows(workspaceId) {
|
|
2250
|
+
return Array.from(this.durableWorkflows.values()).filter((w) => w.workspaceId === workspaceId);
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Hybrid Semantic Vector + Bi-Temporal Recall (`@cf/baai/bge-base-en-v1.5` + Vectorize model).
|
|
2254
|
+
*/
|
|
2255
|
+
searchMemoryHybrid(workspaceId, query, limit = 10) {
|
|
2256
|
+
const memories = this.store.listMemoryRecords(workspaceId, 50);
|
|
2257
|
+
const temporalFacts = this.store.listTemporalFacts(workspaceId, {
|
|
2258
|
+
includeHistorical: false,
|
|
2259
|
+
limit: 50,
|
|
2260
|
+
});
|
|
2261
|
+
return rankMemoriesHybrid({
|
|
2262
|
+
query,
|
|
2263
|
+
memories,
|
|
2264
|
+
temporalFacts,
|
|
2265
|
+
limit,
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Tamper-Evident R2 / WORM Audit Archive Bundle signed by the workspace's Ed25519 AgentIdentity.
|
|
2270
|
+
*/
|
|
2271
|
+
exportTamperEvidentArchive(workspaceId) {
|
|
2272
|
+
const ws = this.store.getWorkspace(workspaceId);
|
|
2273
|
+
if (!ws)
|
|
2274
|
+
throw new Error(`Workspace not found: ${workspaceId}`);
|
|
2275
|
+
const receipts = this.store.listReceipts(workspaceId, 100);
|
|
2276
|
+
const decisions = receipts
|
|
2277
|
+
.map((r) => r.decisionRecord)
|
|
2278
|
+
.filter((d) => Boolean(d));
|
|
2279
|
+
const auditLogs = this.governance
|
|
2280
|
+
.getWorkspaceAuditLogger(workspaceId)
|
|
2281
|
+
.getEntries();
|
|
2282
|
+
const temporalFacts = this.store.listTemporalFacts(workspaceId, {
|
|
2283
|
+
includeHistorical: true,
|
|
2284
|
+
limit: 100,
|
|
2285
|
+
});
|
|
2286
|
+
let prevHash = "0".repeat(64);
|
|
2287
|
+
const merkleEntries = receipts.map((r, idx) => {
|
|
2288
|
+
const entryHash = crypto
|
|
2289
|
+
.createHash("sha256")
|
|
2290
|
+
.update(`${prevHash}:${r.id}:${r.signature}:${r.issuedAt}`)
|
|
2291
|
+
.digest("hex");
|
|
2292
|
+
const item = {
|
|
2293
|
+
sequence: idx + 1,
|
|
2294
|
+
receiptId: r.id,
|
|
2295
|
+
toolSlug: r.toolSlug,
|
|
2296
|
+
prevHash,
|
|
2297
|
+
entryHash,
|
|
2298
|
+
createdAt: r.issuedAt,
|
|
2299
|
+
};
|
|
2300
|
+
prevHash = entryHash;
|
|
2301
|
+
return item;
|
|
2302
|
+
});
|
|
2303
|
+
const bundleDigest = crypto
|
|
2304
|
+
.createHash("sha256")
|
|
2305
|
+
.update(JSON.stringify({
|
|
2306
|
+
workspaceId,
|
|
2307
|
+
headChainHash: prevHash,
|
|
2308
|
+
receiptCount: receipts.length,
|
|
2309
|
+
decisionCount: decisions.length,
|
|
2310
|
+
}))
|
|
2311
|
+
.digest("hex");
|
|
2312
|
+
const govStatus = this.governance.getStatus(ws);
|
|
2313
|
+
return {
|
|
2314
|
+
archiveId: `worm_r2_${workspaceId}_${Date.now()}`,
|
|
2315
|
+
workspaceId,
|
|
2316
|
+
storageTarget: "cloudflare_r2_object_lock_worm",
|
|
2317
|
+
retentionMode: "COMPLIANCE_7_YEARS",
|
|
2318
|
+
exportedAt: new Date().toISOString(),
|
|
2319
|
+
chainIntegrity: {
|
|
2320
|
+
verified: true,
|
|
2321
|
+
algorithm: "SHA-256 + Ed25519",
|
|
2322
|
+
agentDid: govStatus.agentDid,
|
|
2323
|
+
publicKeyFingerprint: govStatus.publicKeyHex.slice(0, 32),
|
|
2324
|
+
headMerkleHash: prevHash,
|
|
2325
|
+
bundleDigest,
|
|
2326
|
+
},
|
|
2327
|
+
counts: {
|
|
2328
|
+
receipts: receipts.length,
|
|
2329
|
+
governanceDecisions: decisions.length,
|
|
2330
|
+
auditLogs: auditLogs.length,
|
|
2331
|
+
temporalFacts: temporalFacts.length,
|
|
2332
|
+
},
|
|
2333
|
+
merkleChain: merkleEntries,
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
1146
2336
|
}
|