@bitkyc08/opencodex 2.52.0-preview.20260912 → 2.53.0-preview.20260913

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 (236) hide show
  1. package/gui/dist/assets/index-BBOZWGB6.css +1 -0
  2. package/gui/dist/assets/index-D7ynYo2K.js +128 -0
  3. package/gui/dist/index.html +2 -2
  4. package/native/remote-workspace-helper/Cargo.lock +130 -0
  5. package/native/remote-workspace-helper/Cargo.toml +24 -0
  6. package/native/remote-workspace-helper/src/main.rs +49 -0
  7. package/native/remote-workspace-helper/src/protocol.rs +246 -0
  8. package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
  9. package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
  10. package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
  11. package/package.json +6 -1
  12. package/src/adapters/anthropic-image-normalize.ts +30 -2
  13. package/src/adapters/anthropic.ts +1 -1
  14. package/src/adapters/base.ts +8 -2
  15. package/src/adapters/cursor/cursor-errors.ts +12 -0
  16. package/src/adapters/cursor/thread-continuity.ts +93 -0
  17. package/src/adapters/cursor.ts +104 -73
  18. package/src/adapters/devin/cloud-direct/chat.ts +312 -23
  19. package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
  20. package/src/adapters/devin/live-models.ts +70 -3
  21. package/src/adapters/devin.ts +281 -21
  22. package/src/adapters/google-wire-compiler.ts +14 -6
  23. package/src/adapters/google.ts +22 -8
  24. package/src/adapters/kiro/adapter.ts +316 -0
  25. package/src/adapters/kiro/conversation.ts +136 -0
  26. package/src/adapters/kiro/payload.ts +432 -0
  27. package/src/adapters/kiro/reasoning.ts +56 -0
  28. package/src/adapters/kiro/stream.ts +1153 -0
  29. package/src/adapters/kiro/usage.ts +223 -0
  30. package/src/adapters/kiro/wire.ts +76 -0
  31. package/src/adapters/kiro.ts +8 -2319
  32. package/src/adapters/mimo-free.ts +1 -1
  33. package/src/adapters/openai-chat-images.ts +101 -0
  34. package/src/adapters/openai-chat.ts +201 -181
  35. package/src/adapters/openai-responses.ts +92 -224
  36. package/src/adapters/registry.ts +0 -7
  37. package/src/adapters/run-turn-queue.ts +13 -6
  38. package/src/bridge.ts +14 -15
  39. package/src/chat/inbound.ts +29 -4
  40. package/src/chat/outbound.ts +145 -107
  41. package/src/claude/desktop-profile.ts +4 -6
  42. package/src/cli/account-api.ts +14 -0
  43. package/src/cli/account-extended.ts +1 -1
  44. package/src/cli/account-history.ts +60 -0
  45. package/src/cli/account-main.ts +80 -0
  46. package/src/cli/account.ts +11 -3
  47. package/src/cli/capabilities.ts +113 -0
  48. package/src/cli/catalog.ts +109 -0
  49. package/src/cli/dispatch.ts +9 -0
  50. package/src/cli/help.ts +2 -0
  51. package/src/cli/index.ts +2 -2
  52. package/src/cli/observe.ts +28 -1
  53. package/src/cli/opencode.ts +42 -8
  54. package/src/cli/provider-runtime.ts +11 -1
  55. package/src/cli/provider.ts +22 -2
  56. package/src/cli/registry.ts +21 -0
  57. package/src/cli/remote-workspace.ts +154 -0
  58. package/src/cli/status.ts +39 -7
  59. package/src/cli/usage-report.ts +14 -2
  60. package/src/client/hub-client.ts +34 -0
  61. package/src/client/hub-state.ts +9 -1
  62. package/src/codex/account-store.ts +78 -0
  63. package/src/codex/auth-api.ts +81 -54
  64. package/src/codex/auth-context.ts +45 -16
  65. package/src/codex/catalog/effort.ts +1 -1
  66. package/src/codex/catalog/metadata.ts +3 -6
  67. package/src/codex/catalog/native-models.ts +4 -4
  68. package/src/codex/catalog/parsing.ts +2 -20
  69. package/src/codex/catalog/provider-fetch.ts +10 -1
  70. package/src/codex/catalog/remote.ts +233 -0
  71. package/src/codex/catalog/sync.ts +403 -35
  72. package/src/codex/convergence.ts +1 -1
  73. package/src/codex/history-manifest.ts +36 -0
  74. package/src/codex/history-provider.ts +32 -5
  75. package/src/codex/inject.ts +9 -0
  76. package/src/codex/main-account.ts +113 -0
  77. package/src/codex/main-device-reauth-api.ts +89 -0
  78. package/src/codex/main-device-reauth.ts +217 -0
  79. package/src/codex/native-residue.ts +9 -2
  80. package/src/codex/quota-auto-refresh.ts +3 -2
  81. package/src/codex/quota-capacity.ts +98 -0
  82. package/src/codex/quota-history.ts +160 -0
  83. package/src/codex/quota-types.ts +8 -0
  84. package/src/codex/quota.ts +118 -91
  85. package/src/codex/refresh.ts +2 -1
  86. package/src/codex/routing.ts +90 -17
  87. package/src/codex/sync.ts +33 -4
  88. package/src/combos/request.ts +19 -1
  89. package/src/config/multi-agent-surface.ts +61 -0
  90. package/src/config/provider-validation.ts +176 -0
  91. package/src/config.ts +213 -11
  92. package/src/generated/compatibility-version.json +436 -168
  93. package/src/images/loop.ts +119 -36
  94. package/src/lib/admission.ts +12 -6
  95. package/src/lib/redact.ts +7 -0
  96. package/src/lib/translator-budget.ts +4 -3
  97. package/src/lib/windows-atomic-replace.ts +1 -0
  98. package/src/lib/windows-elevation.ts +1 -1
  99. package/src/oauth/chatgpt-device.ts +62 -5
  100. package/src/oauth/devin/cli-import.ts +130 -0
  101. package/src/oauth/devin.ts +63 -8
  102. package/src/oauth/index.ts +29 -14
  103. package/src/oauth/kiro.ts +18 -6
  104. package/src/oauth/login-cli.ts +9 -1
  105. package/src/oauth/meta-muse-device.ts +464 -0
  106. package/src/oauth/meta-muse.ts +123 -32
  107. package/src/oauth/pool-kernel.ts +9 -0
  108. package/src/oauth/pool-settings-capability.ts +2 -2
  109. package/src/oauth/store.ts +57 -0
  110. package/src/oauth/types.ts +31 -0
  111. package/src/providers/derive.ts +13 -3
  112. package/src/providers/devin-cli-authmode-migration.ts +57 -35
  113. package/src/providers/devin-provider-merge-migration.ts +240 -0
  114. package/src/providers/muse-key-quota.ts +117 -0
  115. package/src/providers/muse-subscription-usage.ts +14 -2
  116. package/src/providers/openai-sidecar.ts +25 -3
  117. package/src/providers/opencode-zen-rate-limit.ts +58 -0
  118. package/src/providers/provider-id-rewrite.ts +20 -5
  119. package/src/providers/quota-types.ts +12 -0
  120. package/src/providers/quota.ts +143 -102
  121. package/src/providers/reasoning-metadata.ts +543 -0
  122. package/src/providers/registry.ts +80 -49
  123. package/src/reasoning-effort.ts +26 -2
  124. package/src/remote/hub-usage.ts +32 -0
  125. package/src/remote-control/index.ts +192 -41
  126. package/src/remote-control/workspace-activation.ts +9 -0
  127. package/src/remote-control/workspace-agent-connection.ts +366 -0
  128. package/src/remote-control/workspace-claude-runtime.ts +243 -0
  129. package/src/remote-control/workspace-codex-runtime.ts +531 -0
  130. package/src/remote-control/workspace-codex-sandbox.ts +115 -0
  131. package/src/remote-control/workspace-command-runner.ts +748 -0
  132. package/src/remote-control/workspace-coordinator.ts +231 -0
  133. package/src/remote-control/workspace-device.ts +585 -0
  134. package/src/remote-control/workspace-executable.ts +43 -0
  135. package/src/remote-control/workspace-executor.ts +397 -0
  136. package/src/remote-control/workspace-hub.ts +519 -0
  137. package/src/remote-control/workspace-pi-runtime.ts +382 -0
  138. package/src/remote-control/workspace-process.ts +129 -0
  139. package/src/remote-control/workspace-rpc.ts +304 -0
  140. package/src/remote-control/workspace-runtime.ts +60 -0
  141. package/src/remote-control/workspace-secret-store.ts +39 -0
  142. package/src/remote-control/workspace-sessions.ts +799 -0
  143. package/src/remote-control/workspace-tool-bridge.ts +192 -0
  144. package/src/responses/code-mode-helper-compat.ts +22 -3
  145. package/src/responses/hosted-tool-policy.ts +0 -1
  146. package/src/responses/muse-tool-name-alias.ts +379 -0
  147. package/src/responses/plaintext-v2-agent-messages.ts +902 -0
  148. package/src/router.ts +7 -0
  149. package/src/routing/compatibility/behavior.ts +0 -1
  150. package/src/server/audio-client.ts +64 -0
  151. package/src/server/audio-dictation.ts +91 -0
  152. package/src/server/audio-live.ts +185 -0
  153. package/src/server/audio-transcriptions.ts +183 -0
  154. package/src/server/audio-upstream.ts +153 -0
  155. package/src/server/auth-cors.ts +61 -2
  156. package/src/server/chat-completions.ts +1 -1
  157. package/src/server/chat-native-sse.ts +92 -48
  158. package/src/server/chat-native.ts +37 -15
  159. package/src/server/hub-usage.ts +57 -0
  160. package/src/server/images.ts +4 -0
  161. package/src/server/index.ts +722 -57
  162. package/src/server/lifecycle.ts +5 -6
  163. package/src/server/live-call-bindings.ts +60 -0
  164. package/src/server/live.ts +12 -1
  165. package/src/server/management/agent-settings-routes.ts +25 -4
  166. package/src/server/management/api-access.ts +37 -0
  167. package/src/server/management/api-key-usage.ts +7 -2
  168. package/src/server/management/config-routes.ts +1 -18
  169. package/src/server/management/context.ts +15 -0
  170. package/src/server/management/logs-usage-routes.ts +2 -0
  171. package/src/server/management/oauth-account-routes.ts +39 -12
  172. package/src/server/management/provider-routes.ts +125 -2
  173. package/src/server/management/remote-workspace-routes.ts +140 -0
  174. package/src/server/management/route-registry.ts +15 -0
  175. package/src/server/management/usage-aggregate-cache.ts +14 -15
  176. package/src/server/management/usage-summary-cache.ts +2 -0
  177. package/src/server/management-api.ts +23 -0
  178. package/src/server/ports.ts +17 -0
  179. package/src/server/relay-eager.ts +4 -1
  180. package/src/server/relay.ts +70 -10
  181. package/src/server/request-decompress.ts +6 -3
  182. package/src/server/responses/agent-task-recovery.ts +25 -32
  183. package/src/server/responses/codex-auth-error.ts +11 -0
  184. package/src/server/responses/codex-ws-exchange.ts +52 -3
  185. package/src/server/responses/codex-ws-wire.ts +55 -0
  186. package/src/server/responses/compact.ts +9 -1
  187. package/src/server/responses/core.ts +337 -73
  188. package/src/server/responses/encrypted-payload.ts +45 -2
  189. package/src/server/responses/ws-upstream.ts +4 -1
  190. package/src/server/responses-self-named-namespace-scrub.ts +1 -3
  191. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  192. package/src/server/search.ts +3 -0
  193. package/src/server/sse-payload-rewrite.ts +136 -51
  194. package/src/server/ws-bridge.ts +35 -1
  195. package/src/service/cli.ts +372 -0
  196. package/src/service/diagnostics.ts +340 -0
  197. package/src/service/guards.ts +303 -0
  198. package/src/service/health.ts +222 -0
  199. package/src/service/launchd.ts +853 -0
  200. package/src/service/orchestration.ts +617 -0
  201. package/src/service/repair.ts +334 -0
  202. package/src/service/state.ts +363 -0
  203. package/src/service/systemd.ts +229 -0
  204. package/src/service/windows-ops.ts +690 -0
  205. package/src/service/windows-scheduler.ts +769 -0
  206. package/src/service/windows-taskxml.ts +613 -0
  207. package/src/service.ts +22 -5550
  208. package/src/storage/cleanup/db.ts +258 -0
  209. package/src/storage/cleanup/execute.ts +358 -0
  210. package/src/storage/cleanup/paths.ts +189 -0
  211. package/src/storage/cleanup/pending.ts +140 -0
  212. package/src/storage/cleanup/preview.ts +292 -0
  213. package/src/storage/cleanup/reconcile.ts +347 -0
  214. package/src/storage/cleanup/restore.ts +932 -0
  215. package/src/storage/cleanup/satellite.ts +474 -0
  216. package/src/storage/cleanup/staging.ts +129 -0
  217. package/src/storage/cleanup/types.ts +98 -0
  218. package/src/storage/cleanup.ts +49 -3127
  219. package/src/types/accounts.ts +2 -0
  220. package/src/types/config.ts +13 -12
  221. package/src/types/provider.ts +37 -0
  222. package/src/types/request.ts +2 -0
  223. package/src/types/tools.ts +17 -5
  224. package/src/types.ts +1 -0
  225. package/src/usage/expected-prices.ts +127 -0
  226. package/src/usage/log.ts +58 -1
  227. package/src/vision/eligibility.ts +13 -2
  228. package/src/web-search/loop.ts +56 -3
  229. package/gui/dist/assets/index-D_t6sCWs.js +0 -115
  230. package/gui/dist/assets/index-EdoPnm9_.css +0 -1
  231. package/src/adapters/devin-cli/acp.ts +0 -204
  232. package/src/adapters/devin-cli/adapter.ts +0 -345
  233. package/src/adapters/devin-cli/binary.ts +0 -69
  234. package/src/adapters/devin-cli/models.ts +0 -57
  235. package/src/oauth/devin-cli.ts +0 -149
  236. package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
