@4xeoz/re-entry 0.2.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/README.md +337 -0
- package/node_modules/@webmcp-challenge/reentry-core/README.md +112 -0
- package/node_modules/@webmcp-challenge/reentry-core/package.json +38 -0
- package/node_modules/@webmcp-challenge/reentry-core/protocol/test-vectors/v0.1.json +47 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/agent-adapter.mjs +438 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/cloud-receiver-http.mjs +268 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/host-sdk.mjs +278 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/index.mjs +3 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/local-connector-client.mjs +530 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/managed-context-adapter.mjs +275 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/protocol.mjs +845 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/receiver-core.mjs +867 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/receiver-delivery.mjs +613 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/receiver-http-contract.mjs +24 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/receiver-support.mjs +151 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/sqlite-receiver-schema.mjs +184 -0
- package/node_modules/@webmcp-challenge/reentry-core/src/sqlite-receiver-store.mjs +573 -0
- package/package.json +44 -0
- package/src/browser-prompt.mjs +18 -0
- package/src/codex-discovery.mjs +246 -0
- package/src/codex-exec-adapter.mjs +197 -0
- package/src/codex-queue-adapter.mjs +214 -0
- package/src/credentials.mjs +87 -0
- package/src/index.mjs +6 -0
- package/src/local-connector.mjs +82 -0
- package/src/macos-service.mjs +184 -0
- package/src/main.mjs +733 -0
- package/src/pairing-client.mjs +545 -0
- package/src/terminal-ui.mjs +99 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
EVENT_TYPE,
|
|
5
|
+
MANIFEST_TYPE,
|
|
6
|
+
PROTOCOL_VERSION,
|
|
7
|
+
ProtocolValidationError,
|
|
8
|
+
createContinuationEvent,
|
|
9
|
+
createContinuationEventEnvelope,
|
|
10
|
+
createReentryManifest,
|
|
11
|
+
validatePublicBinding,
|
|
12
|
+
} from "./protocol.mjs";
|
|
13
|
+
|
|
14
|
+
const SDK_OPTION_FIELDS = Object.freeze([
|
|
15
|
+
"origin",
|
|
16
|
+
"privateKey",
|
|
17
|
+
"keyId",
|
|
18
|
+
"clock",
|
|
19
|
+
"createId",
|
|
20
|
+
]);
|
|
21
|
+
const MANIFEST_INPUT_FIELDS = Object.freeze([
|
|
22
|
+
"manifestId",
|
|
23
|
+
"correlationId",
|
|
24
|
+
"issuedAt",
|
|
25
|
+
"offerExpiresAt",
|
|
26
|
+
"workflow",
|
|
27
|
+
"display",
|
|
28
|
+
"grantRequest",
|
|
29
|
+
]);
|
|
30
|
+
const MANIFEST_REQUIRED_FIELDS = Object.freeze([
|
|
31
|
+
"offerExpiresAt",
|
|
32
|
+
"workflow",
|
|
33
|
+
"display",
|
|
34
|
+
"grantRequest",
|
|
35
|
+
]);
|
|
36
|
+
const MANIFEST_WORKFLOW_FIELDS = Object.freeze([
|
|
37
|
+
"id",
|
|
38
|
+
"type",
|
|
39
|
+
"stateVersion",
|
|
40
|
+
"canonicalUrl",
|
|
41
|
+
]);
|
|
42
|
+
const MANIFEST_DISPLAY_FIELDS = Object.freeze(["title", "reason"]);
|
|
43
|
+
const MANIFEST_GRANT_FIELDS = Object.freeze([
|
|
44
|
+
"eventType",
|
|
45
|
+
"grantExpiresAt",
|
|
46
|
+
"humanBoundary",
|
|
47
|
+
]);
|
|
48
|
+
const EVENT_INPUT_FIELDS = Object.freeze([
|
|
49
|
+
"binding",
|
|
50
|
+
"workflow",
|
|
51
|
+
"eventId",
|
|
52
|
+
"occurredAt",
|
|
53
|
+
"deliveryTimestamp",
|
|
54
|
+
]);
|
|
55
|
+
const EVENT_REQUIRED_FIELDS = Object.freeze(["binding", "workflow"]);
|
|
56
|
+
const EVENT_WORKFLOW_FIELDS = Object.freeze(["id", "stateVersion", "canonicalUrl"]);
|
|
57
|
+
|
|
58
|
+
export class ReentryHostSdk {
|
|
59
|
+
#origin;
|
|
60
|
+
#privateKey;
|
|
61
|
+
#keyId;
|
|
62
|
+
#clock;
|
|
63
|
+
#createId;
|
|
64
|
+
|
|
65
|
+
constructor(options) {
|
|
66
|
+
requireSdkRecord(
|
|
67
|
+
options,
|
|
68
|
+
SDK_OPTION_FIELDS,
|
|
69
|
+
["origin", "privateKey", "keyId"],
|
|
70
|
+
"Host SDK options",
|
|
71
|
+
);
|
|
72
|
+
this.#origin = requireCanonicalOrigin(options.origin);
|
|
73
|
+
this.#privateKey = options.privateKey;
|
|
74
|
+
this.#keyId = options.keyId;
|
|
75
|
+
this.#clock = options.clock ?? (() => new Date());
|
|
76
|
+
this.#createId = options.createId ?? ((prefix) => `${prefix}_${randomUUID()}`);
|
|
77
|
+
|
|
78
|
+
if (typeof this.#clock !== "function") {
|
|
79
|
+
throw new TypeError("Host SDK clock must be a function");
|
|
80
|
+
}
|
|
81
|
+
if (typeof this.#createId !== "function") {
|
|
82
|
+
throw new TypeError("Host SDK createId must be a function");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
issueManifest(input) {
|
|
87
|
+
requireSdkRecord(
|
|
88
|
+
input,
|
|
89
|
+
MANIFEST_INPUT_FIELDS,
|
|
90
|
+
MANIFEST_REQUIRED_FIELDS,
|
|
91
|
+
"Host Manifest input",
|
|
92
|
+
);
|
|
93
|
+
requireSdkRecord(
|
|
94
|
+
input.workflow,
|
|
95
|
+
MANIFEST_WORKFLOW_FIELDS,
|
|
96
|
+
MANIFEST_WORKFLOW_FIELDS,
|
|
97
|
+
"Host Manifest workflow",
|
|
98
|
+
);
|
|
99
|
+
requireSdkRecord(
|
|
100
|
+
input.display,
|
|
101
|
+
MANIFEST_DISPLAY_FIELDS,
|
|
102
|
+
MANIFEST_DISPLAY_FIELDS,
|
|
103
|
+
"Host Manifest display",
|
|
104
|
+
);
|
|
105
|
+
requireSdkRecord(
|
|
106
|
+
input.grantRequest,
|
|
107
|
+
MANIFEST_GRANT_FIELDS,
|
|
108
|
+
MANIFEST_GRANT_FIELDS,
|
|
109
|
+
"Host Manifest grant request",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const now = this.#readClock();
|
|
113
|
+
return createReentryManifest({
|
|
114
|
+
type: MANIFEST_TYPE,
|
|
115
|
+
protocol_version: PROTOCOL_VERSION,
|
|
116
|
+
manifest_id: this.#resolveId("manifest", input.manifestId),
|
|
117
|
+
correlation_id: this.#resolveId("correlation", input.correlationId),
|
|
118
|
+
issuer_origin: this.#origin,
|
|
119
|
+
issued_at: input.issuedAt ?? now.toISOString(),
|
|
120
|
+
offer_expires_at: input.offerExpiresAt,
|
|
121
|
+
workflow: {
|
|
122
|
+
id: input.workflow.id,
|
|
123
|
+
type: input.workflow.type,
|
|
124
|
+
state_version: input.workflow.stateVersion,
|
|
125
|
+
canonical_url: input.workflow.canonicalUrl,
|
|
126
|
+
},
|
|
127
|
+
display: {
|
|
128
|
+
title: input.display.title,
|
|
129
|
+
reason: input.display.reason,
|
|
130
|
+
},
|
|
131
|
+
grant_request: {
|
|
132
|
+
event_type: input.grantRequest.eventType,
|
|
133
|
+
grant_expires_at: input.grantRequest.grantExpiresAt,
|
|
134
|
+
max_runs: 1,
|
|
135
|
+
human_boundary: input.grantRequest.humanBoundary,
|
|
136
|
+
},
|
|
137
|
+
}, {
|
|
138
|
+
privateKey: this.#privateKey,
|
|
139
|
+
keyId: this.#keyId,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
issueEvent(input) {
|
|
144
|
+
requireSdkRecord(
|
|
145
|
+
input,
|
|
146
|
+
EVENT_INPUT_FIELDS,
|
|
147
|
+
EVENT_REQUIRED_FIELDS,
|
|
148
|
+
"Host event input",
|
|
149
|
+
);
|
|
150
|
+
requireSdkRecord(
|
|
151
|
+
input.workflow,
|
|
152
|
+
EVENT_WORKFLOW_FIELDS,
|
|
153
|
+
EVENT_WORKFLOW_FIELDS,
|
|
154
|
+
"Host event workflow",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const now = this.#readClock();
|
|
158
|
+
const binding = validatePublicBinding(input.binding);
|
|
159
|
+
assertLiveBinding(binding, now);
|
|
160
|
+
if (input.workflow.id !== binding.workflow_id) {
|
|
161
|
+
throw sdkValidation(
|
|
162
|
+
"host_workflow_binding_mismatch",
|
|
163
|
+
"Host workflow does not match the Receiver-issued binding",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const event = createContinuationEvent({
|
|
168
|
+
type: EVENT_TYPE,
|
|
169
|
+
protocol_version: PROTOCOL_VERSION,
|
|
170
|
+
event_id: this.#resolveId("event", input.eventId),
|
|
171
|
+
correlation_id: binding.correlation_id,
|
|
172
|
+
binding_id: binding.binding_id,
|
|
173
|
+
issuer_origin: this.#origin,
|
|
174
|
+
workflow_id: binding.workflow_id,
|
|
175
|
+
event_type: binding.event_type,
|
|
176
|
+
event_sequence: 1,
|
|
177
|
+
state_version: input.workflow.stateVersion,
|
|
178
|
+
occurred_at: input.occurredAt ?? now.toISOString(),
|
|
179
|
+
canonical_url: input.workflow.canonicalUrl,
|
|
180
|
+
});
|
|
181
|
+
const envelope = createContinuationEventEnvelope(event, {
|
|
182
|
+
privateKey: this.#privateKey,
|
|
183
|
+
keyId: this.#keyId,
|
|
184
|
+
timestamp: input.deliveryTimestamp ?? String(Math.floor(now.getTime() / 1_000)),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return Object.freeze({
|
|
188
|
+
event,
|
|
189
|
+
body: envelope.body,
|
|
190
|
+
headers: envelope.headers,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#readClock() {
|
|
195
|
+
const value = this.#clock();
|
|
196
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
|
|
197
|
+
throw new TypeError("Host SDK clock must return a valid Date");
|
|
198
|
+
}
|
|
199
|
+
return new Date(value.getTime());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#resolveId(prefix, supplied) {
|
|
203
|
+
if (supplied !== undefined) return supplied;
|
|
204
|
+
const created = this.#createId(prefix);
|
|
205
|
+
if (typeof created !== "string") {
|
|
206
|
+
throw new TypeError("Host SDK createId must return a string");
|
|
207
|
+
}
|
|
208
|
+
return created;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function assertLiveBinding(binding, now) {
|
|
213
|
+
if (binding.status !== "active") {
|
|
214
|
+
throw sdkValidation("host_binding_inactive", "Host binding is not active");
|
|
215
|
+
}
|
|
216
|
+
if (binding.runs_remaining !== 1) {
|
|
217
|
+
throw sdkValidation("host_binding_exhausted", "Host binding has no remaining run");
|
|
218
|
+
}
|
|
219
|
+
if (Date.parse(binding.expires_at) <= now.getTime()) {
|
|
220
|
+
throw sdkValidation("host_binding_expired", "Host binding has expired", 410);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function requireSdkRecord(value, allowedFields, requiredFields, label) {
|
|
225
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
226
|
+
throw sdkValidation("host_input_invalid", `${label} must be an object`);
|
|
227
|
+
}
|
|
228
|
+
const prototype = Object.getPrototypeOf(value);
|
|
229
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
230
|
+
throw sdkValidation("host_input_invalid", `${label} must be a plain object`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
234
|
+
if (typeof key === "symbol") {
|
|
235
|
+
throw sdkValidation("host_input_invalid", `${label} cannot contain symbol properties`);
|
|
236
|
+
}
|
|
237
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
238
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) {
|
|
239
|
+
throw sdkValidation(
|
|
240
|
+
"host_input_invalid",
|
|
241
|
+
`${label} must contain enumerable data properties only`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const fields = Object.keys(value);
|
|
247
|
+
if (fields.some((field) => !allowedFields.includes(field))) {
|
|
248
|
+
throw sdkValidation("host_input_fields_invalid", `${label} contains an unsupported field`);
|
|
249
|
+
}
|
|
250
|
+
if (requiredFields.some((field) => !fields.includes(field))) {
|
|
251
|
+
throw sdkValidation("host_input_fields_invalid", `${label} is missing a required field`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function requireCanonicalOrigin(value) {
|
|
256
|
+
if (typeof value !== "string") {
|
|
257
|
+
throw sdkValidation("host_origin_invalid", "Host origin must be a canonical HTTP(S) origin");
|
|
258
|
+
}
|
|
259
|
+
let parsed;
|
|
260
|
+
try {
|
|
261
|
+
parsed = new URL(value);
|
|
262
|
+
} catch {
|
|
263
|
+
throw sdkValidation("host_origin_invalid", "Host origin must be a canonical HTTP(S) origin");
|
|
264
|
+
}
|
|
265
|
+
if (
|
|
266
|
+
!["http:", "https:"].includes(parsed.protocol) ||
|
|
267
|
+
parsed.origin !== value ||
|
|
268
|
+
parsed.username ||
|
|
269
|
+
parsed.password
|
|
270
|
+
) {
|
|
271
|
+
throw sdkValidation("host_origin_invalid", "Host origin must be a canonical HTTP(S) origin");
|
|
272
|
+
}
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function sdkValidation(code, message, statusCode) {
|
|
277
|
+
return new ProtocolValidationError(code, message, statusCode);
|
|
278
|
+
}
|