@opengeni/api-router 0.12.5 → 0.12.12
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/{chunk-3BHOMOSD.js → chunk-5TULDPWX.js} +589 -153
- package/dist/chunk-5TULDPWX.js.map +1 -0
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/app.ts +12 -0
- package/src/github-access.ts +1 -0
- package/src/index.ts +8 -3
- package/src/mcp/files.ts +77 -0
- package/src/mcp/server.ts +196 -86
- package/src/routes/files.ts +11 -0
- package/src/routes/github.ts +325 -20
- package/src/routes/scheduled-tasks.ts +1 -0
- package/src/routes/sessions.ts +12 -23
- package/src/routes/workspace-capture.ts +108 -8
- package/dist/chunk-3BHOMOSD.js.map +0 -1
|
@@ -21,13 +21,13 @@ import {
|
|
|
21
21
|
import { dbSql, getWorkspace as getWorkspace2 } from "@opengeni/db";
|
|
22
22
|
import { createObservability } from "@opengeni/observability";
|
|
23
23
|
import { createObjectStorage } from "@opengeni/storage";
|
|
24
|
-
import { WebStandardStreamableHTTPServerTransport as
|
|
24
|
+
import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport3 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
25
25
|
import { Hono } from "hono";
|
|
26
26
|
import { bodyLimit } from "hono/body-limit";
|
|
27
27
|
import { cors } from "hono/cors";
|
|
28
28
|
import { HTTPException as HTTPException26 } from "hono/http-exception";
|
|
29
29
|
import {
|
|
30
|
-
hasPermission as
|
|
30
|
+
hasPermission as hasPermission10,
|
|
31
31
|
requireAccessGrant as requireAccessGrant18,
|
|
32
32
|
requirePermission,
|
|
33
33
|
requireSessionAuthorization as requireSessionAuthorization3,
|
|
@@ -291,6 +291,8 @@ import { requireLimit as requireLimit8 } from "@opengeni/core";
|
|
|
291
291
|
// src/mcp/server.ts
|
|
292
292
|
import {
|
|
293
293
|
CreateScheduledTaskRequest,
|
|
294
|
+
DEFAULT_FIRST_PARTY_MCP_TOOLS,
|
|
295
|
+
FIRST_PARTY_MCP_TOOL_NAMES,
|
|
294
296
|
defaultRepositoryMountPath,
|
|
295
297
|
SESSION_EVENT_RAW_DELTA_TYPES,
|
|
296
298
|
SessionEventLatestClass,
|
|
@@ -335,7 +337,6 @@ import {
|
|
|
335
337
|
MEMORY_CORRECT_TOOL_DESCRIPTION,
|
|
336
338
|
MEMORY_SAVE_TOOL_DESCRIPTION,
|
|
337
339
|
MEMORY_SEARCH_TOOL_DESCRIPTION,
|
|
338
|
-
requireFile,
|
|
339
340
|
requireScheduledTask,
|
|
340
341
|
requireSession,
|
|
341
342
|
saveWorkspaceMemory,
|
|
@@ -356,7 +357,9 @@ import {
|
|
|
356
357
|
GitHubAppConfigurationError,
|
|
357
358
|
githubAppMissingSettings
|
|
358
359
|
} from "@opengeni/github";
|
|
359
|
-
import {
|
|
360
|
+
import {
|
|
361
|
+
McpServer
|
|
362
|
+
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
360
363
|
import * as z4 from "zod/v4";
|
|
361
364
|
import {
|
|
362
365
|
hasPermission as hasPermission2,
|
|
@@ -418,6 +421,7 @@ async function listWorkspaceGitHubInstallationBindings(deps, workspaceId) {
|
|
|
418
421
|
),
|
|
419
422
|
repositoryScope: installation.repositoryScope,
|
|
420
423
|
repositoryCount: installation.repositoryIds.length,
|
|
424
|
+
configureUrl: null,
|
|
421
425
|
createdAt: installation.createdAt,
|
|
422
426
|
updatedAt: installation.updatedAt
|
|
423
427
|
}));
|
|
@@ -2607,17 +2611,124 @@ function slackPostOutcomeMayBeAmbiguous(error) {
|
|
|
2607
2611
|
}
|
|
2608
2612
|
|
|
2609
2613
|
// src/mcp/server.ts
|
|
2614
|
+
var FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
2615
|
+
set_session_title: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
2616
|
+
goal_set: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
2617
|
+
goal_update: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
2618
|
+
goal_complete: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
2619
|
+
goal_pause: { sessionRequired: true, allOf: ["goals:manage"] },
|
|
2620
|
+
memory_search: { sessionRequired: true, allOf: ["documents:search"] },
|
|
2621
|
+
memory_save: { sessionRequired: true, allOf: ["documents:search"] },
|
|
2622
|
+
memory_correct: { sessionRequired: true, allOf: ["documents:search"] },
|
|
2623
|
+
sandboxes_list: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
2624
|
+
sandbox_attach: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
2625
|
+
sandbox_swap: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
2626
|
+
run_on: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
2627
|
+
sandbox_provision: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
2628
|
+
rig_list: { allOf: ["rigs:use"] },
|
|
2629
|
+
rig_get: { allOf: ["rigs:use"] },
|
|
2630
|
+
rig_propose_change: { allOf: ["rigs:use"] },
|
|
2631
|
+
rig_verify: { allOf: ["rigs:use"] },
|
|
2632
|
+
rig_promote: { allOf: ["rigs:manage"] },
|
|
2633
|
+
sessions_list: { allOf: ["sessions:read"] },
|
|
2634
|
+
session_get: { allOf: ["sessions:read"] },
|
|
2635
|
+
session_events: { allOf: ["sessions:read"] },
|
|
2636
|
+
session_create: { allOf: ["sessions:create"] },
|
|
2637
|
+
session_send_message: { allOf: ["sessions:control"] },
|
|
2638
|
+
session_pause: { allOf: ["sessions:control"] },
|
|
2639
|
+
session_resume: { allOf: ["sessions:control"] },
|
|
2640
|
+
session_steer: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
2641
|
+
set_other_session_title: { allOf: ["sessions:control"] },
|
|
2642
|
+
variable_set_list: { allOf: ["variable-sets:use"] },
|
|
2643
|
+
environment_list: { allOf: ["variable-sets:use"] },
|
|
2644
|
+
variable_set_set_variable: { allOf: ["variable-sets:manage"] },
|
|
2645
|
+
environment_set_variable: { allOf: ["variable-sets:manage"] },
|
|
2646
|
+
github_connect_link: { allOf: ["github:use"] },
|
|
2647
|
+
github_token: { sessionRequired: true, allOf: ["github:use"] },
|
|
2648
|
+
github_repositories_list: { allOf: ["github:use"] },
|
|
2649
|
+
social_connections_list: { allOf: ["connections:read"] },
|
|
2650
|
+
social_posts_recent: { allOf: ["connections:read"] },
|
|
2651
|
+
social_daily_analysis_context: { allOf: ["connections:read"] },
|
|
2652
|
+
scheduled_tasks_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
|
|
2653
|
+
scheduled_tasks_get: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
|
|
2654
|
+
scheduled_tasks_create: { allOf: ["scheduled_tasks:manage"] },
|
|
2655
|
+
scheduled_tasks_update: { allOf: ["scheduled_tasks:manage"] },
|
|
2656
|
+
scheduled_tasks_pause: { allOf: ["scheduled_tasks:manage"] },
|
|
2657
|
+
scheduled_tasks_resume: { allOf: ["scheduled_tasks:manage"] },
|
|
2658
|
+
scheduled_tasks_trigger: { allOf: ["scheduled_tasks:run"] },
|
|
2659
|
+
scheduled_tasks_delete: { allOf: ["scheduled_tasks:manage"] },
|
|
2660
|
+
scheduled_task_runs_list: { anyOf: ["scheduled_tasks:manage", "scheduled_tasks:run"] },
|
|
2661
|
+
slack_bot_list_channels: { allOf: ["connections:read"] },
|
|
2662
|
+
slack_bot_channel_history: { allOf: ["connections:read"] },
|
|
2663
|
+
slack_bot_list_users: { allOf: ["connections:read"] },
|
|
2664
|
+
slack_bot_post_message: { allOf: ["connections:read"] }
|
|
2665
|
+
};
|
|
2666
|
+
var FIRST_PARTY_MCP_TOOL_NAME_SET = new Set(FIRST_PARTY_MCP_TOOL_NAMES);
|
|
2667
|
+
var PolicyMcpServer = class extends McpServer {
|
|
2668
|
+
constructor(grant, sessionId, selectedTools, allowUncatalogued) {
|
|
2669
|
+
super({ name: "opengeni", version: "1.0.0" });
|
|
2670
|
+
this.grant = grant;
|
|
2671
|
+
this.sessionId = sessionId;
|
|
2672
|
+
this.selectedTools = selectedTools;
|
|
2673
|
+
this.allowUncatalogued = allowUncatalogued;
|
|
2674
|
+
}
|
|
2675
|
+
registeredToolCount = 0;
|
|
2676
|
+
registerTool(name, config, cb) {
|
|
2677
|
+
const catalogued = FIRST_PARTY_MCP_TOOL_NAME_SET.has(name);
|
|
2678
|
+
let admitted = this.allowUncatalogued && !catalogued;
|
|
2679
|
+
if (catalogued) {
|
|
2680
|
+
const toolName = name;
|
|
2681
|
+
const policy = FIRST_PARTY_TOOL_AUTHORIZATION[toolName];
|
|
2682
|
+
const authorized = (!policy.sessionRequired || this.sessionId !== null) && (policy.allOf?.every((permission) => hasPermission2(this.grant.permissions, permission)) ?? true) && (policy.anyOf?.some((permission) => hasPermission2(this.grant.permissions, permission)) ?? true);
|
|
2683
|
+
const selected = this.selectedTools === null || this.selectedTools.has(toolName);
|
|
2684
|
+
admitted = authorized && selected;
|
|
2685
|
+
}
|
|
2686
|
+
if (!admitted) {
|
|
2687
|
+
return {
|
|
2688
|
+
...config.title ? { title: config.title } : {},
|
|
2689
|
+
...config.description ? { description: config.description } : {},
|
|
2690
|
+
...config.annotations ? { annotations: config.annotations } : {},
|
|
2691
|
+
...config._meta ? { _meta: config._meta } : {},
|
|
2692
|
+
handler: cb,
|
|
2693
|
+
enabled: false,
|
|
2694
|
+
enable() {
|
|
2695
|
+
},
|
|
2696
|
+
disable() {
|
|
2697
|
+
},
|
|
2698
|
+
update() {
|
|
2699
|
+
},
|
|
2700
|
+
remove() {
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
this.registeredToolCount += 1;
|
|
2705
|
+
return super.registerTool(name, config, cb);
|
|
2706
|
+
}
|
|
2707
|
+
ensureToolsListHandler() {
|
|
2708
|
+
if (this.registeredToolCount > 0) return;
|
|
2709
|
+
super.registerTool(
|
|
2710
|
+
"__opengeni_empty_first_party_surface__",
|
|
2711
|
+
{
|
|
2712
|
+
description: "Internal disabled placeholder for an empty first-party surface.",
|
|
2713
|
+
inputSchema: z4.object({})
|
|
2714
|
+
},
|
|
2715
|
+
async () => ({
|
|
2716
|
+
content: [{ type: "text", text: '{"unavailable":true}' }]
|
|
2717
|
+
})
|
|
2718
|
+
).disable();
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2610
2721
|
function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
2611
|
-
const server = new McpServer({
|
|
2612
|
-
name: "opengeni",
|
|
2613
|
-
version: "1.0.0"
|
|
2614
|
-
});
|
|
2615
2722
|
const json = (value) => ({
|
|
2616
2723
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
2617
2724
|
});
|
|
2618
2725
|
const can = (permission) => hasPermission2(grant.permissions, permission);
|
|
2619
2726
|
const toolspaceMode = options.toolspace != null;
|
|
2620
2727
|
const sessionId = typeof grant.metadata?.["sessionId"] === "string" ? grant.metadata["sessionId"] : null;
|
|
2728
|
+
const selectedTools = sessionId !== null && !toolspaceMode ? new Set(
|
|
2729
|
+
grant.metadata?.["firstPartyMcpTools"] ?? DEFAULT_FIRST_PARTY_MCP_TOOLS
|
|
2730
|
+
) : null;
|
|
2731
|
+
const server = new PolicyMcpServer(grant, sessionId, selectedTools, toolspaceMode);
|
|
2621
2732
|
if (sessionId !== null && (!toolspaceMode || can("sessions:control"))) {
|
|
2622
2733
|
server.registerTool(
|
|
2623
2734
|
"set_session_title",
|
|
@@ -2657,44 +2768,6 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
2657
2768
|
registerGitHubTokenTool(server, deps, grant, sessionId, json);
|
|
2658
2769
|
}
|
|
2659
2770
|
}
|
|
2660
|
-
if (!toolspaceMode || can("files:read")) {
|
|
2661
|
-
server.registerTool(
|
|
2662
|
-
"files_get_download_url",
|
|
2663
|
-
{
|
|
2664
|
-
description: "Create a short-lived download URL for a ready file asset.",
|
|
2665
|
-
inputSchema: { fileId: z4.string().uuid() }
|
|
2666
|
-
},
|
|
2667
|
-
async ({ fileId }) => {
|
|
2668
|
-
if (!deps.objectStorage) {
|
|
2669
|
-
throw new Error("object storage is not configured");
|
|
2670
|
-
}
|
|
2671
|
-
const file = await requireFile(deps.db, grant.workspaceId, fileId);
|
|
2672
|
-
if (file.status !== "ready") {
|
|
2673
|
-
throw new Error(`file is ${file.status}`);
|
|
2674
|
-
}
|
|
2675
|
-
const signed = await deps.objectStorage.createGetUrl({
|
|
2676
|
-
key: file.objectKey
|
|
2677
|
-
});
|
|
2678
|
-
return json({
|
|
2679
|
-
file: {
|
|
2680
|
-
id: file.id,
|
|
2681
|
-
filename: file.filename,
|
|
2682
|
-
safeFilename: file.safeFilename,
|
|
2683
|
-
contentType: file.contentType,
|
|
2684
|
-
sizeBytes: file.sizeBytes,
|
|
2685
|
-
sha256: file.sha256,
|
|
2686
|
-
status: file.status,
|
|
2687
|
-
createdAt: file.createdAt,
|
|
2688
|
-
updatedAt: file.updatedAt
|
|
2689
|
-
},
|
|
2690
|
-
downloadUrl: {
|
|
2691
|
-
url: signed.url,
|
|
2692
|
-
expiresAt: signed.expiresAt.toISOString()
|
|
2693
|
-
}
|
|
2694
|
-
});
|
|
2695
|
-
}
|
|
2696
|
-
);
|
|
2697
|
-
}
|
|
2698
2771
|
if (!toolspaceMode || can("github:use")) {
|
|
2699
2772
|
server.registerTool(
|
|
2700
2773
|
"github_repositories_list",
|
|
@@ -2985,7 +3058,8 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
2985
3058
|
await deps.workflowClient.triggerScheduledTask({
|
|
2986
3059
|
task,
|
|
2987
3060
|
agentRunUsageIdempotencyKey,
|
|
2988
|
-
triggerWorkflowId
|
|
3061
|
+
triggerWorkflowId,
|
|
3062
|
+
initiator: { kind: "subject", subjectId: grant.subjectId }
|
|
2989
3063
|
});
|
|
2990
3064
|
await recordWorkspaceUsage(deps, {
|
|
2991
3065
|
accountId: grant.accountId,
|
|
@@ -3031,6 +3105,7 @@ function buildOpenGeniMcpServer(deps, grant, options = {}) {
|
|
|
3031
3105
|
);
|
|
3032
3106
|
}
|
|
3033
3107
|
registerToolspaceProxyTools(server, options.toolspace ?? null);
|
|
3108
|
+
server.ensureToolsListHandler();
|
|
3034
3109
|
return server;
|
|
3035
3110
|
}
|
|
3036
3111
|
function registerSlackBotTools(server, deps, grant, sessionId, json) {
|
|
@@ -3658,7 +3733,7 @@ function registerRigTools(server, deps, grant, can, sessionId, json) {
|
|
|
3658
3733
|
);
|
|
3659
3734
|
}
|
|
3660
3735
|
}
|
|
3661
|
-
function exactAgentCommandContext(grant, callerSessionId) {
|
|
3736
|
+
function exactAgentCommandContext(grant, callerSessionId, authorizationSurface) {
|
|
3662
3737
|
const turnId = grant.metadata?.["turnId"];
|
|
3663
3738
|
const attemptId = grant.metadata?.["attemptId"];
|
|
3664
3739
|
const executionGeneration = grant.metadata?.["executionGeneration"];
|
|
@@ -3672,7 +3747,8 @@ function exactAgentCommandContext(grant, callerSessionId) {
|
|
|
3672
3747
|
callerSessionId,
|
|
3673
3748
|
callerTurnId: turnId,
|
|
3674
3749
|
callerAttemptId: attemptId,
|
|
3675
|
-
callerExecutionGeneration: executionGeneration
|
|
3750
|
+
callerExecutionGeneration: executionGeneration,
|
|
3751
|
+
...authorizationSurface ? { authorizationSurface } : {}
|
|
3676
3752
|
};
|
|
3677
3753
|
}
|
|
3678
3754
|
function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSessionId, toolspaceMode, json) {
|
|
@@ -3896,6 +3972,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
3896
3972
|
firstPartyMcpPermissions: z4.array(z4.string()).optional().describe(
|
|
3897
3973
|
"Optional first-party capability set for the child. Omit to inherit this session's effective permissions. An explicit set may only narrow capabilities held by this session. A goal-bearing child requires goals:manage in the resulting set; creation fails rather than adding it implicitly."
|
|
3898
3974
|
),
|
|
3975
|
+
firstPartyMcpTools: z4.array(z4.enum(FIRST_PARTY_MCP_TOOL_NAMES)).optional().describe(
|
|
3976
|
+
"Exact model-visible first-party tool selection for the child. Omit to inherit this session's effective selection. This does not grant permissions."
|
|
3977
|
+
),
|
|
3899
3978
|
// Shared-sandbox placement (addendum 05 §D). OMIT (default) to SHARE the
|
|
3900
3979
|
// creator's box — one filesystem/repo/desktop, N independent conversations;
|
|
3901
3980
|
// this is the SAFE DEFAULT. Pass "new" for a fresh isolated box (a different
|
|
@@ -3949,7 +4028,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
3949
4028
|
server.registerTool(
|
|
3950
4029
|
"session_send_message",
|
|
3951
4030
|
{
|
|
3952
|
-
description: "Send information to another session. From an OpenGeni worker this becomes a coalescible
|
|
4031
|
+
description: "Send information to another session. From an OpenGeni worker this becomes a canonical coalescible machine input: it appears in the target's compact incoming queue group, is durably added to model history when claimed, and remains visible in the timeline. A sessionless operator call appends one human/API prompt.",
|
|
3953
4032
|
inputSchema: {
|
|
3954
4033
|
sessionId: z4.string().uuid(),
|
|
3955
4034
|
text: z4.string().min(1),
|
|
@@ -3987,7 +4066,6 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
3987
4066
|
targetSessionId,
|
|
3988
4067
|
{
|
|
3989
4068
|
text,
|
|
3990
|
-
toolsProvided: false,
|
|
3991
4069
|
delivery: "send",
|
|
3992
4070
|
origin: "operator",
|
|
3993
4071
|
clientEventId: idempotencyKey,
|
|
@@ -4010,16 +4088,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4010
4088
|
}
|
|
4011
4089
|
},
|
|
4012
4090
|
async ({ sessionId, idempotencyKey, reason }) => {
|
|
4013
|
-
const authorization = await authorizeFirstPartySession(
|
|
4014
|
-
deps,
|
|
4015
|
-
grant,
|
|
4016
|
-
sessionId,
|
|
4017
|
-
"session.control"
|
|
4018
|
-
);
|
|
4019
4091
|
if (callerSessionId !== null) {
|
|
4020
4092
|
const controlled2 = await controlAgentSessionWorkstream(
|
|
4021
4093
|
deps,
|
|
4022
|
-
exactAgentCommandContext(grant, callerSessionId),
|
|
4094
|
+
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
4023
4095
|
{
|
|
4024
4096
|
targetSessionId: sessionId,
|
|
4025
4097
|
action: "pause",
|
|
@@ -4032,7 +4104,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4032
4104
|
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
4033
4105
|
serializeEffectiveSessionControl(controlled2.control),
|
|
4034
4106
|
sessionId,
|
|
4035
|
-
authorization?.relatedSessionAccess ?? "root"
|
|
4107
|
+
controlled2.authorization?.relatedSessionAccess ?? "root"
|
|
4036
4108
|
),
|
|
4037
4109
|
interruptionCount: controlled2.interruptionCount,
|
|
4038
4110
|
replay: controlled2.replay
|
|
@@ -4044,7 +4116,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4044
4116
|
accountId: grant.accountId,
|
|
4045
4117
|
workspaceId: grant.workspaceId,
|
|
4046
4118
|
sessionId,
|
|
4047
|
-
subjectId: grant.subjectId
|
|
4119
|
+
subjectId: grant.subjectId,
|
|
4120
|
+
authorizationSurface: "first_party_mcp"
|
|
4048
4121
|
},
|
|
4049
4122
|
{
|
|
4050
4123
|
action: "pause",
|
|
@@ -4052,14 +4125,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4052
4125
|
...reason ? { reason } : {}
|
|
4053
4126
|
}
|
|
4054
4127
|
);
|
|
4055
|
-
return json(
|
|
4056
|
-
...controlled,
|
|
4057
|
-
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
4058
|
-
controlled.effectiveControl,
|
|
4059
|
-
sessionId,
|
|
4060
|
-
authorization?.relatedSessionAccess ?? "root"
|
|
4061
|
-
)
|
|
4062
|
-
});
|
|
4128
|
+
return json(controlled);
|
|
4063
4129
|
}
|
|
4064
4130
|
);
|
|
4065
4131
|
server.registerTool(
|
|
@@ -4073,16 +4139,10 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4073
4139
|
}
|
|
4074
4140
|
},
|
|
4075
4141
|
async ({ sessionId, idempotencyKey, reason }) => {
|
|
4076
|
-
const authorization = await authorizeFirstPartySession(
|
|
4077
|
-
deps,
|
|
4078
|
-
grant,
|
|
4079
|
-
sessionId,
|
|
4080
|
-
"session.control"
|
|
4081
|
-
);
|
|
4082
4142
|
if (callerSessionId !== null) {
|
|
4083
4143
|
const controlled2 = await controlAgentSessionWorkstream(
|
|
4084
4144
|
deps,
|
|
4085
|
-
exactAgentCommandContext(grant, callerSessionId),
|
|
4145
|
+
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
4086
4146
|
{
|
|
4087
4147
|
targetSessionId: sessionId,
|
|
4088
4148
|
action: "resume",
|
|
@@ -4095,7 +4155,7 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4095
4155
|
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
4096
4156
|
serializeEffectiveSessionControl(controlled2.control),
|
|
4097
4157
|
sessionId,
|
|
4098
|
-
authorization?.relatedSessionAccess ?? "root"
|
|
4158
|
+
controlled2.authorization?.relatedSessionAccess ?? "root"
|
|
4099
4159
|
),
|
|
4100
4160
|
interruptionCount: controlled2.interruptionCount,
|
|
4101
4161
|
replay: controlled2.replay
|
|
@@ -4107,7 +4167,8 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4107
4167
|
accountId: grant.accountId,
|
|
4108
4168
|
workspaceId: grant.workspaceId,
|
|
4109
4169
|
sessionId,
|
|
4110
|
-
subjectId: grant.subjectId
|
|
4170
|
+
subjectId: grant.subjectId,
|
|
4171
|
+
authorizationSurface: "first_party_mcp"
|
|
4111
4172
|
},
|
|
4112
4173
|
{
|
|
4113
4174
|
action: "resume",
|
|
@@ -4115,21 +4176,14 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4115
4176
|
...reason ? { reason } : {}
|
|
4116
4177
|
}
|
|
4117
4178
|
);
|
|
4118
|
-
return json(
|
|
4119
|
-
...controlled,
|
|
4120
|
-
effectiveControl: projectEffectiveControlForRelatedAccess(
|
|
4121
|
-
controlled.effectiveControl,
|
|
4122
|
-
sessionId,
|
|
4123
|
-
authorization?.relatedSessionAccess ?? "root"
|
|
4124
|
-
)
|
|
4125
|
-
});
|
|
4179
|
+
return json(controlled);
|
|
4126
4180
|
}
|
|
4127
4181
|
);
|
|
4128
4182
|
if (callerSessionId !== null) {
|
|
4129
4183
|
server.registerTool(
|
|
4130
4184
|
"session_steer",
|
|
4131
4185
|
{
|
|
4132
|
-
description: "Atomically replace another session's current direction and resume it. The
|
|
4186
|
+
description: "Atomically replace another session's current direction and resume it. The Agent Steer is a typed machine input shown in the target queue/timeline and durably retained in model history; it never impersonates a human prompt.",
|
|
4133
4187
|
inputSchema: {
|
|
4134
4188
|
sessionId: z4.string().uuid(),
|
|
4135
4189
|
instruction: z4.string().min(1),
|
|
@@ -4137,10 +4191,9 @@ function registerWorkspaceOrchestrationTools(server, deps, grant, can, callerSes
|
|
|
4137
4191
|
}
|
|
4138
4192
|
},
|
|
4139
4193
|
async ({ sessionId, instruction, idempotencyKey }) => {
|
|
4140
|
-
await authorizeFirstPartySession(deps, grant, sessionId, "session.steer");
|
|
4141
4194
|
const result = await steerAgentSession(
|
|
4142
4195
|
deps,
|
|
4143
|
-
exactAgentCommandContext(grant, callerSessionId),
|
|
4196
|
+
exactAgentCommandContext(grant, callerSessionId, "first_party_mcp"),
|
|
4144
4197
|
{ targetSessionId: sessionId, instruction, idempotencyKey }
|
|
4145
4198
|
);
|
|
4146
4199
|
return json({
|
|
@@ -4314,14 +4367,16 @@ function registerGitHubConnectTool(server, deps, grant, options, json) {
|
|
|
4314
4367
|
const { settings } = deps;
|
|
4315
4368
|
const missing = githubAppMissingSettings(settings);
|
|
4316
4369
|
const slug = settings.githubAppSlug?.trim() || null;
|
|
4370
|
+
const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
|
|
4317
4371
|
if (missing.length > 0 || !slug) {
|
|
4318
4372
|
return json({
|
|
4319
4373
|
configured: false,
|
|
4320
4374
|
status: "disabled",
|
|
4321
|
-
|
|
4375
|
+
setupMode,
|
|
4376
|
+
appSlug: setupMode === "operator" ? slug : null,
|
|
4322
4377
|
installUrl: null,
|
|
4323
4378
|
linkUrl: null,
|
|
4324
|
-
missing
|
|
4379
|
+
missing: setupMode === "operator" ? missing : []
|
|
4325
4380
|
});
|
|
4326
4381
|
}
|
|
4327
4382
|
const installations = await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId);
|
|
@@ -4334,13 +4389,18 @@ function registerGitHubConnectTool(server, deps, grant, options, json) {
|
|
|
4334
4389
|
...githubBrowserGrantClaims(settings, grant)
|
|
4335
4390
|
}) : null;
|
|
4336
4391
|
const connectUrl = state ? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/connect?state=${encodeURIComponent(state)}` : null;
|
|
4392
|
+
const installationViews = installations.map((installation) => ({
|
|
4393
|
+
...installation,
|
|
4394
|
+
configureUrl: state && baseUrl ? `${baseUrl}/v1/workspaces/${grant.workspaceId}/github/installations/${installation.installationId}/configure?state=${encodeURIComponent(state)}` : null
|
|
4395
|
+
}));
|
|
4337
4396
|
return json({
|
|
4338
4397
|
configured: true,
|
|
4339
4398
|
status,
|
|
4340
|
-
|
|
4399
|
+
setupMode,
|
|
4400
|
+
appSlug: setupMode === "operator" ? slug : null,
|
|
4341
4401
|
installUrl: connectUrl,
|
|
4342
4402
|
linkUrl: connectUrl,
|
|
4343
|
-
installations,
|
|
4403
|
+
installations: installationViews,
|
|
4344
4404
|
missing: []
|
|
4345
4405
|
});
|
|
4346
4406
|
}
|
|
@@ -5881,8 +5941,8 @@ function registerCatalogAssetRoutes(app, deps) {
|
|
|
5881
5941
|
if (!contentType) {
|
|
5882
5942
|
throw new HTTPException5(404, { message: "asset not found" });
|
|
5883
5943
|
}
|
|
5884
|
-
const
|
|
5885
|
-
if (!
|
|
5944
|
+
const object5 = await objectStorage.getObjectBytes(key);
|
|
5945
|
+
if (!object5) {
|
|
5886
5946
|
throw new HTTPException5(404, { message: "asset not found" });
|
|
5887
5947
|
}
|
|
5888
5948
|
const etag = etagForKey(key);
|
|
@@ -5895,7 +5955,7 @@ function registerCatalogAssetRoutes(app, deps) {
|
|
|
5895
5955
|
if (ifNoneMatchSatisfied(c.req.header("if-none-match"), etag)) {
|
|
5896
5956
|
return c.body(null, 304, headers);
|
|
5897
5957
|
}
|
|
5898
|
-
return c.body(new Uint8Array(
|
|
5958
|
+
return c.body(new Uint8Array(object5.bytes), 200, { ...headers, "Content-Type": contentType });
|
|
5899
5959
|
});
|
|
5900
5960
|
}
|
|
5901
5961
|
function catalogAssetKeyFromPath(pathname) {
|
|
@@ -8931,7 +8991,7 @@ import {
|
|
|
8931
8991
|
queueDocumentForReindex,
|
|
8932
8992
|
searchDocuments as searchDocuments2
|
|
8933
8993
|
} from "@opengeni/documents";
|
|
8934
|
-
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
8994
|
+
import { WebStandardStreamableHTTPServerTransport as WebStandardStreamableHTTPServerTransport2 } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
8935
8995
|
import { HTTPException as HTTPException11 } from "hono/http-exception";
|
|
8936
8996
|
import { requireAccessGrant as requireAccessGrant5 } from "@opengeni/core";
|
|
8937
8997
|
import { recordWorkspaceUsage as recordWorkspaceUsage3, requireLimit as requireLimit3 } from "@opengeni/core";
|
|
@@ -9162,8 +9222,90 @@ import {
|
|
|
9162
9222
|
import { HTTPException as HTTPException10 } from "hono/http-exception";
|
|
9163
9223
|
import { requireAccessGrant as requireAccessGrant4 } from "@opengeni/core";
|
|
9164
9224
|
import { recordWorkspaceUsage as recordWorkspaceUsage2, requireLimit as requireLimit2 } from "@opengeni/core";
|
|
9225
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
9226
|
+
|
|
9227
|
+
// src/mcp/files.ts
|
|
9228
|
+
import { requireFile } from "@opengeni/db";
|
|
9229
|
+
import { hasPermission as hasPermission7 } from "@opengeni/core";
|
|
9230
|
+
import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9231
|
+
import * as z3 from "zod/v4";
|
|
9232
|
+
function buildFilesMcpServer(deps, grant) {
|
|
9233
|
+
const server = new McpServer3({
|
|
9234
|
+
name: "opengeni-files",
|
|
9235
|
+
version: "1.0.0"
|
|
9236
|
+
});
|
|
9237
|
+
if (!hasPermission7(grant.permissions, "files:read")) {
|
|
9238
|
+
server.registerTool(
|
|
9239
|
+
"__opengeni_empty_files_surface__",
|
|
9240
|
+
{
|
|
9241
|
+
description: "Internal disabled placeholder for an unauthorized files surface.",
|
|
9242
|
+
inputSchema: z3.object({})
|
|
9243
|
+
},
|
|
9244
|
+
async () => ({
|
|
9245
|
+
content: [{ type: "text", text: '{"unavailable":true}' }]
|
|
9246
|
+
})
|
|
9247
|
+
).disable();
|
|
9248
|
+
return server;
|
|
9249
|
+
}
|
|
9250
|
+
server.registerTool(
|
|
9251
|
+
"files_get_download_url",
|
|
9252
|
+
{
|
|
9253
|
+
description: "Create a short-lived download URL for a ready file asset.",
|
|
9254
|
+
inputSchema: { fileId: z3.string().uuid() }
|
|
9255
|
+
},
|
|
9256
|
+
async ({ fileId }) => {
|
|
9257
|
+
if (!deps.objectStorage) {
|
|
9258
|
+
throw new Error("object storage is not configured");
|
|
9259
|
+
}
|
|
9260
|
+
const file = await requireFile(deps.db, grant.workspaceId, fileId);
|
|
9261
|
+
if (file.status !== "ready") {
|
|
9262
|
+
throw new Error(`file is ${file.status}`);
|
|
9263
|
+
}
|
|
9264
|
+
const signed = await deps.objectStorage.createGetUrl({ key: file.objectKey });
|
|
9265
|
+
return {
|
|
9266
|
+
content: [
|
|
9267
|
+
{
|
|
9268
|
+
type: "text",
|
|
9269
|
+
text: JSON.stringify(
|
|
9270
|
+
{
|
|
9271
|
+
file: {
|
|
9272
|
+
id: file.id,
|
|
9273
|
+
filename: file.filename,
|
|
9274
|
+
safeFilename: file.safeFilename,
|
|
9275
|
+
contentType: file.contentType,
|
|
9276
|
+
sizeBytes: file.sizeBytes,
|
|
9277
|
+
sha256: file.sha256,
|
|
9278
|
+
status: file.status,
|
|
9279
|
+
createdAt: file.createdAt,
|
|
9280
|
+
updatedAt: file.updatedAt
|
|
9281
|
+
},
|
|
9282
|
+
downloadUrl: {
|
|
9283
|
+
url: signed.url,
|
|
9284
|
+
expiresAt: signed.expiresAt.toISOString()
|
|
9285
|
+
}
|
|
9286
|
+
},
|
|
9287
|
+
null,
|
|
9288
|
+
2
|
|
9289
|
+
)
|
|
9290
|
+
}
|
|
9291
|
+
]
|
|
9292
|
+
};
|
|
9293
|
+
}
|
|
9294
|
+
);
|
|
9295
|
+
return server;
|
|
9296
|
+
}
|
|
9297
|
+
|
|
9298
|
+
// src/routes/files.ts
|
|
9165
9299
|
function registerFileRoutes(app, deps) {
|
|
9166
9300
|
const { db, objectStorage } = deps;
|
|
9301
|
+
app.all("/v1/workspaces/:workspaceId/mcp/files", async (c) => {
|
|
9302
|
+
const workspaceId = c.req.param("workspaceId");
|
|
9303
|
+
const grant = await requireAccessGrant4(c, deps, workspaceId, "files:read");
|
|
9304
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
|
|
9305
|
+
const server = buildFilesMcpServer(deps, grant);
|
|
9306
|
+
await server.connect(transport);
|
|
9307
|
+
return await transport.handleRequest(c.req.raw);
|
|
9308
|
+
});
|
|
9167
9309
|
app.post("/v1/workspaces/:workspaceId/files/uploads", async (c) => {
|
|
9168
9310
|
const workspaceId = c.req.param("workspaceId");
|
|
9169
9311
|
const grant = await requireAccessGrant4(c, deps, workspaceId, "files:upload");
|
|
@@ -9972,7 +10114,7 @@ function registerDocumentRoutes(app, deps) {
|
|
|
9972
10114
|
const workspaceId = c.req.param("workspaceId");
|
|
9973
10115
|
const grant = await requireAccessGrant5(c, deps, workspaceId, "documents:search");
|
|
9974
10116
|
const sessionId = typeof grant.metadata?.sessionId === "string" ? grant.metadata.sessionId : void 0;
|
|
9975
|
-
const transport = new
|
|
10117
|
+
const transport = new WebStandardStreamableHTTPServerTransport2({ enableJsonResponse: true });
|
|
9976
10118
|
const server = buildDocumentsMcpServer(
|
|
9977
10119
|
db,
|
|
9978
10120
|
grant.accountId,
|
|
@@ -11653,6 +11795,7 @@ import {
|
|
|
11653
11795
|
buildGitHubAppManifest,
|
|
11654
11796
|
convertGitHubAppManifest,
|
|
11655
11797
|
createSignedState as createSignedState5,
|
|
11798
|
+
discoverGitHubInstallationBindingCandidates,
|
|
11656
11799
|
envLinesFromGitHubManifestConversion,
|
|
11657
11800
|
GitHubAppApiError,
|
|
11658
11801
|
GitHubAppConfigurationError as GitHubAppConfigurationError2,
|
|
@@ -11667,7 +11810,7 @@ import {
|
|
|
11667
11810
|
} from "@opengeni/github";
|
|
11668
11811
|
import { deleteCookie, setCookie } from "hono/cookie";
|
|
11669
11812
|
import { HTTPException as HTTPException17 } from "hono/http-exception";
|
|
11670
|
-
import { hasPermission as
|
|
11813
|
+
import { hasPermission as hasPermission8, requireAccessGrant as requireAccessGrant10 } from "@opengeni/core";
|
|
11671
11814
|
var githubStateCookie = "opengeni_github_state";
|
|
11672
11815
|
var githubBindingStateMaxAgeSeconds = 10 * 60;
|
|
11673
11816
|
var legacyInstallationChooserDisabledMessage = "The legacy repository-admin GitHub installation chooser is disabled; use the GitHub owner-consent connect flow";
|
|
@@ -11680,7 +11823,8 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11680
11823
|
const slug = settings.githubAppSlug?.trim() || null;
|
|
11681
11824
|
const installations = missing.length === 0 ? await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId) : [];
|
|
11682
11825
|
const status = githubBindingStatus(missing.length === 0, installations);
|
|
11683
|
-
const
|
|
11826
|
+
const setupMode = settings.productAccessMode === "managed" ? "platform" : "operator";
|
|
11827
|
+
const canManage = hasPermission8(grant.permissions, "github:manage");
|
|
11684
11828
|
const connectState = missing.length === 0 && slug && canManage ? createSignedState5(githubStateSecret, {
|
|
11685
11829
|
accountId: grant.accountId,
|
|
11686
11830
|
workspaceId: grant.workspaceId,
|
|
@@ -11688,16 +11832,21 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11688
11832
|
...githubBrowserGrantClaims(settings, grant)
|
|
11689
11833
|
}) : null;
|
|
11690
11834
|
const connectUrl = connectState ? `${openGeniBaseUrl(settings, c)}/v1/workspaces/${grant.workspaceId}/github/connect?state=${encodeURIComponent(connectState)}` : null;
|
|
11835
|
+
const installationViews = installations.map((installation) => ({
|
|
11836
|
+
...installation,
|
|
11837
|
+
configureUrl: connectState ? `${openGeniBaseUrl(settings, c)}/v1/workspaces/${grant.workspaceId}/github/installations/${installation.installationId}/configure?state=${encodeURIComponent(connectState)}` : null
|
|
11838
|
+
}));
|
|
11691
11839
|
return c.json({
|
|
11692
11840
|
configured: missing.length === 0,
|
|
11693
11841
|
status,
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11842
|
+
setupMode,
|
|
11843
|
+
appId: setupMode === "operator" ? settings.githubAppId ?? null : null,
|
|
11844
|
+
clientId: setupMode === "operator" ? settings.githubClientId ?? null : null,
|
|
11845
|
+
appSlug: setupMode === "operator" ? slug : null,
|
|
11697
11846
|
installUrl: connectUrl,
|
|
11698
11847
|
linkUrl: connectUrl,
|
|
11699
|
-
installations,
|
|
11700
|
-
missing
|
|
11848
|
+
installations: installationViews,
|
|
11849
|
+
missing: setupMode === "operator" ? missing : []
|
|
11701
11850
|
});
|
|
11702
11851
|
});
|
|
11703
11852
|
app.get("/v1/workspaces/:workspaceId/github/connect", async (c) => {
|
|
@@ -11710,8 +11859,8 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11710
11859
|
if (!statePayload || statePayload.intent !== "installation_authority" || statePayload.workspaceId !== workspaceId || typeof statePayload.accountId !== "string" || !isFreshGitHubBindingState(statePayload)) {
|
|
11711
11860
|
throw new HTTPException17(400, { message: "invalid or expired GitHub installation state" });
|
|
11712
11861
|
}
|
|
11713
|
-
const
|
|
11714
|
-
if (!
|
|
11862
|
+
const clientId = settings.githubClientId?.trim();
|
|
11863
|
+
if (!clientId || githubAppMissingSettings2(settings).length > 0) {
|
|
11715
11864
|
throw new HTTPException17(409, {
|
|
11716
11865
|
message: JSON.stringify({
|
|
11717
11866
|
message: "GitHub App is not configured",
|
|
@@ -11719,9 +11868,19 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11719
11868
|
})
|
|
11720
11869
|
});
|
|
11721
11870
|
}
|
|
11722
|
-
|
|
11871
|
+
const discoveryState = createSignedState5(githubStateSecret, {
|
|
11872
|
+
accountId: statePayload.accountId,
|
|
11873
|
+
workspaceId,
|
|
11874
|
+
intent: "installation_authority_discovery",
|
|
11875
|
+
...continuedGitHubBrowserGrantClaims(statePayload)
|
|
11876
|
+
});
|
|
11877
|
+
setGitHubStateCookie(c, deps, discoveryState);
|
|
11723
11878
|
return c.redirect(
|
|
11724
|
-
|
|
11879
|
+
githubOAuthAuthorizeUrl({
|
|
11880
|
+
clientId,
|
|
11881
|
+
state: discoveryState,
|
|
11882
|
+
redirectUri: `${openGeniBaseUrl(settings, c)}/v1/github/oauth/callback`
|
|
11883
|
+
})
|
|
11725
11884
|
);
|
|
11726
11885
|
});
|
|
11727
11886
|
app.get("/v1/workspaces/:workspaceId/github/repositories", async (c) => {
|
|
@@ -11773,7 +11932,46 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11773
11932
|
}
|
|
11774
11933
|
return c.body(null, 204);
|
|
11775
11934
|
});
|
|
11935
|
+
app.get(
|
|
11936
|
+
"/v1/workspaces/:workspaceId/github/installations/:installationId/configure",
|
|
11937
|
+
async (c) => {
|
|
11938
|
+
const workspaceId = c.req.param("workspaceId");
|
|
11939
|
+
const installationId = parsePositiveInteger(c.req.param("installationId"));
|
|
11940
|
+
const state = c.req.query("state");
|
|
11941
|
+
if (installationId === null || !state) {
|
|
11942
|
+
throw new HTTPException17(400, { message: "invalid GitHub installation configuration" });
|
|
11943
|
+
}
|
|
11944
|
+
const statePayload = readSignedState4(state, githubStateSecret);
|
|
11945
|
+
if (!statePayload || statePayload.intent !== "installation_authority" || statePayload.workspaceId !== workspaceId || typeof statePayload.accountId !== "string" || !isFreshGitHubBindingState(statePayload)) {
|
|
11946
|
+
throw new HTTPException17(400, {
|
|
11947
|
+
message: "invalid or expired GitHub installation configuration state"
|
|
11948
|
+
});
|
|
11949
|
+
}
|
|
11950
|
+
const grant = await requireGitHubManageGrant(c, deps, workspaceId, statePayload);
|
|
11951
|
+
if (grant.accountId !== statePayload.accountId) {
|
|
11952
|
+
throw new HTTPException17(403, {
|
|
11953
|
+
message: "GitHub installation state does not match this workspace"
|
|
11954
|
+
});
|
|
11955
|
+
}
|
|
11956
|
+
const installation = (await listWorkspaceGitHubInstallationBindings(deps, grant.workspaceId)).find((candidate) => candidate.installationId === installationId);
|
|
11957
|
+
if (!installation) {
|
|
11958
|
+
throw new HTTPException17(404, { message: "GitHub installation binding not found" });
|
|
11959
|
+
}
|
|
11960
|
+
const configureState = createSignedState5(githubStateSecret, {
|
|
11961
|
+
accountId: grant.accountId,
|
|
11962
|
+
workspaceId: grant.workspaceId,
|
|
11963
|
+
expectedInstallationId: installationId,
|
|
11964
|
+
intent: "installation_authority_install",
|
|
11965
|
+
...continuedGitHubBrowserGrantClaims(statePayload)
|
|
11966
|
+
});
|
|
11967
|
+
setGitHubStateCookie(c, deps, configureState);
|
|
11968
|
+
const configureUrl = githubInstallationSettingsUrl(installation);
|
|
11969
|
+
configureUrl.searchParams.set("state", configureState);
|
|
11970
|
+
return c.redirect(configureUrl.toString());
|
|
11971
|
+
}
|
|
11972
|
+
);
|
|
11776
11973
|
app.post("/v1/workspaces/:workspaceId/github/app-manifest", async (c) => {
|
|
11974
|
+
assertOperatorGitHubAppSetup(settings);
|
|
11777
11975
|
const workspaceId = c.req.param("workspaceId");
|
|
11778
11976
|
const grant = await requireAccessGrant10(c, deps, workspaceId, "github:manage");
|
|
11779
11977
|
const payload = GitHubAppManifestCreate.parse(await c.req.json());
|
|
@@ -11802,6 +12000,7 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11802
12000
|
});
|
|
11803
12001
|
});
|
|
11804
12002
|
app.get("/v1/github/app-manifest/callback", async (c) => {
|
|
12003
|
+
assertOperatorGitHubAppSetup(settings);
|
|
11805
12004
|
const code = c.req.query("code");
|
|
11806
12005
|
const state = c.req.query("state");
|
|
11807
12006
|
if (!code) {
|
|
@@ -11821,12 +12020,15 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11821
12020
|
}
|
|
11822
12021
|
});
|
|
11823
12022
|
const handleGitHubInstallCallback = async (c) => {
|
|
11824
|
-
const state = c.req.query("state")
|
|
12023
|
+
const state = c.req.query("state") ?? allCookieValues(c, githubStateCookie).find((candidate) => {
|
|
12024
|
+
const payload = readSignedState4(candidate, githubStateSecret);
|
|
12025
|
+
return payload?.intent === "installation_authority_install" && isFreshGitHubBindingState(payload);
|
|
12026
|
+
});
|
|
11825
12027
|
if (!state) {
|
|
11826
12028
|
throw new HTTPException17(400, { message: "missing GitHub installation state" });
|
|
11827
12029
|
}
|
|
11828
12030
|
const statePayload = readSignedState4(state, githubStateSecret);
|
|
11829
|
-
if (!statePayload || statePayload.intent !== "
|
|
12031
|
+
if (!statePayload || statePayload.intent !== "installation_authority_install" || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || !isFreshGitHubBindingState(statePayload)) {
|
|
11830
12032
|
throw new HTTPException17(400, { message: "invalid or expired GitHub installation state" });
|
|
11831
12033
|
}
|
|
11832
12034
|
requireGitHubStateCookie(c, state);
|
|
@@ -11847,6 +12049,11 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11847
12049
|
if (installationId === null) {
|
|
11848
12050
|
throw new HTTPException17(400, { message: "missing or invalid GitHub installation_id" });
|
|
11849
12051
|
}
|
|
12052
|
+
if (statePayload.expectedInstallationId !== void 0 && statePayload.expectedInstallationId !== installationId) {
|
|
12053
|
+
throw new HTTPException17(409, {
|
|
12054
|
+
message: "GitHub returned a different installation than the one being configured"
|
|
12055
|
+
});
|
|
12056
|
+
}
|
|
11850
12057
|
const clientId = settings.githubClientId?.trim();
|
|
11851
12058
|
if (!clientId) {
|
|
11852
12059
|
throw new HTTPException17(409, {
|
|
@@ -11884,7 +12091,62 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11884
12091
|
throw new HTTPException17(400, { message: "missing GitHub OAuth state" });
|
|
11885
12092
|
}
|
|
11886
12093
|
const statePayload = readSignedState4(state, githubStateSecret);
|
|
11887
|
-
if (!statePayload ||
|
|
12094
|
+
if (!statePayload || typeof statePayload.accountId !== "string" || typeof statePayload.workspaceId !== "string" || !isFreshGitHubBindingState(statePayload)) {
|
|
12095
|
+
throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
|
|
12096
|
+
}
|
|
12097
|
+
if (statePayload.intent === "installation_authority_discovery") {
|
|
12098
|
+
requireGitHubStateCookie(c, state);
|
|
12099
|
+
const grant2 = await requireGitHubManageGrant(c, deps, statePayload.workspaceId, statePayload);
|
|
12100
|
+
if (grant2.accountId !== statePayload.accountId) {
|
|
12101
|
+
throw new HTTPException17(403, {
|
|
12102
|
+
message: "GitHub OAuth state does not match this workspace"
|
|
12103
|
+
});
|
|
12104
|
+
}
|
|
12105
|
+
let candidates;
|
|
12106
|
+
try {
|
|
12107
|
+
candidates = deps.githubAppApi?.discoverInstallationBindingCandidates ? await deps.githubAppApi.discoverInstallationBindingCandidates({ code }) : deps.githubAppApi ? null : await discoverGitHubInstallationBindingCandidates(settings, { code });
|
|
12108
|
+
} catch (error) {
|
|
12109
|
+
throw githubAuthorityHttpError(error);
|
|
12110
|
+
}
|
|
12111
|
+
if (!candidates) {
|
|
12112
|
+
throw new HTTPException17(409, {
|
|
12113
|
+
message: "The configured GitHub provider cannot discover owner-authorized installations"
|
|
12114
|
+
});
|
|
12115
|
+
}
|
|
12116
|
+
if (!isConsistentGitHubBindingCandidates(candidates)) {
|
|
12117
|
+
throw new HTTPException17(409, {
|
|
12118
|
+
message: "GitHub installation discovery proof is stale or invalid"
|
|
12119
|
+
});
|
|
12120
|
+
}
|
|
12121
|
+
const selectionState = createSignedState5(githubStateSecret, {
|
|
12122
|
+
accountId: grant2.accountId,
|
|
12123
|
+
workspaceId: grant2.workspaceId,
|
|
12124
|
+
intent: "installation_authority_selection",
|
|
12125
|
+
allowedInstallationIds: candidates.map(({ installation }) => installation.installationId),
|
|
12126
|
+
...continuedGitHubBrowserGrantClaims(statePayload)
|
|
12127
|
+
});
|
|
12128
|
+
if (candidates.length === 0) {
|
|
12129
|
+
return redirectToGitHubInstallation(c, deps, selectionState);
|
|
12130
|
+
}
|
|
12131
|
+
if (candidates.length === 1) {
|
|
12132
|
+
return redirectToExactGitHubAuthorization(
|
|
12133
|
+
c,
|
|
12134
|
+
deps,
|
|
12135
|
+
selectionState,
|
|
12136
|
+
candidates[0].installation.installationId
|
|
12137
|
+
);
|
|
12138
|
+
}
|
|
12139
|
+
setGitHubStateCookie(c, deps, selectionState);
|
|
12140
|
+
return c.html(
|
|
12141
|
+
githubInstallationChooserHtml(
|
|
12142
|
+
candidates,
|
|
12143
|
+
selectionState,
|
|
12144
|
+
grant2.workspaceId,
|
|
12145
|
+
openGeniBaseUrl(settings, c)
|
|
12146
|
+
)
|
|
12147
|
+
);
|
|
12148
|
+
}
|
|
12149
|
+
if (statePayload.intent !== "installation_authority_oauth") {
|
|
11888
12150
|
throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
|
|
11889
12151
|
}
|
|
11890
12152
|
const installationId = parsePositiveInteger(String(statePayload.installationId ?? ""));
|
|
@@ -11955,6 +12217,41 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11955
12217
|
)
|
|
11956
12218
|
);
|
|
11957
12219
|
});
|
|
12220
|
+
app.post("/v1/workspaces/:workspaceId/github/installations/select", async (c) => {
|
|
12221
|
+
const workspaceId = c.req.param("workspaceId");
|
|
12222
|
+
const form = new URLSearchParams(await c.req.text());
|
|
12223
|
+
const state = form.get("state");
|
|
12224
|
+
if (!state) {
|
|
12225
|
+
throw new HTTPException17(400, { message: "missing GitHub installation selection state" });
|
|
12226
|
+
}
|
|
12227
|
+
const statePayload = readSignedState4(state, githubStateSecret);
|
|
12228
|
+
if (!statePayload || statePayload.intent !== "installation_authority_selection" || typeof statePayload.accountId !== "string" || statePayload.accountId.length === 0 || statePayload.workspaceId !== workspaceId || !isFreshGitHubBindingState(statePayload)) {
|
|
12229
|
+
throw new HTTPException17(400, {
|
|
12230
|
+
message: "invalid or expired GitHub installation selection state"
|
|
12231
|
+
});
|
|
12232
|
+
}
|
|
12233
|
+
requireGitHubStateCookie(c, state);
|
|
12234
|
+
const grant = await requireGitHubManageGrant(c, deps, workspaceId, statePayload);
|
|
12235
|
+
if (grant.accountId !== statePayload.accountId) {
|
|
12236
|
+
throw new HTTPException17(403, {
|
|
12237
|
+
message: "GitHub installation state does not match this workspace"
|
|
12238
|
+
});
|
|
12239
|
+
}
|
|
12240
|
+
const selected = form.get("installation_id");
|
|
12241
|
+
if (selected === "new") {
|
|
12242
|
+
return redirectToGitHubInstallation(c, deps, state);
|
|
12243
|
+
}
|
|
12244
|
+
const installationId = parsePositiveInteger(selected);
|
|
12245
|
+
if (installationId === null) {
|
|
12246
|
+
throw new HTTPException17(400, { message: "invalid GitHub installation selection" });
|
|
12247
|
+
}
|
|
12248
|
+
if (!Array.isArray(statePayload.allowedInstallationIds) || !statePayload.allowedInstallationIds.includes(installationId)) {
|
|
12249
|
+
throw new HTTPException17(403, {
|
|
12250
|
+
message: "GitHub installation was not in the owner-authorized selection"
|
|
12251
|
+
});
|
|
12252
|
+
}
|
|
12253
|
+
return redirectToExactGitHubAuthorization(c, deps, state, installationId);
|
|
12254
|
+
});
|
|
11958
12255
|
app.post("/v1/workspaces/:workspaceId/github/installations", async (c) => {
|
|
11959
12256
|
const workspaceId = c.req.param("workspaceId");
|
|
11960
12257
|
const form = new URLSearchParams(await c.req.text());
|
|
@@ -11966,11 +12263,62 @@ function registerGitHubRoutes(app, deps) {
|
|
|
11966
12263
|
if (!statePayload || typeof statePayload.accountId !== "string" || statePayload.accountId.length === 0 || statePayload.workspaceId !== workspaceId) {
|
|
11967
12264
|
throw new HTTPException17(400, { message: "invalid or expired GitHub OAuth state" });
|
|
11968
12265
|
}
|
|
11969
|
-
throw
|
|
12266
|
+
throw new HTTPException17(410, { message: legacyInstallationChooserDisabledMessage });
|
|
12267
|
+
});
|
|
12268
|
+
}
|
|
12269
|
+
function assertOperatorGitHubAppSetup(settings) {
|
|
12270
|
+
if (settings.productAccessMode === "managed") {
|
|
12271
|
+
throw new HTTPException17(404, {
|
|
12272
|
+
message: "GitHub App creation is unavailable in platform-managed deployments"
|
|
12273
|
+
});
|
|
12274
|
+
}
|
|
12275
|
+
}
|
|
12276
|
+
function redirectToGitHubInstallation(c, deps, sourceState) {
|
|
12277
|
+
const payload = readSignedState4(sourceState, deps.githubStateSecret);
|
|
12278
|
+
const slug = deps.settings.githubAppSlug?.trim();
|
|
12279
|
+
if (!payload || typeof payload.accountId !== "string" || typeof payload.workspaceId !== "string" || !slug) {
|
|
12280
|
+
throw new HTTPException17(409, { message: "GitHub App installation is unavailable" });
|
|
12281
|
+
}
|
|
12282
|
+
const installState = createSignedState5(deps.githubStateSecret, {
|
|
12283
|
+
accountId: payload.accountId,
|
|
12284
|
+
workspaceId: payload.workspaceId,
|
|
12285
|
+
intent: "installation_authority_install",
|
|
12286
|
+
...continuedGitHubBrowserGrantClaims(payload)
|
|
11970
12287
|
});
|
|
12288
|
+
setGitHubStateCookie(c, deps, installState);
|
|
12289
|
+
return c.redirect(
|
|
12290
|
+
`https://github.com/apps/${encodeURIComponent(slug)}/installations/new?state=${encodeURIComponent(installState)}`
|
|
12291
|
+
);
|
|
12292
|
+
}
|
|
12293
|
+
function githubInstallationSettingsUrl(installation) {
|
|
12294
|
+
if (installation.accountType === "Organization" && installation.accountLogin) {
|
|
12295
|
+
return new URL(
|
|
12296
|
+
`https://github.com/organizations/${encodeURIComponent(installation.accountLogin)}/settings/installations/${installation.installationId}`
|
|
12297
|
+
);
|
|
12298
|
+
}
|
|
12299
|
+
return new URL(`https://github.com/settings/installations/${installation.installationId}`);
|
|
11971
12300
|
}
|
|
11972
|
-
function
|
|
11973
|
-
|
|
12301
|
+
function redirectToExactGitHubAuthorization(c, deps, sourceState, installationId) {
|
|
12302
|
+
const payload = readSignedState4(sourceState, deps.githubStateSecret);
|
|
12303
|
+
const clientId = deps.settings.githubClientId?.trim();
|
|
12304
|
+
if (!payload || typeof payload.accountId !== "string" || typeof payload.workspaceId !== "string" || !clientId) {
|
|
12305
|
+
throw new HTTPException17(409, { message: "GitHub user authorization is unavailable" });
|
|
12306
|
+
}
|
|
12307
|
+
const oauthState = createSignedState5(deps.githubStateSecret, {
|
|
12308
|
+
accountId: payload.accountId,
|
|
12309
|
+
workspaceId: payload.workspaceId,
|
|
12310
|
+
installationId,
|
|
12311
|
+
intent: "installation_authority_oauth",
|
|
12312
|
+
...continuedGitHubBrowserGrantClaims(payload)
|
|
12313
|
+
});
|
|
12314
|
+
setGitHubStateCookie(c, deps, oauthState);
|
|
12315
|
+
return c.redirect(
|
|
12316
|
+
githubOAuthAuthorizeUrl({
|
|
12317
|
+
clientId,
|
|
12318
|
+
state: oauthState,
|
|
12319
|
+
redirectUri: `${openGeniBaseUrl(deps.settings, c)}/v1/github/oauth/callback`
|
|
12320
|
+
})
|
|
12321
|
+
);
|
|
11974
12322
|
}
|
|
11975
12323
|
function setGitHubStateCookie(c, deps, state) {
|
|
11976
12324
|
setCookie(c, githubStateCookie, state, {
|
|
@@ -12047,9 +12395,30 @@ function githubSuccessHtml(envLines) {
|
|
|
12047
12395
|
function githubSetupSuccessHtml(account, returnUrl) {
|
|
12048
12396
|
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Connected</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0 0 18px;color:#d4d4d8}.button{display:inline-flex;align-items:center;justify-content:center;min-height:36px;border-radius:6px;border:1px solid #3f3f46;padding:0 12px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;text-decoration:none}</style></head><body><main><h1>GitHub App connected</h1><p>${escapeHtml2(account)} is now available to this OpenGeni workspace through an explicit repository allowlist.</p><a class="button" href="${escapeHtml2(returnUrl)}">Back to OpenGeni</a></main></body></html>`;
|
|
12049
12397
|
}
|
|
12398
|
+
function githubInstallationChooserHtml(candidates, state, workspaceId, baseUrl) {
|
|
12399
|
+
const action = `${baseUrl}/v1/workspaces/${encodeURIComponent(workspaceId)}/github/installations/select`;
|
|
12400
|
+
const options = candidates.map(({ installation, authorityKind }) => {
|
|
12401
|
+
const account = escapeHtml2(
|
|
12402
|
+
installation.accountLogin ?? `installation ${installation.installationId}`
|
|
12403
|
+
);
|
|
12404
|
+
const label = authorityKind === "personal_owner" ? "Personal account" : "Organization owner";
|
|
12405
|
+
return `<label class="option"><input type="radio" name="installation_id" value="${installation.installationId}" required><span><strong>${account}</strong><small>${label}</small></span></label>`;
|
|
12406
|
+
}).join("");
|
|
12407
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Choose GitHub installation</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:12px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px}p{margin:0 0 18px;color:#d4d4d8}.options{display:grid;gap:8px;margin-bottom:18px}.option{display:flex;align-items:center;gap:12px;border:1px solid #3f3f46;border-radius:8px;padding:12px;cursor:pointer}.option span{display:grid;gap:2px}.option small{color:#a1a1aa}button{min-height:38px;border-radius:7px;border:1px solid #3f3f46;padding:0 14px;background:#f4f4f5;color:#09090b;font:600 14px system-ui,sans-serif;cursor:pointer}.secondary{margin-left:8px;background:transparent;color:#f4f4f5}</style></head><body><main><h1>Choose a GitHub account</h1><p>Only installations where GitHub proved you are the personal owner or an active organization owner are shown.</p><form method="post" action="${escapeHtml2(action)}"><input type="hidden" name="state" value="${escapeHtml2(state)}"><div class="options">${options}</div><button type="submit">Connect selected</button><button class="secondary" type="submit" name="installation_id" value="new" formnovalidate>Install on another account</button></form></main></body></html>`;
|
|
12408
|
+
}
|
|
12050
12409
|
function githubSetupPendingHtml() {
|
|
12051
12410
|
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>GitHub App Requested</title><style>body{font-family:system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#0b0b0d;color:#f4f4f5}main{width:min(640px,calc(100vw - 32px));border:1px solid #27272a;border-radius:8px;padding:28px;background:#111114}h1{margin:0 0 10px;font-size:24px;line-height:1.2}p{margin:0;color:#d4d4d8}</style></head><body><main><h1>GitHub App request sent</h1><p>A GitHub organization owner must approve the installation. OpenGeni has not created a workspace binding.</p></main></body></html>`;
|
|
12052
12411
|
}
|
|
12412
|
+
function isConsistentGitHubBindingCandidates(candidates) {
|
|
12413
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12414
|
+
return candidates.every(({ installation, authorityKind }) => {
|
|
12415
|
+
if (!Number.isSafeInteger(installation.installationId) || installation.installationId <= 0 || !Number.isSafeInteger(installation.accountId) || installation.accountId <= 0 || !installation.accountLogin?.trim() || installation.suspended || ids.has(installation.installationId)) {
|
|
12416
|
+
return false;
|
|
12417
|
+
}
|
|
12418
|
+
ids.add(installation.installationId);
|
|
12419
|
+
return authorityKind === "personal_owner" ? installation.accountType === "User" : installation.accountType === "Organization";
|
|
12420
|
+
});
|
|
12421
|
+
}
|
|
12053
12422
|
function parsePositiveInteger(value) {
|
|
12054
12423
|
if (!value || !/^\d+$/.test(value)) {
|
|
12055
12424
|
return null;
|
|
@@ -12618,7 +12987,8 @@ function registerScheduledTaskRoutes(app, deps) {
|
|
|
12618
12987
|
await workflowClient.triggerScheduledTask({
|
|
12619
12988
|
task,
|
|
12620
12989
|
agentRunUsageIdempotencyKey,
|
|
12621
|
-
triggerWorkflowId
|
|
12990
|
+
triggerWorkflowId,
|
|
12991
|
+
initiator: { kind: "subject", subjectId: grant.subjectId }
|
|
12622
12992
|
});
|
|
12623
12993
|
await recordWorkspaceUsage4(deps, {
|
|
12624
12994
|
accountId: grant.accountId,
|
|
@@ -12750,6 +13120,7 @@ import {
|
|
|
12750
13120
|
SessionContextBusyError,
|
|
12751
13121
|
HumanInputResponseValidationError,
|
|
12752
13122
|
latestWorkspaceCapture,
|
|
13123
|
+
sessionLatestWorkspaceCapture,
|
|
12753
13124
|
workspaceCaptureAtRevision
|
|
12754
13125
|
} from "@opengeni/db";
|
|
12755
13126
|
import {
|
|
@@ -12758,7 +13129,7 @@ import {
|
|
|
12758
13129
|
coalesceSessionEventDeltas,
|
|
12759
13130
|
publishDurableSessionEvents as publishDurableSessionEvents2
|
|
12760
13131
|
} from "@opengeni/events";
|
|
12761
|
-
import { z as
|
|
13132
|
+
import { z as z5, ZodError } from "zod";
|
|
12762
13133
|
|
|
12763
13134
|
// src/sandbox/channel-a.ts
|
|
12764
13135
|
import {
|
|
@@ -13560,11 +13931,69 @@ import { HTTPException as HTTPException21 } from "hono/http-exception";
|
|
|
13560
13931
|
var CAPTURE_INLINE_MANIFEST_MAX_BYTES = 2 * 1024 * 1024;
|
|
13561
13932
|
var CAPTURE_INLINE_FILE_MAX_BYTES = 256 * 1024;
|
|
13562
13933
|
var CAPTURE_SIGNED_URL_TTL_SECONDS = 300;
|
|
13934
|
+
var WorkspaceCaptureManifestCache = class {
|
|
13935
|
+
constructor(maxEntries = 64, maxBytes = 16 * 1024 * 1024) {
|
|
13936
|
+
this.maxEntries = maxEntries;
|
|
13937
|
+
this.maxBytes = maxBytes;
|
|
13938
|
+
}
|
|
13939
|
+
#entries = /* @__PURE__ */ new Map();
|
|
13940
|
+
#retainedBytes = 0;
|
|
13941
|
+
get(row) {
|
|
13942
|
+
const key = row.manifestKey;
|
|
13943
|
+
if (!key) return null;
|
|
13944
|
+
const entry = this.#entries.get(key);
|
|
13945
|
+
if (!entry || entry.identity !== manifestIdentity(row)) return null;
|
|
13946
|
+
this.#entries.delete(key);
|
|
13947
|
+
this.#entries.set(key, entry);
|
|
13948
|
+
return entry;
|
|
13949
|
+
}
|
|
13950
|
+
getInlineResponse(row) {
|
|
13951
|
+
const key = row.manifestKey;
|
|
13952
|
+
if (!key) return null;
|
|
13953
|
+
const entry = this.#entries.get(key);
|
|
13954
|
+
if (!entry || entry.identity !== manifestIdentity(row) || !entry.inlineResponse) return null;
|
|
13955
|
+
this.#entries.delete(key);
|
|
13956
|
+
this.#entries.set(key, entry);
|
|
13957
|
+
return entry.inlineResponse;
|
|
13958
|
+
}
|
|
13959
|
+
setInlineResponse(row, response) {
|
|
13960
|
+
const key = row.manifestKey;
|
|
13961
|
+
if (!key) return;
|
|
13962
|
+
const entry = this.#entries.get(key);
|
|
13963
|
+
if (!entry || entry.identity !== manifestIdentity(row)) return;
|
|
13964
|
+
entry.inlineResponse = response;
|
|
13965
|
+
}
|
|
13966
|
+
set(row, loaded) {
|
|
13967
|
+
const key = row.manifestKey;
|
|
13968
|
+
if (!key || this.maxEntries <= 0 || this.maxBytes <= 0 || loaded.byteLength > CAPTURE_INLINE_MANIFEST_MAX_BYTES || loaded.byteLength > this.maxBytes) {
|
|
13969
|
+
return;
|
|
13970
|
+
}
|
|
13971
|
+
const previous = this.#entries.get(key);
|
|
13972
|
+
if (previous) {
|
|
13973
|
+
this.#retainedBytes -= previous.byteLength;
|
|
13974
|
+
this.#entries.delete(key);
|
|
13975
|
+
}
|
|
13976
|
+
this.#entries.set(key, { ...loaded, identity: manifestIdentity(row) });
|
|
13977
|
+
this.#retainedBytes += loaded.byteLength;
|
|
13978
|
+
while (this.#entries.size > this.maxEntries || this.#retainedBytes > this.maxBytes) {
|
|
13979
|
+
const oldestKey = this.#entries.keys().next().value;
|
|
13980
|
+
if (oldestKey === void 0) break;
|
|
13981
|
+
const oldest = this.#entries.get(oldestKey);
|
|
13982
|
+
this.#entries.delete(oldestKey);
|
|
13983
|
+
if (oldest) this.#retainedBytes -= oldest.byteLength;
|
|
13984
|
+
}
|
|
13985
|
+
}
|
|
13986
|
+
};
|
|
13987
|
+
function manifestIdentity(row) {
|
|
13988
|
+
return `${row.sessionId}\0${row.turnId}\0${row.revision}\0${row.leaseEpoch}\0${row.capturedAt}`;
|
|
13989
|
+
}
|
|
13563
13990
|
function signedUrl(signed) {
|
|
13564
13991
|
return { url: signed.url, expiresAt: signed.expiresAt.toISOString() };
|
|
13565
13992
|
}
|
|
13566
|
-
async function loadManifest(row, storage) {
|
|
13993
|
+
async function loadManifest(row, storage, cache) {
|
|
13567
13994
|
if (!row.manifestKey) return null;
|
|
13995
|
+
const cached = cache?.get(row);
|
|
13996
|
+
if (cached) return cached;
|
|
13568
13997
|
const stats = WorkspaceCaptureStats.safeParse(row.stats);
|
|
13569
13998
|
if (!stats.success) {
|
|
13570
13999
|
console.warn(
|
|
@@ -13599,9 +14028,11 @@ async function loadManifest(row, storage) {
|
|
|
13599
14028
|
);
|
|
13600
14029
|
return null;
|
|
13601
14030
|
}
|
|
13602
|
-
|
|
14031
|
+
const loaded = { manifest, byteLength: blob.bytes.byteLength, stats: servedStats };
|
|
14032
|
+
cache?.set(row, loaded);
|
|
14033
|
+
return loaded;
|
|
13603
14034
|
}
|
|
13604
|
-
async function serveWorkspaceCapture(row, storage) {
|
|
14035
|
+
async function serveWorkspaceCapture(row, storage, cache) {
|
|
13605
14036
|
if (!row) {
|
|
13606
14037
|
return { available: false };
|
|
13607
14038
|
}
|
|
@@ -13620,7 +14051,9 @@ async function serveWorkspaceCapture(row, storage) {
|
|
|
13620
14051
|
});
|
|
13621
14052
|
}
|
|
13622
14053
|
if (row.state !== "available" || !row.manifestKey) return { available: false };
|
|
13623
|
-
const
|
|
14054
|
+
const cachedResponse = cache?.getInlineResponse(row);
|
|
14055
|
+
if (cachedResponse) return cachedResponse;
|
|
14056
|
+
const loaded = await loadManifest(row, storage, cache);
|
|
13624
14057
|
if (!loaded) return { available: false };
|
|
13625
14058
|
const meta = {
|
|
13626
14059
|
available: true,
|
|
@@ -13632,11 +14065,16 @@ async function serveWorkspaceCapture(row, storage) {
|
|
|
13632
14065
|
stats: loaded.stats
|
|
13633
14066
|
};
|
|
13634
14067
|
if (loaded.byteLength <= CAPTURE_INLINE_MANIFEST_MAX_BYTES) {
|
|
13635
|
-
|
|
14068
|
+
const response = GetWorkspaceCaptureResponse.parse({
|
|
13636
14069
|
...meta,
|
|
13637
14070
|
manifest: loaded.manifest,
|
|
13638
14071
|
manifestUrl: null
|
|
13639
14072
|
});
|
|
14073
|
+
if (!response.available) {
|
|
14074
|
+
throw new Error("validated inline capture response lost its available discriminator");
|
|
14075
|
+
}
|
|
14076
|
+
cache?.setInlineResponse(row, response);
|
|
14077
|
+
return response;
|
|
13640
14078
|
}
|
|
13641
14079
|
const signed = await storage.createGetUrl({
|
|
13642
14080
|
key: row.manifestKey,
|
|
@@ -13708,6 +14146,7 @@ async function serveWorkspaceCaptureFile(row, path, storage) {
|
|
|
13708
14146
|
// src/routes/sessions.ts
|
|
13709
14147
|
function registerSessionRoutes(app, deps) {
|
|
13710
14148
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
14149
|
+
const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
|
|
13711
14150
|
const ptyIdentity = (pty) => ({
|
|
13712
14151
|
leaseId: pty.leaseId,
|
|
13713
14152
|
sandboxGroupId: pty.sandboxGroupId,
|
|
@@ -13962,7 +14401,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
13962
14401
|
const workspaceId = c.req.param("workspaceId");
|
|
13963
14402
|
const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:read");
|
|
13964
14403
|
const sessionId = c.req.param("sessionId");
|
|
13965
|
-
if (!
|
|
14404
|
+
if (!z5.string().uuid().safeParse(sessionId).success) {
|
|
13966
14405
|
throw new HTTPException22(404, { message: "session not found" });
|
|
13967
14406
|
}
|
|
13968
14407
|
const session = await getSessionForSubject(
|
|
@@ -13981,7 +14420,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
13981
14420
|
const workspaceId = c.req.param("workspaceId");
|
|
13982
14421
|
const grant = await requireAccessGrant14(c, deps, workspaceId, "sessions:read");
|
|
13983
14422
|
const sessionId = c.req.param("sessionId");
|
|
13984
|
-
if (!
|
|
14423
|
+
if (!z5.string().uuid().safeParse(sessionId).success) {
|
|
13985
14424
|
throw new HTTPException22(404, { message: "session not found" });
|
|
13986
14425
|
}
|
|
13987
14426
|
const parsed = UpdateSessionPinRequest.safeParse(await c.req.json().catch(() => null));
|
|
@@ -14671,8 +15110,6 @@ function registerSessionRoutes(app, deps) {
|
|
|
14671
15110
|
text: payload.text,
|
|
14672
15111
|
turnInstructions: payload.turnInstructions ?? null,
|
|
14673
15112
|
resources: payload.resources,
|
|
14674
|
-
tools: payload.tools,
|
|
14675
|
-
toolsProvided: userMessagePayloadHasOwnProperty({ payload: raw }, "tools"),
|
|
14676
15113
|
model: payload.model ?? null,
|
|
14677
15114
|
reasoningEffort: payload.reasoningEffort ?? null,
|
|
14678
15115
|
mcpCredentialUpdates: payload.mcpCredentialUpdates ?? [],
|
|
@@ -14707,8 +15144,6 @@ function registerSessionRoutes(app, deps) {
|
|
|
14707
15144
|
text: event.payload.text,
|
|
14708
15145
|
turnInstructions: event.payload.turnInstructions ?? null,
|
|
14709
15146
|
resources: event.payload.resources ?? [],
|
|
14710
|
-
tools: event.payload.tools ?? [],
|
|
14711
|
-
toolsProvided: userMessagePayloadHasOwnProperty(rawEvent, "tools"),
|
|
14712
15147
|
model: event.payload.model ?? null,
|
|
14713
15148
|
reasoningEffort: event.payload.reasoningEffort ?? null,
|
|
14714
15149
|
mcpCredentialUpdates: event.payload.mcpCredentialUpdates ?? [],
|
|
@@ -15305,15 +15740,16 @@ function registerSessionRoutes(app, deps) {
|
|
|
15305
15740
|
const workspaceId = c.req.param("workspaceId") ?? "";
|
|
15306
15741
|
await requireAccessGrant14(c, deps, workspaceId, "files:read");
|
|
15307
15742
|
const sessionId = c.req.param("sessionId") ?? "";
|
|
15308
|
-
const
|
|
15309
|
-
if (!
|
|
15743
|
+
const lookup = await sessionLatestWorkspaceCapture(db, workspaceId, sessionId);
|
|
15744
|
+
if (!lookup.sessionExists) {
|
|
15310
15745
|
throw new HTTPException22(404, { message: "session not found" });
|
|
15311
15746
|
}
|
|
15312
15747
|
if (!objectStorage) {
|
|
15313
15748
|
return c.json({ available: false });
|
|
15314
15749
|
}
|
|
15315
|
-
|
|
15316
|
-
|
|
15750
|
+
return c.json(
|
|
15751
|
+
await serveWorkspaceCapture(lookup.capture, objectStorage, workspaceCaptureManifestCache)
|
|
15752
|
+
);
|
|
15317
15753
|
});
|
|
15318
15754
|
app.get("/v1/workspaces/:workspaceId/sessions/:sessionId/workspace/capture/file", async (c) => {
|
|
15319
15755
|
const workspaceId = c.req.param("workspaceId") ?? "";
|
|
@@ -15681,7 +16117,7 @@ function eventEnumList(raw, schema, name) {
|
|
|
15681
16117
|
}
|
|
15682
16118
|
function sessionListQuery(query, allowCursor = true) {
|
|
15683
16119
|
const parentSessionId = query.parentSessionId;
|
|
15684
|
-
if (parentSessionId !== void 0 && parentSessionId !== "null" && !
|
|
16120
|
+
if (parentSessionId !== void 0 && parentSessionId !== "null" && !z5.string().uuid().safeParse(parentSessionId).success) {
|
|
15685
16121
|
throw new HTTPException22(400, {
|
|
15686
16122
|
message: 'parentSessionId must be a session id or the literal "null"'
|
|
15687
16123
|
});
|
|
@@ -15737,18 +16173,6 @@ function optionalEventSequence(raw) {
|
|
|
15737
16173
|
}
|
|
15738
16174
|
return Math.floor(sequence);
|
|
15739
16175
|
}
|
|
15740
|
-
function hasOwnProperty(value, key) {
|
|
15741
|
-
return Boolean(
|
|
15742
|
-
value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key)
|
|
15743
|
-
);
|
|
15744
|
-
}
|
|
15745
|
-
function userMessagePayloadHasOwnProperty(value, key) {
|
|
15746
|
-
if (!value || typeof value !== "object") {
|
|
15747
|
-
return false;
|
|
15748
|
-
}
|
|
15749
|
-
const payload = value.payload;
|
|
15750
|
-
return hasOwnProperty(payload, key);
|
|
15751
|
-
}
|
|
15752
16176
|
function sessionCreateErrorResponse(c, error) {
|
|
15753
16177
|
if (error instanceof SessionSpawnDeniedError2) {
|
|
15754
16178
|
return c.json(
|
|
@@ -15837,7 +16261,7 @@ import {
|
|
|
15837
16261
|
listSocialPosts as listSocialPosts2
|
|
15838
16262
|
} from "@opengeni/db";
|
|
15839
16263
|
import { HTTPException as HTTPException23 } from "hono/http-exception";
|
|
15840
|
-
import { z as
|
|
16264
|
+
import { z as z6 } from "zod";
|
|
15841
16265
|
import { requireAccessGrant as requireAccessGrant15 } from "@opengeni/core";
|
|
15842
16266
|
function registerSocialRoutes(app, deps) {
|
|
15843
16267
|
const { db } = deps;
|
|
@@ -15927,7 +16351,7 @@ function parseConnectionIds(raw) {
|
|
|
15927
16351
|
return void 0;
|
|
15928
16352
|
}
|
|
15929
16353
|
const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
15930
|
-
const parsed =
|
|
16354
|
+
const parsed = z6.array(z6.string().uuid()).safeParse(values);
|
|
15931
16355
|
if (!parsed.success) {
|
|
15932
16356
|
throw new HTTPException23(422, {
|
|
15933
16357
|
message: "connectionIds must be a comma-separated list of UUIDs"
|
|
@@ -15988,7 +16412,7 @@ import {
|
|
|
15988
16412
|
} from "@opengeni/db";
|
|
15989
16413
|
import { boundWorkspaceControlHttpPage } from "@opengeni/events";
|
|
15990
16414
|
import { HTTPException as HTTPException24 } from "hono/http-exception";
|
|
15991
|
-
import { hasPermission as
|
|
16415
|
+
import { hasPermission as hasPermission9, requireAccessContext as requireAccessContext2, requireAccessGrant as requireAccessGrant16 } from "@opengeni/core";
|
|
15992
16416
|
import { requireLimit as requireLimit7 } from "@opengeni/core";
|
|
15993
16417
|
import {
|
|
15994
16418
|
assertWorkspaceDeletable,
|
|
@@ -16235,7 +16659,7 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
16235
16659
|
const context = await requireAccessContext2(c, deps);
|
|
16236
16660
|
const readableWorkspaceIds = [
|
|
16237
16661
|
...new Set(
|
|
16238
|
-
context.workspaceGrants.filter((grant) =>
|
|
16662
|
+
context.workspaceGrants.filter((grant) => hasPermission9(grant.permissions, "workspace:read")).map((grant) => grant.workspaceId)
|
|
16239
16663
|
)
|
|
16240
16664
|
];
|
|
16241
16665
|
if (readableWorkspaceIds.length > 0) {
|
|
@@ -16524,8 +16948,8 @@ import {
|
|
|
16524
16948
|
WorkspaceInstructionPolicyNotFoundError
|
|
16525
16949
|
} from "@opengeni/db";
|
|
16526
16950
|
import { HTTPException as HTTPException25 } from "hono/http-exception";
|
|
16527
|
-
import { z as
|
|
16528
|
-
var WorkspaceInstructionPolicyRevisionId =
|
|
16951
|
+
import { z as z7 } from "zod";
|
|
16952
|
+
var WorkspaceInstructionPolicyRevisionId = z7.string().uuid();
|
|
16529
16953
|
async function parseBody(context, schema) {
|
|
16530
16954
|
const parsed = schema.safeParse(await context.req.json().catch(() => null));
|
|
16531
16955
|
if (!parsed.success) {
|
|
@@ -17025,7 +17449,7 @@ function createApp(deps) {
|
|
|
17025
17449
|
}
|
|
17026
17450
|
const workspace = await getWorkspace2(routeDeps.db, workspaceId);
|
|
17027
17451
|
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
17028
|
-
const transport = new
|
|
17452
|
+
const transport = new WebStandardStreamableHTTPServerTransport3({
|
|
17029
17453
|
enableJsonResponse: true
|
|
17030
17454
|
});
|
|
17031
17455
|
const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
|
|
@@ -17099,7 +17523,7 @@ function createApp(deps) {
|
|
|
17099
17523
|
}
|
|
17100
17524
|
async function requireMcpAccessGrant(c, deps, workspaceId) {
|
|
17101
17525
|
const grant = await requireAccessGrant18(c, deps, workspaceId);
|
|
17102
|
-
if (
|
|
17526
|
+
if (hasPermission10(grant.permissions, "workspace:read")) {
|
|
17103
17527
|
return grant;
|
|
17104
17528
|
}
|
|
17105
17529
|
if (isToolspaceGrant(deps.settings, grant)) {
|
|
@@ -17280,6 +17704,10 @@ var routeLabelPatterns = [
|
|
|
17280
17704
|
pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/docs$/,
|
|
17281
17705
|
label: "/v1/workspaces/:workspaceId/mcp/docs"
|
|
17282
17706
|
},
|
|
17707
|
+
{
|
|
17708
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/mcp\/files$/,
|
|
17709
|
+
label: "/v1/workspaces/:workspaceId/mcp/files"
|
|
17710
|
+
},
|
|
17283
17711
|
{
|
|
17284
17712
|
pattern: /^\/v1\/workspaces\/[^/]+\/default-rig$/,
|
|
17285
17713
|
label: "/v1/workspaces/:workspaceId/default-rig"
|
|
@@ -17460,6 +17888,14 @@ var routeLabelPatterns = [
|
|
|
17460
17888
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/connect$/,
|
|
17461
17889
|
label: "/v1/workspaces/:workspaceId/github/connect"
|
|
17462
17890
|
},
|
|
17891
|
+
{
|
|
17892
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations\/select$/,
|
|
17893
|
+
label: "/v1/workspaces/:workspaceId/github/installations/select"
|
|
17894
|
+
},
|
|
17895
|
+
{
|
|
17896
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations\/[^/]+\/configure$/,
|
|
17897
|
+
label: "/v1/workspaces/:workspaceId/github/installations/:installationId/configure"
|
|
17898
|
+
},
|
|
17463
17899
|
{
|
|
17464
17900
|
pattern: /^\/v1\/workspaces\/[^/]+\/github\/installations$/,
|
|
17465
17901
|
label: "/v1/workspaces/:workspaceId/github/installations"
|
|
@@ -17641,4 +18077,4 @@ export {
|
|
|
17641
18077
|
withDefaultEnabledCapabilityMcpTools,
|
|
17642
18078
|
workflowIdForSession2 as workflowIdForSession
|
|
17643
18079
|
};
|
|
17644
|
-
//# sourceMappingURL=chunk-
|
|
18080
|
+
//# sourceMappingURL=chunk-5TULDPWX.js.map
|