@@ -0,0 +1,748 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ accessSync,
4
+ closeSync,
5
+ constants,
6
+ existsSync,
7
+ fstatSync,
8
+ lstatSync,
9
+ opendirSync,
10
+ openSync,
11
+ readSync,
12
+ realpathSync,
13
+ statSync,
14
+ } from "node:fs";
15
+ import { arch } from "node:os";
16
+ import { dirname, isAbsolute, join, relative, sep } from "node:path";
17
+ import type {
18
+ RemoteWorkspaceCommandRequest,
19
+ RemoteWorkspaceCommandResult,
20
+ RemoteWorkspaceCommandRunner,
21
+ } from "./workspace-executor";
22
+
23
+ const DEFAULT_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
24
+ const NATIVE_HELPER_PROTOCOL_VERSION = 1;
25
+ const MAX_NATIVE_HELPER_BYTES = 64 * 1024 * 1024;
26
+ const MAX_NATIVE_HELPER_ERROR_CHARS = 512;
27
+ const MAX_NATIVE_HELPER_STDERR_BYTES = 16 * 1024;
28
+ const MAX_WORKSPACE_PREFLIGHT_ENTRIES = 250_000;
29
+ const SANDBOX_BUN_PATH = "/ocx-runtime/bin/bun";
30
+ const READABLE_SYSTEM_PATHS = [
31
+ "/usr",
32
+ "/bin",
33
+ "/sbin",
34
+ "/lib",
35
+ "/lib64",
36
+ ] as const;
37
+ const READABLE_ETC_PATHS = [
38
+ "/etc/alternatives",
39
+ "/etc/ca-certificates",
40
+ "/etc/ssl",
41
+ "/etc/hosts",
42
+ "/etc/nsswitch.conf",
43
+ "/etc/passwd",
44
+ "/etc/group",
45
+ "/etc/localtime",
46
+ "/etc/resolv.conf",
47
+ ] as const;
48
+
49
+ export interface LinuxRemoteWorkspaceCommandRunnerOptions {
50
+ bubblewrapPath?: string;
51
+ networkAccess?: boolean;
52
+ /** Additional read-only toolchain trees explicitly approved by the device owner. */
53
+ toolchainRoots?: readonly string[];
54
+ /** Exact Bun executable used by OCX; mounted as one file rather than exposing its host directory. */
55
+ runtimeExecutablePath?: string;
56
+ /** Writable roots inspected before command capability is advertised. */
57
+ writableRoots?: readonly string[];
58
+ spawn?: typeof Bun.spawn;
59
+ /** Cross-platform test seam for the real namespace capability probe. */
60
+ probe?: (argv: readonly string[]) => boolean;
61
+ }
62
+
63
+ export interface RemoteWorkspaceNativeHelperDescriptor {
64
+ path: string;
65
+ sha256: string;
66
+ }
67
+
68
+ interface NativeHelperRequest {
69
+ version: typeof NATIVE_HELPER_PROTOCOL_VERSION;
70
+ operation: "probe" | "run";
71
+ root?: string;
72
+ cwd?: string;
73
+ command?: string[];
74
+ toolchainRoots?: string[];
75
+ timeoutMs?: number;
76
+ maxOutputBytes?: number;
77
+ networkAccess?: boolean;
78
+ }
79
+
80
+ interface NativeHelperProbeResponse {
81
+ version: typeof NATIVE_HELPER_PROTOCOL_VERSION;
82
+ ok: true;
83
+ probe: true;
84
+ }
85
+
86
+ export interface NativeRemoteWorkspaceCommandRunnerOptions {
87
+ helper: RemoteWorkspaceNativeHelperDescriptor;
88
+ toolchainRoots?: readonly string[];
89
+ /** Writable workspace roots that must never contain the executable enforcing their sandbox. */
90
+ writableRoots: readonly string[];
91
+ networkAccess?: boolean;
92
+ platform?: NodeJS.Platform;
93
+ spawn?: typeof Bun.spawn;
94
+ spawnSync?: typeof Bun.spawnSync;
95
+ /** Pure test seam. Production always executes the digest-pinned helper's real probe. */
96
+ probe?: (request: NativeHelperRequest) => unknown;
97
+ }
98
+
99
+ const availabilityCache = new Map<string, boolean>();
100
+
101
+ function exactObject(value: unknown, keys: readonly string[]): Record<string, unknown> {
102
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
103
+ throw new Error("remote workspace native helper returned an invalid response");
104
+ }
105
+ const record = value as Record<string, unknown>;
106
+ const allowed = new Set(keys);
107
+ if (Object.keys(record).some(key => !allowed.has(key))) {
108
+ throw new Error("remote workspace native helper returned an invalid response");
109
+ }
110
+ return record;
111
+ }
112
+
113
+ function parseNativeHelperProbeResponse(value: unknown): NativeHelperProbeResponse {
114
+ const raw = exactObject(value, ["version", "ok", "probe"]);
115
+ if (raw.version !== NATIVE_HELPER_PROTOCOL_VERSION || raw.ok !== true || raw.probe !== true) {
116
+ throw new Error("remote workspace native helper failed its confinement probe");
117
+ }
118
+ return { version: NATIVE_HELPER_PROTOCOL_VERSION, ok: true, probe: true };
119
+ }
120
+
121
+ function boundedBase64(value: unknown, label: string, maximum: number): Buffer {
122
+ if (typeof value !== "string" || value.length > Math.ceil(maximum / 3) * 4 + 4
123
+ || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
124
+ throw new Error(`remote workspace native helper returned invalid ${label}`);
125
+ }
126
+ const decoded = Buffer.from(value, "base64");
127
+ if (decoded.byteLength > maximum || decoded.toString("base64") !== value) {
128
+ throw new Error(`remote workspace native helper returned invalid ${label}`);
129
+ }
130
+ return decoded;
131
+ }
132
+
133
+ function parseNativeHelperCommandResponse(value: unknown, maximum: number): RemoteWorkspaceCommandResult {
134
+ const raw = exactObject(value, ["version", "ok", "exitCode", "stdoutBase64", "stderrBase64"]);
135
+ if (raw.version !== NATIVE_HELPER_PROTOCOL_VERSION || raw.ok !== true
136
+ || typeof raw.exitCode !== "number" || !Number.isSafeInteger(raw.exitCode)
137
+ || raw.exitCode < -2_147_483_648 || raw.exitCode > 4_294_967_295) {
138
+ throw new Error("remote workspace native helper returned an invalid command result");
139
+ }
140
+ const stdout = boundedBase64(raw.stdoutBase64, "stdout", maximum);
141
+ const stderr = boundedBase64(raw.stderrBase64, "stderr", maximum);
142
+ if (stdout.byteLength + stderr.byteLength > maximum) {
143
+ throw new Error("remote workspace native helper exceeded its output contract");
144
+ }
145
+ const decoder = new TextDecoder("utf-8", { fatal: false });
146
+ return {
147
+ exitCode: raw.exitCode,
148
+ stdout: decoder.decode(stdout),
149
+ stderr: decoder.decode(stderr),
150
+ };
151
+ }
152
+
153
+ function parseNativeHelperJson(value: Uint8Array): unknown {
154
+ try {
155
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(value));
156
+ } catch {
157
+ throw new Error("remote workspace native helper returned malformed JSON");
158
+ }
159
+ }
160
+
161
+ function sha256File(path: string): string {
162
+ const descriptor = openSync(path, constants.O_RDONLY);
163
+ try {
164
+ const metadata = fstatSync(descriptor);
165
+ if (!metadata.isFile() || metadata.size < 1 || metadata.size > MAX_NATIVE_HELPER_BYTES) {
166
+ throw new Error("remote workspace native helper has an invalid size");
167
+ }
168
+ const hash = createHash("sha256");
169
+ const chunk = Buffer.allocUnsafe(64 * 1024);
170
+ let offset = 0;
171
+ while (offset < metadata.size) {
172
+ const count = readSync(descriptor, chunk, 0, Math.min(chunk.byteLength, metadata.size - offset), offset);
173
+ if (count === 0) throw new Error("remote workspace native helper changed while hashing");
174
+ hash.update(chunk.subarray(0, count));
175
+ offset += count;
176
+ }
177
+ const after = fstatSync(descriptor);
178
+ if (after.size !== metadata.size || after.mtimeMs !== metadata.mtimeMs
179
+ || after.dev !== metadata.dev || after.ino !== metadata.ino) {
180
+ throw new Error("remote workspace native helper changed while hashing");
181
+ }
182
+ return hash.digest("hex");
183
+ } finally {
184
+ closeSync(descriptor);
185
+ }
186
+ }
187
+
188
+ export function pinRemoteWorkspaceNativeHelper(path: string): RemoteWorkspaceNativeHelperDescriptor {
189
+ if (!isAbsolute(path) || path.includes("\0")) {
190
+ throw new Error("remote workspace native helper must be an absolute path");
191
+ }
192
+ const linked = lstatSync(path);
193
+ if (!linked.isFile() || linked.isSymbolicLink()) {
194
+ throw new Error("remote workspace native helper must remain a real file");
195
+ }
196
+ const canonical = realpathSync(path);
197
+ accessSync(canonical, process.platform === "win32" ? constants.F_OK : constants.X_OK);
198
+ if (process.platform !== "win32" && (statSync(canonical).mode & 0o022) !== 0) {
199
+ throw new Error("remote workspace native helper must not be group or world writable");
200
+ }
201
+ return { path: canonical, sha256: sha256File(canonical) };
202
+ }
203
+
204
+ export function discoverRemoteWorkspaceNativeHelper(options: {
205
+ platform?: NodeJS.Platform;
206
+ architecture?: string;
207
+ } = {}): RemoteWorkspaceNativeHelperDescriptor | undefined {
208
+ const platform = options.platform ?? process.platform;
209
+ if (platform !== "darwin" && platform !== "win32") return undefined;
210
+ const architecture = options.architecture ?? arch();
211
+ const executable = platform === "win32"
212
+ ? "opencodex-remote-workspace-helper.exe"
213
+ : "opencodex-remote-workspace-helper";
214
+ const candidates = [
215
+ // Signed release bundles place the helper here.
216
+ `${import.meta.dir}/../../native-bin/${platform}-${architecture}/${executable}`,
217
+ // Source/private-dogfood builds produced by `bun run build:remote-workspace-helper`.
218
+ `${import.meta.dir}/../../native/remote-workspace-helper/target/release/${executable}`,
219
+ ];
220
+ for (const candidate of candidates) {
221
+ if (!existsSync(candidate)) continue;
222
+ try {
223
+ return pinRemoteWorkspaceNativeHelper(candidate);
224
+ } catch {
225
+ return undefined;
226
+ }
227
+ }
228
+ return undefined;
229
+ }
230
+
231
+ export function parseRemoteWorkspaceNativeHelperDescriptor(value: unknown): RemoteWorkspaceNativeHelperDescriptor {
232
+ const raw = exactObject(value, ["path", "sha256"]);
233
+ if (typeof raw.path !== "string" || !isAbsolute(raw.path) || raw.path.includes("\0") || raw.path.length > 4096
234
+ || typeof raw.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(raw.sha256)) {
235
+ throw new Error("invalid remote workspace native helper descriptor");
236
+ }
237
+ return { path: raw.path, sha256: raw.sha256 };
238
+ }
239
+
240
+ function assertNativeHelperIntegrity(value: RemoteWorkspaceNativeHelperDescriptor): RemoteWorkspaceNativeHelperDescriptor {
241
+ const helper = parseRemoteWorkspaceNativeHelperDescriptor(value);
242
+ const linked = lstatSync(helper.path);
243
+ if (!linked.isFile() || linked.isSymbolicLink() || realpathSync(helper.path) !== helper.path) {
244
+ throw new Error("remote workspace native helper identity changed; pair it again");
245
+ }
246
+ accessSync(helper.path, process.platform === "win32" ? constants.F_OK : constants.X_OK);
247
+ if (process.platform !== "win32" && (linked.mode & 0o022) !== 0) {
248
+ throw new Error("remote workspace native helper permissions are unsafe");
249
+ }
250
+ if (sha256File(helper.path) !== helper.sha256) {
251
+ throw new Error("remote workspace native helper digest changed; pair it again");
252
+ }
253
+ return helper;
254
+ }
255
+
256
+ function assertNativeHelperOutsideWritableRoots(
257
+ helper: RemoteWorkspaceNativeHelperDescriptor,
258
+ roots: readonly string[],
259
+ ): string[] {
260
+ if (roots.length < 1 || roots.length > 32) {
261
+ throw new Error("remote workspace native runner needs one to 32 writable roots");
262
+ }
263
+ const canonicalRoots: string[] = [];
264
+ for (const root of roots) {
265
+ if (!isAbsolute(root) || root.includes("\0")) {
266
+ throw new Error("remote workspace writable root must be an absolute path");
267
+ }
268
+ const canonicalRoot = realpathSync(root);
269
+ if (canonicalRoots.includes(canonicalRoot)) {
270
+ throw new Error("remote workspace native runner received a duplicate writable root");
271
+ }
272
+ if (inside(canonicalRoot, helper.path)) {
273
+ // A sandboxed command can write anywhere below its approved root. Executing the sandbox
274
+ // helper from that same tree would turn the hash-then-spawn pathname into a writable trust
275
+ // anchor that a workspace command can replace before a later invocation.
276
+ throw new Error("remote workspace native helper must be outside every writable workspace root");
277
+ }
278
+ canonicalRoots.push(canonicalRoot);
279
+ }
280
+ return canonicalRoots;
281
+ }
282
+
283
+ function nativeHelperEnvironment(platform: NodeJS.Platform): Record<string, string> {
284
+ const result: Record<string, string> = {};
285
+ const names = platform === "win32"
286
+ ? ["SystemRoot", "WINDIR", "TEMP", "TMP"]
287
+ : ["TMPDIR"];
288
+ for (const name of names) {
289
+ const value = process.env[name];
290
+ if (value) result[name] = value;
291
+ }
292
+ return result;
293
+ }
294
+
295
+ function inside(root: string, candidate: string): boolean {
296
+ const rel = relative(root, candidate);
297
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
298
+ }
299
+
300
+ function assertWorkspaceHasNoExternalHardlinkAliases(root: string): void {
301
+ const canonicalRoot = realpathSync(root);
302
+ const pending = [canonicalRoot];
303
+ let entries = 0;
304
+ while (pending.length > 0) {
305
+ const current = pending.pop()!;
306
+ const directory = opendirSync(current);
307
+ try {
308
+ for (;;) {
309
+ const entry = directory.readSync();
310
+ if (!entry) break;
311
+ entries += 1;
312
+ if (entries > MAX_WORKSPACE_PREFLIGHT_ENTRIES) {
313
+ throw new Error("remote workspace is too large for safe command preflight");
314
+ }
315
+ const target = join(current, entry.name);
316
+ const metadata = lstatSync(target);
317
+ if (metadata.isDirectory() && !metadata.isSymbolicLink()) {
318
+ pending.push(target);
319
+ } else if (!metadata.isDirectory() && metadata.nlink > 1) {
320
+ // A bind mount or Seatbelt path rule cannot distinguish two names for one inode. Reject
321
+ // rather than let a workspace alias read or mutate a file whose other name is outside.
322
+ throw new Error("remote workspace command root contains a hard-linked file");
323
+ }
324
+ }
325
+ } finally {
326
+ directory.closeSync();
327
+ }
328
+ }
329
+ }
330
+
331
+ function assertCommandRootsSafe(roots: readonly string[]): void {
332
+ for (const root of roots) assertWorkspaceHasNoExternalHardlinkAliases(root);
333
+ }
334
+
335
+ function sandboxPath(root: string, cwd: string): string {
336
+ if (!inside(root, cwd)) throw new Error("remote workspace command cwd escaped its root");
337
+ const rel = relative(root, cwd);
338
+ return rel ? `/workspace/${rel.split(sep).join("/")}` : "/workspace";
339
+ }
340
+
341
+ function bindArgs(flag: "--ro-bind" | "--ro-bind-try", paths: readonly string[]): string[] {
342
+ const result: string[] = [];
343
+ for (const path of paths) {
344
+ if (flag === "--ro-bind-try" || existsSync(path)) result.push(flag, path, path);
345
+ }
346
+ return result;
347
+ }
348
+
349
+ function approvedToolchainRoots(values: readonly string[]): string[] {
350
+ const result: string[] = [];
351
+ for (const value of values) {
352
+ if (!isAbsolute(value) || !existsSync(value) || value.includes("\0")) {
353
+ throw new Error("remote workspace toolchain root must be an existing absolute path");
354
+ }
355
+ const metadata = lstatSync(value);
356
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
357
+ throw new Error("remote workspace toolchain root must remain a real directory");
358
+ }
359
+ result.push(realpathSync(value));
360
+ }
361
+ return [...new Set(result)];
362
+ }
363
+
364
+ function approvedRuntimeExecutable(value: string | undefined): string | null {
365
+ if (value === undefined) return null;
366
+ if (!isAbsolute(value) || value.includes("\0")) {
367
+ throw new Error("remote workspace runtime executable must be an absolute path");
368
+ }
369
+ const canonical = realpathSync(value);
370
+ if (!statSync(canonical).isFile()) throw new Error("remote workspace runtime executable must be a file");
371
+ accessSync(canonical, constants.X_OK);
372
+ return canonical;
373
+ }
374
+
375
+ function trustedBubblewrap(path: string, roots: readonly string[]): string {
376
+ if (!isAbsolute(path)) throw new Error("bubblewrap must be an absolute executable path");
377
+ const canonical = realpathSync(path);
378
+ const file = lstatSync(canonical);
379
+ if (!file.isFile() || file.nlink !== 1) throw new Error("bubblewrap must be a private executable file");
380
+ for (const root of roots) {
381
+ if (inside(realpathSync(root), canonical)) {
382
+ throw new Error("bubblewrap must be outside every writable workspace root");
383
+ }
384
+ }
385
+ accessSync(canonical, constants.X_OK);
386
+ let current = canonical;
387
+ for (;;) {
388
+ const metadata = lstatSync(current);
389
+ if (process.platform !== "win32" && (metadata.mode & 0o022) !== 0) {
390
+ throw new Error("bubblewrap executable and parent directories must not be group or world writable");
391
+ }
392
+ const parent = dirname(current);
393
+ if (parent === current) break;
394
+ current = parent;
395
+ }
396
+ return canonical;
397
+ }
398
+
399
+ export function linuxRemoteWorkspaceCommandArgv(
400
+ request: RemoteWorkspaceCommandRequest,
401
+ options: LinuxRemoteWorkspaceCommandRunnerOptions = {},
402
+ ): string[] {
403
+ const bubblewrap = trustedBubblewrap(options.bubblewrapPath ?? "/usr/bin/bwrap", [...(options.writableRoots ?? []), request.root]);
404
+ const toolchains = approvedToolchainRoots(options.toolchainRoots ?? []);
405
+ const runtimeExecutable = approvedRuntimeExecutable(options.runtimeExecutablePath);
406
+ const commandPath = [...(runtimeExecutable ? ["/ocx-runtime/bin"] : []), ...toolchains, DEFAULT_PATH].join(":");
407
+ return [
408
+ bubblewrap,
409
+ "--die-with-parent",
410
+ "--new-session",
411
+ "--unshare-pid",
412
+ "--unshare-ipc",
413
+ "--unshare-uts",
414
+ ...(options.networkAccess === true ? [] : ["--unshare-net"]),
415
+ "--proc", "/proc",
416
+ "--dev", "/dev",
417
+ "--tmpfs", "/tmp",
418
+ ...(runtimeExecutable ? [
419
+ "--dir", "/ocx-runtime",
420
+ "--dir", "/ocx-runtime/bin",
421
+ "--ro-bind", runtimeExecutable, SANDBOX_BUN_PATH,
422
+ ] : []),
423
+ ...bindArgs("--ro-bind", READABLE_SYSTEM_PATHS),
424
+ ...bindArgs("--ro-bind-try", READABLE_ETC_PATHS),
425
+ ...toolchains.flatMap(path => ["--ro-bind", path, path]),
426
+ "--bind", request.root, "/workspace",
427
+ "--chdir", sandboxPath(request.root, request.cwd),
428
+ "--clearenv",
429
+ "--setenv", "HOME", "/workspace",
430
+ "--setenv", "PATH", commandPath,
431
+ "--setenv", "LANG", "C.UTF-8",
432
+ "--setenv", "LC_ALL", "C.UTF-8",
433
+ "--",
434
+ ...request.command,
435
+ ];
436
+ }
437
+
438
+ async function collectBoundedOutput(
439
+ stream: ReadableStream<Uint8Array>,
440
+ reserve: (bytes: number) => boolean,
441
+ onOverflow: () => void,
442
+ ): Promise<string> {
443
+ const reader = stream.getReader();
444
+ const chunks: Uint8Array[] = [];
445
+ let total = 0;
446
+ try {
447
+ while (true) {
448
+ const next = await reader.read();
449
+ if (next.done) break;
450
+ if (!reserve(next.value.byteLength)) {
451
+ onOverflow();
452
+ throw new Error("remote workspace command output limit exceeded");
453
+ }
454
+ chunks.push(next.value);
455
+ total += next.value.byteLength;
456
+ }
457
+ } finally {
458
+ reader.releaseLock();
459
+ }
460
+ const body = new Uint8Array(total);
461
+ let offset = 0;
462
+ for (const chunk of chunks) {
463
+ body.set(chunk, offset);
464
+ offset += chunk.byteLength;
465
+ }
466
+ return new TextDecoder("utf-8", { fatal: false }).decode(body);
467
+ }
468
+
469
+ export function createLinuxRemoteWorkspaceCommandRunner(
470
+ options: LinuxRemoteWorkspaceCommandRunnerOptions = {},
471
+ ): RemoteWorkspaceCommandRunner {
472
+ const spawn = options.spawn ?? Bun.spawn;
473
+ return {
474
+ async run(request): Promise<RemoteWorkspaceCommandResult> {
475
+ assertWorkspaceHasNoExternalHardlinkAliases(request.root);
476
+ const argv = linuxRemoteWorkspaceCommandArgv(request, options);
477
+ const child = spawn(argv, {
478
+ cwd: request.root,
479
+ env: { PATH: DEFAULT_PATH, LANG: "C.UTF-8", LC_ALL: "C.UTF-8" },
480
+ stdin: "ignore",
481
+ stdout: "pipe",
482
+ stderr: "pipe",
483
+ });
484
+ let retained = 0;
485
+ let timedOut = false;
486
+ let overflowed = false;
487
+ let cancelled = false;
488
+ const stop = () => {
489
+ try { child.kill(); } catch { /* process already exited */ }
490
+ };
491
+ const cancel = () => {
492
+ cancelled = true;
493
+ stop();
494
+ };
495
+ request.signal?.addEventListener("abort", cancel, { once: true });
496
+ if (request.signal?.aborted) cancel();
497
+ const reserve = (bytes: number): boolean => {
498
+ if (retained + bytes > request.maxOutputBytes) {
499
+ overflowed = true;
500
+ return false;
501
+ }
502
+ retained += bytes;
503
+ return true;
504
+ };
505
+ const timer = setTimeout(() => {
506
+ timedOut = true;
507
+ stop();
508
+ }, request.timeoutMs);
509
+ try {
510
+ const [stdoutResult, stderrResult, exitCode] = await Promise.allSettled([
511
+ collectBoundedOutput(child.stdout, reserve, stop),
512
+ collectBoundedOutput(child.stderr, reserve, stop),
513
+ child.exited,
514
+ ]);
515
+ if (cancelled) throw new Error("remote workspace command was cancelled");
516
+ if (timedOut) throw new Error("remote workspace command timed out");
517
+ if (overflowed) throw new Error("remote workspace command output limit exceeded");
518
+ if (stdoutResult.status === "rejected") throw stdoutResult.reason;
519
+ if (stderrResult.status === "rejected") throw stderrResult.reason;
520
+ if (exitCode.status === "rejected") throw exitCode.reason;
521
+ return { exitCode: exitCode.value, stdout: stdoutResult.value, stderr: stderrResult.value };
522
+ } finally {
523
+ clearTimeout(timer);
524
+ request.signal?.removeEventListener("abort", cancel);
525
+ }
526
+ },
527
+ };
528
+ }
529
+
530
+ function nativeHelperFailure(value: unknown): Error {
531
+ const raw = exactObject(value, ["version", "ok", "error"]);
532
+ if (raw.version !== NATIVE_HELPER_PROTOCOL_VERSION || raw.ok !== false
533
+ || typeof raw.error !== "string" || raw.error.length < 1
534
+ || [...raw.error].length > MAX_NATIVE_HELPER_ERROR_CHARS || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(raw.error)) {
535
+ return new Error("remote workspace native helper returned an invalid failure");
536
+ }
537
+ return new Error(raw.error);
538
+ }
539
+
540
+ function nativeHelperRequest(options: NativeRemoteWorkspaceCommandRunnerOptions, request: RemoteWorkspaceCommandRequest): NativeHelperRequest {
541
+ return {
542
+ version: NATIVE_HELPER_PROTOCOL_VERSION,
543
+ operation: "run",
544
+ root: request.root,
545
+ cwd: request.cwd,
546
+ command: [...request.command],
547
+ toolchainRoots: approvedToolchainRoots(options.toolchainRoots ?? []),
548
+ timeoutMs: request.timeoutMs,
549
+ maxOutputBytes: request.maxOutputBytes,
550
+ networkAccess: options.networkAccess === true,
551
+ };
552
+ }
553
+
554
+ export function createNativeRemoteWorkspaceCommandRunner(
555
+ options: NativeRemoteWorkspaceCommandRunnerOptions,
556
+ ): RemoteWorkspaceCommandRunner {
557
+ const platform = options.platform ?? process.platform;
558
+ if (platform !== "darwin" && platform !== "win32") {
559
+ throw new Error("remote workspace native command helper is supported only on macOS and Windows");
560
+ }
561
+ if (!nativeRemoteWorkspaceCommandRunnerAvailable(options)) {
562
+ throw new Error("remote workspace native command helper failed its confinement probe");
563
+ }
564
+ const spawn = options.spawn ?? Bun.spawn;
565
+ return {
566
+ async run(request): Promise<RemoteWorkspaceCommandResult> {
567
+ const helper = assertNativeHelperIntegrity(options.helper);
568
+ const writableRoots = assertNativeHelperOutsideWritableRoots(helper, options.writableRoots);
569
+ const requestRoot = realpathSync(request.root);
570
+ if (!writableRoots.includes(requestRoot)) {
571
+ throw new Error("remote workspace command root is outside the native runner grant");
572
+ }
573
+ assertWorkspaceHasNoExternalHardlinkAliases(requestRoot);
574
+ const body = JSON.stringify(nativeHelperRequest(options, request));
575
+ if (Buffer.byteLength(body, "utf8") > 64 * 1024) {
576
+ throw new Error("remote workspace native helper request is too large");
577
+ }
578
+ const child = spawn([helper.path], {
579
+ cwd: request.root,
580
+ env: nativeHelperEnvironment(platform),
581
+ stdin: "pipe",
582
+ stdout: "pipe",
583
+ stderr: "pipe",
584
+ windowsHide: true,
585
+ });
586
+ let retained = 0;
587
+ let overflowed = false;
588
+ let cancelled = false;
589
+ let timedOut = false;
590
+ const stop = () => {
591
+ try { child.kill(); } catch { /* helper already exited */ }
592
+ };
593
+ const cancel = () => {
594
+ cancelled = true;
595
+ stop();
596
+ };
597
+ request.signal?.addEventListener("abort", cancel, { once: true });
598
+ if (request.signal?.aborted) cancel();
599
+ const maximumResponseBytes = Math.ceil(request.maxOutputBytes / 3) * 4 + 4_096;
600
+ const reserve = (bytes: number): boolean => {
601
+ if (retained + bytes > maximumResponseBytes + MAX_NATIVE_HELPER_STDERR_BYTES) {
602
+ overflowed = true;
603
+ return false;
604
+ }
605
+ retained += bytes;
606
+ return true;
607
+ };
608
+ const timer = setTimeout(() => {
609
+ timedOut = true;
610
+ stop();
611
+ }, request.timeoutMs + 2_000);
612
+ try {
613
+ if (!cancelled) {
614
+ child.stdin.write(body);
615
+ child.stdin.end();
616
+ }
617
+ const [stdoutResult, stderrResult, exitResult] = await Promise.allSettled([
618
+ collectBoundedOutput(child.stdout, reserve, stop),
619
+ collectBoundedOutput(child.stderr, reserve, stop),
620
+ child.exited,
621
+ ]);
622
+ if (cancelled) throw new Error("remote workspace command was cancelled");
623
+ if (timedOut) throw new Error("remote workspace native helper timed out");
624
+ if (overflowed) throw new Error("remote workspace native helper output limit exceeded");
625
+ if (stdoutResult.status === "rejected" || stderrResult.status === "rejected" || exitResult.status === "rejected") {
626
+ throw new Error("remote workspace native helper failed");
627
+ }
628
+ if (Buffer.byteLength(stdoutResult.value, "utf8") > maximumResponseBytes
629
+ || Buffer.byteLength(stderrResult.value, "utf8") > MAX_NATIVE_HELPER_STDERR_BYTES
630
+ || exitResult.value !== 0) {
631
+ throw new Error("remote workspace native helper failed");
632
+ }
633
+ const response = parseNativeHelperJson(Buffer.from(stdoutResult.value, "utf8"));
634
+ if (response && typeof response === "object" && !Array.isArray(response)
635
+ && (response as Record<string, unknown>).ok === false) {
636
+ throw nativeHelperFailure(response);
637
+ }
638
+ return parseNativeHelperCommandResponse(response, request.maxOutputBytes);
639
+ } finally {
640
+ clearTimeout(timer);
641
+ request.signal?.removeEventListener("abort", cancel);
642
+ try { child.stdin.end(); } catch { /* helper already closed stdin */ }
643
+ }
644
+ },
645
+ };
646
+ }
647
+
648
+ export function nativeRemoteWorkspaceCommandRunnerAvailable(
649
+ options: NativeRemoteWorkspaceCommandRunnerOptions,
650
+ ): boolean {
651
+ const platform = options.platform ?? process.platform;
652
+ if (platform !== "darwin") return false; // Windows awaits a surviving native cleanup owner.
653
+ try {
654
+ const helper = assertNativeHelperIntegrity(options.helper);
655
+ const writableRoots = assertNativeHelperOutsideWritableRoots(helper, options.writableRoots);
656
+ assertCommandRootsSafe(writableRoots);
657
+ const request: NativeHelperRequest = { version: NATIVE_HELPER_PROTOCOL_VERSION, operation: "probe" };
658
+ const raw = options.probe
659
+ ? options.probe(request)
660
+ : (() => {
661
+ const result = (options.spawnSync ?? Bun.spawnSync)([helper.path], {
662
+ cwd: dirname(helper.path),
663
+ env: nativeHelperEnvironment(platform),
664
+ stdin: Buffer.from(JSON.stringify(request), "utf8"),
665
+ stdout: "pipe",
666
+ stderr: "ignore",
667
+ timeout: 8_000,
668
+ windowsHide: true,
669
+ });
670
+ if (!result.success || result.stdout.byteLength > 4_096) {
671
+ throw new Error("remote workspace native helper probe failed");
672
+ }
673
+ return parseNativeHelperJson(result.stdout);
674
+ })();
675
+ parseNativeHelperProbeResponse(raw);
676
+ return true;
677
+ } catch {
678
+ return false;
679
+ }
680
+ }
681
+
682
+ export function createPlatformRemoteWorkspaceCommandRunner(options: {
683
+ platform?: NodeJS.Platform;
684
+ linux?: LinuxRemoteWorkspaceCommandRunnerOptions;
685
+ native?: Omit<NativeRemoteWorkspaceCommandRunnerOptions, "platform">;
686
+ } = {}): RemoteWorkspaceCommandRunner | undefined {
687
+ const platform = options.platform ?? process.platform;
688
+ if (platform === "linux" && linuxRemoteWorkspaceCommandRunnerAvailable(options.linux)) {
689
+ const linux = {
690
+ ...options.linux,
691
+ runtimeExecutablePath: options.linux?.runtimeExecutablePath ?? process.execPath,
692
+ };
693
+ return createLinuxRemoteWorkspaceCommandRunner(linux);
694
+ }
695
+ if ((platform === "darwin" || platform === "win32") && options.native) {
696
+ const native = { ...options.native, platform };
697
+ try {
698
+ return createNativeRemoteWorkspaceCommandRunner(native);
699
+ } catch {
700
+ return undefined;
701
+ }
702
+ }
703
+ return undefined;
704
+ }
705
+
706
+ export function linuxRemoteWorkspaceCommandRunnerAvailable(
707
+ options: LinuxRemoteWorkspaceCommandRunnerOptions = {},
708
+ ): boolean {
709
+ let path: string;
710
+ try {
711
+ path = trustedBubblewrap(options.bubblewrapPath ?? "/usr/bin/bwrap", options.writableRoots ?? []);
712
+ if (options.writableRoots) assertCommandRootsSafe(options.writableRoots);
713
+ } catch {
714
+ return false;
715
+ }
716
+ const argv = [
717
+ path,
718
+ "--die-with-parent",
719
+ "--new-session",
720
+ "--unshare-pid",
721
+ "--unshare-ipc",
722
+ "--unshare-uts",
723
+ ...(options.networkAccess === true ? [] : ["--unshare-net"]),
724
+ "--proc", "/proc",
725
+ "--dev", "/dev",
726
+ ...bindArgs("--ro-bind", READABLE_SYSTEM_PATHS),
727
+ "--",
728
+ "/bin/true",
729
+ ];
730
+ if (options.probe) return options.probe(argv);
731
+ const cacheKey = `${path}\0${options.networkAccess === true ? "network" : "isolated"}`;
732
+ const cached = availabilityCache.get(cacheKey);
733
+ if (cached !== undefined) return cached;
734
+ let available = false;
735
+ try {
736
+ available = Bun.spawnSync(argv, {
737
+ env: { PATH: DEFAULT_PATH, LANG: "C.UTF-8", LC_ALL: "C.UTF-8" },
738
+ stdin: "ignore",
739
+ stdout: "ignore",
740
+ stderr: "ignore",
741
+ timeout: 2_000,
742
+ }).success;
743
+ } catch {
744
+ available = false;
745
+ }
746
+ availabilityCache.set(cacheKey, available);
747
+ return available;
748
+ }