@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.
Files changed (29) hide show
  1. package/README.md +337 -0
  2. package/node_modules/@webmcp-challenge/reentry-core/README.md +112 -0
  3. package/node_modules/@webmcp-challenge/reentry-core/package.json +38 -0
  4. package/node_modules/@webmcp-challenge/reentry-core/protocol/test-vectors/v0.1.json +47 -0
  5. package/node_modules/@webmcp-challenge/reentry-core/src/agent-adapter.mjs +438 -0
  6. package/node_modules/@webmcp-challenge/reentry-core/src/cloud-receiver-http.mjs +268 -0
  7. package/node_modules/@webmcp-challenge/reentry-core/src/host-sdk.mjs +278 -0
  8. package/node_modules/@webmcp-challenge/reentry-core/src/index.mjs +3 -0
  9. package/node_modules/@webmcp-challenge/reentry-core/src/local-connector-client.mjs +530 -0
  10. package/node_modules/@webmcp-challenge/reentry-core/src/managed-context-adapter.mjs +275 -0
  11. package/node_modules/@webmcp-challenge/reentry-core/src/protocol.mjs +845 -0
  12. package/node_modules/@webmcp-challenge/reentry-core/src/receiver-core.mjs +867 -0
  13. package/node_modules/@webmcp-challenge/reentry-core/src/receiver-delivery.mjs +613 -0
  14. package/node_modules/@webmcp-challenge/reentry-core/src/receiver-http-contract.mjs +24 -0
  15. package/node_modules/@webmcp-challenge/reentry-core/src/receiver-support.mjs +151 -0
  16. package/node_modules/@webmcp-challenge/reentry-core/src/sqlite-receiver-schema.mjs +184 -0
  17. package/node_modules/@webmcp-challenge/reentry-core/src/sqlite-receiver-store.mjs +573 -0
  18. package/package.json +44 -0
  19. package/src/browser-prompt.mjs +18 -0
  20. package/src/codex-discovery.mjs +246 -0
  21. package/src/codex-exec-adapter.mjs +197 -0
  22. package/src/codex-queue-adapter.mjs +214 -0
  23. package/src/credentials.mjs +87 -0
  24. package/src/index.mjs +6 -0
  25. package/src/local-connector.mjs +82 -0
  26. package/src/macos-service.mjs +184 -0
  27. package/src/main.mjs +733 -0
  28. package/src/pairing-client.mjs +545 -0
  29. package/src/terminal-ui.mjs +99 -0
