@muxoai/sdk 1.2.3 → 1.3.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/.agents/plugins/marketplace.json +22 -0
- package/.claude-plugin/marketplace.json +17 -0
- package/.codex/config.toml +5 -0
- package/ARCHITECTURE.md +111 -0
- package/README.md +21 -12
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +894 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.js +89 -0
- package/dist/engine.d.ts +156 -0
- package/dist/engine.js +876 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/mcp.d.ts +45 -0
- package/dist/mcp.js +914 -0
- package/dist/nl.d.ts +34 -0
- package/dist/nl.js +577 -0
- package/dist/providers/composio-instant.d.ts +14 -0
- package/dist/providers/composio-instant.js +814 -0
- package/dist/providers/composio-sdk.d.ts +41 -0
- package/dist/providers/composio-sdk.js +572 -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 +41 -0
- package/dist/providers/stripe-billing.js +372 -0
- package/dist/providers/stripe-projects.d.ts +53 -0
- package/dist/providers/stripe-projects.js +636 -0
- package/dist/providers/supermemory.d.ts +64 -0
- package/dist/providers/supermemory.js +224 -0
- package/{index.d.ts → dist/sdk.d.ts} +77 -1
- package/dist/sdk.js +270 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +1277 -0
- package/dist/store.d.ts +216 -0
- package/dist/store.js +1449 -0
- package/dist/types.d.ts +285 -0
- package/dist/types.js +1 -0
- package/dist/ui/console-html.d.ts +2 -0
- package/dist/ui/console-html.js +2343 -0
- package/package.json +66 -6
- package/plugins/muxo/.app.json +8 -0
- package/plugins/muxo/.claude-plugin/plugin.json +16 -0
- package/plugins/muxo/.codex-plugin/plugin.json +55 -0
- package/plugins/muxo/.mcp.json +40 -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/mcp.json +38 -0
- package/plugins/muxo/plugin.json +59 -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/plugins/muxo/skills/setup/SKILL.md +28 -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/vendor/microsoft-governance/package.json +64 -0
- package/index.js +0 -1
package/dist/engine.js
ADDED
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
import { resolveNaturalLanguageIntent, } from "./nl.js";
|
|
2
|
+
import { ComposioInstantAdapter } from "./providers/composio-instant.js";
|
|
3
|
+
import { ComposioSdkAdapter } from "./providers/composio-sdk.js";
|
|
4
|
+
import { MicrosoftGovernanceAdapter } from "./providers/microsoft-governance.js";
|
|
5
|
+
import { StripeBillingAdapter } from "./providers/stripe-billing.js";
|
|
6
|
+
import { StripeProjectsAdapter } from "./providers/stripe-projects.js";
|
|
7
|
+
import { SupermemoryAdapter } from "./providers/supermemory.js";
|
|
8
|
+
export class MuxoEngine {
|
|
9
|
+
config;
|
|
10
|
+
store;
|
|
11
|
+
composioSdk;
|
|
12
|
+
composioInstant;
|
|
13
|
+
stripeProjects;
|
|
14
|
+
stripeBilling;
|
|
15
|
+
governance;
|
|
16
|
+
supermemory;
|
|
17
|
+
constructor(config, store) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.store = store;
|
|
20
|
+
this.composioSdk = new ComposioSdkAdapter(config);
|
|
21
|
+
this.composioInstant = new ComposioInstantAdapter(config);
|
|
22
|
+
this.stripeProjects = new StripeProjectsAdapter(config);
|
|
23
|
+
this.stripeBilling = new StripeBillingAdapter(config, store);
|
|
24
|
+
this.governance = new MicrosoftGovernanceAdapter(config.vaultMasterKey);
|
|
25
|
+
this.supermemory = new SupermemoryAdapter(config);
|
|
26
|
+
}
|
|
27
|
+
// 1. get_profile
|
|
28
|
+
getProfile(ctx) {
|
|
29
|
+
return {
|
|
30
|
+
id: ctx.user.id,
|
|
31
|
+
name: ctx.user.name,
|
|
32
|
+
email: ctx.user.email,
|
|
33
|
+
user: ctx.user,
|
|
34
|
+
actorId: ctx.actorId,
|
|
35
|
+
authType: ctx.authType,
|
|
36
|
+
workspaceId: ctx.workspace.id,
|
|
37
|
+
workspaceName: ctx.workspace.name,
|
|
38
|
+
tier: ctx.workspace.tier,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// 2. get_organization
|
|
42
|
+
getOrganization(ctx) {
|
|
43
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
44
|
+
const resources = this.store.listResources(ws.id);
|
|
45
|
+
const connections = this.store.listConnections(ws.id);
|
|
46
|
+
const supplierSettlementSummary = this.store.getSupplierSettlementSummary(ws.id);
|
|
47
|
+
return {
|
|
48
|
+
organization: {
|
|
49
|
+
id: ws.id,
|
|
50
|
+
name: ws.name,
|
|
51
|
+
slug: ws.slug,
|
|
52
|
+
tier: ws.tier,
|
|
53
|
+
usage: {
|
|
54
|
+
monthlyActionsUsed: ws.monthlyActionsUsed,
|
|
55
|
+
monthlyActionsLimit: ws.monthlyActionsLimit,
|
|
56
|
+
monthlyActionsRemaining: Math.max(0, ws.monthlyActionsLimit - ws.monthlyActionsUsed),
|
|
57
|
+
creditsBalance: ws.creditsBalance,
|
|
58
|
+
},
|
|
59
|
+
supplierSettlementSummary,
|
|
60
|
+
policy: ws.policy,
|
|
61
|
+
activeConnectionsCount: connections.filter((c) => c.status === "active").length,
|
|
62
|
+
provisionedResourcesCount: resources.filter((r) => r.status === "active").length,
|
|
63
|
+
projectsEnvironment: ws.projectsEnvName,
|
|
64
|
+
createdAt: ws.createdAt,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// 3. search_tools
|
|
69
|
+
async searchTools(params) {
|
|
70
|
+
const limit = Math.min(100, Math.max(1, Number(params.limit || 25)));
|
|
71
|
+
const lane = params.lane || "all";
|
|
72
|
+
const query = params.query?.trim();
|
|
73
|
+
const results = [];
|
|
74
|
+
if (lane === "all" || lane === "composio_instant") {
|
|
75
|
+
results.push(...this.composioInstant.searchTools(query, limit));
|
|
76
|
+
}
|
|
77
|
+
if (lane === "all" || lane === "composio_sdk") {
|
|
78
|
+
const sdkTools = await this.composioSdk.searchTools(query, limit);
|
|
79
|
+
results.push(...sdkTools);
|
|
80
|
+
}
|
|
81
|
+
if (lane === "all" || lane === "stripe_projects") {
|
|
82
|
+
results.push(...this.stripeProjects.searchTools(query, limit));
|
|
83
|
+
const billingTools = this.stripeBilling.listTools().filter((t) => {
|
|
84
|
+
if (!query)
|
|
85
|
+
return true;
|
|
86
|
+
const q = query.toLowerCase();
|
|
87
|
+
const hay = `${t.slug} ${t.name} ${t.description} ${t.categories.join(" ")}`.toLowerCase();
|
|
88
|
+
return q.split(/\s+/).some((tok) => hay.includes(tok));
|
|
89
|
+
});
|
|
90
|
+
results.push(...billingTools);
|
|
91
|
+
}
|
|
92
|
+
const filtered = params.riskLevel
|
|
93
|
+
? results.filter((t) => t.riskLevel === params.riskLevel)
|
|
94
|
+
: results;
|
|
95
|
+
return {
|
|
96
|
+
tools: filtered.slice(0, limit),
|
|
97
|
+
total: filtered.length,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async listAllCatalogTools() {
|
|
101
|
+
const instant = this.composioInstant.searchTools(undefined, 100);
|
|
102
|
+
const sdk = await this.composioSdk.searchTools(undefined, 100);
|
|
103
|
+
const proj = this.stripeProjects.searchTools(undefined, 100);
|
|
104
|
+
const billing = this.stripeBilling.listTools();
|
|
105
|
+
return [...instant, ...billing, ...sdk, ...proj];
|
|
106
|
+
}
|
|
107
|
+
// 4. inspect_tool
|
|
108
|
+
async inspectTool(slug) {
|
|
109
|
+
const normalized = slug.trim();
|
|
110
|
+
const billing = this.stripeBilling
|
|
111
|
+
.listTools()
|
|
112
|
+
.find((t) => t.slug.toLowerCase() === normalized.toLowerCase() ||
|
|
113
|
+
t.upstreamSlug.toLowerCase() === normalized.toLowerCase());
|
|
114
|
+
if (billing)
|
|
115
|
+
return billing;
|
|
116
|
+
const instant = this.composioInstant.inspectTool(normalized);
|
|
117
|
+
if (instant)
|
|
118
|
+
return instant;
|
|
119
|
+
const proj = this.stripeProjects.inspectTool(normalized);
|
|
120
|
+
if (proj)
|
|
121
|
+
return proj;
|
|
122
|
+
const sdk = await this.composioSdk.inspectTool(normalized);
|
|
123
|
+
if (sdk)
|
|
124
|
+
return sdk;
|
|
125
|
+
throw new Error(`Tool '${normalized}' not found in Muxo catalog. Use search_tools to discover valid slugs.`);
|
|
126
|
+
}
|
|
127
|
+
// 5. connection_status
|
|
128
|
+
async connectionStatus(ctx, params) {
|
|
129
|
+
if (params?.syncLive !== false) {
|
|
130
|
+
// Sync live Composio connected accounts for this workspace
|
|
131
|
+
const liveComposio = await this.composioSdk.listLiveConnections(ctx.workspace.id);
|
|
132
|
+
for (const c of liveComposio) {
|
|
133
|
+
this.store.upsertConnection({
|
|
134
|
+
workspaceId: ctx.workspace.id,
|
|
135
|
+
lane: "composio_sdk",
|
|
136
|
+
providerSlug: c.providerSlug,
|
|
137
|
+
providerName: c.providerName,
|
|
138
|
+
status: c.status,
|
|
139
|
+
externalAccountId: c.externalAccountId,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Sync live Stripe Projects linked providers
|
|
143
|
+
try {
|
|
144
|
+
const projStatus = await this.stripeProjects.getLiveStatus();
|
|
145
|
+
for (const p of projStatus.providers) {
|
|
146
|
+
this.store.upsertConnection({
|
|
147
|
+
workspaceId: ctx.workspace.id,
|
|
148
|
+
lane: "stripe_projects",
|
|
149
|
+
providerSlug: p.slug,
|
|
150
|
+
providerName: p.name,
|
|
151
|
+
status: p.status,
|
|
152
|
+
externalAccountId: p.id,
|
|
153
|
+
redirectUrl: p.redirectUrl,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Keep cached connections if CLI is busy
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
let connections = this.store.listConnections(ctx.workspace.id);
|
|
162
|
+
if (params?.provider) {
|
|
163
|
+
const target = params.provider.toLowerCase();
|
|
164
|
+
connections = connections.filter((c) => c.providerSlug.toLowerCase() === target ||
|
|
165
|
+
c.providerName.toLowerCase().includes(target));
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
workspaceId: ctx.workspace.id,
|
|
169
|
+
instantWalletStatus: "active",
|
|
170
|
+
connections,
|
|
171
|
+
summary: {
|
|
172
|
+
total: connections.length,
|
|
173
|
+
active: connections.filter((c) => c.status === "active").length,
|
|
174
|
+
pending: connections.filter((c) => c.status === "pending").length,
|
|
175
|
+
expired: connections.filter((c) => c.status === "expired").length,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// 6. start_connection
|
|
180
|
+
async startConnection(ctx, params) {
|
|
181
|
+
const rawProvider = params.provider.trim().toLowerCase();
|
|
182
|
+
const isProjectsLane = params.lane === "stripe_projects" || rawProvider.startsWith("resource.");
|
|
183
|
+
const cleanSlug = rawProvider.replace(/^(app\.|resource\.)/, "");
|
|
184
|
+
if (isProjectsLane) {
|
|
185
|
+
const linked = await this.stripeProjects.linkProvider(cleanSlug);
|
|
186
|
+
const record = this.store.upsertConnection({
|
|
187
|
+
workspaceId: ctx.workspace.id,
|
|
188
|
+
lane: "stripe_projects",
|
|
189
|
+
providerSlug: linked.providerSlug,
|
|
190
|
+
providerName: linked.providerSlug.charAt(0).toUpperCase() +
|
|
191
|
+
linked.providerSlug.slice(1),
|
|
192
|
+
status: linked.status,
|
|
193
|
+
redirectUrl: linked.redirectUrl,
|
|
194
|
+
});
|
|
195
|
+
return { ...record, message: linked.message };
|
|
196
|
+
}
|
|
197
|
+
const started = await this.composioSdk.startConnection({
|
|
198
|
+
workspaceId: ctx.workspace.id,
|
|
199
|
+
providerSlug: cleanSlug,
|
|
200
|
+
callbackUrl: params.callbackUrl,
|
|
201
|
+
});
|
|
202
|
+
const record = this.store.upsertConnection({
|
|
203
|
+
workspaceId: ctx.workspace.id,
|
|
204
|
+
lane: "composio_sdk",
|
|
205
|
+
providerSlug: started.providerSlug,
|
|
206
|
+
providerName: started.providerName,
|
|
207
|
+
status: started.status,
|
|
208
|
+
externalAccountId: started.externalAccountId,
|
|
209
|
+
redirectUrl: started.redirectUrl,
|
|
210
|
+
metadata: { callbackUrl: started.callbackUrl },
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
...record,
|
|
214
|
+
callbackUrl: started.callbackUrl,
|
|
215
|
+
message: started.message,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// 6b. wait_for_connection (OAuth handoff polling & recovery, like COMPOSIO_WAIT_FOR_CONNECTIONS)
|
|
219
|
+
async waitForConnection(ctx, params) {
|
|
220
|
+
const cleanSlug = params.provider
|
|
221
|
+
.trim()
|
|
222
|
+
.toLowerCase()
|
|
223
|
+
.replace(/^(app\.|resource\.)/, "");
|
|
224
|
+
const timeoutMs = Math.min(120, Math.max(0, Number(params.timeoutSeconds ?? 15))) * 1000;
|
|
225
|
+
const pollIntervalMs = Math.min(5000, Math.max(200, Number(params.pollIntervalMs ?? 1500)));
|
|
226
|
+
const deadline = Date.now() + timeoutMs;
|
|
227
|
+
let current = this.store
|
|
228
|
+
.listConnections(ctx.workspace.id)
|
|
229
|
+
.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
230
|
+
if (!current || (!current.redirectUrl && current.status !== "active")) {
|
|
231
|
+
try {
|
|
232
|
+
current = await this.startConnection(ctx, { provider: cleanSlug });
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// Proceed with status check
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
while (true) {
|
|
239
|
+
const latestLocal = this.store
|
|
240
|
+
.listConnections(ctx.workspace.id)
|
|
241
|
+
.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
242
|
+
if (latestLocal) {
|
|
243
|
+
current = latestLocal;
|
|
244
|
+
if (latestLocal.status === "active") {
|
|
245
|
+
return {
|
|
246
|
+
connected: true,
|
|
247
|
+
provider: cleanSlug,
|
|
248
|
+
status: "active",
|
|
249
|
+
connection: latestLocal,
|
|
250
|
+
redirectUrl: null,
|
|
251
|
+
message: `${latestLocal.providerName} OAuth handoff completed and connection is active.`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (current?.externalAccountId && current.lane === "composio_sdk") {
|
|
256
|
+
const direct = await this.composioSdk.getConnectedAccountStatus(current.externalAccountId);
|
|
257
|
+
if (direct && direct.status === "active") {
|
|
258
|
+
current = this.store.upsertConnection({
|
|
259
|
+
workspaceId: ctx.workspace.id,
|
|
260
|
+
lane: "composio_sdk",
|
|
261
|
+
providerSlug: cleanSlug,
|
|
262
|
+
providerName: cleanSlug.charAt(0).toUpperCase() + cleanSlug.slice(1),
|
|
263
|
+
status: "active",
|
|
264
|
+
externalAccountId: direct.id,
|
|
265
|
+
redirectUrl: null,
|
|
266
|
+
});
|
|
267
|
+
return {
|
|
268
|
+
connected: true,
|
|
269
|
+
provider: cleanSlug,
|
|
270
|
+
status: "active",
|
|
271
|
+
connection: current,
|
|
272
|
+
redirectUrl: null,
|
|
273
|
+
message: `${current.providerName} OAuth handoff completed and connection is active.`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const synced = await this.connectionStatus(ctx, {
|
|
278
|
+
provider: cleanSlug,
|
|
279
|
+
syncLive: true,
|
|
280
|
+
});
|
|
281
|
+
const list = synced.connections || [];
|
|
282
|
+
const found = list.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
283
|
+
if (found) {
|
|
284
|
+
current = found;
|
|
285
|
+
if (found.status === "active") {
|
|
286
|
+
return {
|
|
287
|
+
connected: true,
|
|
288
|
+
provider: cleanSlug,
|
|
289
|
+
status: "active",
|
|
290
|
+
connection: found,
|
|
291
|
+
redirectUrl: null,
|
|
292
|
+
message: `${found.providerName} connection is active and ready for governed execution.`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (Date.now() >= deadline) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
300
|
+
}
|
|
301
|
+
if (!current) {
|
|
302
|
+
current = await this.startConnection(ctx, { provider: cleanSlug });
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
connected: false,
|
|
306
|
+
provider: cleanSlug,
|
|
307
|
+
status: current.status,
|
|
308
|
+
connection: current,
|
|
309
|
+
redirectUrl: current.redirectUrl,
|
|
310
|
+
message: current.redirectUrl
|
|
311
|
+
? `Awaiting user OAuth authorization for ${current.providerName}. Open ${current.redirectUrl} in a browser, then re-run wait_for_connection.`
|
|
312
|
+
: `Connection for ${current.providerName} is currently ${current.status}.`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// 6c. Browser OAuth callback handoff handler (GET /oauth/callback)
|
|
316
|
+
async handleOAuthCallback(params) {
|
|
317
|
+
const cleanSlug = params.provider
|
|
318
|
+
.trim()
|
|
319
|
+
.toLowerCase()
|
|
320
|
+
.replace(/^(app\.|resource\.)/, "");
|
|
321
|
+
const providerName = cleanSlug.charAt(0).toUpperCase() + cleanSlug.slice(1);
|
|
322
|
+
const ws = this.store.getWorkspace(params.workspaceId);
|
|
323
|
+
if (!ws) {
|
|
324
|
+
throw new Error(`Workspace '${params.workspaceId}' not found`);
|
|
325
|
+
}
|
|
326
|
+
const existing = this.store
|
|
327
|
+
.listConnections(ws.id)
|
|
328
|
+
.find((c) => c.providerSlug.toLowerCase() === cleanSlug);
|
|
329
|
+
const accountId = params.connectedAccountId || existing?.externalAccountId || null;
|
|
330
|
+
let resolvedStatus = "pending";
|
|
331
|
+
if (accountId) {
|
|
332
|
+
const live = await this.composioSdk.getConnectedAccountStatus(accountId);
|
|
333
|
+
if (live) {
|
|
334
|
+
resolvedStatus = live.status;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (resolvedStatus !== "active") {
|
|
338
|
+
const liveList = await this.composioSdk.listLiveConnections(ws.id);
|
|
339
|
+
const match = liveList.find((c) => c.providerSlug === cleanSlug);
|
|
340
|
+
if (match) {
|
|
341
|
+
resolvedStatus = match.status;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const rawCallbackStatus = String(params.status || "").toLowerCase();
|
|
345
|
+
if (rawCallbackStatus === "success" ||
|
|
346
|
+
rawCallbackStatus === "active" ||
|
|
347
|
+
rawCallbackStatus === "connected") {
|
|
348
|
+
resolvedStatus = "active";
|
|
349
|
+
}
|
|
350
|
+
const updated = this.store.upsertConnection({
|
|
351
|
+
workspaceId: ws.id,
|
|
352
|
+
lane: "composio_sdk",
|
|
353
|
+
providerSlug: cleanSlug,
|
|
354
|
+
providerName,
|
|
355
|
+
status: resolvedStatus,
|
|
356
|
+
externalAccountId: accountId,
|
|
357
|
+
redirectUrl: resolvedStatus === "active" ? null : existing?.redirectUrl,
|
|
358
|
+
});
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
workspaceId: ws.id,
|
|
362
|
+
providerSlug: cleanSlug,
|
|
363
|
+
providerName,
|
|
364
|
+
status: updated.status,
|
|
365
|
+
externalAccountId: updated.externalAccountId,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
// 7. plan_action
|
|
369
|
+
async planAction(ctx, params) {
|
|
370
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
371
|
+
let tool;
|
|
372
|
+
let resolvedInput = { ...(params.input || {}) };
|
|
373
|
+
let resolvedReason = params.reason || `Planned execution of ${params.toolSlug}`;
|
|
374
|
+
try {
|
|
375
|
+
tool = await this.inspectTool(params.toolSlug);
|
|
376
|
+
}
|
|
377
|
+
catch (inspectErr) {
|
|
378
|
+
// Natural-language fallback: if an agent or human passed plain English as toolSlug/instruction, resolve it!
|
|
379
|
+
const nlSource = `${params.toolSlug} ${params.reason || ""}`.trim();
|
|
380
|
+
const allTools = await this.listAllCatalogTools();
|
|
381
|
+
const byokEnv = this.store.getDecryptedWorkspaceEnv(ws.id);
|
|
382
|
+
const nl = await resolveNaturalLanguageIntent({
|
|
383
|
+
prompt: nlSource,
|
|
384
|
+
tools: allTools,
|
|
385
|
+
explicitInput: resolvedInput,
|
|
386
|
+
byokKeys: byokEnv,
|
|
387
|
+
});
|
|
388
|
+
if (!nl.resolved || !nl.tool) {
|
|
389
|
+
throw inspectErr;
|
|
390
|
+
}
|
|
391
|
+
tool = nl.tool;
|
|
392
|
+
resolvedInput = nl.input;
|
|
393
|
+
resolvedReason = params.reason || nl.reason || nlSource;
|
|
394
|
+
}
|
|
395
|
+
const byokActive = this.store.hasByokForLane(ws.id, tool.lane, tool.slug);
|
|
396
|
+
const effectiveCreditCost = byokActive ? 0 : tool.creditCost;
|
|
397
|
+
if (ws.monthlyActionsUsed >= ws.monthlyActionsLimit) {
|
|
398
|
+
throw new Error(`Monthly action limit reached (${ws.monthlyActionsUsed}/${ws.monthlyActionsLimit}). Upgrade to Starter ($29/mo) to unlock 5,000 actions/mo.`);
|
|
399
|
+
}
|
|
400
|
+
if (effectiveCreditCost > ws.creditsBalance) {
|
|
401
|
+
throw new Error(`Insufficient workspace credits for ${tool.slug} (requires ${effectiveCreditCost}, available ${ws.creditsBalance}). Top up credits or configure a BYOK key.`);
|
|
402
|
+
}
|
|
403
|
+
if (effectiveCreditCost > ws.policy.maxCreditsPerRun) {
|
|
404
|
+
throw new Error(`Tool credit cost (${effectiveCreditCost}) exceeds workspace policy maxCreditsPerRun (${ws.policy.maxCreditsPerRun}).`);
|
|
405
|
+
}
|
|
406
|
+
// 1. Evaluate Microsoft Agent Governance Pipeline:
|
|
407
|
+
// Agent -> McpSecurityScanner -> PolicyEngine -> AgentIdentity (Ed25519 DID) -> AuditLogger -> DecisionRecord
|
|
408
|
+
const decisionRecord = this.governance.evaluatePlan({
|
|
409
|
+
workspace: ws,
|
|
410
|
+
actorId: ctx.actorId,
|
|
411
|
+
tool,
|
|
412
|
+
input: resolvedInput,
|
|
413
|
+
reason: resolvedReason,
|
|
414
|
+
});
|
|
415
|
+
if (decisionRecord.verdict === "deny") {
|
|
416
|
+
throw new Error(`Microsoft Agent Governance denied plan for ${tool.slug} (rule: ${decisionRecord.matchedRule}): ${decisionRecord.reason}`);
|
|
417
|
+
}
|
|
418
|
+
// 2. Recall Supermemory v5 contextual brain for this workspace + tool intent
|
|
419
|
+
const memoryQuery = `${tool.slug} ${tool.name} ${resolvedReason}`.trim();
|
|
420
|
+
const memoryContext = await this.supermemory.getContextForWorkspace(ws.id, memoryQuery);
|
|
421
|
+
const requiresHumanApproval = decisionRecord.verdict === "escalate" ||
|
|
422
|
+
(ws.policy.mode === "enforce" &&
|
|
423
|
+
ws.policy.requireApprovalFor.includes(tool.riskLevel));
|
|
424
|
+
const approvalStatus = requiresHumanApproval
|
|
425
|
+
? "pending_approval"
|
|
426
|
+
: "auto_approved";
|
|
427
|
+
const plan = this.store.createPlan({
|
|
428
|
+
workspaceId: ws.id,
|
|
429
|
+
actorId: ctx.actorId,
|
|
430
|
+
toolSlug: tool.slug,
|
|
431
|
+
upstreamSlug: tool.upstreamSlug,
|
|
432
|
+
lane: tool.lane,
|
|
433
|
+
riskLevel: tool.riskLevel,
|
|
434
|
+
input: resolvedInput,
|
|
435
|
+
reason: resolvedReason,
|
|
436
|
+
creditCost: effectiveCreditCost,
|
|
437
|
+
approvalStatus,
|
|
438
|
+
decisionRecord,
|
|
439
|
+
memoryContext,
|
|
440
|
+
});
|
|
441
|
+
// 3. Check external user OAuth connection state for Connected Apps (composio_sdk)
|
|
442
|
+
let authHandoff;
|
|
443
|
+
if (tool.lane === "composio_sdk" && tool.requiresAuth) {
|
|
444
|
+
let existingConn = this.store
|
|
445
|
+
.listConnections(ws.id)
|
|
446
|
+
.find((c) => c.lane === "composio_sdk" &&
|
|
447
|
+
c.providerSlug.toLowerCase() === tool.providerSlug.toLowerCase());
|
|
448
|
+
if (!existingConn || existingConn.status !== "active") {
|
|
449
|
+
try {
|
|
450
|
+
if (!existingConn || !existingConn.redirectUrl) {
|
|
451
|
+
existingConn = await this.startConnection(ctx, {
|
|
452
|
+
provider: tool.providerSlug,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// Keep existing connection state if offline
|
|
458
|
+
}
|
|
459
|
+
if (!existingConn || existingConn.status !== "active") {
|
|
460
|
+
authHandoff = {
|
|
461
|
+
required: true,
|
|
462
|
+
provider: tool.providerSlug,
|
|
463
|
+
status: existingConn?.status || "pending",
|
|
464
|
+
redirectUrl: existingConn?.redirectUrl || null,
|
|
465
|
+
externalAccountId: existingConn?.externalAccountId || null,
|
|
466
|
+
instructions: existingConn?.redirectUrl
|
|
467
|
+
? `Ask the user to authorize ${tool.providerName} at ${existingConn.redirectUrl}, then call wait_for_connection({ provider: "${tool.providerSlug}" }) before executing ${plan.id}.`
|
|
468
|
+
: `Call start_connection({ provider: "${tool.providerSlug}" }) to authorize ${tool.providerName} before executing ${plan.id}.`,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const executeToolName = `execute_${tool.riskLevel}`;
|
|
474
|
+
let nextStep = approvalStatus === "pending_approval"
|
|
475
|
+
? `Plan ${plan.id} requires human owner approval in the Muxo Console or via approve_plan before calling ${executeToolName}.`
|
|
476
|
+
: `Plan ${plan.id} is auto-approved. Call ${executeToolName}({ plan_id: "${plan.id}" }) to execute.`;
|
|
477
|
+
if (authHandoff?.required) {
|
|
478
|
+
nextStep = `${authHandoff.instructions} Once connected, ${nextStep}`;
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
plan,
|
|
482
|
+
tool,
|
|
483
|
+
decisionRecord,
|
|
484
|
+
memoryContext,
|
|
485
|
+
...(authHandoff ? { authHandoff } : {}),
|
|
486
|
+
nextStep,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
// 8. get_plan
|
|
490
|
+
getPlan(ctx, planId) {
|
|
491
|
+
const plan = this.store.getPlan(ctx.workspace.id, planId);
|
|
492
|
+
if (!plan)
|
|
493
|
+
throw new Error(`Plan '${planId}' not found in workspace.`);
|
|
494
|
+
return plan;
|
|
495
|
+
}
|
|
496
|
+
// Human owner approval / rejection
|
|
497
|
+
approvePlan(ctx, planId, decision = "approved") {
|
|
498
|
+
const plan = this.getPlan(ctx, planId);
|
|
499
|
+
if (plan.approvalStatus === "executed") {
|
|
500
|
+
throw new Error(`Plan '${planId}' has already been executed.`);
|
|
501
|
+
}
|
|
502
|
+
return this.store.updatePlanApproval({
|
|
503
|
+
workspaceId: ctx.workspace.id,
|
|
504
|
+
planId,
|
|
505
|
+
approvalStatus: decision,
|
|
506
|
+
approvedBy: ctx.user.email,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
// 9. get_run_context
|
|
510
|
+
async getRunContext(ctx, query) {
|
|
511
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
512
|
+
const resources = this.store.listResources(ws.id);
|
|
513
|
+
const activeEnv = this.store.getDecryptedWorkspaceEnv(ws.id);
|
|
514
|
+
const redactedEnv = {};
|
|
515
|
+
for (const [k, v] of Object.entries(activeEnv)) {
|
|
516
|
+
redactedEnv[k] =
|
|
517
|
+
v.length > 8 ? `${v.slice(0, 4)}••••${v.slice(-3)}` : "••••••••";
|
|
518
|
+
}
|
|
519
|
+
const memoryContext = await this.supermemory.getContextForWorkspace(ws.id, query);
|
|
520
|
+
return {
|
|
521
|
+
workspaceId: ws.id,
|
|
522
|
+
workspaceSlug: ws.slug,
|
|
523
|
+
projectsEnvironment: ws.projectsEnvName,
|
|
524
|
+
policy: ws.policy,
|
|
525
|
+
creditsBalance: ws.creditsBalance,
|
|
526
|
+
monthlyActionsRemaining: Math.max(0, ws.monthlyActionsLimit - ws.monthlyActionsUsed),
|
|
527
|
+
provisionedResources: resources.filter((r) => r.status === "active"),
|
|
528
|
+
environmentVariablesRedacted: redactedEnv,
|
|
529
|
+
supermemoryBrain: memoryContext,
|
|
530
|
+
governance: this.governance.getStatus(ws),
|
|
531
|
+
envPullHint: "Run `muxo env pull` or call GET /api/v1/resources/env to write decrypted credentials to your local .env file.",
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
// Supermemory Workspace Brain Operations
|
|
535
|
+
async getMemoryOverview(ctx, query) {
|
|
536
|
+
const context = await this.supermemory.getContextForWorkspace(ctx.workspace.id, query);
|
|
537
|
+
const recentMemories = this.store.listMemoryRecords(ctx.workspace.id, 25);
|
|
538
|
+
return {
|
|
539
|
+
enabled: context.enabled,
|
|
540
|
+
namespace: context.namespace,
|
|
541
|
+
context,
|
|
542
|
+
recentMemories,
|
|
543
|
+
memories: recentMemories,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
async addWorkspaceMemory(ctx, params) {
|
|
547
|
+
const kind = params.kind || "fact";
|
|
548
|
+
const remote = await this.supermemory.addMemory({
|
|
549
|
+
workspaceId: ctx.workspace.id,
|
|
550
|
+
title: params.title,
|
|
551
|
+
content: params.content,
|
|
552
|
+
kind,
|
|
553
|
+
metadata: params.metadata,
|
|
554
|
+
});
|
|
555
|
+
return this.store.saveMemoryRecord({
|
|
556
|
+
workspaceId: ctx.workspace.id,
|
|
557
|
+
namespace: remote.namespace,
|
|
558
|
+
documentId: remote.documentId,
|
|
559
|
+
title: params.title,
|
|
560
|
+
content: remote.sanitizedContent,
|
|
561
|
+
kind,
|
|
562
|
+
metadata: {
|
|
563
|
+
...(params.metadata || {}),
|
|
564
|
+
supermemoryStatus: remote.status,
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
async deleteWorkspaceMemory(ctx, idOrDocId) {
|
|
569
|
+
const local = this.store.deleteMemoryRecord(ctx.workspace.id, idOrDocId);
|
|
570
|
+
const docId = local?.documentId || idOrDocId;
|
|
571
|
+
const remote = await this.supermemory.deleteMemory(ctx.workspace.id, docId);
|
|
572
|
+
return {
|
|
573
|
+
deleted: Boolean(local || remote.deleted),
|
|
574
|
+
record: local,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
// 10-15. execute_<risk_level>
|
|
578
|
+
async executePlanForRisk(ctx, planId, expectedRisk) {
|
|
579
|
+
const plan = this.getPlan(ctx, planId);
|
|
580
|
+
if (plan.riskLevel !== expectedRisk) {
|
|
581
|
+
throw new Error(`Risk lane mismatch: Plan '${plan.id}' has risk level '${plan.riskLevel}', so it must be executed with 'execute_${plan.riskLevel}' instead of 'execute_${expectedRisk}'.`);
|
|
582
|
+
}
|
|
583
|
+
if (plan.runId) {
|
|
584
|
+
const existingRun = this.store.getRun(ctx.workspace.id, plan.runId);
|
|
585
|
+
const existingReceipt = this.store.getReceipt(ctx.workspace.id, plan.runId);
|
|
586
|
+
if (existingRun && existingReceipt) {
|
|
587
|
+
return { run: existingRun, receipt: existingReceipt };
|
|
588
|
+
}
|
|
589
|
+
throw new Error(`Plan '${plan.id}' was already claimed by run '${plan.runId}' (execute-once invariant).`);
|
|
590
|
+
}
|
|
591
|
+
if (plan.approvalStatus === "pending_approval") {
|
|
592
|
+
throw new Error(`Plan '${plan.id}' (${plan.riskLevel}) is awaiting human approval. Approve it in the Muxo Console or via CLI 'muxo approve ${plan.id}' before executing.`);
|
|
593
|
+
}
|
|
594
|
+
if (plan.approvalStatus === "rejected") {
|
|
595
|
+
throw new Error(`Plan '${plan.id}' was rejected and cannot be executed.`);
|
|
596
|
+
}
|
|
597
|
+
const ws = this.store.getWorkspace(ctx.workspace.id);
|
|
598
|
+
const tool = await this.inspectTool(plan.toolSlug);
|
|
599
|
+
const execDecisionRecord = this.governance.evaluateExecution({
|
|
600
|
+
workspace: ws,
|
|
601
|
+
actorId: ctx.actorId,
|
|
602
|
+
plan,
|
|
603
|
+
tool,
|
|
604
|
+
});
|
|
605
|
+
if (execDecisionRecord.verdict === "deny") {
|
|
606
|
+
throw new Error(`Microsoft Agent Governance denied execution of plan '${plan.id}': ${execDecisionRecord.reason}`);
|
|
607
|
+
}
|
|
608
|
+
let activeConnId = null;
|
|
609
|
+
if (plan.lane === "composio_sdk" && tool.requiresAuth) {
|
|
610
|
+
let connections = this.store.listConnections(ctx.workspace.id);
|
|
611
|
+
let activeConn = connections.find((c) => c.lane === "composio_sdk" &&
|
|
612
|
+
c.providerSlug === tool.providerSlug &&
|
|
613
|
+
c.status === "active");
|
|
614
|
+
if (!activeConn) {
|
|
615
|
+
await this.waitForConnection(ctx, {
|
|
616
|
+
provider: tool.providerSlug,
|
|
617
|
+
timeoutSeconds: 0,
|
|
618
|
+
}).catch(() => { });
|
|
619
|
+
connections = this.store.listConnections(ctx.workspace.id);
|
|
620
|
+
activeConn = connections.find((c) => c.lane === "composio_sdk" &&
|
|
621
|
+
c.providerSlug === tool.providerSlug &&
|
|
622
|
+
c.status === "active");
|
|
623
|
+
}
|
|
624
|
+
if (!activeConn) {
|
|
625
|
+
throw new Error(`No active OAuth connection for ${tool.providerSlug}. Complete OAuth handoff and call wait_for_connection({ provider: "${tool.providerSlug}" }) before executing plan '${plan.id}'.`);
|
|
626
|
+
}
|
|
627
|
+
activeConnId = activeConn.externalAccountId;
|
|
628
|
+
}
|
|
629
|
+
const run = this.store.createRun(plan, ctx.actorId);
|
|
630
|
+
const startedMs = Date.now();
|
|
631
|
+
try {
|
|
632
|
+
let output;
|
|
633
|
+
if (plan.toolSlug.startsWith("billing.")) {
|
|
634
|
+
output = await this.stripeBilling.executeTool({
|
|
635
|
+
workspace: ws,
|
|
636
|
+
customerEmail: ctx.user.email,
|
|
637
|
+
toolSlug: plan.toolSlug,
|
|
638
|
+
input: plan.input,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
else if (plan.lane === "composio_instant") {
|
|
642
|
+
output = await this.composioInstant.executeTool({
|
|
643
|
+
workspaceId: ctx.workspace.id,
|
|
644
|
+
tool,
|
|
645
|
+
input: plan.input,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
else if (plan.lane === "composio_sdk") {
|
|
649
|
+
output = await this.composioSdk.executeTool({
|
|
650
|
+
workspaceId: ctx.workspace.id,
|
|
651
|
+
tool,
|
|
652
|
+
input: plan.input,
|
|
653
|
+
connectedAccountId: activeConnId,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
else if (plan.lane === "stripe_projects") {
|
|
657
|
+
const projResult = await this.stripeProjects.executeTool({
|
|
658
|
+
workspaceId: ctx.workspace.id,
|
|
659
|
+
tool,
|
|
660
|
+
input: plan.input,
|
|
661
|
+
});
|
|
662
|
+
output = projResult.output;
|
|
663
|
+
if (projResult.provisionedResource) {
|
|
664
|
+
this.store.upsertResource({
|
|
665
|
+
workspaceId: ctx.workspace.id,
|
|
666
|
+
...projResult.provisionedResource,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
throw new Error(`Unsupported execution lane: ${plan.lane}`);
|
|
672
|
+
}
|
|
673
|
+
const durationMs = Date.now() - startedMs;
|
|
674
|
+
const finalized = this.store.finalizeRun({
|
|
675
|
+
workspaceId: ctx.workspace.id,
|
|
676
|
+
runId: run.id,
|
|
677
|
+
status: "succeeded",
|
|
678
|
+
output,
|
|
679
|
+
actionsMetered: 1,
|
|
680
|
+
creditsMetered: plan.creditCost,
|
|
681
|
+
durationMs,
|
|
682
|
+
decisionRecord: execDecisionRecord,
|
|
683
|
+
});
|
|
684
|
+
// Record secret-redacted post-run memory in Supermemory for stateful actions
|
|
685
|
+
if (plan.riskLevel !== "read") {
|
|
686
|
+
const summary = `Executed ${plan.toolSlug} (${plan.lane}, risk=${plan.riskLevel}) for reason "${plan.reason}" -> status=succeeded (receipt=${finalized.receipt.id}).`;
|
|
687
|
+
this.supermemory
|
|
688
|
+
.recordExecutionMemory({
|
|
689
|
+
workspaceId: ctx.workspace.id,
|
|
690
|
+
toolSlug: plan.toolSlug,
|
|
691
|
+
lane: plan.lane,
|
|
692
|
+
riskLevel: plan.riskLevel,
|
|
693
|
+
reason: plan.reason,
|
|
694
|
+
status: "succeeded",
|
|
695
|
+
summary,
|
|
696
|
+
})
|
|
697
|
+
.then((docId) => {
|
|
698
|
+
if (docId) {
|
|
699
|
+
this.store.saveMemoryRecord({
|
|
700
|
+
workspaceId: ctx.workspace.id,
|
|
701
|
+
namespace: this.supermemory.getWorkspaceNamespace(ctx.workspace.id),
|
|
702
|
+
documentId: docId,
|
|
703
|
+
title: `Execution: ${plan.toolSlug} (succeeded)`,
|
|
704
|
+
content: this.supermemory.sanitizeContent(summary),
|
|
705
|
+
kind: "execution_outcome",
|
|
706
|
+
metadata: {
|
|
707
|
+
runId: finalized.run.id,
|
|
708
|
+
receiptId: finalized.receipt.id,
|
|
709
|
+
toolSlug: plan.toolSlug,
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
})
|
|
714
|
+
.catch(() => { });
|
|
715
|
+
}
|
|
716
|
+
return finalized;
|
|
717
|
+
}
|
|
718
|
+
catch (err) {
|
|
719
|
+
const durationMs = Date.now() - startedMs;
|
|
720
|
+
const errorMessage = String(err?.message || err || "Execution failed");
|
|
721
|
+
return this.store.finalizeRun({
|
|
722
|
+
workspaceId: ctx.workspace.id,
|
|
723
|
+
runId: run.id,
|
|
724
|
+
status: "failed",
|
|
725
|
+
output: { error: errorMessage },
|
|
726
|
+
error: errorMessage,
|
|
727
|
+
actionsMetered: 1,
|
|
728
|
+
creditsMetered: 0,
|
|
729
|
+
durationMs,
|
|
730
|
+
decisionRecord: execDecisionRecord,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// 16. get_run
|
|
735
|
+
getRun(ctx, runId) {
|
|
736
|
+
const run = this.store.getRun(ctx.workspace.id, runId);
|
|
737
|
+
if (!run)
|
|
738
|
+
throw new Error(`Run '${runId}' not found in workspace.`);
|
|
739
|
+
return run;
|
|
740
|
+
}
|
|
741
|
+
// 17. cancel_run
|
|
742
|
+
cancelRun(ctx, runId) {
|
|
743
|
+
const run = this.getRun(ctx, runId);
|
|
744
|
+
if (run.status === "succeeded" || run.status === "failed" || run.status === "cancelled") {
|
|
745
|
+
return {
|
|
746
|
+
run,
|
|
747
|
+
receipt: this.store.getReceipt(ctx.workspace.id, run.id),
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const finalized = this.store.finalizeRun({
|
|
751
|
+
workspaceId: ctx.workspace.id,
|
|
752
|
+
runId: run.id,
|
|
753
|
+
status: "cancelled",
|
|
754
|
+
output: { cancelledBy: ctx.actorId },
|
|
755
|
+
error: "Cancelled by operator",
|
|
756
|
+
actionsMetered: 0,
|
|
757
|
+
creditsMetered: 0,
|
|
758
|
+
durationMs: Date.now() - new Date(run.startedAt).getTime(),
|
|
759
|
+
});
|
|
760
|
+
return finalized;
|
|
761
|
+
}
|
|
762
|
+
// 18. get_receipt
|
|
763
|
+
getReceipt(ctx, receiptOrRunId) {
|
|
764
|
+
const receipt = this.store.getReceipt(ctx.workspace.id, receiptOrRunId);
|
|
765
|
+
if (!receipt) {
|
|
766
|
+
throw new Error(`Receipt '${receiptOrRunId}' not found in workspace.`);
|
|
767
|
+
}
|
|
768
|
+
const signatureValid = this.store.verifyReceipt(receipt);
|
|
769
|
+
const governanceSignatureValid = receipt.decisionRecord
|
|
770
|
+
? this.governance.verifyDecisionRecord(receipt.decisionRecord)
|
|
771
|
+
: true;
|
|
772
|
+
return { receipt, signatureValid, governanceSignatureValid };
|
|
773
|
+
}
|
|
774
|
+
// 19. Natural Language Router & End-to-End Executor (`muxo do` / `POST /api/v1/do`)
|
|
775
|
+
async doNaturalLanguage(ctx, params) {
|
|
776
|
+
const allTools = await this.listAllCatalogTools();
|
|
777
|
+
const byokEnv = this.store.getDecryptedWorkspaceEnv(ctx.workspace.id);
|
|
778
|
+
const activeConns = new Set(this.store
|
|
779
|
+
.listConnections(ctx.workspace.id)
|
|
780
|
+
.filter((c) => c.status === "active")
|
|
781
|
+
.map((c) => c.providerSlug.toLowerCase()));
|
|
782
|
+
const resolution = await resolveNaturalLanguageIntent({
|
|
783
|
+
prompt: params.prompt,
|
|
784
|
+
tools: allTools,
|
|
785
|
+
explicitInput: params.input,
|
|
786
|
+
byokKeys: byokEnv,
|
|
787
|
+
activeConnectionProviders: activeConns,
|
|
788
|
+
});
|
|
789
|
+
if (!resolution.resolved || !resolution.tool) {
|
|
790
|
+
return {
|
|
791
|
+
ok: false,
|
|
792
|
+
executed: false,
|
|
793
|
+
status: "unresolved",
|
|
794
|
+
intent: resolution,
|
|
795
|
+
resolution,
|
|
796
|
+
byokPrompt: {
|
|
797
|
+
needed: !byokEnv.OPENAI_API_KEY && !byokEnv.ANTHROPIC_API_KEY && !byokEnv.OPENROUTER_API_KEY,
|
|
798
|
+
provider: "openai",
|
|
799
|
+
envVarName: "OPENAI_API_KEY",
|
|
800
|
+
cliCommand: "muxo byok set openai <sk-...>",
|
|
801
|
+
message: "Tip: Add an OpenAI, Anthropic, or OpenRouter BYOK key (`muxo byok set openai <key>`) for unrestricted natural-language LLM routing.",
|
|
802
|
+
},
|
|
803
|
+
humanMessage: resolution.humanSummary,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
if (resolution.missingInputs.length > 0) {
|
|
807
|
+
return {
|
|
808
|
+
ok: false,
|
|
809
|
+
executed: false,
|
|
810
|
+
status: "missing_inputs",
|
|
811
|
+
intent: resolution,
|
|
812
|
+
resolution,
|
|
813
|
+
humanMessage: `Matched ${resolution.tool.name} (${resolution.tool.slug}), but missing required parameter(s): ${resolution.missingInputs.join(", ")}. Please include them in your request.`,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
const planned = await this.planAction(ctx, {
|
|
817
|
+
toolSlug: resolution.tool.slug,
|
|
818
|
+
input: resolution.input,
|
|
819
|
+
reason: params.prompt,
|
|
820
|
+
});
|
|
821
|
+
if (planned.authHandoff?.required) {
|
|
822
|
+
return {
|
|
823
|
+
ok: false,
|
|
824
|
+
executed: false,
|
|
825
|
+
status: "awaiting_oauth_connection",
|
|
826
|
+
intent: resolution,
|
|
827
|
+
resolution,
|
|
828
|
+
plan: planned.plan,
|
|
829
|
+
authHandoff: planned.authHandoff,
|
|
830
|
+
humanMessage: planned.authHandoff.instructions,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
if (params.dryRun || params.planOnly) {
|
|
834
|
+
return {
|
|
835
|
+
ok: true,
|
|
836
|
+
executed: false,
|
|
837
|
+
status: "planned_dry_run",
|
|
838
|
+
intent: resolution,
|
|
839
|
+
resolution,
|
|
840
|
+
plan: planned.plan,
|
|
841
|
+
humanMessage: `Planned ${planned.tool.name} (${planned.plan.id}) with risk=${planned.plan.riskLevel} and cost=${planned.plan.creditCost} credits.`,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
let currentPlan = planned.plan;
|
|
845
|
+
if (currentPlan.approvalStatus === "pending_approval" &&
|
|
846
|
+
params.autoApprove !== false) {
|
|
847
|
+
currentPlan = this.approvePlan(ctx, currentPlan.id, "approved");
|
|
848
|
+
}
|
|
849
|
+
if (currentPlan.approvalStatus === "pending_approval") {
|
|
850
|
+
return {
|
|
851
|
+
ok: true,
|
|
852
|
+
executed: false,
|
|
853
|
+
status: "awaiting_approval",
|
|
854
|
+
intent: resolution,
|
|
855
|
+
resolution,
|
|
856
|
+
plan: currentPlan,
|
|
857
|
+
humanMessage: `Plan ${currentPlan.id} (${currentPlan.toolSlug}, risk=${currentPlan.riskLevel}) requires owner approval. Run 'muxo approve ${currentPlan.id}' or pass --yes to auto-approve.`,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
const executed = await this.executePlanForRisk(ctx, currentPlan.id, currentPlan.riskLevel);
|
|
861
|
+
const isSuccess = executed.run.status === "succeeded";
|
|
862
|
+
return {
|
|
863
|
+
ok: isSuccess,
|
|
864
|
+
executed: isSuccess,
|
|
865
|
+
status: "executed",
|
|
866
|
+
intent: resolution,
|
|
867
|
+
resolution,
|
|
868
|
+
plan: currentPlan,
|
|
869
|
+
run: executed.run,
|
|
870
|
+
receipt: executed.receipt,
|
|
871
|
+
humanMessage: isSuccess
|
|
872
|
+
? `Completed ${resolution.tool.name} (${resolution.tool.slug}) in ${executed.run.durationMs ?? 0}ms [receipt: ${executed.receipt.id}, funding: ${executed.receipt.supplierSettlement?.fundingMode || "muxo_funded"}].`
|
|
873
|
+
: `Execution of ${resolution.tool.slug} failed: ${executed.run.error || "Unknown error"}`,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
}
|