@opengeni/runtime 2.5.2 → 2.5.3-canary.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.
Files changed (49) hide show
  1. package/dist/{agent-Q2VNH5FV.js → agent-RJQLKFIW.js} +2 -2
  2. package/dist/{chunk-LGLJ5PVD.js → chunk-46BFUGFL.js} +2622 -1368
  3. package/dist/chunk-46BFUGFL.js.map +1 -0
  4. package/dist/{chunk-CD22I76N.js → chunk-FHGLYLBL.js} +2 -2
  5. package/dist/{chunk-25B3EQRW.js → chunk-UNICCRW5.js} +4 -4
  6. package/dist/{chunk-GJC7H2FC.js → chunk-VUEWS4GI.js} +494 -95
  7. package/dist/chunk-VUEWS4GI.js.map +1 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.js +22 -4
  10. package/dist/{localSources-Z5IJIIRV.js → localSources-PVFCAPN2.js} +3 -3
  11. package/dist/mcp-operation-dispatch.d.ts +45 -0
  12. package/dist/mcp-operation-observation.d.ts +137 -0
  13. package/dist/operational-instructions.d.ts +1 -1
  14. package/dist/sandbox/archive-spool.d.ts +14 -0
  15. package/dist/sandbox/host-archive-spool.d.ts +19 -0
  16. package/dist/sandbox/index.d.ts +5 -1
  17. package/dist/sandbox/index.js +7 -3
  18. package/dist/sandbox/routing/backend-resolver.d.ts +1 -0
  19. package/dist/sandbox/selfhosted/file-transfer.d.ts +20 -0
  20. package/dist/sandbox/selfhosted/session.d.ts +7 -0
  21. package/dist/sandbox/workspace-archive.d.ts +7 -0
  22. package/dist/workspace-skills.d.ts +4 -0
  23. package/dist/workspace-tool-gateway.js +4 -4
  24. package/package.json +11 -11
  25. package/src/bundled_default_skills/document-parsing/SKILL.md +36 -0
  26. package/src/bundled_site_skills/opengeni-sites/SKILL.md +1 -1
  27. package/src/index.ts +231 -45
  28. package/src/lazy-tool-transport.ts +2 -0
  29. package/src/mcp-operation-dispatch.ts +144 -0
  30. package/src/mcp-operation-observation.ts +148 -0
  31. package/src/operational-instructions.ts +1 -1
  32. package/src/runtime-skills.ts +7 -2
  33. package/src/sandbox/archive-spool.ts +16 -0
  34. package/src/sandbox/host-archive-spool.ts +1040 -0
  35. package/src/sandbox/index.ts +292 -203
  36. package/src/sandbox/routing/backend-resolver.ts +2 -0
  37. package/src/sandbox/selfhosted/control-rpc.ts +22 -0
  38. package/src/sandbox/selfhosted/fault-rendering.ts +9 -0
  39. package/src/sandbox/selfhosted/file-transfer.ts +160 -0
  40. package/src/sandbox/selfhosted/op-testing.ts +3 -0
  41. package/src/sandbox/selfhosted/session.ts +194 -5
  42. package/src/sandbox/workspace-archive.ts +54 -1
  43. package/src/workspace-skills.ts +171 -31
  44. package/dist/chunk-GJC7H2FC.js.map +0 -1
  45. package/dist/chunk-LGLJ5PVD.js.map +0 -1
  46. /package/dist/{agent-Q2VNH5FV.js.map → agent-RJQLKFIW.js.map} +0 -0
  47. /package/dist/{chunk-CD22I76N.js.map → chunk-FHGLYLBL.js.map} +0 -0
  48. /package/dist/{chunk-25B3EQRW.js.map → chunk-UNICCRW5.js.map} +0 -0
  49. /package/dist/{localSources-Z5IJIIRV.js.map → localSources-PVFCAPN2.js.map} +0 -0
