codexteer 0.16.0 → 0.18.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 (85) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/README.md +154 -16
  3. package/assets/remote-device-key.swift +94 -0
  4. package/bin/codexteer-update.mjs +3 -0
  5. package/bin/codexteer.mjs +6 -2
  6. package/docs/cloud.md +113 -0
  7. package/docs/desktop-cloud.md +36 -0
  8. package/docs/distribution.md +21 -9
  9. package/docs/orchestration.md +120 -0
  10. package/docs/remote-cli-validation.json +139 -0
  11. package/docs/remote-control.md +65 -0
  12. package/docs/remote-device-enrollment.md +91 -0
  13. package/docs/remote-observation.md +57 -0
  14. package/docs/remote-operations.md +89 -0
  15. package/docs/thread-create.md +90 -0
  16. package/package.json +13 -2
  17. package/scripts/update-desktop.applescript +51 -0
  18. package/src/agent-launch.mjs +27 -0
  19. package/src/app-server.mjs +34 -10
  20. package/src/checkpoint.mjs +52 -16
  21. package/src/cli.mjs +233 -19
  22. package/src/cloud-api.mjs +118 -0
  23. package/src/cloud-cli.mjs +319 -0
  24. package/src/cloud-connection.mjs +64 -0
  25. package/src/cloud-id.mjs +16 -0
  26. package/src/cloud-observe.mjs +178 -0
  27. package/src/cloud-send.mjs +33 -0
  28. package/src/cloud.mjs +172 -0
  29. package/src/connection.mjs +23 -5
  30. package/src/creation-verification.mjs +36 -0
  31. package/src/desktop-cloud-api.mjs +119 -0
  32. package/src/desktop-cloud-id.mjs +18 -0
  33. package/src/desktop-cloud-project.mjs +148 -0
  34. package/src/desktop-cloud.mjs +98 -0
  35. package/src/desktop.mjs +3 -1
  36. package/src/directive.mjs +5 -2
  37. package/src/distribution.mjs +6 -6
  38. package/src/evidence.mjs +3 -0
  39. package/src/findings.mjs +7 -7
  40. package/src/help.mjs +155 -26
  41. package/src/journal.mjs +37 -9
  42. package/src/launcher.mjs +16 -9
  43. package/src/monitor.mjs +18 -6
  44. package/src/native-command.mjs +100 -0
  45. package/src/native-rpc.mjs +116 -0
  46. package/src/native-worker.mjs +25 -0
  47. package/src/observe.mjs +21 -7
  48. package/src/orchestrate-prompt.mjs +96 -0
  49. package/src/orchestrate.mjs +29 -0
  50. package/src/paged-read.mjs +7 -1
  51. package/src/projects.mjs +104 -0
  52. package/src/prompt-command.mjs +25 -0
  53. package/src/prompt.mjs +35 -11
  54. package/src/remote-client-info.mjs +16 -0
  55. package/src/remote-control-auth.mjs +109 -0
  56. package/src/remote-control-socket.mjs +137 -0
  57. package/src/remote-control.mjs +134 -0
  58. package/src/remote-device-key.mjs +41 -0
  59. package/src/remote-device-login.mjs +76 -0
  60. package/src/remote-device-protocol.mjs +72 -0
  61. package/src/remote-device-store.mjs +71 -0
  62. package/src/remote-device-worker.mjs +48 -0
  63. package/src/remote-device.mjs +202 -0
  64. package/src/remote-io.mjs +163 -0
  65. package/src/remote-key-access.mjs +76 -0
  66. package/src/remote-key-worker.mjs +38 -0
  67. package/src/remote-keychain.mjs +72 -0
  68. package/src/remote-management.mjs +234 -0
  69. package/src/remote.mjs +132 -0
  70. package/src/resource.mjs +8 -6
  71. package/src/runner.mjs +6 -1
  72. package/src/runtime.mjs +4 -2
  73. package/src/socket-endpoint.mjs +40 -0
  74. package/src/standalone-create.mjs +135 -0
  75. package/src/store.mjs +6 -3
  76. package/src/supervise.mjs +32 -55
  77. package/src/supervision.mjs +41 -19
  78. package/src/target.mjs +25 -0
  79. package/src/task-id.mjs +23 -0
  80. package/src/thread-create.mjs +128 -0
  81. package/src/thread-id.mjs +21 -6
  82. package/src/thread-verify.mjs +108 -0
  83. package/src/turn-repair.mjs +228 -0
  84. package/src/update.mjs +158 -0
  85. package/src/wrapper.mjs +68 -5
