@opengeni/api-router 4.3.3-canary.39786771332001 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/{chunk-S5VJ3GVR.js → chunk-LXUAR3HS.js} +122 -69
- package/dist/{chunk-S5VJ3GVR.js.map → chunk-LXUAR3HS.js.map} +1 -1
- package/dist/http/session-list-hints.d.ts +11 -1
- package/dist/http/sse.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/mcp/user-setup-card.d.ts +21 -0
- package/dist/routes/client-errors.d.ts +1 -0
- package/dist/routes/sessions.d.ts +1 -0
- package/package.json +24 -24
- package/src/app.ts +2 -0
- package/src/http/session-list-hints.ts +18 -5
- package/src/http/sse.ts +2 -2
- package/src/mcp/action-catalog.gen.ts +9 -0
- package/src/mcp/server.ts +37 -47
- package/src/mcp/user-setup-card.ts +42 -0
- package/src/routes/browser-sessions.ts +20 -0
- package/src/routes/interaction-resources.ts +2 -2
- package/src/routes/sessions.ts +30 -0
- package/src/routes/workspace-artifacts.ts +30 -0
- package/src/sandbox-file-artifacts.ts +3 -1
- package/dist/mcp/machine-permission-card.d.ts +0 -33
- package/src/mcp/machine-permission-card.ts +0 -78
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PublishWorkspaceArtifactVersionRequest,
|
|
6
6
|
RollbackWorkspaceArtifactRequest,
|
|
7
7
|
SetWorkspaceArtifactStatusRequest,
|
|
8
|
+
SiteOpenedResponse,
|
|
8
9
|
UpdateWorkspaceArtifactVisibilityRequest,
|
|
9
10
|
UpdateWorkspaceArtifactVisibilityResponse,
|
|
10
11
|
WORKSPACE_ARTIFACT_TITLE_MAX_CHARS,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
resolveSiteAudienceChange,
|
|
25
26
|
SessionAuthorizationDeniedError,
|
|
26
27
|
SessionAuthorizationUnavailableError,
|
|
28
|
+
currentSiteSessionOrigin,
|
|
27
29
|
siteViewerForGrant,
|
|
28
30
|
type ApiRouteDeps,
|
|
29
31
|
} from "@opengeni/core";
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
getWorkspaceArtifact,
|
|
34
36
|
getWorkspaceArtifactContentRef,
|
|
35
37
|
listWorkspaceArtifacts,
|
|
38
|
+
recordSiteOpened,
|
|
36
39
|
nestedPostgresSqlState,
|
|
37
40
|
publishWorkspaceArtifactVersion,
|
|
38
41
|
rollbackWorkspaceArtifact,
|
|
@@ -232,6 +235,33 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
|
|
|
232
235
|
}
|
|
233
236
|
});
|
|
234
237
|
|
|
238
|
+
// Opening a Site counts as this person's activity on its sidebar row. It is
|
|
239
|
+
// personal, needs only read access, and a Site page cannot record it.
|
|
240
|
+
app.post(`${base}/:artifactId/opened`, async (context) => {
|
|
241
|
+
const workspaceId = context.req.param("workspaceId");
|
|
242
|
+
const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
|
|
243
|
+
if (currentSiteSessionOrigin())
|
|
244
|
+
throw new HTTPException(403, { message: "A Site page cannot record opening a Site" });
|
|
245
|
+
const siteId = artifactId(context);
|
|
246
|
+
const viewer = await siteViewerForGrant(deps.db, grant);
|
|
247
|
+
if (!viewer.subjectId)
|
|
248
|
+
throw new HTTPException(403, { message: "A signed-in person is required" });
|
|
249
|
+
try {
|
|
250
|
+
return context.json(
|
|
251
|
+
SiteOpenedResponse.parse(
|
|
252
|
+
await recordSiteOpened(deps.db, {
|
|
253
|
+
workspaceId,
|
|
254
|
+
subjectId: viewer.subjectId,
|
|
255
|
+
viewer,
|
|
256
|
+
siteId,
|
|
257
|
+
}),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
} catch (error) {
|
|
261
|
+
return workspaceArtifactErrorResponse(context, error);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
235
265
|
app.get(base, async (context) => {
|
|
236
266
|
const workspaceId = context.req.param("workspaceId");
|
|
237
267
|
const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
|
|
@@ -461,7 +461,9 @@ export function sandboxArtifactRelativePath(value: string, workspaceRoot = "/wor
|
|
|
461
461
|
}
|
|
462
462
|
if (!relative) {
|
|
463
463
|
throw new HTTPException(400, {
|
|
464
|
-
|
|
464
|
+
// Say which root: on a connected machine the file may exist outside it,
|
|
465
|
+
// and copying it in is the way forward.
|
|
466
|
+
message: `sandbox artifact path must name a file inside this session's workspace root ${workspaceRoot}; ${path} is outside it, so copy the file into the workspace first and use that path`,
|
|
465
467
|
});
|
|
466
468
|
}
|
|
467
469
|
if (absolute.length > SANDBOX_ARTIFACT_ABSOLUTE_PATH_MAX_CHARS) {
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/** The one refusal a card can resolve: the owner just has not allowed this chat. */
|
|
2
|
-
export declare const MACHINE_PERMISSION_REQUIRED_CODE = "personal_machine_permission_required";
|
|
3
|
-
/**
|
|
4
|
-
* What the agent reads after the card was posted for it. The card is posted in
|
|
5
|
-
* this chat's Opengeni timeline; the person may instead be reading the chat in
|
|
6
|
-
* Slack, or this may be a worker or scheduled chat, so the text names the chat
|
|
7
|
-
* link rather than promising the card is in front of them. Choosing the
|
|
8
|
-
* machine on the card sends a message that continues the chat, so the agent
|
|
9
|
-
* has nothing to wait for and nothing to retry.
|
|
10
|
-
*/
|
|
11
|
-
export declare function machinePermissionCardShownReason(chatUrl: string | null): string;
|
|
12
|
-
type MachineRefusal = {
|
|
13
|
-
code?: string | undefined;
|
|
14
|
-
reason?: string | undefined;
|
|
15
|
-
};
|
|
16
|
-
export type MachinePermissionCardPost = () => Promise<{
|
|
17
|
-
eventId: string | null;
|
|
18
|
-
chatUrl: string | null;
|
|
19
|
-
} | null>;
|
|
20
|
-
/**
|
|
21
|
-
* Post the Connected Machine card when a swap or one-off command was refused
|
|
22
|
-
* only because the machine's owner has not allowed this chat yet, and tell the
|
|
23
|
-
* agent it is posted. Every other result is returned unchanged. Posting is best
|
|
24
|
-
* effort: if it cannot be posted, the original refusal still stands.
|
|
25
|
-
*/
|
|
26
|
-
export declare function withMachinePermissionCard<T extends MachineRefusal>(result: T, post: MachinePermissionCardPost): Promise<T & {
|
|
27
|
-
card?: "shown";
|
|
28
|
-
cardEventId?: string | null;
|
|
29
|
-
chatUrl?: string;
|
|
30
|
-
}>;
|
|
31
|
-
/** The Opengeni console link for a chat, or null when no safe web base is configured. */
|
|
32
|
-
export declare function sessionChatUrl(base: string | null | undefined, workspaceId: string, sessionId: string): string | null;
|
|
33
|
-
export {};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/** The one refusal a card can resolve: the owner just has not allowed this chat. */
|
|
2
|
-
export const MACHINE_PERMISSION_REQUIRED_CODE = "personal_machine_permission_required";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* What the agent reads after the card was posted for it. The card is posted in
|
|
6
|
-
* this chat's Opengeni timeline; the person may instead be reading the chat in
|
|
7
|
-
* Slack, or this may be a worker or scheduled chat, so the text names the chat
|
|
8
|
-
* link rather than promising the card is in front of them. Choosing the
|
|
9
|
-
* machine on the card sends a message that continues the chat, so the agent
|
|
10
|
-
* has nothing to wait for and nothing to retry.
|
|
11
|
-
*/
|
|
12
|
-
export function machinePermissionCardShownReason(chatUrl: string | null): string {
|
|
13
|
-
const elsewhere = chatUrl
|
|
14
|
-
? `If they are reading this chat somewhere else (for example Slack, or this is a worker or scheduled chat), give them this link to allow it: ${chatUrl}. `
|
|
15
|
-
: "If they are reading this chat somewhere else (for example Slack, or this is a worker or scheduled chat), tell them to open this chat in Opengeni to allow it. ";
|
|
16
|
-
return (
|
|
17
|
-
"This is a personal (Only me) machine, and its owner has not allowed this chat to use it yet. " +
|
|
18
|
-
"A card where the owner can allow it is now posted in this chat in Opengeni. " +
|
|
19
|
-
"If they are reading this chat in Opengeni, tell them in one short sentence that they can allow it there; do not describe buttons or steps. " +
|
|
20
|
-
elsewhere +
|
|
21
|
-
"Do not wait for it and do not retry: when they allow it, their message continues this chat."
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type MachineRefusal = { code?: string | undefined; reason?: string | undefined };
|
|
26
|
-
|
|
27
|
-
export type MachinePermissionCardPost = () => Promise<{
|
|
28
|
-
eventId: string | null;
|
|
29
|
-
chatUrl: string | null;
|
|
30
|
-
} | null>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Post the Connected Machine card when a swap or one-off command was refused
|
|
34
|
-
* only because the machine's owner has not allowed this chat yet, and tell the
|
|
35
|
-
* agent it is posted. Every other result is returned unchanged. Posting is best
|
|
36
|
-
* effort: if it cannot be posted, the original refusal still stands.
|
|
37
|
-
*/
|
|
38
|
-
export async function withMachinePermissionCard<T extends MachineRefusal>(
|
|
39
|
-
result: T,
|
|
40
|
-
post: MachinePermissionCardPost,
|
|
41
|
-
): Promise<T & { card?: "shown"; cardEventId?: string | null; chatUrl?: string }> {
|
|
42
|
-
if (result.code !== MACHINE_PERMISSION_REQUIRED_CODE) return result;
|
|
43
|
-
let posted: Awaited<ReturnType<MachinePermissionCardPost>>;
|
|
44
|
-
try {
|
|
45
|
-
posted = await post();
|
|
46
|
-
} catch {
|
|
47
|
-
return result;
|
|
48
|
-
}
|
|
49
|
-
if (!posted) return result;
|
|
50
|
-
return {
|
|
51
|
-
...result,
|
|
52
|
-
reason: machinePermissionCardShownReason(posted.chatUrl),
|
|
53
|
-
card: "shown",
|
|
54
|
-
cardEventId: posted.eventId,
|
|
55
|
-
...(posted.chatUrl ? { chatUrl: posted.chatUrl } : {}),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** The Opengeni console link for a chat, or null when no safe web base is configured. */
|
|
60
|
-
export function sessionChatUrl(
|
|
61
|
-
base: string | null | undefined,
|
|
62
|
-
workspaceId: string,
|
|
63
|
-
sessionId: string,
|
|
64
|
-
): string | null {
|
|
65
|
-
if (!base) return null;
|
|
66
|
-
try {
|
|
67
|
-
const url = new URL(
|
|
68
|
-
`/workspaces/${encodeURIComponent(workspaceId)}/sessions/${encodeURIComponent(sessionId)}`,
|
|
69
|
-
base,
|
|
70
|
-
);
|
|
71
|
-
if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username || url.password) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
return url.toString();
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|