@opengeni/api-router 4.3.3-canary.39786771332001 → 4.4.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/{chunk-S5VJ3GVR.js → chunk-3KRHXJPM.js} +28 -50
- package/dist/{chunk-S5VJ3GVR.js.map → chunk-3KRHXJPM.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/user-setup-card.d.ts +21 -0
- package/package.json +24 -24
- package/src/mcp/server.ts +25 -38
- package/src/mcp/user-setup-card.ts +42 -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
package/dist/index.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type UserSetupBlocker } from "@opengeni/contracts";
|
|
2
|
+
export { sessionChatUrl } from "@opengeni/contracts";
|
|
3
|
+
type ToolRefusal = {
|
|
4
|
+
code?: string | undefined;
|
|
5
|
+
reason?: string | undefined;
|
|
6
|
+
};
|
|
7
|
+
export type UserSetupCardPost = (blocker: UserSetupBlocker) => Promise<{
|
|
8
|
+
eventId: string | null;
|
|
9
|
+
chatUrl: string | null;
|
|
10
|
+
} | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Post the setup card when a tool was refused for something the person can fix
|
|
13
|
+
* themselves (USER_SETUP_BLOCKERS), and tell the agent it is posted. Every
|
|
14
|
+
* other result is returned unchanged. Posting is best effort: if it cannot be
|
|
15
|
+
* posted, the original refusal still stands.
|
|
16
|
+
*/
|
|
17
|
+
export declare function withUserSetupCard<T extends ToolRefusal>(result: T, post: UserSetupCardPost): Promise<T & {
|
|
18
|
+
card?: "shown";
|
|
19
|
+
cardEventId?: string | null;
|
|
20
|
+
chatUrl?: string;
|
|
21
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"description": "Opengeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -45,29 +45,29 @@
|
|
|
45
45
|
"@keeper-security/secrets-manager-core": "17.6.0",
|
|
46
46
|
"@llamaindex/liteparse": "2.14.2",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
48
|
-
"@opengeni/agent-proto": "4.
|
|
49
|
-
"@opengeni/artifact-tool": "4.
|
|
50
|
-
"@opengeni/capabilities": "4.
|
|
51
|
-
"@opengeni/codemode": "4.
|
|
52
|
-
"@opengeni/codex": "4.
|
|
53
|
-
"@opengeni/config": "4.
|
|
54
|
-
"@opengeni/contracts": "4.
|
|
55
|
-
"@opengeni/core": "4.
|
|
56
|
-
"@opengeni/db": "4.
|
|
57
|
-
"@opengeni/documents": "4.
|
|
58
|
-
"@opengeni/events": "4.
|
|
59
|
-
"@opengeni/github": "4.
|
|
60
|
-
"@opengeni/interaction": "4.
|
|
61
|
-
"@opengeni/network": "4.
|
|
62
|
-
"@opengeni/observability": "4.
|
|
63
|
-
"@opengeni/runtime": "4.
|
|
64
|
-
"@opengeni/storage": "4.
|
|
65
|
-
"@opengeni/tool-gateway": "4.
|
|
66
|
-
"@opengeni/vaults": "4.
|
|
67
|
-
"@opengeni/vaults-keeper": "4.
|
|
68
|
-
"@opengeni/vaults-onepassword": "4.
|
|
69
|
-
"@opengeni/vaults-protonpass": "4.
|
|
70
|
-
"@opengeni/xai-subscription": "4.
|
|
48
|
+
"@opengeni/agent-proto": "4.4.1",
|
|
49
|
+
"@opengeni/artifact-tool": "4.4.1",
|
|
50
|
+
"@opengeni/capabilities": "4.4.1",
|
|
51
|
+
"@opengeni/codemode": "4.4.1",
|
|
52
|
+
"@opengeni/codex": "4.4.1",
|
|
53
|
+
"@opengeni/config": "4.4.1",
|
|
54
|
+
"@opengeni/contracts": "4.4.1",
|
|
55
|
+
"@opengeni/core": "4.4.1",
|
|
56
|
+
"@opengeni/db": "4.4.1",
|
|
57
|
+
"@opengeni/documents": "4.4.1",
|
|
58
|
+
"@opengeni/events": "4.4.1",
|
|
59
|
+
"@opengeni/github": "4.4.1",
|
|
60
|
+
"@opengeni/interaction": "4.4.1",
|
|
61
|
+
"@opengeni/network": "4.4.1",
|
|
62
|
+
"@opengeni/observability": "4.4.1",
|
|
63
|
+
"@opengeni/runtime": "4.4.1",
|
|
64
|
+
"@opengeni/storage": "4.4.1",
|
|
65
|
+
"@opengeni/tool-gateway": "4.4.1",
|
|
66
|
+
"@opengeni/vaults": "4.4.1",
|
|
67
|
+
"@opengeni/vaults-keeper": "4.4.1",
|
|
68
|
+
"@opengeni/vaults-onepassword": "4.4.1",
|
|
69
|
+
"@opengeni/vaults-protonpass": "4.4.1",
|
|
70
|
+
"@opengeni/xai-subscription": "4.4.1",
|
|
71
71
|
"@temporalio/client": "^1.17.0",
|
|
72
72
|
"ajv": "^8.20.0",
|
|
73
73
|
"better-auth": "^1.6.14",
|
package/src/mcp/server.ts
CHANGED
|
@@ -18,11 +18,7 @@ import { enableMachineScreenControl, enrollmentAccess } from "../sandbox/screen-
|
|
|
18
18
|
import { registerNotificationTools } from "./notification-tools";
|
|
19
19
|
import { excelAddinSetup, registerExcelTools } from "./excel-tools";
|
|
20
20
|
import { capabilityAccountReadiness } from "./capability-account-readiness";
|
|
21
|
-
import {
|
|
22
|
-
sessionChatUrl,
|
|
23
|
-
withMachinePermissionCard,
|
|
24
|
-
type MachinePermissionCardPost,
|
|
25
|
-
} from "./machine-permission-card";
|
|
21
|
+
import { sessionChatUrl, withUserSetupCard, type UserSetupCardPost } from "./user-setup-card";
|
|
26
22
|
import {
|
|
27
23
|
prepareWorkspaceArtifactUpload,
|
|
28
24
|
prepareWorkspaceArtifactPublication,
|
|
@@ -215,6 +211,7 @@ import {
|
|
|
215
211
|
CONNECTED_MACHINE_CAPABILITY_ID,
|
|
216
212
|
EXCEL_ADDIN_CAPABILITY_ID,
|
|
217
213
|
nativeConnectionCapabilityRecommendations,
|
|
214
|
+
userSetupCardPayload,
|
|
218
215
|
OPENGENI_BROWSER_EXTENSION_URL,
|
|
219
216
|
requireLiveAgentAttemptAuthorization,
|
|
220
217
|
requireSessionAuthorization,
|
|
@@ -4323,23 +4320,23 @@ function registerFleetTools(
|
|
|
4323
4320
|
};
|
|
4324
4321
|
};
|
|
4325
4322
|
|
|
4326
|
-
// A refusal
|
|
4327
|
-
//
|
|
4328
|
-
//
|
|
4329
|
-
//
|
|
4330
|
-
//
|
|
4331
|
-
//
|
|
4332
|
-
//
|
|
4333
|
-
//
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4323
|
+
// A refusal the person can fix themselves (USER_SETUP_BLOCKERS, for example
|
|
4324
|
+
// the owner not having allowed this chat to use their machine yet) posts the
|
|
4325
|
+
// setup card right away, in the agent's own turn, so the person sees the fix
|
|
4326
|
+
// without the agent having to find and call the card tool. Only exact agent
|
|
4327
|
+
// attempts post it. A machine card names the machine the agent asked for, so
|
|
4328
|
+
// it can lead with allowing that machine. A repeated refusal for the same
|
|
4329
|
+
// machine in the same turn reuses that turn's card; the lookup and append run
|
|
4330
|
+
// atomically under the attempt fence so parallel refusals cannot stack
|
|
4331
|
+
// copies. Cards for every other capability are never limited.
|
|
4332
|
+
const userSetupCard =
|
|
4333
|
+
(toolName: string, target: string): UserSetupCardPost =>
|
|
4334
|
+
async (blocker) => {
|
|
4337
4335
|
const requestedSandboxId = UUID_TARGET.test(target) ? target.toLowerCase() : null;
|
|
4338
4336
|
if (!exactAgentAttemptClaims(grant)) return null;
|
|
4339
4337
|
const claims = exactAgentCommandContext(grant, sessionId);
|
|
4340
|
-
const
|
|
4341
|
-
|
|
4342
|
-
)!;
|
|
4338
|
+
const payload = userSetupCardPayload(blocker, { toolName, requestedSandboxId });
|
|
4339
|
+
if (!payload) return null;
|
|
4343
4340
|
const appended = await appendTurnCapabilityCardOnce(
|
|
4344
4341
|
deps.db,
|
|
4345
4342
|
grant.workspaceId,
|
|
@@ -4347,23 +4344,13 @@ function registerFleetTools(
|
|
|
4347
4344
|
claims.callerTurnId,
|
|
4348
4345
|
claims.callerExecutionGeneration,
|
|
4349
4346
|
claims.callerAttemptId,
|
|
4350
|
-
|
|
4351
|
-
{
|
|
4352
|
-
type: "tool.auth_needed",
|
|
4353
|
-
payload: {
|
|
4354
|
-
...capabilityCardPayload(item, {
|
|
4355
|
-
action: "connect",
|
|
4356
|
-
rationale: "Allow this chat to use your computer.",
|
|
4357
|
-
toolName,
|
|
4358
|
-
}),
|
|
4359
|
-
...(requestedSandboxId ? { requestedSandboxId } : {}),
|
|
4360
|
-
},
|
|
4361
|
-
},
|
|
4347
|
+
blocker.capabilityId,
|
|
4348
|
+
{ type: "tool.auth_needed", payload },
|
|
4362
4349
|
requestedSandboxId,
|
|
4363
4350
|
);
|
|
4364
4351
|
await publishDurableSessionEvents(deps.bus, grant.workspaceId, sessionId, appended.events);
|
|
4365
4352
|
if (!appended.accepted) {
|
|
4366
|
-
throw new Error("The calling turn was replaced before the
|
|
4353
|
+
throw new Error("The calling turn was replaced before the setup card committed.");
|
|
4367
4354
|
}
|
|
4368
4355
|
return {
|
|
4369
4356
|
eventId: appended.existingEventId ?? appended.events[0]?.id ?? null,
|
|
@@ -4398,9 +4385,9 @@ function registerFleetTools(
|
|
|
4398
4385
|
},
|
|
4399
4386
|
async ({ target }) =>
|
|
4400
4387
|
json(
|
|
4401
|
-
await
|
|
4388
|
+
await withUserSetupCard(
|
|
4402
4389
|
await swapActiveSandbox(services, await fleetContext(), target),
|
|
4403
|
-
|
|
4390
|
+
userSetupCard("sandbox_attach", target),
|
|
4404
4391
|
),
|
|
4405
4392
|
),
|
|
4406
4393
|
);
|
|
@@ -4414,9 +4401,9 @@ function registerFleetTools(
|
|
|
4414
4401
|
},
|
|
4415
4402
|
async ({ target }) =>
|
|
4416
4403
|
json(
|
|
4417
|
-
await
|
|
4404
|
+
await withUserSetupCard(
|
|
4418
4405
|
await swapActiveSandbox(services, await fleetContext(), target),
|
|
4419
|
-
|
|
4406
|
+
userSetupCard("sandbox_swap", target),
|
|
4420
4407
|
),
|
|
4421
4408
|
),
|
|
4422
4409
|
);
|
|
@@ -4458,12 +4445,12 @@ function registerFleetTools(
|
|
|
4458
4445
|
)
|
|
4459
4446
|
: null;
|
|
4460
4447
|
return json(
|
|
4461
|
-
await
|
|
4448
|
+
await withUserSetupCard(
|
|
4462
4449
|
await runOnSandbox(services, await fleetContext(), target, typedOp, {
|
|
4463
4450
|
...(transientExecEnvironment ? { transientExecEnvironment } : {}),
|
|
4464
4451
|
...(secretResolver ? { secretResolver } : {}),
|
|
4465
4452
|
}),
|
|
4466
|
-
|
|
4453
|
+
userSetupCard("run_on", target),
|
|
4467
4454
|
),
|
|
4468
4455
|
);
|
|
4469
4456
|
},
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type UserSetupBlocker,
|
|
3
|
+
userSetupBlocker,
|
|
4
|
+
userSetupCardShownMessage,
|
|
5
|
+
} from "@opengeni/contracts";
|
|
6
|
+
|
|
7
|
+
export { sessionChatUrl } from "@opengeni/contracts";
|
|
8
|
+
|
|
9
|
+
type ToolRefusal = { code?: string | undefined; reason?: string | undefined };
|
|
10
|
+
|
|
11
|
+
export type UserSetupCardPost = (blocker: UserSetupBlocker) => Promise<{
|
|
12
|
+
eventId: string | null;
|
|
13
|
+
chatUrl: string | null;
|
|
14
|
+
} | null>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Post the setup card when a tool was refused for something the person can fix
|
|
18
|
+
* themselves (USER_SETUP_BLOCKERS), and tell the agent it is posted. Every
|
|
19
|
+
* other result is returned unchanged. Posting is best effort: if it cannot be
|
|
20
|
+
* posted, the original refusal still stands.
|
|
21
|
+
*/
|
|
22
|
+
export async function withUserSetupCard<T extends ToolRefusal>(
|
|
23
|
+
result: T,
|
|
24
|
+
post: UserSetupCardPost,
|
|
25
|
+
): Promise<T & { card?: "shown"; cardEventId?: string | null; chatUrl?: string }> {
|
|
26
|
+
const blocker = userSetupBlocker(result.code);
|
|
27
|
+
if (!blocker) return result;
|
|
28
|
+
let posted: Awaited<ReturnType<UserSetupCardPost>>;
|
|
29
|
+
try {
|
|
30
|
+
posted = await post(blocker);
|
|
31
|
+
} catch {
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
if (!posted) return result;
|
|
35
|
+
return {
|
|
36
|
+
...result,
|
|
37
|
+
reason: userSetupCardShownMessage(blocker, posted.chatUrl),
|
|
38
|
+
card: "shown",
|
|
39
|
+
cardEventId: posted.eventId,
|
|
40
|
+
...(posted.chatUrl ? { chatUrl: posted.chatUrl } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -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
|
-
}
|