@@ -0,0 +1,202 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { discoverRuntime } from "./runtime.mjs";
3
+ import { RpcClient } from "./rpc.mjs";
4
+ import { authIdentity, controlFailure } from "./remote-control-auth.mjs";
5
+ import { RemoteControlSocket } from "./remote-control-socket.mjs";
6
+ import { deviceKey, dedicatedConnectionProof } from "./remote-device-key.mjs";
7
+ import { readDevice, saveDevice, withDeviceLock, dedicatedEnrollment, keySelection } from "./remote-device-store.mjs";
8
+ import { prepareKeychainRuntime, keychainOperation } from "./remote-keychain.mjs";
9
+ import { ENROLL_START, ENROLL_FINISH, PAIR_PATH, deviceIdentity, enrollmentPayload, validateRegistration, verifyEnrollmentProof, officialDeviceRequest } from "./remote-device-protocol.mjs";
10
+ import { stepUpLogin, validateStepUpToken } from "./remote-device-login.mjs";
11
+ import { REMOTE_CLIENT_INFO } from "./remote-client-info.mjs";
12
+
13
+ export async function withDeviceAccount(operation, { discover = discoverRuntime, connect = RpcClient.connect, request = officialDeviceRequest } = {}) {
14
+ const runtime = await discover(), local = await connect(runtime.paths.socket);
15
+ try {
16
+ const identity = authIdentity(await local.request("getAuthStatus", { includeToken: true, refreshToken: false }));
17
+ const current = async () => {
18
+ const latest = await discover(runtime.paths.home), status = await local.request("getAuthStatus", { includeToken: true, refreshToken: false });
19
+ if (latest.state.instance !== runtime.state.instance || latest.paths.socket !== runtime.paths.socket || JSON.stringify(authIdentity(status)) !== JSON.stringify(identity)) {
20
+ throw controlFailure("The selected account or runtime changed. No device operation was sent to another account.", "REMOTE_IDENTITY_CHANGED");
21
+ }
22
+ return { identity, token: status.authToken };
23
+ };
24
+ return await operation({ home: runtime.paths.home, identity, current,
25
+ request: async (route, options) => request(await current(), route, options) });
26
+ } finally { local.close(); }
27
+ }
28
+
29
+ export function deviceStatus(record) {
30
+ return { credential_source: "codexteer-dedicated", registration_status: record?.phase ?? "not_enrolled", client_id: record?.clientId ?? null,
31
+ controller_name: REMOTE_CLIENT_INFO.controller_name,
32
+ key_protection: record?.key?.protectionClass ?? null, key_provider: record?.key_provider ?? "openai-native", paired_host_ids: record?.pairings.map(pair => pair.host_id) ?? [],
33
+ pairing_unknown: record?.pairing_unknown ?? false, instruction_sent: false };
34
+ }
35
+
36
+ // Read only our registered public metadata; never expose other controllers,
37
+ // account identities or last-seen location from the account-wide response.
38
+ export async function deviceIdentityInfo(context) {
39
+ const record = await readDevice(context.home, context.identity);
40
+ dedicatedEnrollment(record, context.identity);
41
+ const seen = new Set(); let cursor;
42
+ for (let page = 0; page < 100; page++) {
43
+ const result = await context.request(`/wham/remote/control/clients?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`, { method: "GET" });
44
+ if (!Array.isArray(result?.items)) throw controlFailure("Invalid controller metadata response.", "REMOTE_DEVICE_METADATA_INVALID");
45
+ const own = result.items.find(item => item?.client_id === record.clientId);
46
+ if (own) {
47
+ const text = key => typeof own[key] === "string" && own[key].length <= 512 ? own[key] : null;
48
+ const display = text("display_name");
49
+ return { ...deviceStatus(record), server_display_name: display, server_platform: text("platform"), server_device_model: text("device_model"),
50
+ name_matches: display === REMOTE_CLIENT_INFO.controller_name, custom_name_update: "unsupported" };
51
+ }
52
+ if (result.cursor == null) throw controlFailure("Dedicated controller was not present in the account list.", "REMOTE_DEVICE_METADATA_NOT_FOUND");
53
+ if (typeof result.cursor !== "string" || !result.cursor || result.cursor.length > 4096 || seen.has(result.cursor)) throw controlFailure("Invalid controller metadata pagination.", "REMOTE_DEVICE_METADATA_INVALID");
54
+ seen.add(result.cursor); cursor = result.cursor;
55
+ }
56
+ throw controlFailure("Controller metadata exceeded its pagination limit.", "REMOTE_DEVICE_METADATA_INVALID");
57
+ }
58
+
59
+ export async function readPairingCode(input = process.stdin) {
60
+ if (!input.isTTY) {
61
+ let text = "";
62
+ for await (const chunk of input) { text += chunk.toString("utf8"); if (text.length > 256) throw controlFailure("Invalid pairing code input.", "REMOTE_PAIR_INPUT_INVALID"); }
63
+ return text.trim();
64
+ }
65
+ return new Promise((resolve, reject) => {
66
+ let text = "";
67
+ const finish = (error, result) => { input.removeListener("data", data); input.removeListener("end", ended); input.removeListener("error", failed); input.setRawMode(false); input.pause(); error ? reject(error) : resolve(result); };
68
+ const failed = () => finish(controlFailure("Pairing code input was cancelled.", "REMOTE_PAIR_INPUT_INVALID"));
69
+ const ended = () => finish(null, text.trim());
70
+ const data = chunk => {
71
+ for (const char of chunk.toString("utf8")) {
72
+ if (char === "\u0003") return failed();
73
+ if (["\n", "\r", "\u0004"].includes(char)) return ended();
74
+ if (["\u007f", "\b"].includes(char)) text = text.slice(0, -1);
75
+ else if (/^[A-Za-z0-9-]$/.test(char)) text += char;
76
+ else return failed();
77
+ if (text.length > 128) return failed();
78
+ }
79
+ };
80
+ input.setRawMode(true); input.on("data", data); input.once("end", ended); input.once("error", failed); input.resume();
81
+ });
82
+ }
83
+
84
+ export async function prepareDevice(context, { key = deviceKey, provider = "openai-native", prepare = prepareKeychainRuntime } = {}) {
85
+ const { home, identity } = context;
86
+ if (!["openai-native", "keychain"].includes(provider)) throw controlFailure("--key-provider must be openai-native or keychain.", "REMOTE_KEY_PROVIDER_INVALID");
87
+ return withDeviceLock(home, identity, async () => {
88
+ const existing = await readDevice(home, identity);
89
+ if (existing && !(existing.phase === "key-failed" && !existing.key && !existing.clientId && existing.key_provider !== provider)) {
90
+ if (!["prepared", "enrolled"].includes(existing.phase)) throw controlFailure("A dedicated key operation is unresolved. Inspect the saved phase before creating another key.", "REMOTE_ENROLLMENT_PENDING");
91
+ if ((existing.key_provider ?? "openai-native") !== provider) throw controlFailure("The dedicated key already uses another provider. It was not replaced.", "REMOTE_KEY_PROVIDER_MISMATCH");
92
+ await key("inspect", { enrollment: { ...existing.key, ...keySelection(existing) }, home, identity }); return deviceStatus(existing);
93
+ }
94
+ await context.current();
95
+ const record = { schema: 1, accountId: identity.accountId, accountUserId: identity.accountUserId, phase: "creating-key",
96
+ operation_id: randomUUID(), pairings: [], key_provider: provider, created_at: new Date().toISOString() };
97
+ if (provider === "keychain") { record.key_runtime = await prepare(home, identity); record.candidate_key_id = `codexteer_${randomUUID()}`; }
98
+ await saveDevice(home, identity, record);
99
+ try { record.key = await key("create", { provider, home, identity, runtime: record.key_runtime, keyId: record.candidate_key_id }); }
100
+ catch (error) { if (error.key_creation_rejected) { record.phase = "key-failed"; record.key_error = "MISSING_ENTITLEMENT"; await saveDevice(home, identity, record); } throw error; }
101
+ record.phase = "prepared"; await saveDevice(home, identity, record);
102
+ return deviceStatus(record);
103
+ });
104
+ }
105
+
106
+ export async function recoverDeviceKey(context) {
107
+ const { home, identity } = context;
108
+ return withDeviceLock(home, identity, async () => {
109
+ const record = await readDevice(home, identity);
110
+ if (record?.phase !== "creating-key" || record.key_provider !== "keychain" || !record.candidate_key_id || record.clientId) throw controlFailure("No recoverable key-only preparation exists.", "REMOTE_ENROLLMENT_PENDING");
111
+ record.key = await keychainOperation("recover", { home, identity, runtime: record.key_runtime, keyId: record.candidate_key_id });
112
+ record.phase = "prepared"; await saveDevice(home, identity, record); return deviceStatus(record);
113
+ });
114
+ }
115
+
116
+ export async function enrollDevice(context, { login = stepUpLogin, key = deviceKey, progress } = {}) {
117
+ const { home, identity } = context;
118
+ return withDeviceLock(home, identity, async () => {
119
+ const existing = await readDevice(home, identity);
120
+ if (existing?.phase === "enrolled") return deviceStatus(existing);
121
+ if (existing && existing.phase !== "prepared") throw controlFailure("An unresolved registration already exists. Use remote verify; do not create another device automatically.", "REMOTE_ENROLLMENT_PENDING");
122
+ // Authenticate before any key or server registration exists. Only the
123
+ // official fresh enrollment token stays in this process's memory.
124
+ const stepUp = validateStepUpToken(await login(identity, { progress }), identity);
125
+ await context.current();
126
+ let record = existing ? { ...existing, phase: "starting" } : { schema: 1, accountId: identity.accountId, accountUserId: identity.accountUserId, phase: "starting",
127
+ operation_id: randomUUID(), pairings: [], created_at: new Date().toISOString() };
128
+ await saveDevice(home, identity, record);
129
+ const start = await context.request(ENROLL_START, { body: {} });
130
+ if (typeof start?.client_id !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(start.client_id)
131
+ || ![identity.accountUserId, identity.authUserId].includes(start.account_user_id)) {
132
+ throw controlFailure("Registration start was not confirmed for the current account. Inspect before retrying.", "REMOTE_ENROLLMENT_UNKNOWN");
133
+ }
134
+ record = { ...record, clientId: start.client_id, serverAccountUserId: start.account_user_id, phase: "creating-key" };
135
+ await saveDevice(home, identity, record);
136
+ record.key ??= await key("create"); record.phase = "key-created";
137
+ await saveDevice(home, identity, record);
138
+ const enrolled = { ...record.key, clientId: record.clientId, accountUserId: record.serverAccountUserId, ...keySelection(record) };
139
+ enrollmentPayload(start.device_key_challenge, enrolled, ENROLL_FINISH);
140
+ const proof = verifyEnrollmentProof(await key("enrollment-proof", { home, identity, enrollment: enrolled, challenge: start.device_key_challenge, path: ENROLL_FINISH }), start.device_key_challenge, enrolled);
141
+ record.phase = "finish-unknown"; await saveDevice(home, identity, record);
142
+ const result = await context.request(ENROLL_FINISH, { body: { client_id: enrolled.clientId, step_up_token: stepUp,
143
+ device_identity: deviceIdentity(enrolled), device_key_proof: proof } });
144
+ validateRegistration(result, enrolled);
145
+ record.phase = "enrolled"; await saveDevice(home, identity, record);
146
+ return deviceStatus(record);
147
+ });
148
+ }
149
+
150
+ export async function pairDevice(context, hostId, code) {
151
+ if (!/^remote-control:env_[A-Za-z0-9_-]+$/.test(hostId) || typeof code !== "string" || !/^[A-Za-z0-9-]{6,128}$/.test(code)) {
152
+ throw controlFailure("Pairing requires an explicit Remote Control host and its fresh code via stdin.", "REMOTE_PAIR_INPUT_INVALID");
153
+ }
154
+ const { home, identity } = context;
155
+ return withDeviceLock(home, identity, async () => {
156
+ const record = await readDevice(home, identity), enrollment = dedicatedEnrollment(record, identity);
157
+ if (record.pairing_unknown) throw controlFailure("The last pairing has unknown outcome. Use remote verify before another attempt.", "REMOTE_PAIR_UNKNOWN");
158
+ if (record.pairings.some(pair => pair.host_id === hostId)) return deviceStatus(record);
159
+ record.pairing_unknown = true; await saveDevice(home, identity, record);
160
+ let result;
161
+ try { result = await context.request(PAIR_PATH, { body: { client_id: enrollment.clientId, manual_pairing_code: code } }); }
162
+ catch (error) { if (error.code === "REMOTE_ENROLLMENT_REJECTED") { record.pairing_unknown = false; await saveDevice(home, identity, record); } throw error; }
163
+ if (!/^env_[A-Za-z0-9_-]+$/.test(result?.environment_id ?? "")) throw controlFailure("Pairing acknowledgement was invalid. Use remote verify.", "REMOTE_PAIR_UNKNOWN");
164
+ const actualHost = `remote-control:${result.environment_id}`;
165
+ record.pairings = [...record.pairings.filter(pair => pair.host_id !== actualHost), { host_id: actualHost, verified_at: new Date().toISOString() }];
166
+ record.pairing_unknown = false; await saveDevice(home, identity, record);
167
+ if (actualHost !== hostId) throw controlFailure(`The supplied pairing code belongs to ${actualHost}, not the selected host. No task was opened or sent an instruction.`, "REMOTE_PAIR_HOST_MISMATCH");
168
+ return deviceStatus(record);
169
+ });
170
+ }
171
+
172
+ export async function verifyDevice(context, { key = deviceKey, open = RemoteControlSocket.connect } = {}) {
173
+ const { home, identity } = context;
174
+ return withDeviceLock(home, identity, async () => {
175
+ const record = await readDevice(home, identity);
176
+ if (!record?.key || !record.clientId || !["enrolled", "finish-unknown"].includes(record.phase)
177
+ || ![identity.accountUserId, identity.authUserId].includes(record.serverAccountUserId)) throw controlFailure("No completed or possibly completed dedicated registration can be verified. Inspect its saved phase before creating another.", "REMOTE_ENROLLMENT_PENDING");
178
+ const enrollment = { ...record.key, clientId: record.clientId, accountUserId: record.serverAccountUserId, ...keySelection(record) };
179
+ await key("inspect", { enrollment, home, identity });
180
+ const auth = await context.current();
181
+ const socket = await open({ ...auth, enrollment, environmentId: "env_registration_probe", sign: async challenge => {
182
+ await context.current(); return dedicatedConnectionProof(enrollment, challenge, { home, identity });
183
+ } });
184
+ // Authentication only: do not initialize a stream, open a task or send RPC.
185
+ socket.terminate();
186
+ record.phase = "enrolled"; await saveDevice(home, identity, record);
187
+ const hosts = [], seen = new Set(); let cursor;
188
+ for (let page = 0; page < 100; page++) {
189
+ const route = `/codex/remote/control/clients/${encodeURIComponent(enrollment.clientId)}/environments?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`;
190
+ const response = await context.request(route, { method: "GET" });
191
+ if (!Array.isArray(response?.items) || response.items.some(item => !/^env_[A-Za-z0-9_-]+$/.test(item?.env_id ?? ""))) throw controlFailure("Invalid paired-host list.", "REMOTE_PAIR_UNKNOWN");
192
+ hosts.push(...response.items.map(item => `remote-control:${item.env_id}`));
193
+ if (response.cursor == null) {
194
+ record.pairings = [...new Set(hosts)].map(host_id => ({ host_id, verified_at: new Date().toISOString() }));
195
+ record.pairing_unknown = false; await saveDevice(home, identity, record); return deviceStatus(record);
196
+ }
197
+ if (typeof response.cursor !== "string" || !response.cursor || seen.has(response.cursor) || response.cursor.length > 4096) throw controlFailure("Invalid paired-host pagination.", "REMOTE_PAIR_UNKNOWN");
198
+ seen.add(response.cursor); cursor = response.cursor;
199
+ }
200
+ throw controlFailure("Paired-host history exceeded its limit.", "REMOTE_PAIR_UNKNOWN");
201
+ });
202
+ }
@@ -0,0 +1,163 @@
1
+ import path from "node:path";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { withRemoteClient, requirePairedManagement } from "./remote-management.mjs";
4
+ import { remoteControlConnection } from "./remote-control.mjs";
5
+ import { hostFields } from "./target.mjs";
6
+ import { fetchThread } from "./observe.mjs";
7
+ import { setTimeout as delay } from "node:timers/promises";
8
+ import { normalizeThreadId } from "./thread-id.mjs";
9
+ import { withSupervisedMutation } from "./supervision.mjs";
10
+
11
+ const fail = (message, code = "REMOTE_FILESYSTEM_ERROR") => Object.assign(new Error(message), { code });
12
+ export const remotePathApi = value => /^[A-Za-z]:[\\/]|^\\\\/.test(value) ? path.win32 : path.posix;
13
+ export const resolveRemotePath = (cwd, value) => remotePathApi(cwd).resolve(cwd, value);
14
+ const within = (file, root) => { const relative = remotePathApi(root).relative(root, file); return relative === "" || (!relative.startsWith("..") && !remotePathApi(root).isAbsolute(relative)); };
15
+ const excluded = (file, excludes) => file.split(/[\\/]/).includes(".git") || excludes.some(root => within(file, root));
16
+ const hash = value => createHash("sha256").update(value).digest("hex");
17
+
18
+ async function metadata(client, file) {
19
+ const value = await client.request("fs/getMetadata", { path: file });
20
+ if (typeof value?.isFile !== "boolean" || typeof value.isDirectory !== "boolean" || typeof value.isSymlink !== "boolean" || !Number.isFinite(value.modifiedAtMs)) throw fail("Invalid remote file metadata.");
21
+ if (value.isSymlink) throw fail("Remote symlinks cannot be fingerprinted without a canonical target API.", "REMOTE_SYMLINK_UNSUPPORTED");
22
+ return value;
23
+ }
24
+
25
+ export async function remoteFingerprintFile(file, client) {
26
+ requirePairedManagement();
27
+ if (!remotePathApi(file).isAbsolute(file)) throw fail("Remote evidence requires an absolute path.");
28
+ if (!client) return withRemoteClient(c => remoteFingerprintFile(file, c));
29
+ const before = await metadata(client, file);
30
+ if (!before.isFile) throw fail("Remote evidence must be a regular file.");
31
+ const response = await client.request("fs/readFile", { path: file });
32
+ if (typeof response?.dataBase64 !== "string" || response.dataBase64.length > 48 * 1024 * 1024 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(response.dataBase64)) throw fail("Remote evidence is invalid or exceeds its size limit.");
33
+ const bytes = Buffer.from(response.dataBase64, "base64"), after = await metadata(client, file);
34
+ if (JSON.stringify(before) !== JSON.stringify(after)) throw fail("Remote evidence changed while reading.", "STALE_EVIDENCE");
35
+ return { type: "file", ref: file, path: file, size: bytes.length, sha256: hash(bytes), mtime_ms: after.modifiedAtMs, ctime_ms: after.createdAtMs, ...hostFields() };
36
+ }
37
+
38
+ export async function remoteCaptureInputs(roots, excludes = [], { allowMissing = false, client } = {}) {
39
+ if (!client) return withRemoteClient(c => remoteCaptureInputs(roots, excludes, { allowMissing, client: c }));
40
+ const records = new Map();
41
+ const visit = async file => {
42
+ if (excluded(file, excludes) || records.has(file)) return;
43
+ if (records.size >= 10000) throw fail("Remote checkpoint exceeds 10000 entries.");
44
+ let info;
45
+ try { info = await metadata(client, file); } catch (error) {
46
+ // Generic RPC rejection is not proof of ENOENT. Preserve uncertainty.
47
+ if (allowMissing && error.code === "ENOENT") return; throw error;
48
+ }
49
+ if (info.isDirectory) {
50
+ records.set(file, { type: "directory", ref: file });
51
+ const result = await client.request("fs/readDirectory", { path: file });
52
+ if (!Array.isArray(result?.entries) || result.entries.some(e => typeof e.fileName !== "string" || !e.fileName || /^[.]{1,2}$/.test(e.fileName) || /[\\/\0]/.test(e.fileName))) throw fail("Invalid remote directory entries.");
53
+ for (const entry of result.entries.sort((a, b) => a.fileName.localeCompare(b.fileName))) await visit(remotePathApi(file).join(file, entry.fileName));
54
+ } else if (info.isFile) records.set(file, await remoteFingerprintFile(file, client));
55
+ else throw fail("Remote snapshots require regular files or directories.");
56
+ };
57
+ for (const root of roots) await visit(root);
58
+ const files = [...records.values()].sort((a, b) => a.ref.localeCompare(b.ref));
59
+ return { roots, excludes, files, digest: hash(JSON.stringify(files)) };
60
+ }
61
+
62
+ export async function remoteCheckpointScope(threadId, paths, excludes = []) {
63
+ return withRemoteClient(async client => {
64
+ const thread = (await client.request("thread/read", { threadId, includeTurns: false })).thread;
65
+ if (thread?.id !== threadId || typeof thread.cwd !== "string" || !remotePathApi(thread.cwd).isAbsolute(thread.cwd)) throw fail("Remote checkpoint task scope is unavailable.");
66
+ return { cwd: thread.cwd, roots: [...new Set(paths.map(p => resolveRemotePath(thread.cwd, p)))], excludes: excludes.map(p => resolveRemotePath(thread.cwd, p)) };
67
+ });
68
+ }
69
+
70
+ export async function remoteWatchInputs(snapshot, { route: makeRoute = remoteControlConnection } = {}) {
71
+ requirePairedManagement(); const route = makeRoute(), runtime = await route.discover(), client = await route.connect(runtime.paths.socket);
72
+ const touched = new Set(), ids = new Set(); let error = null;
73
+ const notification = event => {
74
+ if (event.method !== "fs/changed" || !ids.has(event.params?.watchId)) return;
75
+ if (!Array.isArray(event.params.changedPaths)) { error = "Invalid remote watch event."; return; }
76
+ for (const file of event.params.changedPaths) if (typeof file !== "string") error = "Invalid remote watch path.";
77
+ else if (!excluded(file, snapshot.excludes) && snapshot.roots.some(root => within(file, root)) && touched.size < 100) touched.add(file);
78
+ };
79
+ client.on("notification", notification);
80
+ try {
81
+ for (const root of snapshot.roots) { const watchId = randomUUID(); ids.add(watchId); const result = await client.request("fs/watch", { path: root, watchId }); if (typeof result?.path !== "string") throw fail("Remote filesystem watch was not acknowledged."); }
82
+ return { touched, get error() { return error ?? (client.isOpen() ? null : "Remote filesystem watch disconnected."); }, async close() {
83
+ try { for (const watchId of ids) await client.request("fs/unwatch", { watchId }).catch(() => {}); } finally { client.off("notification", notification); client.close(); }
84
+ } };
85
+ } catch (error) { client.close(); throw error; }
86
+ }
87
+
88
+ export async function runRemoteCommand(argv, { cwd, timeoutMs = 0, signal, onStart = async () => {} } = {}, dependencies = {}) {
89
+ requirePairedManagement(); const processId = randomUUID(), started = new Date().toISOString(), timeout = timeoutMs || 86400000;
90
+ return withRemoteClient(async client => {
91
+ if (signal?.aborted) throw fail("Remote command cancelled before dispatch.", "CANCELLED");
92
+ // processId is connection scoped and only this invocation can terminate it.
93
+ let cancelError = null;
94
+ const cancel = () => { client.request("command/exec/terminate", { processId }, { mutation: true }).catch(error => { cancelError = error; }); };
95
+ signal?.addEventListener("abort", cancel, { once: true });
96
+ try {
97
+ await onStart(null, { remote_process_id: processId, execution_backend: "app-server" });
98
+ const result = await client.request("command/exec", { command: argv, ...(cwd ? { cwd } : {}), timeoutMs: timeout, outputBytesCap: 32768, processId }, { mutation: true });
99
+ if (!Number.isInteger(result?.exitCode) || typeof result.stdout !== "string" || typeof result.stderr !== "string") throw Object.assign(fail("Remote command acknowledgement was invalid.", "REMOTE_COMMAND_UNKNOWN"), { uncertain: true });
100
+ return { pid: null, remote_process_id: processId, ...hostFields(), exit_code: result.exitCode, signal: null, timed_out: false, started_at: started,
101
+ completed_at: new Date().toISOString(), output_tail: (result.stdout + result.stderr).slice(-32768), output_truncated: result.stdout.length + result.stderr.length >= 32768,
102
+ error: cancelError ? "Remote cancellation was not acknowledged." : signal?.aborted ? "Remote command interrupted." : null };
103
+ } catch (error) { error.remote_process_id = processId; throw error; }
104
+ finally { signal?.removeEventListener("abort", cancel); }
105
+ }, { ...dependencies, timeoutMs: timeout + 10000 });
106
+ }
107
+
108
+ export function remoteShellScript(argv, { cwd, shell, marker }) {
109
+ if (!["powershell", "posix"].includes(shell) || !/^[a-f0-9-]{36}$/.test(marker) || argv.some(arg => typeof arg !== "string" || arg.includes("\0"))) throw fail("An explicit powershell or posix --shell and valid argv are required.", "REMOTE_SHELL_REQUIRED");
110
+ const tag = `CODEXTEER_EXEC_${marker}`;
111
+ if (shell === "powershell") {
112
+ const q = value => `'${value.replaceAll("'", "''")}'`;
113
+ return `$ErrorActionPreference='Stop'; $codexteerStatus=1; try { ${cwd ? `Set-Location -LiteralPath ${q(cwd)}; ` : ""}& ${argv.map(q).join(" ")}; $codexteerStatus=if ($null -eq $LASTEXITCODE) {0} else {$LASTEXITCODE} } catch { Write-Output $_.Exception.Message }; Write-Output ("\u0060n${tag}:" + $codexteerStatus); exit $codexteerStatus`;
114
+ }
115
+ const q = value => `'${value.replaceAll("'", "'\\''")}'`;
116
+ return `{ ${cwd ? `cd -- ${q(cwd)} && ` : ""}${argv.map(q).join(" ")}; }; codexteer_status=$?; printf '\\n${tag}:%s\\n' "$codexteer_status"; exit "$codexteer_status"`;
117
+ }
118
+
119
+ // This is an explicitly selected, task-scoped host shell (the official API is
120
+ // unsandboxed). Never use it as fallback for a refused command/exec request.
121
+ export async function runRemoteShellCommand(argv, { cwd, threadId, shell, timeoutMs = 0, signal, onStart = async () => {}, supervisor, connection } = {}, dependencies = {}) {
122
+ requirePairedManagement();
123
+ if (!threadId) throw fail("thread-shell requires --thread (checkpoint uses its own task).", "REMOTE_SHELL_REQUIRED");
124
+ threadId = normalizeThreadId(threadId);
125
+ const marker = randomUUID(), command = remoteShellScript(argv, { cwd, shell, marker }), tag = `CODEXTEER_EXEC_${marker}`, started = new Date().toISOString(), timeout = timeoutMs || 3600000;
126
+ return withRemoteClient(async (client, runtime) => {
127
+ const thread = (await client.request("thread/read", { threadId, includeTurns: false })).thread;
128
+ if (thread?.id !== threadId || !["idle", "notLoaded"].includes(thread.status?.type)) throw fail("Explicit thread-shell execution requires the selected task to be idle.", "REMOTE_SHELL_TASK_BUSY");
129
+ if (signal?.aborted) throw fail("Remote shell cancelled before dispatch.", "CANCELLED");
130
+ let accepted = false;
131
+ try {
132
+ await onStart(null, { remote_process_id: marker, execution_backend: "thread-shell", remote_shell: shell });
133
+ // Recheck after every preparation await. Pause/stop shares this lock,
134
+ // so it cannot acknowledge between this check and dispatch acceptance.
135
+ // Release after the RPC receipt; an accepted command is not cancellable
136
+ // through supervisor control and can still be observed while paused.
137
+ const acknowledgement = await withSupervisedMutation(threadId, supervisor, () => {
138
+ if (signal?.aborted) throw fail("Remote shell cancelled before dispatch.", "CANCELLED");
139
+ return client.request("thread/shellCommand", { threadId, command, timeoutMs: timeout }, { mutation: true });
140
+ }, { connection, home: runtime.paths.home }); accepted = true;
141
+ if (!acknowledgement || typeof acknowledgement !== "object" || Array.isArray(acknowledgement)) throw fail("Remote shell acknowledgement was invalid. Inspect the same command before retrying.", "REMOTE_COMMAND_UNKNOWN");
142
+ const deadline = Date.now() + timeout + 10000;
143
+ do {
144
+ if (signal?.aborted) throw fail("Remote shell observation interrupted; the host command may still run until its timeout.", "REMOTE_COMMAND_UNKNOWN");
145
+ const history = await fetchThread(client, threadId);
146
+ const matches = history.turns.flatMap(turn => turn.items ?? []).filter(item => item.type === "commandExecution" && item.command?.includes(tag));
147
+ if (matches.length > 1) throw fail("Remote command identity was not unique.", "REMOTE_COMMAND_UNKNOWN");
148
+ const item = matches[0];
149
+ if (item && ["completed", "failed"].includes(item.status) && Number.isInteger(item.exitCode)) {
150
+ const output = item.aggregatedOutput ?? "", markerLine = new RegExp(`^${tag}:(-?\\d+)\\r?$`, "gm"), markers = [...output.matchAll(markerLine)];
151
+ if (markers.length !== 1 || Number(markers[0][1]) !== item.exitCode) throw fail("Remote shell exit status could not be correlated with its completion marker.", "REMOTE_COMMAND_UNKNOWN");
152
+ // App Server aggregates stdout and stderr; stderr may follow the
153
+ // stdout marker even though the command has already completed.
154
+ const clean = output.replace(new RegExp(`^${tag}:-?\\d+\\r?\\n?`, "m"), "");
155
+ return { pid: null, remote_process_id: marker, thread_id: threadId, execution_backend: "thread-shell", execution_permissions: "host-full-access", ...hostFields(),
156
+ exit_code: item.exitCode, signal: null, timed_out: false, started_at: started, completed_at: new Date().toISOString(), output_tail: clean.slice(-32768), output_truncated: clean.length > 32768, error: null };
157
+ }
158
+ if (Date.now() >= deadline) break; await delay(250);
159
+ } while (true);
160
+ throw fail("Remote shell completion was not observed. Do not replay the command.", "REMOTE_COMMAND_UNKNOWN");
161
+ } catch (error) { error.remote_process_id = marker; if (accepted) error.uncertain = true; throw error; }
162
+ }, dependencies);
163
+ }
@@ -0,0 +1,76 @@
1
+ import { open } from "node:fs/promises";
2
+ import { spawnSync } from "node:child_process";
3
+ import { BUNDLED_NODE } from "./wrapper.mjs";
4
+
5
+ const DESKTOP = "/Applications/ChatGPT.app/Contents/MacOS/ChatGPT";
6
+
7
+ // Read public code-signature metadata only. Do not load Desktop code, attach to
8
+ // its process, alter entitlements, or query/export Keychain item material.
9
+ export function entitlementBlob(signature) {
10
+ if (signature.length < 12 || signature.readUInt32BE(0) !== 0xfade0cc0) throw new Error("Invalid signature container");
11
+ const length = signature.readUInt32BE(4), count = signature.readUInt32BE(8);
12
+ // LC_CODE_SIGNATURE reserves aligned zero-filled capacity beyond the actual
13
+ // SuperBlob. Only that padding may lie outside its declared length.
14
+ if (length < 12 || length > signature.length || signature.subarray(length).some(byte => byte !== 0)
15
+ || count > 1024 || 12 + count * 8 > length) throw new Error("Invalid signature index");
16
+ for (let index = 0; index < count; index++) {
17
+ const slot = 12 + index * 8, offset = signature.readUInt32BE(slot + 4);
18
+ if (signature.readUInt32BE(slot) !== 5) continue;
19
+ if (offset < 12 + count * 8 || offset + 8 > length || signature.readUInt32BE(offset) !== 0xfade7171) throw new Error("Invalid entitlement slot");
20
+ const size = signature.readUInt32BE(offset + 4);
21
+ if (size < 8 || offset + size > length) throw new Error("Invalid entitlement size");
22
+ return signature.subarray(offset + 8, offset + size);
23
+ }
24
+ return null;
25
+ }
26
+
27
+ async function embeddedEntitlements(executable) {
28
+ const file = await open(executable, "r");
29
+ try {
30
+ const exact = async (length, offset) => {
31
+ const bytes = Buffer.alloc(length);
32
+ if ((await file.read(bytes, 0, length, offset)).bytesRead !== length) throw new Error("Incomplete signature metadata");
33
+ return bytes;
34
+ };
35
+ const header = await exact(32, 0);
36
+ // The installed Desktop and bundled Node use little-endian thin Mach-O64.
37
+ // Unknown layouts are reported as unverified, never treated as authorized.
38
+ if (header.readUInt32LE(0) !== 0xfeedfacf) throw new Error("Unrecognized executable layout");
39
+ const count = header.readUInt32LE(16), size = header.readUInt32LE(20);
40
+ if (count > 4096 || size > 1024 * 1024) throw new Error("Invalid load commands");
41
+ const commands = await exact(size, 32);
42
+ for (let index = 0, offset = 0; index < count; index++) {
43
+ if (offset + 8 > size) throw new Error("Incomplete load command");
44
+ const command = commands.readUInt32LE(offset), length = commands.readUInt32LE(offset + 4);
45
+ if (length < 8 || offset + length > size) throw new Error("Invalid load command");
46
+ if (command === 0x1d) {
47
+ if (length < 16) throw new Error("Invalid code signature command");
48
+ const start = commands.readUInt32LE(offset + 8), bytes = commands.readUInt32LE(offset + 12);
49
+ if (bytes > 16 * 1024 * 1024) throw new Error("Signature metadata too large");
50
+ return entitlementBlob(await exact(bytes, start));
51
+ }
52
+ offset += length;
53
+ }
54
+ throw new Error("No code signature metadata");
55
+ } finally { await file.close(); }
56
+ }
57
+
58
+ export async function inspectKeychainIdentities({ read = embeddedEntitlements, run = spawnSync } = {}) {
59
+ const summary = async executable => {
60
+ try {
61
+ const xml = await read(executable);
62
+ if (!xml) throw new Error();
63
+ const result = run("/usr/bin/plutil", ["-convert", "json", "-o", "-", "--", "-"], { input: xml, encoding: "utf8", timeout: 3000, maxBuffer: 1024 * 1024 });
64
+ if (result.status !== 0) throw new Error();
65
+ const values = JSON.parse(result.stdout), groups = values["keychain-access-groups"];
66
+ if (groups !== undefined && (!Array.isArray(groups) || groups.some(value => typeof value !== "string"))) throw new Error();
67
+ return { status: "observed", application_identifier: typeof (values["com.apple.application-identifier"] ?? values["application-identifier"]) === "string",
68
+ keychain_access_group_count: groups?.length ?? 0 };
69
+ } catch { return { status: "unverified" }; }
70
+ };
71
+ const [desktop, helper] = await Promise.all([summary(DESKTOP), summary(BUNDLED_NODE)]);
72
+ return { source: "embedded-public-entitlements", desktop, helper,
73
+ different_keychain_identity: desktop.status !== "observed" || helper.status !== "observed" ? null
74
+ : desktop.application_identifier !== helper.application_identifier || desktop.keychain_access_group_count !== helper.keychain_access_group_count,
75
+ interpretation: "Identity metadata can explain access differences; it does not prove the enrolled key is missing from Desktop." };
76
+ }
@@ -0,0 +1,38 @@
1
+ import { readFile, lstat } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import { readEnrollment, enrollmentHash, connectionPayload, verifyDeviceProof } from "./remote-control-auth.mjs";
4
+
5
+ // No generic signing interface, key creation, enrollment or network access.
6
+ let stage = "input";
7
+ try {
8
+ const file = process.argv[2], info = await lstat(file);
9
+ if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid() || (info.mode & 0o077) || info.size > 16384) throw new Error();
10
+ const input = JSON.parse(await readFile(file, "utf8")); stage = "enrollment";
11
+ if (!["inspect", "sign"].includes(input.operation) || (input.operation === "inspect" && input.challenge !== undefined)) throw new Error();
12
+ const enrollment = await readEnrollment(input.home, input.identity);
13
+ if (enrollmentHash(enrollment) !== input.enrollmentHash) throw new Error();
14
+ const payload = input.operation === "sign" ? Buffer.from(JSON.stringify({ domain: "codex-device-key-sign-payload/v1", payload: connectionPayload(input.challenge, enrollment) })) : null;
15
+ stage = "addon";
16
+ const native = createRequire(import.meta.url)("/Applications/ChatGPT.app/Contents/Resources/native/remote-control-device-key.node");
17
+ stage = "public-key";
18
+ const key = await native.getDeviceKeyPublic(enrollment.keyId);
19
+ stage = "public-key-match";
20
+ for (const field of ["keyId", "algorithm", "protectionClass", "publicKeySpkiDerBase64"]) if (key[field] !== enrollment[field]) throw new Error();
21
+ if (input.operation === "inspect") {
22
+ process.stdout.write(JSON.stringify({ ok: true, operation: "inspect", device_key_access: "verified" }));
23
+ } else {
24
+ stage = "sign";
25
+ const signature = await native.signDeviceKey(enrollment.keyId, payload);
26
+ stage = "verify";
27
+ const proof = verifyDeviceProof({ type: "device_key_proof", keyId: enrollment.keyId, algorithm: signature.algorithm,
28
+ signatureDerBase64: signature.signatureDerBase64, signedPayloadBase64: payload.toString("base64") }, input.challenge, enrollment);
29
+ process.stdout.write(JSON.stringify({ ok: true, proof }));
30
+ }
31
+ } catch (error) {
32
+ const status = error?.message?.match(/(?:OSStatus|status|Security\.framework error code)[^\d-]*(-?\d+)/i)?.[1];
33
+ const known = { "No keychain is available. You may need to restart your computer.": "KEYCHAIN_UNAVAILABLE",
34
+ "device key not found": "DEVICE_KEY_NOT_FOUND", "invalid device key id": "DEVICE_KEY_ID_INVALID",
35
+ remote_control_device_key_unusable: "DEVICE_KEY_UNUSABLE", remote_control_device_key_store_unavailable: "DEVICE_KEY_STORE_UNAVAILABLE" };
36
+ const nativeCode = known[error?.message] ?? known[error?.errorCode] ?? (typeof error?.code === "number" || /^[A-Z][A-Z0-9_:-]{0,79}$/.test(error?.code ?? "") ? error.code : status ? Number(status) : null);
37
+ process.stdout.write(JSON.stringify({ ok: false, code: "REMOTE_AUTH_SIGNING_FAILED", stage, native_code: nativeCode })); process.exitCode = 1;
38
+ }
@@ -0,0 +1,72 @@
1
+ import { readFile, lstat, mkdir, writeFile, rename, rm, chmod } from "node:fs/promises";
2
+ import { createHash, randomUUID, verify } from "node:crypto";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+ import { fileURLToPath } from "node:url";
6
+ import path from "node:path";
7
+ import { deviceStore, validateDeviceKey } from "./remote-device-store.mjs";
8
+ import { connectionPayload, controlFailure } from "./remote-control-auth.mjs";
9
+ import { enrollmentPayload } from "./remote-device-protocol.mjs";
10
+
11
+ const execute = promisify(execFile), hash = bytes => createHash("sha256").update(bytes).digest("hex");
12
+ async function owned(file, directory = false) {
13
+ const info = await lstat(file);
14
+ if (info.uid !== process.getuid() || info.isSymbolicLink() || (info.mode & 0o077) || !(directory ? info.isDirectory() : info.isFile())) throw controlFailure("Unsafe dedicated key helper runtime.", "REMOTE_DEVICE_STORE_UNSAFE");
15
+ }
16
+
17
+ export async function prepareKeychainRuntime(home, identity) {
18
+ const { file } = await deviceStore(home, identity, { create: true }), root = path.join(path.dirname(file), "key-helpers");
19
+ await mkdir(root, { mode: 0o700 }).catch(error => { if (error.code !== "EEXIST") throw error; }); await owned(root, true);
20
+ const source = await readFile(new URL("../assets/remote-device-key.swift", import.meta.url)), sourceHash = hash(Buffer.concat([source, Buffer.from(process.arch)]));
21
+ const directory = path.join(root, sourceHash), binary = path.join(directory, "device-key");
22
+ try { await owned(directory, true); await owned(path.join(directory, "manifest.json")); const manifest = JSON.parse(await readFile(path.join(directory, "manifest.json"), "utf8"));
23
+ const runtime = { source: sourceHash, sha256: manifest.sha256 }; await keychainBinary(home, identity, runtime); return runtime;
24
+ } catch (error) { if (error.code !== "ENOENT") throw error; }
25
+ const staging = path.join(root, `.build-${randomUUID()}`); await mkdir(staging, { mode: 0o700 });
26
+ try {
27
+ const swift = path.join(staging, "device-key.swift"), target = path.join(staging, "device-key"); await writeFile(swift, source, { mode: 0o600 });
28
+ try {
29
+ await execute("/usr/bin/xcrun", ["swiftc", swift, "-O", "-framework", "Security", "-framework", "Foundation", "-module-cache-path", path.join(staging, "module-cache"), "-o", target], { timeout: 120000, maxBuffer: 65536 });
30
+ await chmod(target, 0o700);
31
+ // Ad-hoc sign our own new helper only. No Desktop binary or privileged
32
+ // entitlement is modified; no user's signing certificate is accessed.
33
+ await execute("/usr/bin/codesign", ["--sign", "-", "--identifier", "dev.codexteer.device-key", target], { timeout: 10000, maxBuffer: 16384 });
34
+ } catch { throw controlFailure("Cannot build the dedicated Keychain helper. Xcode command-line tools are required; no key was created.", "REMOTE_KEY_HELPER_BUILD_FAILED"); }
35
+ const runtime = { source: sourceHash, sha256: hash(await readFile(target)) };
36
+ await writeFile(path.join(staging, "manifest.json"), JSON.stringify(runtime), { mode: 0o600 });
37
+ await rm(path.join(staging, "module-cache"), { recursive: true, force: true }); await rm(swift);
38
+ await rename(staging, directory); return runtime;
39
+ } finally { await rm(staging, { recursive: true, force: true }); }
40
+ }
41
+
42
+ async function keychainBinary(home, identity, runtime) {
43
+ if (!/^[a-f0-9]{64}$/.test(runtime?.source ?? "") || !/^[a-f0-9]{64}$/.test(runtime?.sha256 ?? "")) throw controlFailure("Invalid pinned key helper.", "REMOTE_DEVICE_STORE_UNSAFE");
44
+ const { file } = await deviceStore(home, identity), root = path.join(path.dirname(file), "key-helpers"), directory = path.join(root, runtime.source), binary = path.join(directory, "device-key");
45
+ await owned(root, true); await owned(directory, true); await owned(binary);
46
+ if (hash(await readFile(binary)) !== runtime.sha256) throw controlFailure("Pinned key helper changed. No signing was attempted.", "REMOTE_DEVICE_STORE_UNSAFE");
47
+ await execute("/usr/bin/codesign", ["--verify", "--strict", binary], { timeout: 10000, maxBuffer: 16384 }); return binary;
48
+ }
49
+
50
+ export async function keychainOperation(operation, input) {
51
+ const { home, identity, runtime } = input, binary = await keychainBinary(home, identity, runtime);
52
+ const { file: record } = await deviceStore(home, identity), request = path.join(path.dirname(record), `.request-${randomUUID()}.json`);
53
+ let payload;
54
+ if (operation === "connection-proof") payload = connectionPayload(input.challenge, input.enrollment);
55
+ else if (operation === "enrollment-proof") payload = enrollmentPayload(input.challenge, input.enrollment, input.path);
56
+ const bytes = payload ? Buffer.from(JSON.stringify({ domain: "codex-device-key-sign-payload/v1", payload })) : null;
57
+ try {
58
+ await writeFile(request, JSON.stringify({ operation: operation === "create" ? "create" : ["inspect", "recover"].includes(operation) ? "inspect" : "sign",
59
+ keyId: input.enrollment?.keyId ?? input.keyId, ...(bytes ? { signedPayloadBase64: bytes.toString("base64") } : {}) }), { mode: 0o600, flag: "wx" });
60
+ let response;
61
+ try { response = JSON.parse((await execute(binary, [request], { timeout: 10000, maxBuffer: 32768 })).stdout); }
62
+ catch { throw controlFailure("Dedicated Keychain helper lost its acknowledgement. Inspect the saved phase before retrying.", "REMOTE_DEDICATED_KEY_UNAVAILABLE"); }
63
+ if (response.ok !== true) throw controlFailure(`Dedicated Keychain operation failed at ${["input", "create", "validate", "public-key", "payload", "sign"].includes(response.stage) ? response.stage : "worker"} (${Number.isSafeInteger(response.os_status) ? response.os_status : "UNVERIFIED"}).`, "REMOTE_DEDICATED_KEY_UNAVAILABLE");
64
+ const key = validateDeviceKey(response.key);
65
+ if (["create", "recover"].includes(operation)) return key;
66
+ for (const field of Object.keys(key)) if (key[field] !== input.enrollment[field]) throw controlFailure("Dedicated Keychain public key changed.", "REMOTE_DEVICE_KEY_INVALID");
67
+ if (operation === "inspect") return;
68
+ if (!verify("sha256", bytes, { key: Buffer.from(key.publicKeySpkiDerBase64, "base64"), type: "spki", format: "der" }, Buffer.from(response.signatureDerBase64, "base64"))) throw controlFailure("Dedicated signature did not match the enrolled key.", "REMOTE_AUTH_PROOF_INVALID");
69
+ return operation === "connection-proof" ? { type: "device_key_proof", keyId: key.keyId, algorithm: key.algorithm, signatureDerBase64: response.signatureDerBase64, signedPayloadBase64: bytes.toString("base64") }
70
+ : { challenge_token: input.challenge.challenge_token, key_id: key.keyId, algorithm: key.algorithm, signature_der_base64: response.signatureDerBase64, signed_payload_base64: bytes.toString("base64") };
71
+ } finally { await rm(request, { force: true }); }
72
+ }