@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/auth.d.ts +2 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +401 -314
- package/dist/auth.js.map +1 -1
- package/dist/capabilities/connection.js +1 -1
- package/dist/capabilities/intake.js +1 -1
- package/dist/capabilities/version-control.js +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +441 -39455
- package/dist/factory.js.map +1 -1
- package/dist/index.js +2 -39466
- package/dist/integrations/base.js +1 -1
- package/dist/integrations/github/config.js +36 -22
- package/dist/integrations/github/config.js.map +1 -1
- package/dist/integrations/github/factory-session.js +33 -37
- package/dist/integrations/github/factory-session.js.map +1 -1
- package/dist/integrations/github/integration.js +1037 -3624
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/issue-triage.js +71 -71
- package/dist/integrations/github/issue-triage.js.map +1 -1
- package/dist/integrations/github/pat.js +47 -36
- package/dist/integrations/github/pat.js.map +1 -1
- package/dist/integrations/github/provenance.js +72 -101
- package/dist/integrations/github/provenance.js.map +1 -1
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +1272 -1833
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.js +203 -471
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +2 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +401 -326
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/session-subscriptions.js +165 -223
- package/dist/integrations/github/session-subscriptions.js.map +1 -1
- package/dist/integrations/github/subscriptions.js +43 -51
- package/dist/integrations/github/subscriptions.js.map +1 -1
- package/dist/integrations/github/token-refresh.js +15 -18
- package/dist/integrations/github/token-refresh.js.map +1 -1
- package/dist/integrations/github/webhook.js +359 -343
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/linear/agent-tools.js +77 -100
- package/dist/integrations/linear/agent-tools.js.map +1 -1
- package/dist/integrations/linear/integration.js +611 -1309
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.js +227 -230
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.js +126 -346
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/linear/storage.js +1 -1
- package/dist/integrations/platform/api-client.js +131 -135
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +275 -573
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +845 -3942
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/integration.js +457 -1309
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/workos/integration.js +80 -85
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +933 -915
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/custom-provider-source.js +110 -93
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/fs.js +401 -349
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/intake.js +165 -165
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/oauth.js +447 -25166
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/projects.js +437 -376
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/provider-credentials.js +76 -58
- package/dist/routes/provider-credentials.js.map +1 -1
- package/dist/routes/route.js +12 -8
- package/dist/routes/route.js.map +1 -1
- package/dist/routes/skills.js +160 -173
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.js +250 -28618
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.js +141 -166
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.js +654 -862
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +23 -35
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/defaults.js +200 -309
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +438 -677
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/index.js +5 -599
- package/dist/rules/processor.js +344 -596
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/resolve.js +21 -18
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/start-coordinator.js +168 -157
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.js +50 -85
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.js +183 -454
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.js +33 -29
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.js +266 -281
- package/dist/rules/validation.js.map +1 -1
- package/dist/sandbox/fleet.js +272 -250
- package/dist/sandbox/fleet.js.map +1 -1
- package/dist/sandbox/reattach.js +13 -6
- package/dist/sandbox/reattach.js.map +1 -1
- package/dist/server-error.js +12 -16
- package/dist/server-error.js.map +1 -1
- package/dist/skills/service.js +30 -35
- package/dist/skills/service.js.map +1 -1
- package/dist/spa-static.d.ts.map +1 -1
- package/dist/spa-static.js +91 -64
- package/dist/spa-static.js.map +1 -1
- package/dist/state-signing.js +73 -44
- package/dist/state-signing.js.map +1 -1
- package/dist/storage/domains/audit/agent-audit.js +60 -59
- package/dist/storage/domains/audit/agent-audit.js.map +1 -1
- package/dist/storage/domains/audit/base.js +169 -112
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.js +155 -153
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/credentials/base.js +259 -243
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.js +138 -143
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.js +75 -57
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.js +310 -245
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.js +117 -102
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/dist/storage/domains/model-packs/base.js +89 -88
- package/dist/storage/domains/model-packs/base.js.map +1 -1
- package/dist/storage/domains/projects/base.js +97 -92
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/queue-health/base.js +111 -76
- package/dist/storage/domains/queue-health/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +9 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +777 -702
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +322 -344
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +1266 -1132
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js +202 -184
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +34 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +2 -3
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +236 -438
- package/dist/workspace.js.map +1 -1
- package/factory-skills/factory-review/SKILL.md +9 -2
- package/package.json +9 -9
- package/dist/capabilities/connection.js.map +0 -1
- package/dist/capabilities/intake.js.map +0 -1
- package/dist/capabilities/version-control.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/base.js.map +0 -1
- package/dist/integrations/linear/storage.js.map +0 -1
- package/dist/rules/index.js.map +0 -1
package/dist/routes/skills.js
CHANGED
|
@@ -1,181 +1,168 @@
|
|
|
1
|
-
|
|
1
|
+
import { Route } from "./route.js";
|
|
2
|
+
import { SkillInvocationError, resolveSkillInvocation } from "../skills/service.js";
|
|
2
3
|
import { registerApiRoute } from "@mastra/core/server";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "SkillInvocationError";
|
|
11
|
-
this.code = code;
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
function escapeSkillBoundary(value) {
|
|
15
|
-
return value.replaceAll("</skill>", "</skill>");
|
|
16
|
-
}
|
|
17
|
-
async function resolveSkillInvocation(controller, input) {
|
|
18
|
-
const session = await controller.getSessionByResource(input.resourceId, input.scope);
|
|
19
|
-
if (!session) throw new SkillInvocationError("session_not_found", "Agent controller session not found.");
|
|
20
|
-
const skills = session.getWorkspace().skills;
|
|
21
|
-
await skills?.maybeRefresh();
|
|
22
|
-
const skill = await skills?.get(input.name);
|
|
23
|
-
if (!skill || skill["user-invocable"] === false) {
|
|
24
|
-
throw new SkillInvocationError("skill_not_found", `Skill not found: ${input.name}.`);
|
|
25
|
-
}
|
|
26
|
-
const args = input.arguments?.trim();
|
|
27
|
-
const content = `${formatSkillActivation(skill)}${args ? `
|
|
28
|
-
|
|
29
|
-
ARGUMENTS: ${args}` : ""}`.trim();
|
|
30
|
-
return {
|
|
31
|
-
session,
|
|
32
|
-
skillName: skill.name,
|
|
33
|
-
message: `<skill name="${skill.name}">
|
|
34
|
-
${escapeSkillBoundary(content)}
|
|
35
|
-
</skill>`
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// src/routes/route.ts
|
|
40
|
-
var Route = class {
|
|
41
|
-
deps;
|
|
42
|
-
constructor(deps) {
|
|
43
|
-
this.deps = deps;
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// src/routes/skills.ts
|
|
48
|
-
var MAX_RESOURCE_ID_LENGTH = 512;
|
|
49
|
-
var MAX_SCOPE_LENGTH = 2048;
|
|
50
|
-
var MAX_ARGUMENTS_LENGTH = 16384;
|
|
51
|
-
var SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
52
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
4
|
+
//#region src/routes/skills.ts
|
|
5
|
+
const MAX_RESOURCE_ID_LENGTH = 512;
|
|
6
|
+
const MAX_SCOPE_LENGTH = 2048;
|
|
7
|
+
const MAX_ARGUMENTS_LENGTH = 16384;
|
|
8
|
+
const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
9
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
53
10
|
function loose(context) {
|
|
54
|
-
|
|
11
|
+
return context;
|
|
55
12
|
}
|
|
56
13
|
function parseBody(value) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
...input.scope ? { scope: input.scope } : {},
|
|
75
|
-
name: input.name,
|
|
76
|
-
...input.arguments !== void 0 ? { arguments: input.arguments } : {}
|
|
77
|
-
};
|
|
14
|
+
if (!value || typeof value !== "object") return void 0;
|
|
15
|
+
const input = value;
|
|
16
|
+
if (typeof input.resourceId !== "string" || input.resourceId.length === 0) return void 0;
|
|
17
|
+
if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return void 0;
|
|
18
|
+
if (input.projectRepositoryId !== void 0 && (typeof input.projectRepositoryId !== "string" || !UUID_RE.test(input.projectRepositoryId))) return;
|
|
19
|
+
if (input.scope !== void 0 && (typeof input.scope !== "string" || input.scope.length > MAX_SCOPE_LENGTH)) return;
|
|
20
|
+
if (typeof input.name !== "string" || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return void 0;
|
|
21
|
+
if (input.arguments !== void 0) {
|
|
22
|
+
if (typeof input.arguments !== "string" || input.arguments.length > MAX_ARGUMENTS_LENGTH) return void 0;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
resourceId: input.resourceId,
|
|
26
|
+
...input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {},
|
|
27
|
+
...input.scope ? { scope: input.scope } : {},
|
|
28
|
+
name: input.name,
|
|
29
|
+
...input.arguments !== void 0 ? { arguments: input.arguments } : {}
|
|
30
|
+
};
|
|
78
31
|
}
|
|
79
32
|
var SkillRoutes = class extends Route {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
33
|
+
async #authorizeSessionAddress(context, address) {
|
|
34
|
+
const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;
|
|
35
|
+
if (!auth.enabled()) return { allowed: true };
|
|
36
|
+
await auth.ensureUser(context);
|
|
37
|
+
const tenant = auth.tenant(context);
|
|
38
|
+
if (!tenant) return {
|
|
39
|
+
allowed: false,
|
|
40
|
+
status: 401,
|
|
41
|
+
code: "unauthorized",
|
|
42
|
+
message: "Authentication required."
|
|
43
|
+
};
|
|
44
|
+
if (address.resourceId === tenant.userId) return { allowed: true };
|
|
45
|
+
if (!UUID_RE.test(address.resourceId) || !address.projectRepositoryId || !UUID_RE.test(address.projectRepositoryId)) return {
|
|
46
|
+
allowed: false,
|
|
47
|
+
status: 400,
|
|
48
|
+
code: "invalid_request",
|
|
49
|
+
message: "Invalid skill invocation request."
|
|
50
|
+
};
|
|
51
|
+
if (!tenant.orgId || !address.scope) return {
|
|
52
|
+
allowed: false,
|
|
53
|
+
status: 403,
|
|
54
|
+
code: "session_forbidden",
|
|
55
|
+
message: "Session access denied."
|
|
56
|
+
};
|
|
57
|
+
if (!storage) return {
|
|
58
|
+
allowed: false,
|
|
59
|
+
status: 403,
|
|
60
|
+
code: "session_forbidden",
|
|
61
|
+
message: "Session access denied."
|
|
62
|
+
};
|
|
63
|
+
if (ensureSourceControlReady) try {
|
|
64
|
+
await ensureSourceControlReady();
|
|
65
|
+
} catch {
|
|
66
|
+
return {
|
|
67
|
+
allowed: false,
|
|
68
|
+
status: 403,
|
|
69
|
+
code: "session_forbidden",
|
|
70
|
+
message: "Session access denied."
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const projectRepository = await storage.projectRepositories.get({
|
|
74
|
+
orgId: tenant.orgId,
|
|
75
|
+
id: address.projectRepositoryId
|
|
76
|
+
});
|
|
77
|
+
if (!projectRepository) return {
|
|
78
|
+
allowed: false,
|
|
79
|
+
status: 403,
|
|
80
|
+
code: "session_forbidden",
|
|
81
|
+
message: "Session access denied."
|
|
82
|
+
};
|
|
83
|
+
const connection = await storage.connections.get({
|
|
84
|
+
orgId: tenant.orgId,
|
|
85
|
+
id: projectRepository.connectionId
|
|
86
|
+
});
|
|
87
|
+
if (!connection || connection.factoryProjectId !== address.resourceId) return {
|
|
88
|
+
allowed: false,
|
|
89
|
+
status: 403,
|
|
90
|
+
code: "session_forbidden",
|
|
91
|
+
message: "Session access denied."
|
|
92
|
+
};
|
|
93
|
+
return await storage.worktrees.findByPath({
|
|
94
|
+
projectRepositoryId: address.projectRepositoryId,
|
|
95
|
+
userId: tenant.userId,
|
|
96
|
+
worktreePath: address.scope
|
|
97
|
+
}) ? { allowed: true } : {
|
|
98
|
+
allowed: false,
|
|
99
|
+
status: 403,
|
|
100
|
+
code: "session_forbidden",
|
|
101
|
+
message: "Session access denied."
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
routes() {
|
|
105
|
+
const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;
|
|
106
|
+
const authorize = customAuthorize ?? ((context, address) => this.#authorizeSessionAddress(context, address));
|
|
107
|
+
const handleSkillRequest = async (context, dispatch) => {
|
|
108
|
+
const c = loose(context);
|
|
109
|
+
if (c.req.param("controllerId") !== controllerId) return c.json({
|
|
110
|
+
error: "controller_not_found",
|
|
111
|
+
message: "Agent controller not found."
|
|
112
|
+
}, 404);
|
|
113
|
+
let rawBody;
|
|
114
|
+
try {
|
|
115
|
+
rawBody = await c.req.json();
|
|
116
|
+
} catch {
|
|
117
|
+
return c.json({
|
|
118
|
+
error: "invalid_request",
|
|
119
|
+
message: "Invalid JSON body."
|
|
120
|
+
}, 400);
|
|
121
|
+
}
|
|
122
|
+
const body = parseBody(rawBody);
|
|
123
|
+
if (!body) return c.json({
|
|
124
|
+
error: "invalid_request",
|
|
125
|
+
message: "Invalid skill invocation request."
|
|
126
|
+
}, 400);
|
|
127
|
+
const authorization = await authorize(c, {
|
|
128
|
+
resourceId: body.resourceId,
|
|
129
|
+
projectRepositoryId: body.projectRepositoryId,
|
|
130
|
+
scope: body.scope
|
|
131
|
+
});
|
|
132
|
+
if (!authorization.allowed) return c.json({
|
|
133
|
+
error: authorization.code,
|
|
134
|
+
message: authorization.message
|
|
135
|
+
}, authorization.status ?? 403);
|
|
136
|
+
try {
|
|
137
|
+
const resolved = await resolveSkillInvocation(controller, body);
|
|
138
|
+
if (dispatch) resolved.session.sendMessage({ content: resolved.message }).catch((error) => {
|
|
139
|
+
console.error("Workspace skill dispatch failed after acceptance", error);
|
|
140
|
+
});
|
|
141
|
+
return c.json({
|
|
142
|
+
ok: true,
|
|
143
|
+
skill: resolved.skillName,
|
|
144
|
+
message: resolved.message
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error instanceof SkillInvocationError) return c.json({
|
|
148
|
+
error: error.code,
|
|
149
|
+
message: error.message
|
|
150
|
+
}, 404);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return [registerApiRoute("/web/agent-controller/:controllerId/skills/prepare", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
requiresAuth: false,
|
|
157
|
+
handler: (context) => handleSkillRequest(context, false)
|
|
158
|
+
}), registerApiRoute("/web/agent-controller/:controllerId/skills/invoke", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
requiresAuth: false,
|
|
161
|
+
handler: (context) => handleSkillRequest(context, true)
|
|
162
|
+
})];
|
|
163
|
+
}
|
|
180
164
|
};
|
|
165
|
+
//#endregion
|
|
166
|
+
export { SkillRoutes };
|
|
167
|
+
|
|
181
168
|
//# sourceMappingURL=skills.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routes/skills.ts","../../src/skills/service.ts","../../src/routes/route.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const worktree = await storage.worktrees.findByPath({\n projectRepositoryId: address.projectRepositoryId,\n userId: tenant.userId,\n worktreePath: address.scope,\n });\n return worktree\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n void resolved.session.sendMessage({ content: resolved.message }).catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n return [\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n","import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { SendAgentSignalResult } from '@mastra/core/agent';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { formatSkillActivation } from '@mastra/core/workspace';\nimport type { Workspace } from '@mastra/core/workspace';\n\nexport interface SkillInvocationInput {\n resourceId: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\nexport interface SkillSession {\n getWorkspace(): Workspace;\n sendMessage(input: { content: string }): Promise<unknown>;\n sendNotificationSignal(\n input: {\n source: string;\n kind: string;\n summary: string;\n priority?: 'low' | 'medium' | 'high' | 'urgent';\n payload?: unknown;\n dedupeKey?: string;\n sourceId?: string;\n },\n options?: {\n ifActive?: { behavior?: 'deliver' | 'persist' };\n ifIdle?: { behavior?: 'persist' | 'wake' };\n requestContext?: RequestContext;\n },\n ): Promise<Pick<SendAgentSignalResult, 'persisted' | 'accepted'>>;\n}\n\nexport class SkillInvocationError extends Error {\n readonly code: 'session_not_found' | 'skill_not_found';\n\n constructor(code: SkillInvocationError['code'], message: string) {\n super(message);\n this.name = 'SkillInvocationError';\n this.code = code;\n }\n}\n\nfunction escapeSkillBoundary(value: string): string {\n return value.replaceAll('</skill>', '</skill>');\n}\n\nexport async function resolveSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ session: SkillSession; skillName: string; message: string }> {\n const session = (await controller.getSessionByResource(input.resourceId, input.scope)) as SkillSession | undefined;\n if (!session) throw new SkillInvocationError('session_not_found', 'Agent controller session not found.');\n\n const skills = session.getWorkspace().skills;\n await skills?.maybeRefresh();\n const skill = await skills?.get(input.name);\n if (!skill || skill['user-invocable'] === false) {\n throw new SkillInvocationError('skill_not_found', `Skill not found: ${input.name}.`);\n }\n\n const args = input.arguments?.trim();\n const content = `${formatSkillActivation(skill)}${args ? `\\n\\nARGUMENTS: ${args}` : ''}`.trim();\n return {\n session,\n skillName: skill.name,\n message: `<skill name=\"${skill.name}\">\\n${escapeSkillBoundary(content)}\\n</skill>`,\n };\n}\n\nexport async function dispatchSkillInvocation(\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>,\n input: SkillInvocationInput,\n): Promise<{ skillName: string; message: string }> {\n const resolved = await resolveSkillInvocation(controller, input);\n await resolved.session.sendMessage({ content: resolved.message });\n return { skillName: resolved.skillName, message: resolved.message };\n}\n","/**\n * Base class for factory route modules.\n *\n * Route modules build Mastra `apiRoutes` from injected dependencies instead of\n * reaching into host globals. The host server (e.g. `mastracode/web`) supplies\n * the auth seam and storage domain handles at construction time, so the routes\n * stay portable and testable with fakes.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\n/**\n * The auth surface factory routes need, implemented by the host server.\n *\n * Local (no-auth) deployments implement this with a stub where `enabled()`\n * returns `false` and `tenant()` returns `undefined` — routes then take their\n * single-user local paths.\n */\nexport interface RouteAuth {\n /** Whether an auth provider is active (tenant mode). */\n enabled(): boolean;\n /**\n * Resolve (and cache) the signed-in user for the request, if any. Must be\n * called before `tenant()` so the request context is populated.\n */\n ensureUser(c: Context): Promise<unknown>;\n /** Tenant identity for the request, when signed in. */\n tenant(c: Context): { orgId?: string; userId: string } | undefined;\n /** Fail-closed check that the caller administers the given organization. */\n isOrganizationAdmin(c: Context, organizationId: string): Promise<boolean>;\n}\n\n/** Dependencies shared by every factory route module. */\nexport interface RouteDependencies {\n auth: RouteAuth;\n}\n\n/**\n * A route module: constructed once at boot with its dependencies, then asked\n * for the `ApiRoute[]` it serves.\n */\nexport abstract class Route<TDeps extends RouteDependencies = RouteDependencies> {\n protected readonly deps: TDeps;\n\n constructor(deps: TDeps) {\n this.deps = deps;\n }\n\n /** Build the Mastra `apiRoutes` served by this module. */\n abstract routes(): ApiRoute[];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"file":"skills.js","names":["#authorizeSessionAddress"],"sources":["../../src/routes/skills.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { resolveSkillInvocation, SkillInvocationError } from '../skills/service.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nconst MAX_RESOURCE_ID_LENGTH = 512;\nconst MAX_SCOPE_LENGTH = 2048;\nconst MAX_ARGUMENTS_LENGTH = 16_384;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\ninterface SkillInvocationBody {\n resourceId: string;\n projectRepositoryId?: string;\n scope?: string;\n name: string;\n arguments?: string;\n}\n\ninterface SessionAuthorizationResult {\n allowed: boolean;\n status?: 400 | 401 | 403;\n code?: 'invalid_request' | 'unauthorized' | 'session_forbidden';\n message?: string;\n}\n\nexport interface SkillRoutesDeps extends RouteDependencies {\n controllerId: string;\n controller: Pick<AgentController<MastraCodeState>, 'getSessionByResource'>;\n sourceControlStorage?: SourceControlStorageHandle;\n ensureSourceControlReady?: () => Promise<void>;\n authorizeSessionAddress?: (\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ) => Promise<SessionAuthorizationResult>;\n}\n\nfunction loose(context: unknown): Context {\n return context as Context;\n}\n\nfunction parseBody(value: unknown): SkillInvocationBody | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Record<string, unknown>;\n if (typeof input.resourceId !== 'string' || input.resourceId.length === 0) return undefined;\n if (input.resourceId.length > MAX_RESOURCE_ID_LENGTH) return undefined;\n if (\n input.projectRepositoryId !== undefined &&\n (typeof input.projectRepositoryId !== 'string' || !UUID_RE.test(input.projectRepositoryId))\n ) {\n return undefined;\n }\n if (input.scope !== undefined && (typeof input.scope !== 'string' || input.scope.length > MAX_SCOPE_LENGTH)) {\n return undefined;\n }\n if (typeof input.name !== 'string' || input.name.length > 64 || !SKILL_NAME_RE.test(input.name)) return undefined;\n if (input.arguments !== undefined) {\n if (typeof input.arguments !== 'string' || input.arguments.length > MAX_ARGUMENTS_LENGTH) return undefined;\n }\n return {\n resourceId: input.resourceId,\n ...(input.projectRepositoryId ? { projectRepositoryId: input.projectRepositoryId } : {}),\n ...(input.scope ? { scope: input.scope } : {}),\n name: input.name,\n ...(input.arguments !== undefined ? { arguments: input.arguments } : {}),\n };\n}\n\nexport class SkillRoutes extends Route<SkillRoutesDeps> {\n async #authorizeSessionAddress(\n context: Context,\n address: { resourceId: string; projectRepositoryId?: string; scope?: string },\n ): Promise<SessionAuthorizationResult> {\n const { auth, sourceControlStorage: storage, ensureSourceControlReady } = this.deps;\n if (!auth.enabled()) return { allowed: true };\n\n await auth.ensureUser(context);\n const tenant = auth.tenant(context);\n if (!tenant) {\n return { allowed: false, status: 401, code: 'unauthorized', message: 'Authentication required.' };\n }\n\n // Personal sessions are keyed by the authenticated WorkOS user id. Their\n // scope is a client-managed local/user-session worktree and needs no app DB.\n if (address.resourceId === tenant.userId) return { allowed: true };\n\n // Factory sessions are keyed by factoryProjectId and explicitly identify the\n // linked repository whose user-owned worktree supplies the session scope.\n if (\n !UUID_RE.test(address.resourceId) ||\n !address.projectRepositoryId ||\n !UUID_RE.test(address.projectRepositoryId)\n ) {\n return { allowed: false, status: 400, code: 'invalid_request', message: 'Invalid skill invocation request.' };\n }\n if (!tenant.orgId || !address.scope) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (!storage) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n if (ensureSourceControlReady) {\n try {\n await ensureSourceControlReady();\n } catch {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n }\n const projectRepository = await storage.projectRepositories.get({\n orgId: tenant.orgId,\n id: address.projectRepositoryId,\n });\n if (!projectRepository) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const connection = await storage.connections.get({ orgId: tenant.orgId, id: projectRepository.connectionId });\n if (!connection || connection.factoryProjectId !== address.resourceId) {\n return { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n const worktree = await storage.worktrees.findByPath({\n projectRepositoryId: address.projectRepositoryId,\n userId: tenant.userId,\n worktreePath: address.scope,\n });\n return worktree\n ? { allowed: true }\n : { allowed: false, status: 403, code: 'session_forbidden', message: 'Session access denied.' };\n }\n\n routes(): ApiRoute[] {\n const { controllerId, controller, authorizeSessionAddress: customAuthorize } = this.deps;\n const authorize =\n customAuthorize ??\n ((context: Context, address: { resourceId: string; projectRepositoryId?: string; scope?: string }) =>\n this.#authorizeSessionAddress(context, address));\n const handleSkillRequest = async (context: unknown, dispatch: boolean) => {\n const c = loose(context);\n if (c.req.param('controllerId') !== controllerId) {\n return c.json({ error: 'controller_not_found', message: 'Agent controller not found.' }, 404);\n }\n\n let rawBody: unknown;\n try {\n rawBody = await c.req.json();\n } catch {\n return c.json({ error: 'invalid_request', message: 'Invalid JSON body.' }, 400);\n }\n const body = parseBody(rawBody);\n if (!body) {\n return c.json({ error: 'invalid_request', message: 'Invalid skill invocation request.' }, 400);\n }\n\n const authorization = await authorize(c, {\n resourceId: body.resourceId,\n projectRepositoryId: body.projectRepositoryId,\n scope: body.scope,\n });\n if (!authorization.allowed) {\n return c.json({ error: authorization.code, message: authorization.message }, authorization.status ?? 403);\n }\n\n try {\n const resolved = await resolveSkillInvocation(controller, body);\n if (dispatch) {\n void resolved.session.sendMessage({ content: resolved.message }).catch((error: unknown) => {\n console.error('Workspace skill dispatch failed after acceptance', error);\n });\n }\n return c.json({ ok: true, skill: resolved.skillName, message: resolved.message });\n } catch (error) {\n if (error instanceof SkillInvocationError) {\n return c.json({ error: error.code, message: error.message }, 404);\n }\n throw error;\n }\n };\n\n return [\n registerApiRoute('/web/agent-controller/:controllerId/skills/prepare', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, false),\n }),\n registerApiRoute('/web/agent-controller/:controllerId/skills/invoke', {\n method: 'POST',\n requiresAuth: false,\n handler: context => handleSkillRequest(context, true),\n }),\n ];\n }\n}\n"],"mappings":"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"}
|