@@ -0,0 +1,87 @@
1
+ import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+
5
+ const CREDENTIAL_FIELDS = Object.freeze([
6
+ "version",
7
+ "receiver_origin",
8
+ "connector_id",
9
+ "connector_token",
10
+ "connector_expires_at",
11
+ ]);
12
+ const IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/;
13
+ const TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
14
+
15
+ export class LocalConnectorCredentialStore {
16
+ #filename;
17
+
18
+ constructor(options) {
19
+ if (!options || typeof options !== "object" || Array.isArray(options) || typeof options.filename !== "string" || options.filename.length === 0) {
20
+ throw new TypeError("Credential store requires a filename");
21
+ }
22
+ this.#filename = options.filename;
23
+ }
24
+
25
+ async load() {
26
+ let value;
27
+ try {
28
+ value = JSON.parse(await readFile(this.#filename, "utf8"));
29
+ } catch (error) {
30
+ if (error?.code === "ENOENT") return null;
31
+ throw credentialFailure("connector_credentials_unreadable", "Connector credentials could not be read", error);
32
+ }
33
+ return normalizeCredentials(value);
34
+ }
35
+
36
+ async save(value) {
37
+ const normalized = normalizeCredentials(value);
38
+ await mkdir(dirname(this.#filename), { recursive: true, mode: 0o700 });
39
+ const temporary = `${this.#filename}.${randomUUID()}.tmp`;
40
+ try {
41
+ await writeFile(temporary, `${JSON.stringify(normalized)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
42
+ await chmod(temporary, 0o600);
43
+ await rename(temporary, this.#filename);
44
+ await chmod(this.#filename, 0o600);
45
+ } catch (error) {
46
+ await unlink(temporary).catch(() => {});
47
+ throw credentialFailure("connector_credentials_unwritable", "Connector credentials could not be stored", error);
48
+ }
49
+ }
50
+ }
51
+
52
+ function normalizeCredentials(value) {
53
+ requireExactRecord(value, CREDENTIAL_FIELDS, CREDENTIAL_FIELDS, "Connector credentials");
54
+ if (value.version !== 1) throw credentialFailure("connector_credentials_invalid", "Connector credential version is unsupported");
55
+ if (
56
+ typeof value.receiver_origin !== "string" ||
57
+ !/^https?:\/\/[^/]+$/.test(value.receiver_origin) ||
58
+ (value.receiver_origin.startsWith("http://") && !/http:\/\/(?:127\.0\.0\.1|localhost|\[::1\])(?::\d+)?$/.test(value.receiver_origin))
59
+ ) {
60
+ throw credentialFailure("connector_credentials_invalid", "Connector receiver origin is invalid");
61
+ }
62
+ if (typeof value.connector_id !== "string" || !IDENTIFIER_PATTERN.test(value.connector_id)) {
63
+ throw credentialFailure("connector_credentials_invalid", "Connector ID is invalid");
64
+ }
65
+ if (typeof value.connector_token !== "string" || !TOKEN_PATTERN.test(value.connector_token)) {
66
+ throw credentialFailure("connector_credentials_invalid", "Connector token is invalid");
67
+ }
68
+ const expires = Date.parse(value.connector_expires_at);
69
+ if (typeof value.connector_expires_at !== "string" || !Number.isFinite(expires) || new Date(expires).toISOString() !== value.connector_expires_at) {
70
+ throw credentialFailure("connector_credentials_invalid", "Connector credential expiry is invalid");
71
+ }
72
+ return Object.freeze({ ...value });
73
+ }
74
+
75
+ function requireExactRecord(value, allowedFields, requiredFields, label) {
76
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw credentialFailure("connector_credentials_invalid", `${label} must be an object`);
77
+ const fields = Object.keys(value);
78
+ if (fields.some((field) => !allowedFields.includes(field)) || requiredFields.some((field) => !fields.includes(field))) {
79
+ throw credentialFailure("connector_credentials_invalid", `${label} fields are invalid`);
80
+ }
81
+ }
82
+
83
+ function credentialFailure(code, message, cause) {
84
+ const error = new Error(`${code}: ${message}`, cause === undefined ? undefined : { cause });
85
+ error.code = code;
86
+ return error;
87
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./credentials.mjs";
2
+ export * from "./codex-discovery.mjs";
3
+ export * from "./codex-exec-adapter.mjs";
4
+ export * from "./local-connector.mjs";
5
+ export * from "./pairing-client.mjs";
6
+ export * from "./terminal-ui.mjs";
@@ -0,0 +1,82 @@
1
+ import { randomBytes } from "node:crypto";
2
+
3
+ import {
4
+ dispatchAgentActivation,
5
+ } from "@webmcp-challenge/reentry-core/agent-adapter";
6
+ import { LocalConnectorClient } from "@webmcp-challenge/reentry-core/local-connector-client";
7
+
8
+ const OPTION_FIELDS = Object.freeze([
9
+ "client",
10
+ "adapter",
11
+ "clock",
12
+ "activationTimeoutMs",
13
+ "createClaimToken",
14
+ ]);
15
+ const MIN_TIMEOUT_MS = 100;
16
+ const MAX_TIMEOUT_MS = 60_000;
17
+
18
+ export class LocalConnector {
19
+ #client;
20
+ #adapter;
21
+ #clock;
22
+ #activationTimeoutMs;
23
+ #createClaimToken;
24
+
25
+ constructor(options) {
26
+ requireExactRecord(options, OPTION_FIELDS, OPTION_FIELDS, "Local Connector options");
27
+ if (!(options.client instanceof LocalConnectorClient)) {
28
+ throw new TypeError("Local Connector client must be a LocalConnectorClient");
29
+ }
30
+ if (!options.adapter || typeof options.adapter.activate !== "function") {
31
+ throw new TypeError("Local Connector adapter must implement activate");
32
+ }
33
+ if (typeof options.clock !== "function") throw new TypeError("Local Connector clock must be a function");
34
+ if (!Number.isSafeInteger(options.activationTimeoutMs) || options.activationTimeoutMs < MIN_TIMEOUT_MS || options.activationTimeoutMs > MAX_TIMEOUT_MS) {
35
+ throw new TypeError("Local Connector activationTimeoutMs is invalid");
36
+ }
37
+ if (typeof options.createClaimToken !== "function") throw new TypeError("Local Connector createClaimToken must be a function");
38
+ this.#client = options.client;
39
+ this.#adapter = options.adapter;
40
+ this.#clock = options.clock;
41
+ this.#activationTimeoutMs = options.activationTimeoutMs;
42
+ this.#createClaimToken = options.createClaimToken;
43
+ }
44
+
45
+ async runOnce() {
46
+ const claimToken = this.#createClaimToken();
47
+ const claim = await this.#client.claimDelivery({ claimToken });
48
+ if (claim === null) return Object.freeze({ status: "idle" });
49
+ const result = await dispatchAgentActivation({
50
+ adapter: this.#adapter,
51
+ lease: claim.lease,
52
+ now: this.#readClock(),
53
+ timeoutMs: this.#activationTimeoutMs,
54
+ });
55
+ return Object.freeze({
56
+ status: "activation_result",
57
+ delivery_id: claim.lease.delivery_id,
58
+ event_id: claim.lease.event_id,
59
+ result,
60
+ });
61
+ }
62
+
63
+ acknowledgeDelivery(input) {
64
+ return this.#client.acknowledgeDelivery(input);
65
+ }
66
+
67
+ #readClock() {
68
+ const value = this.#clock();
69
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new TypeError("Local Connector clock must return a valid Date");
70
+ return new Date(value.getTime());
71
+ }
72
+ }
73
+
74
+ export function createRandomClaimToken() {
75
+ return randomBytes(32).toString("base64url");
76
+ }
77
+
78
+ function requireExactRecord(value, allowedFields, requiredFields, label) {
79
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${label} must be an object`);
80
+ const fields = Object.keys(value);
81
+ if (fields.some((field) => !allowedFields.includes(field)) || requiredFields.some((field) => !fields.includes(field))) throw new TypeError(`${label} fields are invalid`);
82
+ }
@@ -0,0 +1,184 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access, chmod, mkdir, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { randomUUID } from "node:crypto";
6
+
7
+ const LABEL = "com.reentry.local-connector";
8
+
9
+ export async function installMacConnectorService(options) {
10
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
11
+ throw serviceFailure("connector_service_input_invalid", "Service installation options are invalid");
12
+ }
13
+ if (process.platform !== "darwin" && options.allowNonMacForTest !== true) {
14
+ throw serviceFailure("connector_service_platform_unsupported", "Background installation currently supports macOS only");
15
+ }
16
+ const nodeExecutable = requireAbsolutePath(options.nodeExecutable, "Node executable");
17
+ const entrypoint = requireAbsolutePath(options.entrypoint, "Connector entrypoint");
18
+ const workingDirectory = requireAbsolutePath(options.workingDirectory, "Codex working directory");
19
+ const credentialFile = requireAbsolutePath(options.credentialFile, "Credential file");
20
+ const launchAgentsDirectory = options.launchAgentsDirectory ?? join(homedir(), "Library", "LaunchAgents");
21
+ const stateDirectory = options.stateDirectory ?? join(homedir(), ".webmcp-connector");
22
+ const runCommand = options.runCommand ?? runLaunchctl;
23
+ if (typeof runCommand !== "function") {
24
+ throw new TypeError("Service installer runCommand must be a function");
25
+ }
26
+ const plistPath = join(launchAgentsDirectory, `${LABEL}.plist`);
27
+ const stdoutPath = join(stateDirectory, "connector.log");
28
+ const stderrPath = join(stateDirectory, "connector-error.log");
29
+ const plist = renderLaunchAgent({
30
+ nodeExecutable,
31
+ entrypoint,
32
+ workingDirectory,
33
+ credentialFile,
34
+ stdoutPath,
35
+ stderrPath,
36
+ });
37
+
38
+ await mkdir(launchAgentsDirectory, { recursive: true, mode: 0o700 });
39
+ await mkdir(stateDirectory, { recursive: true, mode: 0o700 });
40
+ const temporary = `${plistPath}.${randomUUID()}.tmp`;
41
+ try {
42
+ await writeFile(temporary, plist, { encoding: "utf8", mode: 0o600, flag: "wx" });
43
+ await chmod(temporary, 0o600);
44
+ await rename(temporary, plistPath);
45
+ await chmod(plistPath, 0o600);
46
+ } catch (error) {
47
+ await unlink(temporary).catch(() => {});
48
+ throw serviceFailure("connector_service_write_failed", "Background service could not be installed", error);
49
+ }
50
+
51
+ if (options.load !== false) {
52
+ const domain = `gui/${typeof process.getuid === "function" ? process.getuid() : 0}`;
53
+ await runCommand(["bootout", `${domain}/${LABEL}`]);
54
+ const result = await runCommand(["bootstrap", domain, plistPath]);
55
+ if (result.code !== 0) {
56
+ throw serviceFailure("connector_service_load_failed", "Background service file was written but could not be started");
57
+ }
58
+ }
59
+
60
+ return Object.freeze({
61
+ label: LABEL,
62
+ plistPath,
63
+ stdoutPath,
64
+ stderrPath,
65
+ });
66
+ }
67
+
68
+ export async function inspectMacConnectorService(options = {}) {
69
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
70
+ throw serviceFailure("connector_service_input_invalid", "Service status options are invalid");
71
+ }
72
+ if (process.platform !== "darwin" && options.allowNonMacForTest !== true) {
73
+ return Object.freeze({ supported: false, installed: false, running: false });
74
+ }
75
+ const launchAgentsDirectory = options.launchAgentsDirectory ?? join(homedir(), "Library", "LaunchAgents");
76
+ const runCommand = options.runCommand ?? runLaunchctl;
77
+ if (typeof runCommand !== "function") {
78
+ throw new TypeError("Service status runCommand must be a function");
79
+ }
80
+ const plistPath = join(launchAgentsDirectory, `${LABEL}.plist`);
81
+ try {
82
+ await access(plistPath);
83
+ } catch (error) {
84
+ if (error?.code === "ENOENT") {
85
+ return Object.freeze({ supported: true, installed: false, running: false, plistPath });
86
+ }
87
+ throw serviceFailure("connector_service_status_failed", "Background service status is unavailable", error);
88
+ }
89
+ const domain = `gui/${typeof process.getuid === "function" ? process.getuid() : 0}`;
90
+ const result = await runCommand(["print", `${domain}/${LABEL}`]);
91
+ return Object.freeze({
92
+ supported: true,
93
+ installed: true,
94
+ running: result.code === 0,
95
+ plistPath,
96
+ });
97
+ }
98
+
99
+ export function renderLaunchAgent(options) {
100
+ const argumentsList = [
101
+ options.nodeExecutable,
102
+ options.entrypoint,
103
+ "start",
104
+ "--credential-file",
105
+ options.credentialFile,
106
+ "--codex-cd",
107
+ options.workingDirectory,
108
+ ];
109
+ const argumentsXml = argumentsList.map((value) => ` <string>${escapeXml(value)}</string>`).join("\n");
110
+ return `<?xml version="1.0" encoding="UTF-8"?>
111
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
112
+ <plist version="1.0">
113
+ <dict>
114
+ <key>Label</key>
115
+ <string>${LABEL}</string>
116
+ <key>ProgramArguments</key>
117
+ <array>
118
+ ${argumentsXml}
119
+ </array>
120
+ <key>RunAtLoad</key>
121
+ <true/>
122
+ <key>KeepAlive</key>
123
+ <dict>
124
+ <key>SuccessfulExit</key>
125
+ <false/>
126
+ </dict>
127
+ <key>ProcessType</key>
128
+ <string>Background</string>
129
+ <key>WorkingDirectory</key>
130
+ <string>${escapeXml(options.workingDirectory)}</string>
131
+ <key>StandardOutPath</key>
132
+ <string>${escapeXml(options.stdoutPath)}</string>
133
+ <key>StandardErrorPath</key>
134
+ <string>${escapeXml(options.stderrPath)}</string>
135
+ </dict>
136
+ </plist>
137
+ `;
138
+ }
139
+
140
+ function runLaunchctl(argumentsList) {
141
+ return new Promise((resolve) => {
142
+ const child = spawn("launchctl", argumentsList, { stdio: ["ignore", "pipe", "pipe"] });
143
+ let stderr = "";
144
+ let settled = false;
145
+ const finish = (result) => {
146
+ if (settled) return;
147
+ settled = true;
148
+ resolve(result);
149
+ };
150
+ child.stderr.setEncoding("utf8");
151
+ child.stderr.on("data", (chunk) => {
152
+ if (stderr.length < 4_096) stderr += chunk;
153
+ });
154
+ child.once("error", (error) => finish({ code: -1, stderr: error.message }));
155
+ child.once("close", (code) => finish({ code: code ?? -1, stderr }));
156
+ });
157
+ }
158
+
159
+ function requireAbsolutePath(value, label) {
160
+ if (
161
+ typeof value !== "string" ||
162
+ !isAbsolute(value) ||
163
+ value.length > 4_096 ||
164
+ value.includes("\0")
165
+ ) {
166
+ throw serviceFailure("connector_service_path_invalid", `${label} is invalid`);
167
+ }
168
+ return value;
169
+ }
170
+
171
+ function escapeXml(value) {
172
+ return String(value)
173
+ .replaceAll("&", "&amp;")
174
+ .replaceAll("<", "&lt;")
175
+ .replaceAll(">", "&gt;")
176
+ .replaceAll('"', "&quot;")
177
+ .replaceAll("'", "&apos;");
178
+ }
179
+
180
+ function serviceFailure(code, message, cause) {
181
+ const error = new Error(message, cause === undefined ? undefined : { cause });
182
+ error.code = code;
183
+ return error;
184
+ }