@@ -0,0 +1,144 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { AttemptToolResult } from "@opengeni/contracts";
3
+
4
+ export type RecoverableMcpOperation = {
5
+ operationId: string;
6
+ sourceCallId?: string;
7
+ serverId: string;
8
+ originalTool: string;
9
+ observerTool: string;
10
+ destinationDigest: string;
11
+ argumentDigest: string;
12
+ };
13
+
14
+ export type CapturedMcpOperation = RecoverableMcpOperation & { authorityDigest: string };
15
+
16
+ /** Worker-owned persistence. Its implementation must enforce canonical live
17
+ * attempt identity and store no arguments, URLs, headers, or credentials. */
18
+ export type McpOperationPersistence = {
19
+ capture: (operation: CapturedMcpOperation) => Promise<"created" | "existing">;
20
+ settleOriginal: (
21
+ input:
22
+ | { operationId: string; outcome: "completed"; result: AttemptToolResult }
23
+ | { operationId: string; outcome: "outcome_unknown" },
24
+ ) => Promise<void>;
25
+ };
26
+
27
+ type DispatchContext = {
28
+ operation: RecoverableMcpOperation;
29
+ persistence: McpOperationPersistence;
30
+ captureStarted: boolean;
31
+ captured: boolean;
32
+ closed: boolean;
33
+ };
34
+
35
+ const dispatchContext = new AsyncLocalStorage<DispatchContext>();
36
+
37
+ /** Presence is an execution-routing fact, not authorization. A closed context
38
+ * remains visible to late callbacks so they still hit the dispatch fence. */
39
+ export function hasActiveRecoverableMcpOperation(): boolean {
40
+ return dispatchContext.getStore() !== undefined;
41
+ }
42
+
43
+ export class McpOperationOutcomeUnknownError extends Error {
44
+ readonly code = "mcp_operation_outcome_unknown";
45
+ constructor(
46
+ readonly operationId: string,
47
+ cause: unknown,
48
+ ) {
49
+ super(
50
+ `MCP operation ${operationId} has an unknown outcome. Read the existing operation result; do not repeat the mutation.`,
51
+ { cause },
52
+ );
53
+ this.name = "McpOperationOutcomeUnknownError";
54
+ }
55
+ }
56
+
57
+ /** Establish trusted in-process correlation around one gateway execution. */
58
+ export async function runRecoverableMcpOperation(
59
+ operation: RecoverableMcpOperation,
60
+ persistence: McpOperationPersistence,
61
+ execute: () => Promise<AttemptToolResult>,
62
+ ): Promise<AttemptToolResult> {
63
+ const context: DispatchContext = {
64
+ operation: { ...operation },
65
+ persistence,
66
+ captureStarted: false,
67
+ captured: false,
68
+ closed: false,
69
+ };
70
+ return await dispatchContext.run(context, async () => {
71
+ let result: AttemptToolResult;
72
+ try {
73
+ try {
74
+ result = await execute();
75
+ } catch (error) {
76
+ if (!context.captured) throw error;
77
+ // An uncertain settlement acknowledgment cannot authorize replay or
78
+ // replace the original recovery handle with a database-only error.
79
+ try {
80
+ await persistence.settleOriginal({
81
+ operationId: operation.operationId,
82
+ outcome: "outcome_unknown",
83
+ });
84
+ } catch {
85
+ /* The pre-dispatch record remains the recovery authority. */
86
+ }
87
+ throw new McpOperationOutcomeUnknownError(operation.operationId, error);
88
+ }
89
+ if (!context.captured) {
90
+ throw new Error("Recoverable MCP execution did not cross its durable capture boundary");
91
+ }
92
+ try {
93
+ await persistence.settleOriginal({
94
+ operationId: operation.operationId,
95
+ outcome: "completed",
96
+ result,
97
+ });
98
+ } catch (error) {
99
+ // Never submit a second, contradictory settlement after an ambiguous
100
+ // acknowledgment of the successful one.
101
+ throw new McpOperationOutcomeUnknownError(operation.operationId, error);
102
+ }
103
+ return result;
104
+ } finally {
105
+ context.closed = true;
106
+ }
107
+ });
108
+ }
109
+
110
+ /** Called by the authorized broker immediately before the physical request.
111
+ * A second dispatch with the same identity is never an observation. */
112
+ export async function captureMcpOperationDispatch(input: {
113
+ operationId: string;
114
+ serverId: string;
115
+ toolName: string;
116
+ destinationDigest: string;
117
+ authorityDigest: string;
118
+ }): Promise<void> {
119
+ const context = dispatchContext.getStore();
120
+ if (!context) return;
121
+ const operation = context.operation;
122
+ if (
123
+ input.operationId !== operation.operationId ||
124
+ input.serverId !== operation.serverId ||
125
+ input.toolName !== operation.originalTool ||
126
+ input.destinationDigest !== operation.destinationDigest ||
127
+ !/^[a-f0-9]{64}$/u.test(input.authorityDigest)
128
+ )
129
+ throw new Error("Recoverable MCP dispatch identity does not match its trusted binding");
130
+ if (context.closed || context.captureStarted) {
131
+ throw new Error("MCP operation dispatch is already claimed or closed");
132
+ }
133
+ context.captureStarted = true;
134
+ const result = await context.persistence.capture({
135
+ ...operation,
136
+ authorityDigest: input.authorityDigest,
137
+ });
138
+ if (result !== "created")
139
+ throw new Error("MCP operation was already dispatched; read its existing result");
140
+ // The SDK may have timed out while capture was committing. Do not start a
141
+ // mutation after its enclosing execution has already returned.
142
+ if (context.closed) throw new Error("MCP operation dispatch closed during capture");
143
+ context.captured = true;
144
+ }
@@ -0,0 +1,148 @@
1
+ import { AttemptToolResult } from "@opengeni/contracts";
2
+ import { z } from "zod";
3
+ import { AsyncLocalStorage } from "node:async_hooks";
4
+
5
+ export type McpOperationObservationAuthority = {
6
+ serverId: string;
7
+ observerTool: string;
8
+ destinationDigest: string;
9
+ authorityDigest: string;
10
+ };
11
+ const observationAuthority = new AsyncLocalStorage<{
12
+ binding: McpOperationObservationAuthority;
13
+ closed: boolean;
14
+ }>();
15
+
16
+ /** Pin the actual broker request, not merely the earlier credential lookup. */
17
+ export async function runMcpOperationObservationWithAuthority<T>(
18
+ binding: McpOperationObservationAuthority,
19
+ execute: () => Promise<T>,
20
+ ): Promise<T> {
21
+ const scope = { binding: { ...binding }, closed: false };
22
+ return await observationAuthority.run(scope, async () => {
23
+ try {
24
+ return await execute();
25
+ } finally {
26
+ scope.closed = true;
27
+ }
28
+ });
29
+ }
30
+
31
+ /** The caller is the credential broker, after live authorization and before
32
+ * physical tools/call dispatch. Unrelated ordinary calls remain unchanged. */
33
+ export function assertMcpOperationObservationAuthority(actual: {
34
+ serverId: string;
35
+ toolName?: string;
36
+ destinationDigest: string;
37
+ authorityDigest?: string;
38
+ }): void {
39
+ const scope = observationAuthority.getStore();
40
+ if (!scope) return;
41
+ const expected = scope.binding;
42
+ if (
43
+ scope.closed ||
44
+ actual.serverId !== expected.serverId ||
45
+ actual.toolName !== expected.observerTool ||
46
+ actual.destinationDigest !== expected.destinationDigest ||
47
+ actual.authorityDigest !== expected.authorityDigest
48
+ ) {
49
+ throw new Error("MCP operation observation authority changed before provider use");
50
+ }
51
+ }
52
+
53
+ const fingerprint = z
54
+ .object({
55
+ version: z.literal(1),
56
+ algorithm: z.literal("sha256"),
57
+ value: z.string().regex(/^[a-f0-9]{64}$/u),
58
+ })
59
+ .strict();
60
+
61
+ const identity = {
62
+ version: z.literal(1),
63
+ operationRef: z.string().uuid(),
64
+ fingerprint,
65
+ };
66
+
67
+ const receiptSchema = z.discriminatedUnion("status", [
68
+ z.object({ ...identity, status: z.literal("unknown") }).strict(),
69
+ z
70
+ .object({
71
+ ...identity,
72
+ status: z.literal("pending"),
73
+ evidenceRevision: z.string().min(1),
74
+ })
75
+ .strict(),
76
+ z
77
+ .object({
78
+ ...identity,
79
+ status: z.literal("completed"),
80
+ receiptRevision: z
81
+ .string()
82
+ .min(1)
83
+ .refine((value) => Buffer.byteLength(value, "utf8") <= 1024, {
84
+ message: "MCP receipt revision must fit the ledger's 1024-byte identity field",
85
+ }),
86
+ result: AttemptToolResult,
87
+ })
88
+ .strict(),
89
+ z.object({ ...identity, status: z.literal("conflict") }).strict(),
90
+ ]);
91
+
92
+ const bindingSchema = z
93
+ .object({
94
+ operationId: z.string().uuid(),
95
+ serverId: z.string().min(1),
96
+ originalTool: z.string().min(1),
97
+ observerTool: z.string().min(1),
98
+ argumentDigest: fingerprint.shape.value,
99
+ })
100
+ .strict()
101
+ .refine((value) => value.originalTool !== value.observerTool, {
102
+ message: "The observer must not be the original mutation tool",
103
+ });
104
+
105
+ export type McpObservationBinding = z.infer<typeof bindingSchema>;
106
+ export type McpObservationReceipt = z.infer<typeof receiptSchema>;
107
+
108
+ /**
109
+ * One observation, never a mutation retry. This helper is deliberately not an
110
+ * authority resolver or a scheduler: its caller must load an immutable trusted
111
+ * binding and bind callObserver to that exact authorized provider destination.
112
+ * Tool annotations and model-supplied arguments cannot establish that trust.
113
+ *
114
+ * Durable settlement must additionally recheck authority under its own locks.
115
+ * A successful return alone is not a durable delivery or commit receipt.
116
+ */
117
+ export async function observeMcpOperation(input: {
118
+ binding: McpObservationBinding;
119
+ authorize: (phase: "before_request" | "before_delivery") => Promise<boolean>;
120
+ callObserver: (tool: string, args: Record<string, unknown>) => Promise<unknown>;
121
+ }): Promise<McpObservationReceipt> {
122
+ // Parse into a fresh object before callbacks can mutate the caller's input.
123
+ const binding = bindingSchema.parse(input.binding);
124
+ if (!(await input.authorize("before_request"))) {
125
+ throw new Error("MCP operation observation is not authorized");
126
+ }
127
+ const response = await input.callObserver(binding.observerTool, {
128
+ version: 1,
129
+ operationRef: binding.operationId,
130
+ originalTool: binding.originalTool,
131
+ fingerprint: { version: 1, algorithm: "sha256", value: binding.argumentDigest },
132
+ });
133
+ if (!(await input.authorize("before_delivery"))) {
134
+ throw new Error("MCP operation observation is not authorized for delivery");
135
+ }
136
+ const envelope = AttemptToolResult.parse(response);
137
+ if (envelope.isError) {
138
+ throw new Error("MCP operation observer returned an error, not a receipt");
139
+ }
140
+ const receipt = receiptSchema.parse(envelope.structuredContent);
141
+ if (
142
+ receipt.operationRef !== binding.operationId ||
143
+ receipt.fingerprint.value !== binding.argumentDigest
144
+ ) {
145
+ throw new Error("MCP operation observation receipt identity does not match");
146
+ }
147
+ return receipt;
148
+ }
@@ -159,7 +159,7 @@ Skills are reusable instructions supplied dynamically for the current session. W
159
159
 
