@opengeni/api-router 0.22.2 → 0.23.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/dist/app.js +1 -1
- package/dist/auth/managed-auth.d.ts +0 -30
- package/dist/{chunk-HWXJW5C7.js → chunk-T4T2PGU4.js} +3036 -1362
- package/dist/chunk-T4T2PGU4.js.map +1 -0
- package/dist/http/sse.d.ts +2 -0
- package/dist/index.js +29 -9
- package/dist/index.js.map +1 -1
- package/dist/integrations/oauth-client.d.ts +8 -0
- package/dist/integrations/slack-interactions.d.ts +7 -1
- package/dist/mcp/receipts.d.ts +28 -0
- package/dist/mcp/scheduled-task-view.d.ts +350 -0
- package/dist/mcp/toolspace.d.ts +9 -0
- package/dist/sandbox/auth-callout.d.ts +2 -0
- package/dist/sandbox/channel-a.d.ts +5 -1
- package/package.json +12 -12
- package/src/app.ts +3 -4
- package/src/auth/managed-auth.ts +0 -16
- package/src/http/sse.ts +101 -6
- package/src/index.ts +28 -3
- package/src/integrations/oauth-client.ts +36 -56
- package/src/integrations/slack-interactions.ts +123 -15
- package/src/mcp/documents.ts +42 -25
- package/src/mcp/receipts.ts +95 -0
- package/src/mcp/scheduled-task-view.ts +608 -0
- package/src/mcp/server.ts +812 -182
- package/src/mcp/toolspace.ts +75 -71
- package/src/observability.ts +3 -3
- package/src/routes/api-keys.ts +7 -1
- package/src/routes/codex.ts +7 -4
- package/src/routes/connections.ts +74 -3
- package/src/routes/enrollments.ts +54 -12
- package/src/routes/environments.ts +60 -11
- package/src/routes/files.ts +175 -65
- package/src/routes/install.ts +31 -1
- package/src/routes/machines.ts +1 -1
- package/src/routes/scheduled-tasks.ts +39 -14
- package/src/routes/sessions.ts +77 -12
- package/src/routes/transcription-recordings.ts +65 -33
- package/src/sandbox/auth-callout.ts +16 -4
- package/src/sandbox/channel-a.ts +124 -7
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +1 -1
- package/src/sandbox/viewer.ts +29 -20
- package/dist/chunk-HWXJW5C7.js.map +0 -1
package/src/mcp/server.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
CreateScheduledTaskRequest,
|
|
4
4
|
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type SessionAuthorizationOperation,
|
|
25
25
|
type SessionAuthorizationSurface,
|
|
26
26
|
type Session,
|
|
27
|
+
type ScheduledTask,
|
|
27
28
|
UpdateScheduledTaskRequest,
|
|
28
29
|
normalizeWorkspaceArtifactSlug,
|
|
29
30
|
WORKSPACE_ARTIFACT_HTML_MAX_UTF8_BYTES,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
countVariableSets,
|
|
34
35
|
beginRigChangeVerificationAttempt,
|
|
35
36
|
createVariableSet,
|
|
37
|
+
decryptVariableSetValue,
|
|
36
38
|
deleteScheduledTask,
|
|
37
39
|
encryptVariableSetValue,
|
|
38
40
|
getSession,
|
|
@@ -56,6 +58,8 @@ import {
|
|
|
56
58
|
listRigVersionMonitoringSummaries,
|
|
57
59
|
listSocialPosts,
|
|
58
60
|
recordAuditEvent,
|
|
61
|
+
removeEnrollment,
|
|
62
|
+
readVariableSetSecretAtomically,
|
|
59
63
|
recordSyncedSocialPosts,
|
|
60
64
|
listVariableSets,
|
|
61
65
|
MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
@@ -95,8 +99,10 @@ import type { AnySchema, ZodRawShapeCompat } from "@modelcontextprotocol/sdk/ser
|
|
|
95
99
|
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
96
100
|
import * as z4 from "zod/v4";
|
|
97
101
|
import {
|
|
102
|
+
hasLiteralPermission,
|
|
98
103
|
hasPermission,
|
|
99
104
|
authorizedSocialConnectionsForGrant,
|
|
105
|
+
requireLiveAgentAttemptAuthorization,
|
|
100
106
|
requireSessionAuthorization,
|
|
101
107
|
requireSessionAuthorizationListScope,
|
|
102
108
|
type ResolvedSessionAuthorization,
|
|
@@ -132,17 +138,21 @@ import {
|
|
|
132
138
|
createValidatedScheduledTask,
|
|
133
139
|
manualScheduledTaskTriggerUsageKey,
|
|
134
140
|
manualScheduledTaskTriggerWorkflowId,
|
|
141
|
+
scheduledTaskForGrant,
|
|
142
|
+
scheduledTaskRunForGrant,
|
|
135
143
|
scheduledTaskToolsProvided,
|
|
136
144
|
scheduledTaskTriggerToken,
|
|
145
|
+
ScheduledTaskSyncError,
|
|
137
146
|
syncCreatedScheduledTask,
|
|
138
147
|
syncUpdatedScheduledTask,
|
|
148
|
+
validateScheduledTaskTarget,
|
|
139
149
|
validatedScheduledTaskUpdate,
|
|
140
150
|
} from "@opengeni/core";
|
|
141
151
|
import {
|
|
142
|
-
|
|
152
|
+
acceptSessionUserMessageWithOutcome,
|
|
143
153
|
controlAgentSessionWorkstream,
|
|
144
154
|
controlHumanSessionWorkstream,
|
|
145
|
-
|
|
155
|
+
createSessionForRequestWithOutcome,
|
|
146
156
|
SessionSpawnDeniedError,
|
|
147
157
|
sessionSpawnDenialEnvelope,
|
|
148
158
|
sendAgentSessionMessage,
|
|
@@ -170,6 +180,12 @@ import {
|
|
|
170
180
|
boundRigDetailMcp,
|
|
171
181
|
SESSION_EVENT_MCP_MAX_BYTES,
|
|
172
182
|
} from "./session-view";
|
|
183
|
+
import { mcpMutationReceipt, sessionCreateMutationReceipt } from "./receipts";
|
|
184
|
+
import {
|
|
185
|
+
boundScheduledTaskDetailMcp,
|
|
186
|
+
boundScheduledTaskMcpPage,
|
|
187
|
+
scheduledTaskMcpSummary,
|
|
188
|
+
} from "./scheduled-task-view";
|
|
173
189
|
import type { ToolspaceMcpSurface } from "./toolspace";
|
|
174
190
|
import { ensureSessionGroupReady as ensureViewerSessionGroupReady } from "../sandbox/viewer";
|
|
175
191
|
import {
|
|
@@ -205,13 +221,17 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
205
221
|
memory_search: { sessionRequired: true, allOf: ["documents:search"] },
|
|
206
222
|
memory_save: { sessionRequired: true, allOf: ["documents:search"] },
|
|
207
223
|
memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
|
|
208
|
-
preference_registry_summary: {
|
|
224
|
+
preference_registry_summary: {
|
|
225
|
+
sessionRequired: true,
|
|
226
|
+
allOf: ["workspace:read"],
|
|
227
|
+
},
|
|
209
228
|
preference_registry_get: { sessionRequired: true, allOf: ["workspace:read"] },
|
|
210
229
|
sandboxes_list: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
211
230
|
sandbox_attach: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
212
231
|
sandbox_swap: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
213
232
|
run_on: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
214
233
|
sandbox_provision: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
234
|
+
connected_machine_remove: { allOf: ["enrollments:manage"] },
|
|
215
235
|
rig_list: { allOf: ["rigs:use"] },
|
|
216
236
|
rig_get: { allOf: ["rigs:use"] },
|
|
217
237
|
rig_propose_change: { allOf: ["rigs:use"] },
|
|
@@ -226,10 +246,16 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
226
246
|
session_resume: { allOf: ["sessions:control"] },
|
|
227
247
|
session_steer: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
228
248
|
set_other_session_title: { allOf: ["sessions:control"] },
|
|
229
|
-
variable_set_list: { allOf: ["variable-sets:
|
|
230
|
-
environment_list: { allOf: ["variable-sets:
|
|
231
|
-
|
|
232
|
-
|
|
249
|
+
variable_set_list: { allOf: ["variable-sets:list", "secrets:list"] },
|
|
250
|
+
environment_list: { allOf: ["variable-sets:list", "secrets:list"] },
|
|
251
|
+
variable_set_get_variable: {
|
|
252
|
+
sessionRequired: true,
|
|
253
|
+
allOf: ["variable-sets:read", "secrets:read"],
|
|
254
|
+
},
|
|
255
|
+
variable_set_set_variable: {
|
|
256
|
+
allOf: ["variable-sets:write", "secrets:write"],
|
|
257
|
+
},
|
|
258
|
+
environment_set_variable: { allOf: ["variable-sets:write", "secrets:write"] },
|
|
233
259
|
github_connect_link: { allOf: ["github:use"] },
|
|
234
260
|
github_repositories_list: { allOf: ["github:use"] },
|
|
235
261
|
social_connections_list: { allOf: ["connections:read"] },
|
|
@@ -244,15 +270,21 @@ const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
244
270
|
// Publishes under the user's identity: connections:write keeps it out of the
|
|
245
271
|
// default agent permission set, unlike the read-only social tools above.
|
|
246
272
|
social_post_reply: { allOf: ["connections:write"] },
|
|
247
|
-
scheduled_tasks_list: {
|
|
248
|
-
|
|
273
|
+
scheduled_tasks_list: {
|
|
274
|
+
anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"],
|
|
275
|
+
},
|
|
276
|
+
scheduled_tasks_get: {
|
|
277
|
+
anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"],
|
|
278
|
+
},
|
|
249
279
|
scheduled_tasks_create: { allOf: ["scheduled_tasks:manage"] },
|
|
250
280
|
scheduled_tasks_update: { allOf: ["scheduled_tasks:manage"] },
|
|
251
281
|
scheduled_tasks_pause: { allOf: ["scheduled_tasks:manage"] },
|
|
252
282
|
scheduled_tasks_resume: { allOf: ["scheduled_tasks:manage"] },
|
|
253
283
|
scheduled_tasks_trigger: { allOf: ["scheduled_tasks:run"] },
|
|
254
284
|
scheduled_tasks_delete: { allOf: ["scheduled_tasks:manage"] },
|
|
255
|
-
scheduled_task_runs_list: {
|
|
285
|
+
scheduled_task_runs_list: {
|
|
286
|
+
anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"],
|
|
287
|
+
},
|
|
256
288
|
slack_bot_list_channels: { allOf: ["connections:read"] },
|
|
257
289
|
slack_bot_channel_history: { allOf: ["connections:read"] },
|
|
258
290
|
slack_bot_thread_replies: { allOf: ["connections:read"] },
|
|
@@ -435,6 +467,9 @@ export function buildOpenGeniMcpServer(
|
|
|
435
467
|
if (!toolspaceMode && sessionId !== null && deps.settings.sandboxSelfhostedEnabled) {
|
|
436
468
|
registerFleetTools(server, deps, grant, sessionId, json);
|
|
437
469
|
}
|
|
470
|
+
if (!toolspaceMode && can("enrollments:manage") && deps.settings.sandboxSelfhostedEnabled) {
|
|
471
|
+
registerConnectedMachineTools(server, deps, grant, json);
|
|
472
|
+
}
|
|
438
473
|
if (!toolspaceMode) {
|
|
439
474
|
registerRigTools(server, deps, grant, can, sessionId, json);
|
|
440
475
|
registerSlackBotTools(server, deps, grant, sessionId, json);
|
|
@@ -452,7 +487,7 @@ export function buildOpenGeniMcpServer(
|
|
|
452
487
|
// session by setting a narrower session.firstPartyMcpPermissions (capped to
|
|
453
488
|
// the creator's own grant); operators still cap what any session can be given.
|
|
454
489
|
registerWorkspaceOrchestrationTools(server, deps, grant, can, sessionId, toolspaceMode, json);
|
|
455
|
-
registerVariableSetTools(server, deps, grant, can, json);
|
|
490
|
+
registerVariableSetTools(server, deps, grant, can, sessionId, toolspaceMode, json);
|
|
456
491
|
if (can("github:use")) {
|
|
457
492
|
registerGitHubConnectTool(server, deps, grant, options, json);
|
|
458
493
|
}
|
|
@@ -620,10 +655,17 @@ export function buildOpenGeniMcpServer(
|
|
|
620
655
|
const authority = await requireAuthorizedSocialConnection(connectionId);
|
|
621
656
|
const result = await socialSearchLive(
|
|
622
657
|
deps,
|
|
623
|
-
{
|
|
658
|
+
{
|
|
659
|
+
workspaceId: grant.workspaceId,
|
|
660
|
+
connectionId,
|
|
661
|
+
subjectId: authority.subjectId,
|
|
662
|
+
},
|
|
624
663
|
{ query, subreddit, limit },
|
|
625
664
|
);
|
|
626
|
-
return json({
|
|
665
|
+
return json({
|
|
666
|
+
provider: result.connection.provider,
|
|
667
|
+
posts: result.posts,
|
|
668
|
+
});
|
|
627
669
|
},
|
|
628
670
|
);
|
|
629
671
|
|
|
@@ -642,10 +684,17 @@ export function buildOpenGeniMcpServer(
|
|
|
642
684
|
const authority = await requireAuthorizedSocialConnection(connectionId);
|
|
643
685
|
const result = await socialMentionsLive(
|
|
644
686
|
deps,
|
|
645
|
-
{
|
|
687
|
+
{
|
|
688
|
+
workspaceId: grant.workspaceId,
|
|
689
|
+
connectionId,
|
|
690
|
+
subjectId: authority.subjectId,
|
|
691
|
+
},
|
|
646
692
|
{ sinceId, limit },
|
|
647
693
|
);
|
|
648
|
-
return json({
|
|
694
|
+
return json({
|
|
695
|
+
provider: result.connection.provider,
|
|
696
|
+
posts: result.posts,
|
|
697
|
+
});
|
|
649
698
|
},
|
|
650
699
|
);
|
|
651
700
|
|
|
@@ -664,10 +713,17 @@ export function buildOpenGeniMcpServer(
|
|
|
664
713
|
const authority = await requireAuthorizedSocialConnection(connectionId);
|
|
665
714
|
const result = await socialThreadLive(
|
|
666
715
|
deps,
|
|
667
|
-
{
|
|
716
|
+
{
|
|
717
|
+
workspaceId: grant.workspaceId,
|
|
718
|
+
connectionId,
|
|
719
|
+
subjectId: authority.subjectId,
|
|
720
|
+
},
|
|
668
721
|
{ id, limit },
|
|
669
722
|
);
|
|
670
|
-
return json({
|
|
723
|
+
return json({
|
|
724
|
+
provider: result.connection.provider,
|
|
725
|
+
posts: result.posts,
|
|
726
|
+
});
|
|
671
727
|
},
|
|
672
728
|
);
|
|
673
729
|
}
|
|
@@ -690,7 +746,11 @@ export function buildOpenGeniMcpServer(
|
|
|
690
746
|
const authority = await requireAuthorizedSocialConnection(connectionId);
|
|
691
747
|
const result = await socialOwnPostsLive(
|
|
692
748
|
deps,
|
|
693
|
-
{
|
|
749
|
+
{
|
|
750
|
+
workspaceId: grant.workspaceId,
|
|
751
|
+
connectionId,
|
|
752
|
+
subjectId: authority.subjectId,
|
|
753
|
+
},
|
|
694
754
|
{ limit },
|
|
695
755
|
);
|
|
696
756
|
// A post without a provider timestamp is skipped rather than recorded
|
|
@@ -736,7 +796,11 @@ export function buildOpenGeniMcpServer(
|
|
|
736
796
|
const authority = await requireAuthorizedSocialConnection(connectionId);
|
|
737
797
|
const result = await socialPostReply(
|
|
738
798
|
deps,
|
|
739
|
-
{
|
|
799
|
+
{
|
|
800
|
+
workspaceId: grant.workspaceId,
|
|
801
|
+
connectionId,
|
|
802
|
+
subjectId: authority.subjectId,
|
|
803
|
+
},
|
|
740
804
|
{ inReplyToId, text },
|
|
741
805
|
);
|
|
742
806
|
// Outbound publishes leave a durable, secret-free receipt (house
|
|
@@ -769,22 +833,47 @@ export function buildOpenGeniMcpServer(
|
|
|
769
833
|
server.registerTool(
|
|
770
834
|
"scheduled_tasks_list",
|
|
771
835
|
{
|
|
772
|
-
description:
|
|
773
|
-
|
|
836
|
+
description:
|
|
837
|
+
"List compact scheduled-task summaries. Prompts, goal text, resource/tool bodies, and metadata values are represented by byte/count facts; page with offset and use scheduled_tasks_get for a bounded explicit detail projection.",
|
|
838
|
+
inputSchema: {
|
|
839
|
+
limit: z4.number().int().positive().max(50).optional(),
|
|
840
|
+
offset: z4.number().int().nonnegative().max(10_000).optional(),
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
async ({ limit: requestedLimit, offset: requestedOffset }) => {
|
|
844
|
+
const limit = requestedLimit ?? 25;
|
|
845
|
+
const offset = requestedOffset ?? 0;
|
|
846
|
+
const rows = await listScheduledTasks(deps.db, grant.workspaceId, limit + 1, offset);
|
|
847
|
+
return json(
|
|
848
|
+
boundScheduledTaskMcpPage({
|
|
849
|
+
tasks: rows.slice(0, limit).map((task) => scheduledTaskForGrant(task, grant)),
|
|
850
|
+
limit,
|
|
851
|
+
offset,
|
|
852
|
+
sourceHasMore: rows.length > limit,
|
|
853
|
+
}),
|
|
854
|
+
);
|
|
774
855
|
},
|
|
775
|
-
async ({ limit }) =>
|
|
776
|
-
json({
|
|
777
|
-
tasks: await listScheduledTasks(deps.db, grant.workspaceId, limit ?? 100),
|
|
778
|
-
}),
|
|
779
856
|
);
|
|
780
857
|
|
|
781
858
|
server.registerTool(
|
|
782
859
|
"scheduled_tasks_get",
|
|
783
860
|
{
|
|
784
|
-
description:
|
|
785
|
-
|
|
861
|
+
description:
|
|
862
|
+
"Get one scheduled task. The default is the same compact summary used by scheduled_tasks_list; pass includeEntity=true for a bounded projection with an 8 KiB prompt preview, bounded goal fields, resource/tool identity previews, and metadata keys without values.",
|
|
863
|
+
inputSchema: {
|
|
864
|
+
id: z4.string().uuid(),
|
|
865
|
+
includeEntity: z4.boolean().optional(),
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
async ({ id, includeEntity }) => {
|
|
869
|
+
const task = scheduledTaskForGrant(
|
|
870
|
+
await requireScheduledTask(deps.db, grant.workspaceId, id),
|
|
871
|
+
grant,
|
|
872
|
+
);
|
|
873
|
+
return json(
|
|
874
|
+
includeEntity ? boundScheduledTaskDetailMcp(task) : scheduledTaskMcpSummary(task),
|
|
875
|
+
);
|
|
786
876
|
},
|
|
787
|
-
async ({ id }) => json(await requireScheduledTask(deps.db, grant.workspaceId, id)),
|
|
788
877
|
);
|
|
789
878
|
|
|
790
879
|
server.registerTool(
|
|
@@ -795,6 +884,7 @@ export function buildOpenGeniMcpServer(
|
|
|
795
884
|
name: z4.string(),
|
|
796
885
|
schedule: z4.unknown(),
|
|
797
886
|
runMode: z4.string().optional(),
|
|
887
|
+
targetSessionId: z4.string().uuid().nullable().optional(),
|
|
798
888
|
overlapPolicy: z4.string().optional(),
|
|
799
889
|
agentConfig: z4.unknown(),
|
|
800
890
|
status: z4.string().optional(),
|
|
@@ -823,13 +913,29 @@ export function buildOpenGeniMcpServer(
|
|
|
823
913
|
grant,
|
|
824
914
|
payload,
|
|
825
915
|
toolsProvided: scheduledTaskToolsProvided(args),
|
|
916
|
+
sessionAuthorization: deps.sessionAuthorization,
|
|
917
|
+
authorizationSurface: "first_party_mcp",
|
|
826
918
|
});
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
919
|
+
try {
|
|
920
|
+
await syncCreatedScheduledTask({
|
|
921
|
+
db: deps.db,
|
|
922
|
+
workflowClient: deps.workflowClient,
|
|
923
|
+
task,
|
|
924
|
+
});
|
|
925
|
+
} catch (error) {
|
|
926
|
+
if (!(error instanceof ScheduledTaskSyncError) || error.persistenceRestored) {
|
|
927
|
+
throw error;
|
|
928
|
+
}
|
|
929
|
+
return json(
|
|
930
|
+
scheduledTaskReceipt("scheduled_tasks_create", task, "partial_failure", true, {
|
|
931
|
+
partialFailure: { stage: "schedule_sync", retryable: true },
|
|
932
|
+
warnings: [
|
|
933
|
+
"The task database record committed, but Temporal schedule synchronization failed.",
|
|
934
|
+
],
|
|
935
|
+
}),
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
return json(scheduledTaskReceipt("scheduled_tasks_create", task, "created", true));
|
|
833
939
|
},
|
|
834
940
|
);
|
|
835
941
|
|
|
@@ -842,6 +948,7 @@ export function buildOpenGeniMcpServer(
|
|
|
842
948
|
name: z4.string().optional(),
|
|
843
949
|
schedule: z4.unknown().optional(),
|
|
844
950
|
runMode: z4.string().optional(),
|
|
951
|
+
targetSessionId: z4.string().uuid().nullable().optional(),
|
|
845
952
|
overlapPolicy: z4.string().optional(),
|
|
846
953
|
agentConfig: z4.unknown().optional(),
|
|
847
954
|
status: z4.string().optional(),
|
|
@@ -867,6 +974,8 @@ export function buildOpenGeniMcpServer(
|
|
|
867
974
|
existing,
|
|
868
975
|
payload,
|
|
869
976
|
toolsProvided: scheduledTaskToolsProvided(raw),
|
|
977
|
+
sessionAuthorization: deps.sessionAuthorization,
|
|
978
|
+
authorizationSurface: "first_party_mcp",
|
|
870
979
|
});
|
|
871
980
|
const task = await updateScheduledTask(deps.db, grant.workspaceId, id, update);
|
|
872
981
|
await syncUpdatedScheduledTask({
|
|
@@ -875,7 +984,7 @@ export function buildOpenGeniMcpServer(
|
|
|
875
984
|
previous,
|
|
876
985
|
task,
|
|
877
986
|
});
|
|
878
|
-
return json(task);
|
|
987
|
+
return json(scheduledTaskForGrant(task, grant));
|
|
879
988
|
},
|
|
880
989
|
);
|
|
881
990
|
|
|
@@ -897,7 +1006,7 @@ export function buildOpenGeniMcpServer(
|
|
|
897
1006
|
previous,
|
|
898
1007
|
task,
|
|
899
1008
|
});
|
|
900
|
-
return json(task);
|
|
1009
|
+
return json(scheduledTaskForGrant(task, grant));
|
|
901
1010
|
},
|
|
902
1011
|
);
|
|
903
1012
|
|
|
@@ -919,7 +1028,7 @@ export function buildOpenGeniMcpServer(
|
|
|
919
1028
|
previous,
|
|
920
1029
|
task,
|
|
921
1030
|
});
|
|
922
|
-
return json(task);
|
|
1031
|
+
return json(scheduledTaskForGrant(task, grant));
|
|
923
1032
|
},
|
|
924
1033
|
);
|
|
925
1034
|
|
|
@@ -935,6 +1044,18 @@ export function buildOpenGeniMcpServer(
|
|
|
935
1044
|
},
|
|
936
1045
|
async ({ id, triggerId }) => {
|
|
937
1046
|
const task = await requireScheduledTask(deps.db, grant.workspaceId, id);
|
|
1047
|
+
await validateScheduledTaskTarget({
|
|
1048
|
+
db: deps.db,
|
|
1049
|
+
sessionAuthorization: deps.sessionAuthorization,
|
|
1050
|
+
authorizationSurface: "first_party_mcp",
|
|
1051
|
+
grant,
|
|
1052
|
+
targetSessionId: task.targetSessionId,
|
|
1053
|
+
runMode: task.runMode,
|
|
1054
|
+
variableSetId: task.variableSetId,
|
|
1055
|
+
rigId: task.rigId,
|
|
1056
|
+
agentConfig: task.agentConfig,
|
|
1057
|
+
missingTargetStatus: 404,
|
|
1058
|
+
});
|
|
938
1059
|
await requireLimit(deps, {
|
|
939
1060
|
accountId: grant.accountId,
|
|
940
1061
|
workspaceId: grant.workspaceId,
|
|
@@ -955,18 +1076,36 @@ export function buildOpenGeniMcpServer(
|
|
|
955
1076
|
triggerWorkflowId,
|
|
956
1077
|
initiator: { kind: "subject", subjectId: grant.subjectId },
|
|
957
1078
|
});
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1079
|
+
try {
|
|
1080
|
+
await recordWorkspaceUsage(deps, {
|
|
1081
|
+
accountId: grant.accountId,
|
|
1082
|
+
workspaceId: grant.workspaceId,
|
|
1083
|
+
subjectId: grant.subjectId,
|
|
1084
|
+
eventType: "agent_run.created",
|
|
1085
|
+
quantity: 1,
|
|
1086
|
+
unit: "run",
|
|
1087
|
+
sourceResourceType: "scheduled_task",
|
|
1088
|
+
sourceResourceId: task.id,
|
|
1089
|
+
idempotencyKey: agentRunUsageIdempotencyKey,
|
|
1090
|
+
});
|
|
1091
|
+
} catch {
|
|
1092
|
+
return json(
|
|
1093
|
+
scheduledTaskReceipt("scheduled_tasks_trigger", task, "partial_failure", true, {
|
|
1094
|
+
partialFailure: { stage: "usage_recording", retryable: true },
|
|
1095
|
+
warnings: [
|
|
1096
|
+
"The Temporal run trigger completed, but usage recording failed; retry with the same triggerId.",
|
|
1097
|
+
],
|
|
1098
|
+
idempotencyStatus: triggerId ? "unknown" : "not_requested",
|
|
1099
|
+
facts: { triggerWorkflowId },
|
|
1100
|
+
}),
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
return json(
|
|
1104
|
+
scheduledTaskReceipt("scheduled_tasks_trigger", task, "triggered", true, {
|
|
1105
|
+
idempotencyStatus: triggerId ? "unknown" : "not_requested",
|
|
1106
|
+
facts: { triggerWorkflowId },
|
|
1107
|
+
}),
|
|
1108
|
+
);
|
|
970
1109
|
},
|
|
971
1110
|
);
|
|
972
1111
|
|
|
@@ -981,8 +1120,28 @@ export function buildOpenGeniMcpServer(
|
|
|
981
1120
|
await deps.workflowClient.deleteScheduledTaskSchedule({
|
|
982
1121
|
temporalScheduleId: task.temporalScheduleId,
|
|
983
1122
|
});
|
|
984
|
-
|
|
985
|
-
|
|
1123
|
+
try {
|
|
1124
|
+
await deleteScheduledTask(deps.db, grant.workspaceId, id);
|
|
1125
|
+
} catch {
|
|
1126
|
+
return json(
|
|
1127
|
+
scheduledTaskReceipt("scheduled_tasks_delete", task, "partial_failure", true, {
|
|
1128
|
+
partialFailure: { stage: "database_delete", retryable: true },
|
|
1129
|
+
warnings: [
|
|
1130
|
+
"The Temporal schedule was deleted, but the task database record remains.",
|
|
1131
|
+
],
|
|
1132
|
+
}),
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
return json(
|
|
1136
|
+
mcpMutationReceipt({
|
|
1137
|
+
operation: "scheduled_tasks_delete",
|
|
1138
|
+
committed: true,
|
|
1139
|
+
outcome: "deleted",
|
|
1140
|
+
changed: true,
|
|
1141
|
+
resource: { type: "scheduled_task", id: task.id, state: "deleted" },
|
|
1142
|
+
idempotency: { status: "not_supported" },
|
|
1143
|
+
}),
|
|
1144
|
+
);
|
|
986
1145
|
},
|
|
987
1146
|
);
|
|
988
1147
|
|
|
@@ -997,7 +1156,9 @@ export function buildOpenGeniMcpServer(
|
|
|
997
1156
|
},
|
|
998
1157
|
async ({ taskId, limit }) =>
|
|
999
1158
|
json({
|
|
1000
|
-
runs: await listScheduledTaskRuns(deps.db, grant.workspaceId, taskId, limit ?? 100)
|
|
1159
|
+
runs: (await listScheduledTaskRuns(deps.db, grant.workspaceId, taskId, limit ?? 100)).map(
|
|
1160
|
+
(run) => scheduledTaskRunForGrant(run, grant),
|
|
1161
|
+
),
|
|
1001
1162
|
}),
|
|
1002
1163
|
);
|
|
1003
1164
|
}
|
|
@@ -1441,10 +1602,27 @@ function registerGoalTools(
|
|
|
1441
1602
|
event: { type: "goal.completed", evidence },
|
|
1442
1603
|
},
|
|
1443
1604
|
);
|
|
1605
|
+
const changed = events.length > 0;
|
|
1444
1606
|
if (events.length > 0) {
|
|
1445
1607
|
await deps.bus.publish(grant.workspaceId, sessionId, events);
|
|
1446
1608
|
}
|
|
1447
|
-
return json(
|
|
1609
|
+
return json(
|
|
1610
|
+
mcpMutationReceipt({
|
|
1611
|
+
operation: "goal_complete",
|
|
1612
|
+
committed: true,
|
|
1613
|
+
outcome: changed ? "updated" : "unchanged",
|
|
1614
|
+
changed,
|
|
1615
|
+
resource: {
|
|
1616
|
+
type: "session_goal",
|
|
1617
|
+
id: goal.id,
|
|
1618
|
+
version: goal.version,
|
|
1619
|
+
state: goal.status,
|
|
1620
|
+
},
|
|
1621
|
+
timestamp: goal.updatedAt,
|
|
1622
|
+
idempotency: { status: "not_supported" },
|
|
1623
|
+
nextAction: { tool: "session_get", arguments: { sessionId } },
|
|
1624
|
+
}),
|
|
1625
|
+
);
|
|
1448
1626
|
},
|
|
1449
1627
|
);
|
|
1450
1628
|
|
|
@@ -1478,10 +1656,27 @@ function registerGoalTools(
|
|
|
1478
1656
|
},
|
|
1479
1657
|
},
|
|
1480
1658
|
);
|
|
1659
|
+
const changed = events.length > 0;
|
|
1481
1660
|
if (events.length > 0) {
|
|
1482
1661
|
await deps.bus.publish(grant.workspaceId, sessionId, events);
|
|
1483
1662
|
}
|
|
1484
|
-
return json(
|
|
1663
|
+
return json(
|
|
1664
|
+
mcpMutationReceipt({
|
|
1665
|
+
operation: "goal_pause",
|
|
1666
|
+
committed: true,
|
|
1667
|
+
outcome: changed ? "updated" : "unchanged",
|
|
1668
|
+
changed,
|
|
1669
|
+
resource: {
|
|
1670
|
+
type: "session_goal",
|
|
1671
|
+
id: goal.id,
|
|
1672
|
+
version: goal.version,
|
|
1673
|
+
state: goal.status,
|
|
1674
|
+
},
|
|
1675
|
+
timestamp: goal.updatedAt,
|
|
1676
|
+
idempotency: { status: "not_supported" },
|
|
1677
|
+
nextAction: { tool: "session_get", arguments: { sessionId } },
|
|
1678
|
+
}),
|
|
1679
|
+
);
|
|
1485
1680
|
},
|
|
1486
1681
|
);
|
|
1487
1682
|
}
|
|
@@ -1599,7 +1794,11 @@ function registerWorkspaceArtifactTools(
|
|
|
1599
1794
|
const actualHash = createHash("sha256").update(object.bytes).digest("hex");
|
|
1600
1795
|
if (actualHash !== ref.version.contentSha256)
|
|
1601
1796
|
throw new Error("Artifact content failed integrity verification");
|
|
1602
|
-
return json({
|
|
1797
|
+
return json({
|
|
1798
|
+
detail,
|
|
1799
|
+
version: ref.version,
|
|
1800
|
+
html: new TextDecoder().decode(object.bytes),
|
|
1801
|
+
});
|
|
1603
1802
|
},
|
|
1604
1803
|
);
|
|
1605
1804
|
|
|
@@ -1761,6 +1960,38 @@ function registerPreferenceRegistryTools(
|
|
|
1761
1960
|
|
|
1762
1961
|
const MemoryKindSchema = z4.enum(["preference", "semantic", "procedural", "decision", "episodic"]);
|
|
1763
1962
|
|
|
1963
|
+
function scheduledTaskReceipt(
|
|
1964
|
+
operation: string,
|
|
1965
|
+
task: ScheduledTask,
|
|
1966
|
+
outcome: "created" | "updated" | "unchanged" | "triggered" | "partial_failure",
|
|
1967
|
+
changed: boolean,
|
|
1968
|
+
options: {
|
|
1969
|
+
partialFailure?: { stage: string; retryable: boolean };
|
|
1970
|
+
warnings?: string[];
|
|
1971
|
+
idempotencyStatus?: "not_supported" | "not_requested" | "applied" | "replayed" | "unknown";
|
|
1972
|
+
facts?: Record<string, string | number | boolean | null>;
|
|
1973
|
+
} = {},
|
|
1974
|
+
) {
|
|
1975
|
+
return mcpMutationReceipt({
|
|
1976
|
+
operation,
|
|
1977
|
+
committed: true,
|
|
1978
|
+
outcome,
|
|
1979
|
+
changed,
|
|
1980
|
+
resource: {
|
|
1981
|
+
type: "scheduled_task",
|
|
1982
|
+
id: task.id,
|
|
1983
|
+
version: task.updatedAt,
|
|
1984
|
+
state: task.status,
|
|
1985
|
+
},
|
|
1986
|
+
timestamp: task.updatedAt,
|
|
1987
|
+
idempotency: { status: options.idempotencyStatus ?? "not_supported" },
|
|
1988
|
+
...(options.partialFailure ? { partialFailure: options.partialFailure } : {}),
|
|
1989
|
+
...(options.warnings ? { warnings: options.warnings } : {}),
|
|
1990
|
+
...(options.facts ? { facts: options.facts } : {}),
|
|
1991
|
+
nextAction: { tool: "scheduled_tasks_get", arguments: { id: task.id } },
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1764
1995
|
function memoryPreview(text: string): string {
|
|
1765
1996
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1766
1997
|
return normalized.length <= 120 ? normalized : `${normalized.slice(0, 119)}…`;
|
|
@@ -1836,7 +2067,46 @@ function registerMemoryTools(
|
|
|
1836
2067
|
},
|
|
1837
2068
|
},
|
|
1838
2069
|
]);
|
|
1839
|
-
|
|
2070
|
+
const changed = !result.deduped || result.updated || result.superseded !== null;
|
|
2071
|
+
const outcome =
|
|
2072
|
+
result.updated || result.superseded !== null
|
|
2073
|
+
? "updated"
|
|
2074
|
+
: result.deduped
|
|
2075
|
+
? "unchanged"
|
|
2076
|
+
: "created";
|
|
2077
|
+
return json(
|
|
2078
|
+
mcpMutationReceipt({
|
|
2079
|
+
operation: "memory_save",
|
|
2080
|
+
committed: true,
|
|
2081
|
+
outcome,
|
|
2082
|
+
changed,
|
|
2083
|
+
resource: {
|
|
2084
|
+
type: "knowledge_memory",
|
|
2085
|
+
id: result.memory.id,
|
|
2086
|
+
state: result.memory.status,
|
|
2087
|
+
},
|
|
2088
|
+
relatedResources: result.superseded
|
|
2089
|
+
? [
|
|
2090
|
+
{
|
|
2091
|
+
type: "knowledge_memory",
|
|
2092
|
+
id: result.superseded.id,
|
|
2093
|
+
state: result.superseded.status,
|
|
2094
|
+
},
|
|
2095
|
+
]
|
|
2096
|
+
: undefined,
|
|
2097
|
+
timestamp: result.memory.updatedAt,
|
|
2098
|
+
idempotency: { status: "not_supported" },
|
|
2099
|
+
warnings: !result.embedded
|
|
2100
|
+
? ["Memory committed without a vector embedding; keyword search remains available."]
|
|
2101
|
+
: [],
|
|
2102
|
+
facts: {
|
|
2103
|
+
deduped: result.deduped,
|
|
2104
|
+
dedupeReason: result.dedupeReason,
|
|
2105
|
+
updatedInPlace: result.updated,
|
|
2106
|
+
embedded: result.embedded,
|
|
2107
|
+
},
|
|
2108
|
+
}),
|
|
2109
|
+
);
|
|
1840
2110
|
},
|
|
1841
2111
|
);
|
|
1842
2112
|
|
|
@@ -1881,7 +2151,31 @@ function registerMemoryTools(
|
|
|
1881
2151
|
},
|
|
1882
2152
|
},
|
|
1883
2153
|
]);
|
|
1884
|
-
return json(
|
|
2154
|
+
return json(
|
|
2155
|
+
mcpMutationReceipt({
|
|
2156
|
+
operation: "memory_correct",
|
|
2157
|
+
committed: true,
|
|
2158
|
+
outcome: "updated",
|
|
2159
|
+
changed: true,
|
|
2160
|
+
resource: {
|
|
2161
|
+
type: "knowledge_memory",
|
|
2162
|
+
id: result.memory.id,
|
|
2163
|
+
state: result.memory.status,
|
|
2164
|
+
},
|
|
2165
|
+
relatedResources: result.replacement
|
|
2166
|
+
? [
|
|
2167
|
+
{
|
|
2168
|
+
type: "knowledge_memory",
|
|
2169
|
+
id: result.replacement.id,
|
|
2170
|
+
state: result.replacement.status,
|
|
2171
|
+
},
|
|
2172
|
+
]
|
|
2173
|
+
: undefined,
|
|
2174
|
+
timestamp: (result.replacement ?? result.memory).updatedAt,
|
|
2175
|
+
idempotency: { status: "not_supported" },
|
|
2176
|
+
facts: { correctionAction: result.action },
|
|
2177
|
+
}),
|
|
2178
|
+
);
|
|
1885
2179
|
},
|
|
1886
2180
|
);
|
|
1887
2181
|
}
|
|
@@ -1928,7 +2222,7 @@ function registerFleetTools(
|
|
|
1928
2222
|
"sandboxes_list",
|
|
1929
2223
|
{
|
|
1930
2224
|
description:
|
|
1931
|
-
"List the sandboxes this session can run on: its own session sandbox plus enrolled selfhosted machines. `
|
|
2225
|
+
"List the sandboxes this session can run on: its own session sandbox plus enrolled selfhosted machines. `operationAvailability` is authoritative for ordinary shell/files use: `wakeable` means the next ordinary operation will wake or restore the idle managed home sandbox, even when `liveness=offline`, `leaseLiveness=cold|draining`, or `attachable=false`. `attachable` describes an already-live swap target, not ordinary operation availability. `recovering` requires a bounded retry/typed recovery result; `unavailable` is not usable. Provider, lease, route, archive, restore, workspace, lease epoch, and route epoch remain separate truth dimensions. Use an entry `id` as an attach/swap/run_on target.",
|
|
1932
2226
|
inputSchema: {},
|
|
1933
2227
|
},
|
|
1934
2228
|
async () => json(await listFleet(services, await fleetContext())),
|
|
@@ -2000,6 +2294,45 @@ function registerFleetTools(
|
|
|
2000
2294
|
);
|
|
2001
2295
|
}
|
|
2002
2296
|
|
|
2297
|
+
// Workspace-admin/operator surface for removing a connected machine. This is
|
|
2298
|
+
// deliberately separate from the session-scoped fleet tools: a worker can list,
|
|
2299
|
+
// attach, or run on a machine only with session authority, while removal requires
|
|
2300
|
+
// the explicit high-trust enrollments:manage permission and never accepts a
|
|
2301
|
+
// sandbox id. A Modal record therefore cannot be removed through this operation.
|
|
2302
|
+
function registerConnectedMachineTools(
|
|
2303
|
+
server: McpServer,
|
|
2304
|
+
deps: ApiRouteDeps,
|
|
2305
|
+
grant: AccessGrant,
|
|
2306
|
+
json: JsonResult,
|
|
2307
|
+
): void {
|
|
2308
|
+
server.registerTool(
|
|
2309
|
+
"connected_machine_remove",
|
|
2310
|
+
{
|
|
2311
|
+
description:
|
|
2312
|
+
"Remove one enrolled self-hosted machine while it is offline. Access is revoked, future heartbeat/reconnect credentials are rejected, session/route/lease/archive history is retained, and a fresh human-approved device-flow enrollment is required to reconnect. Pass the enrollmentId from the Machines surface, never a Modal sandbox id. Blocked outcomes include the exact active dependency and the action needed before retrying.",
|
|
2313
|
+
inputSchema: {
|
|
2314
|
+
enrollmentId: z4.string().uuid(),
|
|
2315
|
+
expectedUpdatedAt: z4.string().datetime({ offset: true }).optional(),
|
|
2316
|
+
idempotencyKey: z4.string().trim().min(1).max(200).optional(),
|
|
2317
|
+
},
|
|
2318
|
+
},
|
|
2319
|
+
async ({ enrollmentId, expectedUpdatedAt, idempotencyKey }) => {
|
|
2320
|
+
const result = await removeEnrollment(deps.db, {
|
|
2321
|
+
accountId: grant.accountId,
|
|
2322
|
+
workspaceId: grant.workspaceId,
|
|
2323
|
+
enrollmentId,
|
|
2324
|
+
operationKey: idempotencyKey?.trim() || randomUUID(),
|
|
2325
|
+
...(expectedUpdatedAt ? { expectedUpdatedAt } : {}),
|
|
2326
|
+
subjectId: grant.subjectId,
|
|
2327
|
+
});
|
|
2328
|
+
if (!result) {
|
|
2329
|
+
throw new Error("machine enrollment not found in this workspace");
|
|
2330
|
+
}
|
|
2331
|
+
return json({ revoked: result.removed, ...result });
|
|
2332
|
+
},
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2003
2336
|
async function beginMcpRigVerificationAttempt(
|
|
2004
2337
|
deps: ApiRouteDeps,
|
|
2005
2338
|
workspaceId: string,
|
|
@@ -2101,12 +2434,60 @@ function registerRigTools(
|
|
|
2101
2434
|
sessionId ? { proposedBy: `session:${sessionId}` } : {},
|
|
2102
2435
|
);
|
|
2103
2436
|
const verifying = await beginMcpRigVerificationAttempt(deps, grant.workspaceId, change.id);
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2437
|
+
const attempt = verificationAttempt(verifying);
|
|
2438
|
+
try {
|
|
2439
|
+
await deps.workflowClient.startRigVerification({
|
|
2440
|
+
workspaceId: grant.workspaceId,
|
|
2441
|
+
changeId: change.id,
|
|
2442
|
+
workflowId: `rig-verification-change-${change.id}-attempt-${attempt}`,
|
|
2443
|
+
});
|
|
2444
|
+
} catch {
|
|
2445
|
+
return json(
|
|
2446
|
+
mcpMutationReceipt({
|
|
2447
|
+
operation: "rig_propose_change",
|
|
2448
|
+
committed: true,
|
|
2449
|
+
outcome: "partial_failure",
|
|
2450
|
+
changed: true,
|
|
2451
|
+
resource: {
|
|
2452
|
+
type: "rig_change",
|
|
2453
|
+
id: verifying.id,
|
|
2454
|
+
version: verifying.updatedAt,
|
|
2455
|
+
state: verifying.status,
|
|
2456
|
+
},
|
|
2457
|
+
relatedResources: [{ type: "rig", id: rig.id }],
|
|
2458
|
+
timestamp: verifying.updatedAt,
|
|
2459
|
+
idempotency: { status: "not_supported" },
|
|
2460
|
+
partialFailure: {
|
|
2461
|
+
stage: "verification_workflow_start",
|
|
2462
|
+
retryable: true,
|
|
2463
|
+
},
|
|
2464
|
+
warnings: [
|
|
2465
|
+
"The rig change and verifying transition committed, but verification workflow start failed.",
|
|
2466
|
+
],
|
|
2467
|
+
facts: { verificationAttempt: attempt },
|
|
2468
|
+
nextAction: { tool: "rig_get", arguments: { rigId: rig.id } },
|
|
2469
|
+
}),
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
return json(
|
|
2473
|
+
mcpMutationReceipt({
|
|
2474
|
+
operation: "rig_propose_change",
|
|
2475
|
+
committed: true,
|
|
2476
|
+
outcome: "created",
|
|
2477
|
+
changed: true,
|
|
2478
|
+
resource: {
|
|
2479
|
+
type: "rig_change",
|
|
2480
|
+
id: verifying.id,
|
|
2481
|
+
version: verifying.updatedAt,
|
|
2482
|
+
state: verifying.status,
|
|
2483
|
+
},
|
|
2484
|
+
relatedResources: [{ type: "rig", id: rig.id }],
|
|
2485
|
+
timestamp: verifying.updatedAt,
|
|
2486
|
+
idempotency: { status: "not_supported" },
|
|
2487
|
+
facts: { verificationStarted: true, verificationAttempt: attempt },
|
|
2488
|
+
nextAction: { tool: "rig_get", arguments: { rigId: rig.id } },
|
|
2489
|
+
}),
|
|
2490
|
+
);
|
|
2110
2491
|
},
|
|
2111
2492
|
);
|
|
2112
2493
|
|
|
@@ -2129,12 +2510,63 @@ function registerRigTools(
|
|
|
2129
2510
|
grant.workspaceId,
|
|
2130
2511
|
change.id,
|
|
2131
2512
|
);
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2513
|
+
const attempt = verificationAttempt(verifying);
|
|
2514
|
+
try {
|
|
2515
|
+
await deps.workflowClient.startRigVerification({
|
|
2516
|
+
workspaceId: grant.workspaceId,
|
|
2517
|
+
changeId: change.id,
|
|
2518
|
+
workflowId: `rig-verification-change-${change.id}-attempt-${attempt}`,
|
|
2519
|
+
});
|
|
2520
|
+
} catch {
|
|
2521
|
+
return json(
|
|
2522
|
+
mcpMutationReceipt({
|
|
2523
|
+
operation: "rig_verify",
|
|
2524
|
+
committed: true,
|
|
2525
|
+
outcome: "partial_failure",
|
|
2526
|
+
changed: true,
|
|
2527
|
+
resource: {
|
|
2528
|
+
type: "rig_change",
|
|
2529
|
+
id: verifying.id,
|
|
2530
|
+
version: verifying.updatedAt,
|
|
2531
|
+
state: verifying.status,
|
|
2532
|
+
},
|
|
2533
|
+
relatedResources: [{ type: "rig", id: rig.id }],
|
|
2534
|
+
timestamp: verifying.updatedAt,
|
|
2535
|
+
idempotency: { status: "not_supported" },
|
|
2536
|
+
partialFailure: {
|
|
2537
|
+
stage: "verification_workflow_start",
|
|
2538
|
+
retryable: true,
|
|
2539
|
+
},
|
|
2540
|
+
warnings: [
|
|
2541
|
+
"The verifying transition committed, but verification workflow start failed.",
|
|
2542
|
+
],
|
|
2543
|
+
facts: { verificationAttempt: attempt },
|
|
2544
|
+
nextAction: { tool: "rig_get", arguments: { rigId: rig.id } },
|
|
2545
|
+
}),
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
return json(
|
|
2549
|
+
mcpMutationReceipt({
|
|
2550
|
+
operation: "rig_verify",
|
|
2551
|
+
committed: true,
|
|
2552
|
+
outcome: "accepted",
|
|
2553
|
+
changed: true,
|
|
2554
|
+
resource: {
|
|
2555
|
+
type: "rig_change",
|
|
2556
|
+
id: verifying.id,
|
|
2557
|
+
version: verifying.updatedAt,
|
|
2558
|
+
state: verifying.status,
|
|
2559
|
+
},
|
|
2560
|
+
relatedResources: [{ type: "rig", id: rig.id }],
|
|
2561
|
+
timestamp: verifying.updatedAt,
|
|
2562
|
+
idempotency: { status: "not_supported" },
|
|
2563
|
+
facts: {
|
|
2564
|
+
verificationStarted: true,
|
|
2565
|
+
verificationAttempt: attempt,
|
|
2566
|
+
},
|
|
2567
|
+
nextAction: { tool: "rig_get", arguments: { rigId: rig.id } },
|
|
2568
|
+
}),
|
|
2569
|
+
);
|
|
2138
2570
|
}
|
|
2139
2571
|
if (!rig.activeVersion) {
|
|
2140
2572
|
throw new Error("rig has no active version");
|
|
@@ -2144,7 +2576,23 @@ function registerRigTools(
|
|
|
2144
2576
|
versionId: rig.activeVersion.id,
|
|
2145
2577
|
workflowId: `rig-verification-version-${rig.activeVersion.id}-${crypto.randomUUID()}`,
|
|
2146
2578
|
});
|
|
2147
|
-
return json(
|
|
2579
|
+
return json(
|
|
2580
|
+
mcpMutationReceipt({
|
|
2581
|
+
operation: "rig_verify",
|
|
2582
|
+
committed: true,
|
|
2583
|
+
outcome: "accepted",
|
|
2584
|
+
changed: true,
|
|
2585
|
+
resource: {
|
|
2586
|
+
type: "rig_version",
|
|
2587
|
+
id: rig.activeVersion.id,
|
|
2588
|
+
version: rig.activeVersion.version,
|
|
2589
|
+
state: "verification_started",
|
|
2590
|
+
},
|
|
2591
|
+
relatedResources: [{ type: "rig", id: rig.id }],
|
|
2592
|
+
idempotency: { status: "not_supported" },
|
|
2593
|
+
nextAction: { tool: "rig_get", arguments: { rigId: rig.id } },
|
|
2594
|
+
}),
|
|
2595
|
+
);
|
|
2148
2596
|
},
|
|
2149
2597
|
);
|
|
2150
2598
|
}
|
|
@@ -2163,8 +2611,36 @@ function registerRigTools(
|
|
|
2163
2611
|
async ({ rigId, changeId }) => {
|
|
2164
2612
|
const rig = await requireRigForApi(deps.db, grant.workspaceId, rigId);
|
|
2165
2613
|
const change = await requireRigChangeForApi(deps.db, grant.workspaceId, rig.id, changeId);
|
|
2614
|
+
const promoted = await promoteVerifiedDefinitionEditChangeForApi(
|
|
2615
|
+
{ db: deps.db },
|
|
2616
|
+
grant,
|
|
2617
|
+
rig,
|
|
2618
|
+
change,
|
|
2619
|
+
);
|
|
2166
2620
|
return json(
|
|
2167
|
-
|
|
2621
|
+
mcpMutationReceipt({
|
|
2622
|
+
operation: "rig_promote",
|
|
2623
|
+
committed: true,
|
|
2624
|
+
outcome: "updated",
|
|
2625
|
+
changed: true,
|
|
2626
|
+
resource: {
|
|
2627
|
+
type: "rig_version",
|
|
2628
|
+
id: promoted.version.id,
|
|
2629
|
+
version: promoted.version.version,
|
|
2630
|
+
state: "active",
|
|
2631
|
+
},
|
|
2632
|
+
relatedResources: [
|
|
2633
|
+
{
|
|
2634
|
+
type: "rig_change",
|
|
2635
|
+
id: promoted.change.id,
|
|
2636
|
+
state: promoted.change.status,
|
|
2637
|
+
},
|
|
2638
|
+
{ type: "rig", id: rig.id },
|
|
2639
|
+
],
|
|
2640
|
+
timestamp: promoted.version.createdAt,
|
|
2641
|
+
idempotency: { status: "not_supported" },
|
|
2642
|
+
nextAction: { tool: "rig_get", arguments: { rigId: rig.id } },
|
|
2643
|
+
}),
|
|
2168
2644
|
);
|
|
2169
2645
|
},
|
|
2170
2646
|
);
|
|
@@ -2384,7 +2860,10 @@ function registerWorkspaceOrchestrationTools(
|
|
|
2384
2860
|
compactSessionEventResult(
|
|
2385
2861
|
event,
|
|
2386
2862
|
latestClass!,
|
|
2387
|
-
dbPage.coveredSequence ?? {
|
|
2863
|
+
dbPage.coveredSequence ?? {
|
|
2864
|
+
first: event.sequence,
|
|
2865
|
+
last: event.sequence,
|
|
2866
|
+
},
|
|
2388
2867
|
),
|
|
2389
2868
|
)
|
|
2390
2869
|
: null,
|
|
@@ -2508,8 +2987,13 @@ function registerWorkspaceOrchestrationTools(
|
|
|
2508
2987
|
if (callerSessionId !== null) {
|
|
2509
2988
|
await authorizeFirstPartySession(deps, grant, callerSessionId, "session.child.create");
|
|
2510
2989
|
}
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2990
|
+
const result = await createSessionForRequestWithOutcome(
|
|
2991
|
+
deps,
|
|
2992
|
+
grant,
|
|
2993
|
+
grant.workspaceId,
|
|
2994
|
+
args,
|
|
2995
|
+
);
|
|
2996
|
+
return json(sessionCreateMutationReceipt(result, Boolean(args.idempotencyKey)));
|
|
2513
2997
|
} catch (error) {
|
|
2514
2998
|
if (error instanceof SessionSpawnDeniedError) {
|
|
2515
2999
|
return {
|
|
@@ -2549,17 +3033,33 @@ function registerWorkspaceOrchestrationTools(
|
|
|
2549
3033
|
exactAgentCommandContext(grant, callerSessionId),
|
|
2550
3034
|
{ targetSessionId, text, idempotencyKey },
|
|
2551
3035
|
);
|
|
2552
|
-
return json(
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
3036
|
+
return json(
|
|
3037
|
+
mcpMutationReceipt({
|
|
3038
|
+
operation: "session_send_message",
|
|
3039
|
+
committed: true,
|
|
3040
|
+
outcome: result.replay ? "replayed" : "accepted",
|
|
3041
|
+
changed: !result.replay,
|
|
3042
|
+
resource: {
|
|
3043
|
+
type: "session_system_update",
|
|
3044
|
+
id: result.updateId,
|
|
3045
|
+
state: result.effectiveState,
|
|
3046
|
+
},
|
|
3047
|
+
relatedResources: [{ type: "session", id: targetSessionId }],
|
|
3048
|
+
timestamp: result.receipt.createdAt.toISOString(),
|
|
3049
|
+
idempotency: { status: result.replay ? "replayed" : "applied" },
|
|
3050
|
+
facts: {
|
|
3051
|
+
delivery: "coalesced_internal_update",
|
|
3052
|
+
wakeRequested: result.wakeRevision !== null,
|
|
3053
|
+
resumeRequired: result.effectiveState === "paused",
|
|
3054
|
+
},
|
|
3055
|
+
nextAction: {
|
|
3056
|
+
tool: "session_get",
|
|
3057
|
+
arguments: { sessionId: targetSessionId },
|
|
3058
|
+
},
|
|
3059
|
+
}),
|
|
3060
|
+
);
|
|
2561
3061
|
}
|
|
2562
|
-
const { accepted, turn } = await
|
|
3062
|
+
const { accepted, turn, replay } = await acceptSessionUserMessageWithOutcome(
|
|
2563
3063
|
deps,
|
|
2564
3064
|
grant,
|
|
2565
3065
|
grant.workspaceId,
|
|
@@ -2574,7 +3074,30 @@ function registerWorkspaceOrchestrationTools(
|
|
|
2574
3074
|
),
|
|
2575
3075
|
},
|
|
2576
3076
|
);
|
|
2577
|
-
return json(
|
|
3077
|
+
return json(
|
|
3078
|
+
mcpMutationReceipt({
|
|
3079
|
+
operation: "session_send_message",
|
|
3080
|
+
committed: true,
|
|
3081
|
+
outcome: replay ? "replayed" : "accepted",
|
|
3082
|
+
changed: !replay,
|
|
3083
|
+
resource: {
|
|
3084
|
+
type: "session_turn",
|
|
3085
|
+
id: turn.id,
|
|
3086
|
+
version: turn.version,
|
|
3087
|
+
state: turn.status,
|
|
3088
|
+
},
|
|
3089
|
+
relatedResources: [
|
|
3090
|
+
{ type: "session", id: targetSessionId },
|
|
3091
|
+
{ type: "session_event", id: accepted.id, state: accepted.type },
|
|
3092
|
+
],
|
|
3093
|
+
timestamp: accepted.occurredAt,
|
|
3094
|
+
idempotency: { status: replay ? "replayed" : "applied" },
|
|
3095
|
+
nextAction: {
|
|
3096
|
+
tool: "session_get",
|
|
3097
|
+
arguments: { sessionId: targetSessionId },
|
|
3098
|
+
},
|
|
3099
|
+
}),
|
|
3100
|
+
);
|
|
2578
3101
|
},
|
|
2579
3102
|
);
|
|
2580
3103
|
|
|
@@ -2701,13 +3224,28 @@ function registerWorkspaceOrchestrationTools(
|
|
|
2701
3224
|
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
2702
3225
|
{ targetSessionId: sessionId, instruction, idempotencyKey },
|
|
2703
3226
|
);
|
|
2704
|
-
return json(
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
3227
|
+
return json(
|
|
3228
|
+
mcpMutationReceipt({
|
|
3229
|
+
operation: "session_steer",
|
|
3230
|
+
committed: true,
|
|
3231
|
+
outcome: result.replay ? "replayed" : "updated",
|
|
3232
|
+
changed: !result.replay,
|
|
3233
|
+
resource: {
|
|
3234
|
+
type: "session_system_update",
|
|
3235
|
+
id: result.updateId,
|
|
3236
|
+
state: result.effectiveState,
|
|
3237
|
+
},
|
|
3238
|
+
relatedResources: [{ type: "session", id: sessionId }],
|
|
3239
|
+
timestamp: result.receipt.createdAt.toISOString(),
|
|
3240
|
+
idempotency: { status: result.replay ? "replayed" : "applied" },
|
|
3241
|
+
facts: {
|
|
3242
|
+
interruptionCount: result.interruptionCount,
|
|
3243
|
+
stoppingPreviousAttempt: result.interruptionCount > 0,
|
|
3244
|
+
},
|
|
3245
|
+
updateId: result.updateId,
|
|
3246
|
+
nextAction: { tool: "session_get", arguments: { sessionId } },
|
|
3247
|
+
}),
|
|
3248
|
+
);
|
|
2711
3249
|
},
|
|
2712
3250
|
);
|
|
2713
3251
|
}
|
|
@@ -2736,15 +3274,16 @@ function registerWorkspaceOrchestrationTools(
|
|
|
2736
3274
|
}
|
|
2737
3275
|
}
|
|
2738
3276
|
|
|
2739
|
-
//
|
|
2740
|
-
//
|
|
2741
|
-
//
|
|
2742
|
-
// write-only — responses carry names and metadata, never values.
|
|
3277
|
+
// Variable-set management for agents. Generic reads remain metadata-only.
|
|
3278
|
+
// Plaintext has one dedicated tool that additionally requires literal
|
|
3279
|
+
// secrets:read plus exact live attempt/session authorization.
|
|
2743
3280
|
function registerVariableSetTools(
|
|
2744
3281
|
server: McpServer,
|
|
2745
3282
|
deps: ApiRouteDeps,
|
|
2746
3283
|
grant: AccessGrant,
|
|
2747
3284
|
can: (permission: Permission) => boolean,
|
|
3285
|
+
sessionId: string | null,
|
|
3286
|
+
toolspaceMode: boolean,
|
|
2748
3287
|
json: JsonResult,
|
|
2749
3288
|
): void {
|
|
2750
3289
|
const registerListTool = (name: string, description: string): void => {
|
|
@@ -2760,93 +3299,114 @@ function registerVariableSetTools(
|
|
|
2760
3299
|
},
|
|
2761
3300
|
);
|
|
2762
3301
|
};
|
|
2763
|
-
const setVariableHandler =
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
);
|
|
2790
|
-
}
|
|
2791
|
-
const trimmedVariableSetName = targetName?.trim();
|
|
2792
|
-
if (targetName !== undefined && !trimmedVariableSetName) {
|
|
2793
|
-
throw new Error("variable set name is required");
|
|
2794
|
-
}
|
|
2795
|
-
let created = false;
|
|
2796
|
-
let variableSet =
|
|
2797
|
-
targetId !== undefined
|
|
2798
|
-
? await getVariableSet(deps.db, grant.workspaceId, targetId)
|
|
2799
|
-
: await getVariableSetByName(deps.db, grant.workspaceId, trimmedVariableSetName!);
|
|
2800
|
-
if (!variableSet && targetId !== undefined) {
|
|
2801
|
-
throw new Error("variable set/environment not found");
|
|
2802
|
-
}
|
|
2803
|
-
if (!variableSet) {
|
|
2804
|
-
if ((await countVariableSets(deps.db, grant.workspaceId)) >= MAX_ENVIRONMENTS_PER_WORKSPACE) {
|
|
3302
|
+
const setVariableHandler =
|
|
3303
|
+
(operation: "variable_set_set_variable" | "environment_set_variable") =>
|
|
3304
|
+
async ({
|
|
3305
|
+
variableSetId,
|
|
3306
|
+
variableSetName,
|
|
3307
|
+
environmentId,
|
|
3308
|
+
environmentName,
|
|
3309
|
+
name,
|
|
3310
|
+
value,
|
|
3311
|
+
}: {
|
|
3312
|
+
variableSetId?: string | undefined;
|
|
3313
|
+
variableSetName?: string | undefined;
|
|
3314
|
+
environmentId?: string | undefined;
|
|
3315
|
+
environmentName?: string | undefined;
|
|
3316
|
+
name: string;
|
|
3317
|
+
value: string;
|
|
3318
|
+
}) => {
|
|
3319
|
+
const key = requireVariableSetEncryption(deps.settings);
|
|
3320
|
+
const parsedName = VariableSetVariableName.safeParse(name);
|
|
3321
|
+
if (!parsedName.success) {
|
|
3322
|
+
throw new Error("variable set/environment variable names must match ^[A-Z][A-Z0-9_]*$");
|
|
3323
|
+
}
|
|
3324
|
+
assertAllowedVariableSetVariableName(parsedName.data);
|
|
3325
|
+
const targetId = variableSetId ?? environmentId;
|
|
3326
|
+
const targetName = variableSetName ?? environmentName;
|
|
3327
|
+
if ((targetId === undefined) === (targetName === undefined)) {
|
|
2805
3328
|
throw new Error(
|
|
2806
|
-
|
|
3329
|
+
"provide exactly one of variableSetId or variableSetName; deprecated aliases must provide exactly one of environmentId or environmentName",
|
|
2807
3330
|
);
|
|
2808
3331
|
}
|
|
2809
|
-
|
|
3332
|
+
const trimmedVariableSetName = targetName?.trim();
|
|
3333
|
+
if (targetName !== undefined && !trimmedVariableSetName) {
|
|
3334
|
+
throw new Error("variable set name is required");
|
|
3335
|
+
}
|
|
3336
|
+
let created = false;
|
|
3337
|
+
let variableSet =
|
|
3338
|
+
targetId !== undefined
|
|
3339
|
+
? await getVariableSet(deps.db, grant.workspaceId, targetId)
|
|
3340
|
+
: await getVariableSetByName(deps.db, grant.workspaceId, trimmedVariableSetName!);
|
|
3341
|
+
if (!variableSet && targetId !== undefined) {
|
|
3342
|
+
throw new Error("variable set/environment not found");
|
|
3343
|
+
}
|
|
3344
|
+
if (!variableSet) {
|
|
3345
|
+
if (
|
|
3346
|
+
(await countVariableSets(deps.db, grant.workspaceId)) >= MAX_ENVIRONMENTS_PER_WORKSPACE
|
|
3347
|
+
) {
|
|
3348
|
+
throw new Error(
|
|
3349
|
+
`a workspace supports at most ${MAX_ENVIRONMENTS_PER_WORKSPACE} variable sets`,
|
|
3350
|
+
);
|
|
3351
|
+
}
|
|
3352
|
+
variableSet = await createVariableSet(deps.db, {
|
|
3353
|
+
accountId: grant.accountId,
|
|
3354
|
+
workspaceId: grant.workspaceId,
|
|
3355
|
+
name: trimmedVariableSetName!,
|
|
3356
|
+
});
|
|
3357
|
+
created = true;
|
|
3358
|
+
await recordVariableSetAuditEvent(deps.db, {
|
|
3359
|
+
grant,
|
|
3360
|
+
action: "variable_set.created",
|
|
3361
|
+
variableSetId: variableSet.id,
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
const exists = variableSet.variables.some((variable) => variable.name === parsedName.data);
|
|
3365
|
+
if (!exists && variableSet.variables.length >= MAX_VARIABLES_PER_ENVIRONMENT) {
|
|
3366
|
+
throw new Error(
|
|
3367
|
+
`a variable set supports at most ${MAX_VARIABLES_PER_ENVIRONMENT} variables`,
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3370
|
+
const metadata = await setVariableSetVariable(deps.db, {
|
|
2810
3371
|
accountId: grant.accountId,
|
|
2811
3372
|
workspaceId: grant.workspaceId,
|
|
2812
|
-
|
|
3373
|
+
variableSetId: variableSet.id,
|
|
3374
|
+
name: parsedName.data,
|
|
3375
|
+
valueEncrypted: encryptVariableSetValue(key, value),
|
|
2813
3376
|
});
|
|
2814
|
-
created = true;
|
|
2815
3377
|
await recordVariableSetAuditEvent(deps.db, {
|
|
2816
3378
|
grant,
|
|
2817
|
-
action: "variable_set.
|
|
3379
|
+
action: "variable_set.variable.set",
|
|
2818
3380
|
variableSetId: variableSet.id,
|
|
3381
|
+
variableName: parsedName.data,
|
|
2819
3382
|
});
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
3383
|
+
return json(
|
|
3384
|
+
mcpMutationReceipt({
|
|
3385
|
+
operation,
|
|
3386
|
+
committed: true,
|
|
3387
|
+
outcome: exists ? "updated" : "created",
|
|
3388
|
+
changed: true,
|
|
3389
|
+
resource: {
|
|
3390
|
+
type: "variable_set",
|
|
3391
|
+
id: variableSet.id,
|
|
3392
|
+
version: metadata.version,
|
|
3393
|
+
state: "variable_written",
|
|
3394
|
+
},
|
|
3395
|
+
timestamp: metadata.updatedAt,
|
|
3396
|
+
idempotency: { status: "not_supported" },
|
|
3397
|
+
facts: {
|
|
3398
|
+
variableCreated: !exists,
|
|
3399
|
+
variableSetCreated: created,
|
|
3400
|
+
deprecatedAlias: operation === "environment_set_variable",
|
|
3401
|
+
},
|
|
3402
|
+
nextAction: { tool: "variable_set_list", arguments: {} },
|
|
3403
|
+
}),
|
|
3404
|
+
);
|
|
2842
3405
|
};
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
});
|
|
2848
|
-
};
|
|
2849
|
-
const registerSetTool = (name: string, description: string): void => {
|
|
3406
|
+
const registerSetTool = (
|
|
3407
|
+
name: "variable_set_set_variable" | "environment_set_variable",
|
|
3408
|
+
description: string,
|
|
3409
|
+
): void => {
|
|
2850
3410
|
server.registerTool(
|
|
2851
3411
|
name,
|
|
2852
3412
|
{
|
|
@@ -2860,28 +3420,97 @@ function registerVariableSetTools(
|
|
|
2860
3420
|
value: z4.string().min(1).max(32768),
|
|
2861
3421
|
},
|
|
2862
3422
|
},
|
|
2863
|
-
setVariableHandler,
|
|
3423
|
+
setVariableHandler(name),
|
|
2864
3424
|
);
|
|
2865
3425
|
};
|
|
2866
|
-
if (can("variable-sets:
|
|
3426
|
+
if (can("variable-sets:list") && can("secrets:list")) {
|
|
2867
3427
|
registerListTool(
|
|
2868
3428
|
"variable_set_list",
|
|
2869
|
-
"List variable sets with variable names and metadata (versions, timestamps).
|
|
3429
|
+
"List variable sets with variable names and metadata (versions, timestamps). Plaintext values are never returned by list operations.",
|
|
2870
3430
|
);
|
|
2871
3431
|
registerListTool(
|
|
2872
3432
|
"environment_list",
|
|
2873
|
-
"(deprecated alias of variable_set_list) List variable sets with variable names and metadata (versions, timestamps).
|
|
3433
|
+
"(deprecated alias of variable_set_list) List variable sets with variable names and metadata (versions, timestamps). Plaintext values are never returned by list operations.",
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
if (
|
|
3438
|
+
!toolspaceMode &&
|
|
3439
|
+
sessionId !== null &&
|
|
3440
|
+
can("variable-sets:read") &&
|
|
3441
|
+
hasLiteralPermission(grant.permissions, "secrets:read")
|
|
3442
|
+
) {
|
|
3443
|
+
server.registerTool(
|
|
3444
|
+
"variable_set_get_variable",
|
|
3445
|
+
{
|
|
3446
|
+
description:
|
|
3447
|
+
"Retrieve one exact plaintext variable value. This is a dedicated high-trust read: use it only when the current task requires the configured value. The access is audited against this exact live attempt.",
|
|
3448
|
+
inputSchema: {
|
|
3449
|
+
variableSetId: z4.string().uuid().optional(),
|
|
3450
|
+
variableSetName: z4.string().min(1).max(120).optional(),
|
|
3451
|
+
name: z4.string().min(1),
|
|
3452
|
+
},
|
|
3453
|
+
},
|
|
3454
|
+
async ({ variableSetId, variableSetName, name }) => {
|
|
3455
|
+
let target: { variableSetId: string } | { variableSetName: string };
|
|
3456
|
+
if (variableSetId !== undefined) {
|
|
3457
|
+
if (variableSetName !== undefined) {
|
|
3458
|
+
throw new Error("provide exactly one of variableSetId or variableSetName");
|
|
3459
|
+
}
|
|
3460
|
+
target = { variableSetId };
|
|
3461
|
+
} else {
|
|
3462
|
+
if (variableSetName === undefined) {
|
|
3463
|
+
throw new Error("provide exactly one of variableSetId or variableSetName");
|
|
3464
|
+
}
|
|
3465
|
+
target = { variableSetName };
|
|
3466
|
+
}
|
|
3467
|
+
if (
|
|
3468
|
+
("variableSetId" in target && target.variableSetId.length === 0) ||
|
|
3469
|
+
("variableSetName" in target && target.variableSetName.trim().length === 0)
|
|
3470
|
+
) {
|
|
3471
|
+
throw new Error("provide exactly one of variableSetId or variableSetName");
|
|
3472
|
+
}
|
|
3473
|
+
const parsedName = VariableSetVariableName.safeParse(name);
|
|
3474
|
+
if (!parsedName.success) {
|
|
3475
|
+
throw new Error("variable set variable names must match ^[A-Z][A-Z0-9_]*$");
|
|
3476
|
+
}
|
|
3477
|
+
assertAllowedVariableSetVariableName(parsedName.data);
|
|
3478
|
+
const claims = preferenceAttemptClaims(grant);
|
|
3479
|
+
if (!claims || claims.sessionId !== sessionId) {
|
|
3480
|
+
throw new Error("Exact signed secret-read attempt authority is required.");
|
|
3481
|
+
}
|
|
3482
|
+
await requireLiveAgentAttemptAuthorization(deps.db, grant, sessionId);
|
|
3483
|
+
await authorizeFirstPartySession(deps, grant, sessionId, "session.secret.read");
|
|
3484
|
+
const key = requireVariableSetEncryption(deps.settings);
|
|
3485
|
+
const secret = await readVariableSetSecretAtomically(deps.db, {
|
|
3486
|
+
accountId: grant.accountId,
|
|
3487
|
+
workspaceId: grant.workspaceId,
|
|
3488
|
+
subjectId: grant.subjectId,
|
|
3489
|
+
...target,
|
|
3490
|
+
name: parsedName.data,
|
|
3491
|
+
actor: {
|
|
3492
|
+
kind: "agent_attempt",
|
|
3493
|
+
sessionId: claims.sessionId,
|
|
3494
|
+
turnId: claims.turnId,
|
|
3495
|
+
attemptId: claims.attemptId,
|
|
3496
|
+
executionGeneration: claims.executionGeneration,
|
|
3497
|
+
},
|
|
3498
|
+
decrypt: (valueEncrypted) => decryptVariableSetValue(key, valueEncrypted),
|
|
3499
|
+
});
|
|
3500
|
+
if (!secret) throw new Error("variable set variable not found");
|
|
3501
|
+
return json(secret);
|
|
3502
|
+
},
|
|
2874
3503
|
);
|
|
2875
3504
|
}
|
|
2876
3505
|
|
|
2877
|
-
if (can("variable-sets:
|
|
3506
|
+
if (can("variable-sets:write") && can("secrets:write")) {
|
|
2878
3507
|
registerSetTool(
|
|
2879
3508
|
"variable_set_set_variable",
|
|
2880
|
-
"Set or rotate one variable in a variable set. Target by variableSetId, or by variableSetName (created if it does not exist). The value is encrypted at rest and injected into sandboxes of sessions the variable set is attached to
|
|
3509
|
+
"Set or rotate one variable in a variable set. Target by variableSetId, or by variableSetName (created if it does not exist). The value is encrypted at rest and injected into sandboxes of sessions the variable set is attached to. Reading it requires the dedicated permissioned secret-read operation.",
|
|
2881
3510
|
);
|
|
2882
3511
|
registerSetTool(
|
|
2883
3512
|
"environment_set_variable",
|
|
2884
|
-
"(deprecated alias of variable_set_set_variable) Set or rotate one variable in a variable set. Target by variableSetId, or by variableSetName (created if it does not exist). The value is encrypted at rest and injected into sandboxes of sessions the variable set is attached to
|
|
3513
|
+
"(deprecated alias of variable_set_set_variable) Set or rotate one variable in a variable set. Target by variableSetId, or by variableSetName (created if it does not exist). The value is encrypted at rest and injected into sandboxes of sessions the variable set is attached to. Reading it requires the dedicated permissioned secret-read operation.",
|
|
2885
3514
|
);
|
|
2886
3515
|
}
|
|
2887
3516
|
}
|
|
@@ -2976,7 +3605,8 @@ function repositoryWithScheduledTaskResource(
|
|
|
2976
3605
|
kind: "repository",
|
|
2977
3606
|
uri,
|
|
2978
3607
|
ref: repository.defaultBranch,
|
|
2979
|
-
|
|
3608
|
+
provider: "github",
|
|
3609
|
+
mountPath: defaultRepositoryMountPath(uri, "github"),
|
|
2980
3610
|
...(repository.private
|
|
2981
3611
|
? {
|
|
2982
3612
|
githubInstallationId: repository.installationId,
|