160
160
  Use \`session_events\` for conversation history: its default returns user and completed assistant messages, not execution noise. Cursors only paginate. Request \`results\` for final outcomes, \`tools\` for tool receipts, or \`debug\` for explicit diagnostics; request large tool bodies only when needed. Use the returned continuation cursor rather than rereading whole pages. Audit reads do not acknowledge command completion.
161
161
 
162
- For a yielded command, use \`command_read\` to read available output and status, or \`command_wait\` to wait briefly using the same command interface. Keep the command ID and output cursor. A terminal read suppresses any still-pending completion notification; a running read does not. Earlier tool results and delivered messages never change. Use \`command_input\` only to send input where supported, not to poll output. An unsupported input capability does not imply output is unavailable. Give foreground commands a realistic requested wait; an internal polling slice is not a reason to return a background handle.
162
+ For a yielded command, use \`command_read\` to read available output and status, or \`command_wait\` to wait briefly using the same command interface. Keep the command ID and output cursor. A terminal read suppresses any still-pending completion notification; a running read does not. Earlier tool results and delivered messages never change. Use \`command_input\` only to send input where supported, not to poll output. An unsupported input capability does not imply output is unavailable. Give foreground commands a realistic requested wait; default to 10 seconds (yield_time_ms: 10000). An internal polling slice is not a reason to return a background handle.
163
163
 
164
164
  If the user asks to create, inspect, continue, pause, resume, steer, rename, or otherwise manage a session, use the corresponding session tool. Pause affects the selected workstream and its descendants: pausing an ancestor also stops you, so you cannot then Resume yourself. Coordinate disjoint edits through messages instead of ancestor Pause.
165
165
 
@@ -128,7 +128,7 @@ const emptyNativeToolSkillSet: NativeToolSkillSet = Object.freeze({
128
128
  export function loadNativeToolSkillArtifacts(
129
129
  nativeTools: NativeToolSkillSet & Readonly<{ projects?: boolean }>,
130
130
  ): readonly RuntimeSkillArtifact[] {
131
- const directories: string[] = [];
131
+ const directories: string[] = ["bundled_default_skills"];
132
132
  if (nativeTools.projects) directories.push("bundled_project_skills");
133
133
  if (nativeTools.editableArtifacts) directories.push("bundled_artifact_skills");
134
134
  if (nativeTools.sites) directories.push("bundled_site_skills");
@@ -384,7 +384,12 @@ function nativeToolSkillSources(nativeTools: NativeToolSkillSet): Array<{
384
384
  names: string[];
385
385
  reason: string;
386
386
  }> {
387
- const sources: Array<{ names: string[]; reason: string }> = [];
387
+ const sources: Array<{ names: string[]; reason: string }> = [
388
+ {
389
+ names: skillDirNames(packagedSkillDirectory("bundled_default_skills")),
390
+ reason: "included by default",
391
+ },
392
+ ];
388
393
  if (nativeTools.editableArtifacts) {
389
394
  sources.push({
390
395
  names: skillDirNames(packagedSkillDirectory("bundled_artifact_skills")),
@@ -0,0 +1,16 @@
1
+ import type { TarWorkspaceArchiveDescriptor } from "@opengeni/contracts";
2
+
3
+ /** Process-owned, reopenable archive bytes. Never persisted as a local path. */
4
+ export type WorkspaceArchiveSpool = {
5
+ path: string;
6
+ byteSize: number;
7
+ sha256: string;
8
+ open: () => AsyncIterable<Uint8Array>;
9
+ dispose: () => Promise<void>;
10
+ };
11
+
12
+ export type VerifiedHostWorkspaceArchive = {
13
+ kind: "host_spool";
14
+ spool: WorkspaceArchiveSpool;
15
+ descriptor: TarWorkspaceArchiveDescriptor;
16
+ };