@addai/node 0.30.13 → 0.30.15

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 (143) hide show
  1. package/dist/auto-update.d.ts +3 -0
  2. package/dist/auto-update.js +15 -0
  3. package/dist/capabilities.d.ts +17 -3
  4. package/dist/capabilities.js +18 -8
  5. package/dist/claude-models.d.ts +4 -12
  6. package/dist/claude-models.js +12 -88
  7. package/dist/claude-print.d.ts +15 -0
  8. package/dist/claude-print.js +16 -0
  9. package/dist/codex-models.d.ts +81 -0
  10. package/dist/codex-models.js +385 -0
  11. package/dist/codex-spawn.d.ts +19 -8
  12. package/dist/codex-spawn.js +68 -16
  13. package/dist/command-runner.js +31 -9
  14. package/dist/desktop/docker.js +2 -0
  15. package/dist/desktop/manager.d.ts +2 -1
  16. package/dist/desktop/manager.js +51 -1
  17. package/dist/desktop/provider.d.ts +1 -1
  18. package/dist/desktop/provider.js +2 -0
  19. package/dist/desktop/relay-client.js +7 -0
  20. package/dist/desktop/spec.d.ts +2 -0
  21. package/dist/direct/aios-bridge.d.ts +13 -0
  22. package/dist/direct/aios-bridge.js +177 -0
  23. package/dist/direct/aios-mcp.d.ts +2 -0
  24. package/dist/direct/aios-mcp.js +132 -0
  25. package/dist/direct/aios-tools.d.ts +351 -0
  26. package/dist/direct/aios-tools.js +120 -0
  27. package/dist/direct/board-chat-provider.d.ts +64 -0
  28. package/dist/direct/board-chat-provider.js +154 -0
  29. package/dist/direct/board-chat.d.ts +40 -0
  30. package/dist/direct/board-chat.js +414 -0
  31. package/dist/direct/config.d.ts +2 -0
  32. package/dist/direct/config.js +60 -0
  33. package/dist/direct/launch.d.ts +14 -0
  34. package/dist/direct/launch.js +121 -0
  35. package/dist/direct/protocol.d.ts +46 -0
  36. package/dist/direct/protocol.js +84 -0
  37. package/dist/direct/relay-main.d.ts +1 -0
  38. package/dist/direct/relay-main.js +21 -0
  39. package/dist/direct/relay.d.ts +26 -0
  40. package/dist/direct/relay.js +334 -0
  41. package/dist/direct/sessions.d.ts +61 -0
  42. package/dist/direct/sessions.js +368 -0
  43. package/dist/direct/supervisor.d.ts +9 -0
  44. package/dist/direct/supervisor.js +137 -0
  45. package/dist/direct/terminal.d.ts +1 -0
  46. package/dist/direct/terminal.js +106 -0
  47. package/dist/direct/worker.d.ts +1 -0
  48. package/dist/direct/worker.js +278 -0
  49. package/dist/entitybox/container-action.cjs +85 -0
  50. package/dist/entitybox/container-watchdog.cjs +70 -0
  51. package/dist/entitybox/model-entry.d.ts +3 -0
  52. package/dist/entitybox/model-entry.js +233 -0
  53. package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
  54. package/dist/entitybox/model-mcp-proxy.js +85 -0
  55. package/dist/entitybox-application-boundary.d.ts +59 -0
  56. package/dist/entitybox-application-boundary.js +161 -0
  57. package/dist/entitybox-application-guard-core.d.ts +44 -0
  58. package/dist/entitybox-application-guard-core.js +164 -0
  59. package/dist/entitybox-application-guard-process.d.ts +1 -0
  60. package/dist/entitybox-application-guard-process.js +83 -0
  61. package/dist/entitybox-application-guard.d.ts +46 -0
  62. package/dist/entitybox-application-guard.js +239 -0
  63. package/dist/entitybox-capacity-health-host.d.ts +12 -0
  64. package/dist/entitybox-capacity-health-host.js +80 -0
  65. package/dist/entitybox-capacity-health-worker.d.ts +22 -0
  66. package/dist/entitybox-capacity-health-worker.js +168 -0
  67. package/dist/entitybox-capacity-host.d.ts +17 -0
  68. package/dist/entitybox-capacity-host.js +155 -0
  69. package/dist/entitybox-capacity-owner-process.d.ts +1 -0
  70. package/dist/entitybox-capacity-owner-process.js +32 -0
  71. package/dist/entitybox-capacity-owner.d.ts +16 -0
  72. package/dist/entitybox-capacity-owner.js +90 -0
  73. package/dist/entitybox-capacity-programs.d.ts +7 -0
  74. package/dist/entitybox-capacity-programs.js +49 -0
  75. package/dist/entitybox-capacity-provisioner.d.ts +82 -0
  76. package/dist/entitybox-capacity-provisioner.js +404 -0
  77. package/dist/entitybox-capacity-publication.d.ts +29 -0
  78. package/dist/entitybox-capacity-publication.js +199 -0
  79. package/dist/entitybox-capacity-registration.d.ts +36 -0
  80. package/dist/entitybox-capacity-registration.js +75 -0
  81. package/dist/entitybox-capacity-worker.d.ts +29 -0
  82. package/dist/entitybox-capacity-worker.js +113 -0
  83. package/dist/entitybox-computer-task.d.ts +69 -0
  84. package/dist/entitybox-computer-task.js +176 -0
  85. package/dist/entitybox-controller-recovery.d.ts +40 -0
  86. package/dist/entitybox-controller-recovery.js +156 -0
  87. package/dist/entitybox-desktop-action-server.d.ts +34 -0
  88. package/dist/entitybox-desktop-action-server.js +176 -0
  89. package/dist/entitybox-desktop-admission.d.ts +32 -0
  90. package/dist/entitybox-desktop-admission.js +79 -0
  91. package/dist/entitybox-display-binding.d.ts +13 -0
  92. package/dist/entitybox-display-binding.js +31 -0
  93. package/dist/entitybox-execution-container.d.ts +82 -0
  94. package/dist/entitybox-execution-container.js +450 -0
  95. package/dist/entitybox-execution-host.d.ts +40 -0
  96. package/dist/entitybox-execution-host.js +168 -0
  97. package/dist/entitybox-execution-lease.d.ts +81 -0
  98. package/dist/entitybox-execution-lease.js +332 -0
  99. package/dist/entitybox-file-inputs.d.ts +51 -0
  100. package/dist/entitybox-file-inputs.js +182 -0
  101. package/dist/entitybox-file-session.d.ts +38 -0
  102. package/dist/entitybox-file-session.js +82 -0
  103. package/dist/entitybox-host-process.d.ts +12 -0
  104. package/dist/entitybox-host-process.js +120 -0
  105. package/dist/entitybox-install.d.ts +10 -0
  106. package/dist/entitybox-install.js +40 -0
  107. package/dist/entitybox-mcp-broker.d.ts +77 -0
  108. package/dist/entitybox-mcp-broker.js +420 -0
  109. package/dist/entitybox-mcp-contract.d.ts +10 -0
  110. package/dist/entitybox-mcp-contract.js +24 -0
  111. package/dist/entitybox-mcp-guard.d.ts +3 -0
  112. package/dist/entitybox-mcp-guard.js +167 -0
  113. package/dist/entitybox-model-artifact.d.ts +9 -0
  114. package/dist/entitybox-model-artifact.js +83 -0
  115. package/dist/entitybox-native-services.d.ts +33 -0
  116. package/dist/entitybox-native-services.js +172 -0
  117. package/dist/entitybox-operation.d.ts +4 -0
  118. package/dist/entitybox-operation.js +34 -0
  119. package/dist/entitybox-provider-broker.d.ts +41 -0
  120. package/dist/entitybox-provider-broker.js +149 -0
  121. package/dist/entitybox-queue-request.d.ts +32 -0
  122. package/dist/entitybox-queue-request.js +141 -0
  123. package/dist/entitybox-recovery-host.d.ts +12 -0
  124. package/dist/entitybox-recovery-host.js +80 -0
  125. package/dist/entitybox-recovery-store.d.ts +33 -0
  126. package/dist/entitybox-recovery-store.js +150 -0
  127. package/dist/entitybox-recovery-worker.d.ts +30 -0
  128. package/dist/entitybox-recovery-worker.js +134 -0
  129. package/dist/entitybox-vault-provider.d.ts +27 -0
  130. package/dist/entitybox-vault-provider.js +127 -0
  131. package/dist/harness-registry.js +3 -1
  132. package/dist/index.js +72 -38
  133. package/dist/install.d.ts +14 -2
  134. package/dist/install.js +115 -10
  135. package/dist/model-cache.d.ts +38 -0
  136. package/dist/model-cache.js +137 -0
  137. package/dist/request-pump.d.ts +3 -0
  138. package/dist/request-pump.js +21 -7
  139. package/dist/session-runner.d.ts +8 -1
  140. package/dist/session-runner.js +56 -3
  141. package/dist/supabase-client.js +4 -1
  142. package/package.json +5 -1
  143. package/scripts/copy-assets.js +4 -2
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ // Which models this node's Codex login can run — asked of the CLI itself.
3
+ //
4
+ // Until 0.30.14 this was the CODEX_MODELS constant in capabilities.ts, last
5
+ // edited in May: gpt-5, gpt-5-mini, o4, o4-mini. A ChatGPT login refuses every
6
+ // one of them ("The 'gpt-5' model is not supported when using Codex with a
7
+ // ChatGPT account"), so the pickers offered nothing that would run, and none
8
+ // of the models codex has shipped since.
9
+ //
10
+ // Codex's app-server answers `model/list` with each model's id, name, the
11
+ // effort levels it takes and which one is the default. No thread is started
12
+ // and no turn is sent, so nothing is billed.
13
+ //
14
+ // Unlike Claude's, the useful answer needs the login. Logged out, codex lists
15
+ // only the models bundled with that CLI version; logged in, it lists what the
16
+ // account can run. On popadom's 0.155 (2026-10-30) the bundled list had
17
+ // gpt-5.5 but not gpt-6-sol or gpt-6-luna, and the account list the other way
18
+ // round. So the probe uses the login, but never in a way that can rotate it:
19
+ //
20
+ // * auth.json is LINKED into a throwaway CODEX_HOME (a symlink, or a hard
21
+ // link on Windows, where file symlinks need admin rights), never copied.
22
+ // A token refreshed into a copy is lost, and the next refresh from the
23
+ // real file reuses a refresh token that was already rotated, which gets
24
+ // the whole login revoked.
25
+ // * A ChatGPT login is only used while its token is fresh: refreshed under
26
+ // 5 days ago with more than 3 days left on the access token. They last 10
27
+ // days and codex renews them days before that, so it has no reason to
28
+ // refresh one during a probe.
29
+ //
30
+ // Otherwise (no login, a login due a refresh, or no way to link it) the probe
31
+ // runs logged out and says the list is the bundled one (scope 'bundled'). The
32
+ // cache keeps an account list it already has for the same binary over that.
33
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ var desc = Object.getOwnPropertyDescriptor(m, k);
36
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
+ desc = { enumerable: true, get: function() { return m[k]; } };
38
+ }
39
+ Object.defineProperty(o, k2, desc);
40
+ }) : (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ o[k2] = m[k];
43
+ }));
44
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
46
+ }) : function(o, v) {
47
+ o["default"] = v;
48
+ });
49
+ var __importStar = (this && this.__importStar) || (function () {
50
+ var ownKeys = function(o) {
51
+ ownKeys = Object.getOwnPropertyNames || function (o) {
52
+ var ar = [];
53
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
+ return ar;
55
+ };
56
+ return ownKeys(o);
57
+ };
58
+ return function (mod) {
59
+ if (mod && mod.__esModule) return mod;
60
+ var result = {};
61
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
+ __setModuleDefault(result, mod);
63
+ return result;
64
+ };
65
+ })();
66
+ Object.defineProperty(exports, "__esModule", { value: true });
67
+ exports.parseModelList = parseModelList;
68
+ exports.offeredEfforts = offeredEfforts;
69
+ exports.loginForProbe = loginForProbe;
70
+ exports.probeCodexModels = probeCodexModels;
71
+ exports.createCodexModelCache = createCodexModelCache;
72
+ exports.codexModelsFor = codexModelsFor;
73
+ exports.codexModelEfforts = codexModelEfforts;
74
+ const child_process_1 = require("child_process");
75
+ const fs = __importStar(require("fs"));
76
+ const os = __importStar(require("os"));
77
+ const path = __importStar(require("path"));
78
+ const model_cache_1 = require("./model-cache");
79
+ const win_1 = require("./win");
80
+ // Same rule as claude-models.ts: anything that does not look like a model id
81
+ // is dropped rather than shipped to every picker.
82
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:[\]-]{0,119}$/;
83
+ const MAX_ENTRIES = 64;
84
+ const MAX_PAGES = 5;
85
+ /**
86
+ * Turn the `data` of a `model/list` answer into a report. Hidden models
87
+ * (codex's internal reviewers and the like) are left out. Returns null when
88
+ * nothing usable is in there.
89
+ */
90
+ function parseModelList(raw, scope) {
91
+ if (!Array.isArray(raw))
92
+ return null;
93
+ const catalog = [];
94
+ const seen = new Set();
95
+ for (const item of raw.slice(0, MAX_ENTRIES)) {
96
+ if (!item || typeof item !== 'object')
97
+ continue;
98
+ const o = item;
99
+ if (o.hidden === true)
100
+ continue;
101
+ const id = [o.model, o.id].find((v) => typeof v === 'string' && SAFE_ID.test(v.trim()))?.trim();
102
+ if (!id || seen.has(id))
103
+ continue;
104
+ seen.add(id);
105
+ const name = typeof o.displayName === 'string' && o.displayName.trim() ? o.displayName.trim().slice(0, 80) : id;
106
+ const efforts = Array.isArray(o.supportedReasoningEfforts)
107
+ ? o.supportedReasoningEfforts
108
+ .map(e => (typeof e === 'string' ? e : e?.reasoningEffort))
109
+ .filter((e) => typeof e === 'string' && /^[a-z]{1,20}$/.test(e))
110
+ : [];
111
+ catalog.push({
112
+ id, resolved: id, name, alias: false,
113
+ ...(efforts.length ? { efforts } : {}),
114
+ ...(o.isDefault === true && !catalog.some(e => e.default) ? { default: true } : {}),
115
+ });
116
+ }
117
+ if (catalog.length === 0)
118
+ return null;
119
+ // The default first — it is what a blank model runs. The rest stay in
120
+ // codex's own order (newest first).
121
+ catalog.sort((a, b) => Number(Boolean(b.default)) - Number(Boolean(a.default)));
122
+ const defaultModel = catalog.find(e => e.default)?.id;
123
+ return { models: catalog.map(e => e.id), catalog, ...(defaultModel ? { defaultModel } : {}), scope };
124
+ }
125
+ /** The effort levels this platform stores (the DB's check constraints) and
126
+ * codex also knows. Codex's `ultra` is not one of them. */
127
+ const PLATFORM_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
128
+ /** What a picker can offer: every level some listed model takes, in order. */
129
+ function offeredEfforts(report) {
130
+ const any = new Set(report.catalog.flatMap(e => e.efforts ?? []));
131
+ return PLATFORM_EFFORTS.filter(e => any.has(e));
132
+ }
133
+ /* ── the login ─────────────────────────────────────────────────────────── */
134
+ const DAY_MS = 24 * 60 * 60 * 1000;
135
+ const FRESH_FOR_MS = 5 * DAY_MS;
136
+ const MIN_LEFT_MS = 3 * DAY_MS;
137
+ const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
138
+ /** When the access token runs out, from its own `exp` claim; null if it is
139
+ * not a JWT we can read. Only the expiry is looked at. */
140
+ function accessTokenExpiry(token) {
141
+ if (!nonEmpty(token))
142
+ return null;
143
+ try {
144
+ const payload = JSON.parse(Buffer.from(token.split('.')[1] ?? '', 'base64url').toString('utf8'));
145
+ return typeof payload.exp === 'number' ? payload.exp * 1000 : null;
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ /**
152
+ * May the probe use this login? Only if nothing about it can change while the
153
+ * probe runs: an API key never does, and a ChatGPT login only while its token
154
+ * is fresh. Never throws.
155
+ */
156
+ function loginForProbe(codexHome, now = Date.now()) {
157
+ const file = path.join(codexHome, 'auth.json');
158
+ let parsed;
159
+ try {
160
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
161
+ }
162
+ catch {
163
+ return { use: false, why: 'no login' };
164
+ }
165
+ const tokens = (parsed.tokens ?? null);
166
+ if (!tokens || !(nonEmpty(tokens.access_token) || nonEmpty(tokens.refresh_token))) {
167
+ return nonEmpty(parsed.OPENAI_API_KEY) ? { use: true, file } : { use: false, why: 'no login' };
168
+ }
169
+ const refreshed = nonEmpty(parsed.last_refresh) ? Date.parse(parsed.last_refresh) : NaN;
170
+ if (!Number.isFinite(refreshed) || now - refreshed > FRESH_FOR_MS) {
171
+ return { use: false, why: 'the login is due a refresh, so it was left alone' };
172
+ }
173
+ const expires = accessTokenExpiry(tokens.access_token);
174
+ if (expires !== null && expires - now < MIN_LEFT_MS) {
175
+ return { use: false, why: 'the access token runs out soon, so the login was left alone' };
176
+ }
177
+ return { use: true, file };
178
+ }
179
+ /** Put the login in the probe's CODEX_HOME without copying it. */
180
+ function linkLogin(file, dir) {
181
+ const target = path.join(dir, 'auth.json');
182
+ try {
183
+ if (process.platform === 'win32')
184
+ fs.linkSync(file, target);
185
+ else
186
+ fs.symlinkSync(file, target);
187
+ return true;
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ }
193
+ /* ── the probe ─────────────────────────────────────────────────────────── */
194
+ const PROBE_ARGS = ['app-server', '--listen', 'stdio://'];
195
+ const MAX_LINE = 2 * 1024 * 1024;
196
+ /**
197
+ * Ask one codex binary for its models. Never throws; resolves within
198
+ * `timeoutMs` however the CLI behaves, and leaves no process behind.
199
+ */
200
+ function probeCodexModels(bin, opts = {}) {
201
+ return new Promise(done => {
202
+ let settled = false;
203
+ let inv;
204
+ try {
205
+ inv = (opts.resolve ?? win_1.resolveCliInvocation)(bin, PROBE_ARGS);
206
+ }
207
+ catch (err) {
208
+ done({ ok: false, reason: err.message || String(err) });
209
+ return;
210
+ }
211
+ // The probe's cwd and its CODEX_HOME: no config.toml, no MCP servers, and
212
+ // the state codex writes (sqlite logs and the like) stays out of ~/.codex.
213
+ const dir = opts.dir ?? (0, model_cache_1.makeProbeDir)('codex-');
214
+ if (!dir) {
215
+ done({ ok: false, reason: 'no folder to probe from' });
216
+ return;
217
+ }
218
+ const login = loginForProbe(opts.codexHome ?? path.join(os.homedir(), '.codex'), opts.now ?? Date.now());
219
+ const linked = login.use && linkLogin(login.file, dir);
220
+ const scope = linked ? 'account' : 'bundled';
221
+ // Removing the link never touches the real file. It goes as soon as there
222
+ // is an answer, not when codex gets round to exiting.
223
+ const unlinkLogin = () => {
224
+ if (linked) {
225
+ try {
226
+ fs.unlinkSync(path.join(dir, 'auth.json'));
227
+ }
228
+ catch { /* already gone, or swept later */ }
229
+ }
230
+ };
231
+ const cleanup = () => {
232
+ unlinkLogin();
233
+ if (opts.dir)
234
+ return;
235
+ try {
236
+ fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
237
+ }
238
+ catch { /* swept later */ }
239
+ };
240
+ const env = { ...process.env, CODEX_HOME: dir };
241
+ let child;
242
+ try {
243
+ child = (0, child_process_1.spawn)(inv.file, inv.args, { cwd: dir, env, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
244
+ }
245
+ catch (err) {
246
+ cleanup();
247
+ done({ ok: false, reason: err.message || String(err) });
248
+ return;
249
+ }
250
+ child.once('close', cleanup);
251
+ const finish = (result) => {
252
+ if (settled)
253
+ return;
254
+ settled = true;
255
+ clearTimeout(timer);
256
+ unlinkLogin();
257
+ // The app-server exits when its input ends; the kill is the backstop.
258
+ try {
259
+ child.stdin?.end();
260
+ }
261
+ catch { /* already closed */ }
262
+ const reap = setTimeout(() => {
263
+ if (child.exitCode === null && child.signalCode === null)
264
+ (0, win_1.killProcessTree)(child);
265
+ }, 3000);
266
+ reap.unref();
267
+ child.once('exit', () => clearTimeout(reap));
268
+ done(result);
269
+ };
270
+ const timer = setTimeout(() => {
271
+ finish({ ok: false, reason: `no answer within ${opts.timeoutMs ?? 30000}ms` });
272
+ (0, win_1.killProcessTree)(child);
273
+ }, opts.timeoutMs ?? 30000);
274
+ timer.unref();
275
+ child.on('error', err => finish({ ok: false, reason: err.message || String(err) }));
276
+ child.on('close', (code, signal) => {
277
+ finish({ ok: false, reason: `exited ${code ?? signal} before answering${stderr ? `: ${stderr.trim().slice(0, 200)}` : ''}` });
278
+ });
279
+ child.stdin?.on('error', () => { });
280
+ let stderr = '';
281
+ child.stderr?.on('data', (d) => { if (stderr.length < 4096)
282
+ stderr += d.toString('utf8'); });
283
+ const send = (msg) => {
284
+ try {
285
+ child.stdin?.write(JSON.stringify(msg) + '\n');
286
+ }
287
+ catch { /* reported via 'close' / 'error' */ }
288
+ };
289
+ const models = [];
290
+ let page = 0;
291
+ const listPage = (cursor) => {
292
+ page += 1;
293
+ send({ id: 1 + page, method: 'model/list', params: { includeHidden: false, limit: 100, ...(cursor ? { cursor } : {}) } });
294
+ };
295
+ let buf = '';
296
+ child.stdout?.on('data', (d) => {
297
+ buf += d.toString('utf8');
298
+ if (buf.length > MAX_LINE) {
299
+ finish({ ok: false, reason: 'oversized output' });
300
+ return;
301
+ }
302
+ let nl;
303
+ while ((nl = buf.indexOf('\n')) >= 0) {
304
+ const line = buf.slice(0, nl).trim();
305
+ buf = buf.slice(nl + 1);
306
+ if (!line.startsWith('{'))
307
+ continue;
308
+ let msg;
309
+ try {
310
+ msg = JSON.parse(line);
311
+ }
312
+ catch {
313
+ continue;
314
+ }
315
+ // Notifications and requests from the server carry a method; only
316
+ // answers to our own requests matter here.
317
+ if (typeof msg.method === 'string' || typeof msg.id !== 'number')
318
+ continue;
319
+ if (msg.error) {
320
+ const what = msg.id === 1 ? 'initialize' : 'model/list';
321
+ finish({ ok: false, reason: `${what} refused: ${String(msg.error?.message ?? 'unknown').slice(0, 200)}` });
322
+ return;
323
+ }
324
+ if (msg.id === 1) {
325
+ send({ method: 'initialized', params: {} });
326
+ listPage();
327
+ continue;
328
+ }
329
+ if (msg.id !== 1 + page)
330
+ continue;
331
+ const data = msg.result?.data;
332
+ if (Array.isArray(data))
333
+ models.push(...data);
334
+ const next = msg.result?.nextCursor;
335
+ if (typeof next === 'string' && next && page < MAX_PAGES && models.length < MAX_ENTRIES) {
336
+ listPage(next);
337
+ continue;
338
+ }
339
+ const report = parseModelList(models, scope);
340
+ finish(report
341
+ ? { ok: true, report }
342
+ : { ok: false, reason: `model/list answered without a usable model${scope === 'bundled' && !login.use ? ` (${login.why})` : ''}` });
343
+ return;
344
+ }
345
+ });
346
+ send({
347
+ id: 1, method: 'initialize',
348
+ params: { clientInfo: { name: 'ainode_model_probe', title: '+Ai node model probe', version: '1.0.0' }, capabilities: { experimentalApi: true } },
349
+ });
350
+ });
351
+ }
352
+ /** Refreshed every 6h, retried 30 min after a failure — see model-cache.ts. */
353
+ function createCodexModelCache(deps) {
354
+ return (0, model_cache_1.createModelCache)({
355
+ ...deps,
356
+ agent: 'codex',
357
+ describe: report => [
358
+ report.defaultModel ? `default ${report.defaultModel}` : '',
359
+ report.scope === 'bundled' ? 'the list bundled with the CLI; the login was left alone' : '',
360
+ ].filter(Boolean).join('; '),
361
+ // A probe that had to leave the login alone sees less than one that used
362
+ // it; the account's list stays until the next probe can read it again.
363
+ keep: (held, next) => held.scope === 'account' && next.scope === 'bundled',
364
+ });
365
+ }
366
+ const sharedCache = createCodexModelCache({
367
+ probe: bin => probeCodexModels(bin),
368
+ now: () => Date.now(),
369
+ log: line => console.log(line),
370
+ });
371
+ /** What capabilities.ts reports for codex's models. */
372
+ function codexModelsFor(bin, key) {
373
+ return sharedCache.get(bin, key);
374
+ }
375
+ /**
376
+ * The effort levels a model takes, from the last list codex gave: the
377
+ * default model's when none is named. Null when codex never said, or the
378
+ * model is not in its list.
379
+ */
380
+ function codexModelEfforts(model, report = sharedCache.last()) {
381
+ if (!report)
382
+ return null;
383
+ const entry = model ? report.catalog.find(e => e.id === model) : report.catalog.find(e => e.default);
384
+ return entry?.efforts?.length ? entry.efforts : null;
385
+ }
@@ -12,6 +12,9 @@ export interface CodexInput {
12
12
  prompt: string;
13
13
  model?: string;
14
14
  effortLevel?: RuntimeEffortLevel;
15
+ /** The effort levels codex listed for this model (codexModelEfforts), so
16
+ * effortLevel can be clamped to one it takes. */
17
+ supportedEfforts?: readonly string[] | null;
15
18
  /** Sandbox for normal non-bypass runs. Defaults to workspace-write. */
16
19
  sandboxMode?: 'read-only' | 'workspace-write' | 'danger-full-access';
17
20
  /** Skip Codex approvals and sandboxing. Codex's "yolo" equivalent. */
@@ -49,6 +52,11 @@ export declare function codexSupportsEnvHttpHeaders(bin: string, probe?: (bin: s
49
52
  /** Read `codex mcp list --json` output: did the probe server's header survive?
50
53
  * Exported for tests. */
51
54
  export declare function codexListShowsEnvHeaders(stdout: string): boolean;
55
+ /** Whether the host still holds the rollout for a codex thread. Codex keeps
56
+ * each thread as sessions/YYYY/MM/DD/rollout-<timestamp>-<threadId>.jsonl;
57
+ * without that file `exec resume` can only fail with "thread/resume failed:
58
+ * no rollout found for thread id", on every retry. */
59
+ export declare function codexRolloutExists(threadId: string, sessionsDir?: string): boolean;
52
60
  /** First line of an AGENTS.md this daemon wrote. A project's own AGENTS.md
53
61
  * never carries it and is never touched. */
54
62
  export declare const AGENTS_MD_MARKER = "<!-- ainode: entity system prompt \u2014 rewritten before every turn; edits here do not persist -->";
@@ -73,12 +81,15 @@ export interface CodexHandle {
73
81
  onActivity(cb: () => void): void;
74
82
  done: Promise<number>;
75
83
  }
76
- /** Clamp our effort ladder to a value codex's CLI accepts. codex only knows
77
- * `minimal|low|medium|high` and FAIL-FASTS (exit 2, dead-letter) on anything
78
- * else — but RuntimeEffortLevel also carries `xhigh`/`max`, and a ladder
79
- * failover (e.g. claude(max) → codex) forwards req.effort across agents
80
- * unchanged. Map the extra-high tiers down to `high`; drop anything
81
- * unrecognised so we emit no `-c reasoning.effort` at all rather than a bad
82
- * one. Mirrors grokEffort() in grok-spawn.ts. */
83
- export declare function codexEffort(level?: RuntimeEffortLevel): string | null;
84
+ /** Clamp our effort ladder to a value this codex accepts. A codex that does
85
+ * not know a level FAIL-FASTS (exit 2, dead-letter), and a ladder failover
86
+ * (e.g. claude(max) → codex) forwards req.effort across agents unchanged.
87
+ *
88
+ * `supported` is what codex listed for the model about to run
89
+ * (codexModelEfforts): the level asked for if the model takes it, else the
90
+ * highest it takes below that. Without a list — an older codex, or one that
91
+ * has not answered yet — only `minimal|low|medium|high` is safe, so xhigh
92
+ * and max become high, as they always did. Anything unrecognised emits no
93
+ * effort at all rather than a bad one. Mirrors grokEffort() in grok-spawn.ts. */
94
+ export declare function codexEffort(level?: RuntimeEffortLevel, supported?: readonly string[] | null): string | null;
84
95
  export declare function spawnCodex(input: CodexInput): CodexHandle;
@@ -44,6 +44,7 @@ exports.AGENTS_MD_MARKER = void 0;
44
44
  exports.writeCodexHome = writeCodexHome;
45
45
  exports.codexSupportsEnvHttpHeaders = codexSupportsEnvHttpHeaders;
46
46
  exports.codexListShowsEnvHeaders = codexListShowsEnvHeaders;
47
+ exports.codexRolloutExists = codexRolloutExists;
47
48
  exports.writeCodexAgentsMd = writeCodexAgentsMd;
48
49
  exports.codexEffort = codexEffort;
49
50
  exports.spawnCodex = spawnCodex;
@@ -173,12 +174,19 @@ function writeCodexHome(servers, workingDirectory, opts = {}) {
173
174
  (0, win_1.linkOrCopyFile)(real, path.join(dir, file));
174
175
  }
175
176
  }
176
- // Also link the sessions dir so codex can resume across runs.
177
- // 'junction' works unprivileged on Windows; ignored on POSIX.
178
- const sessionsDir = path.join(hostHome, 'sessions');
179
- if (fs.existsSync(sessionsDir)) {
180
- (0, win_1.linkDir)(sessionsDir, path.join(dir, 'sessions'));
181
- }
177
+ }
178
+ // Link the durable sessions dir so codex can resume across runs.
179
+ // 'junction' works unprivileged on Windows; ignored on POSIX. Create it
180
+ // first, even when ~/.codex itself is missing (API-key auth never makes
181
+ // it): otherwise the rollout lands in this scratch home, which is deleted
182
+ // on exit, and the next turn fails "no rollout found for thread id".
183
+ const sessionsDir = path.join(hostHome, 'sessions');
184
+ try {
185
+ fs.mkdirSync(sessionsDir, { recursive: true });
186
+ }
187
+ catch { /* link below is skipped */ }
188
+ if (fs.existsSync(sessionsDir)) {
189
+ (0, win_1.linkDir)(sessionsDir, path.join(dir, 'sessions'));
182
190
  }
183
191
  // The registry installer writes selected skills to .claude/skills for
184
192
  // Claude. Codex discovers skills from CODEX_HOME/skills, so bridge the
@@ -259,6 +267,30 @@ function codexMcpListJson(bin, codexHome) {
259
267
  env: { ...process.env, CODEX_HOME: codexHome },
260
268
  });
261
269
  }
270
+ /** Whether the host still holds the rollout for a codex thread. Codex keeps
271
+ * each thread as sessions/YYYY/MM/DD/rollout-<timestamp>-<threadId>.jsonl;
272
+ * without that file `exec resume` can only fail with "thread/resume failed:
273
+ * no rollout found for thread id", on every retry. */
274
+ function codexRolloutExists(threadId, sessionsDir = path.join(os.homedir(), '.codex', 'sessions')) {
275
+ const suffix = `-${threadId}.jsonl`;
276
+ const walk = (dir, depth) => {
277
+ let entries;
278
+ try {
279
+ entries = fs.readdirSync(dir, { withFileTypes: true });
280
+ }
281
+ catch {
282
+ return false;
283
+ }
284
+ for (const e of entries) {
285
+ if (e.isFile() && e.name.startsWith('rollout-') && e.name.endsWith(suffix))
286
+ return true;
287
+ if (depth < 4 && (e.isDirectory() || e.isSymbolicLink()) && walk(path.join(dir, e.name), depth + 1))
288
+ return true;
289
+ }
290
+ return false;
291
+ };
292
+ return walk(sessionsDir, 0);
293
+ }
262
294
  /** First line of an AGENTS.md this daemon wrote. A project's own AGENTS.md
263
295
  * never carries it and is never touched. */
264
296
  exports.AGENTS_MD_MARKER = '<!-- ainode: entity system prompt — rewritten before every turn; edits here do not persist -->';
@@ -328,14 +360,31 @@ function lineToEvents(line) {
328
360
  // new "codex:" namespace which adds zero value.
329
361
  return [{ type: `claude:codex.${type || 'unknown'}`, raw: line }];
330
362
  }
331
- /** Clamp our effort ladder to a value codex's CLI accepts. codex only knows
332
- * `minimal|low|medium|high` and FAIL-FASTS (exit 2, dead-letter) on anything
333
- * else — but RuntimeEffortLevel also carries `xhigh`/`max`, and a ladder
334
- * failover (e.g. claude(max) → codex) forwards req.effort across agents
335
- * unchanged. Map the extra-high tiers down to `high`; drop anything
336
- * unrecognised so we emit no `-c reasoning.effort` at all rather than a bad
337
- * one. Mirrors grokEffort() in grok-spawn.ts. */
338
- function codexEffort(level) {
363
+ /** codex's effort levels, lowest first. */
364
+ const CODEX_EFFORT_LADDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
365
+ /** Clamp our effort ladder to a value this codex accepts. A codex that does
366
+ * not know a level FAIL-FASTS (exit 2, dead-letter), and a ladder failover
367
+ * (e.g. claude(max) → codex) forwards req.effort across agents unchanged.
368
+ *
369
+ * `supported` is what codex listed for the model about to run
370
+ * (codexModelEfforts): the level asked for if the model takes it, else the
371
+ * highest it takes below that. Without a list — an older codex, or one that
372
+ * has not answered yet — only `minimal|low|medium|high` is safe, so xhigh
373
+ * and max become high, as they always did. Anything unrecognised emits no
374
+ * effort at all rather than a bad one. Mirrors grokEffort() in grok-spawn.ts. */
375
+ function codexEffort(level, supported) {
376
+ const want = CODEX_EFFORT_LADDER.indexOf(level ?? '');
377
+ if (want < 0)
378
+ return null;
379
+ if (supported?.length) {
380
+ let best = -1;
381
+ for (const s of supported) {
382
+ const i = CODEX_EFFORT_LADDER.indexOf(s);
383
+ if (i >= 0 && i <= want && i > best)
384
+ best = i;
385
+ }
386
+ return best >= 0 ? CODEX_EFFORT_LADDER[best] : null;
387
+ }
339
388
  switch (level) {
340
389
  case 'low': return 'low';
341
390
  case 'medium': return 'medium';
@@ -363,9 +412,12 @@ function spawnCodex(input) {
363
412
  baseFlags.push('--sandbox', input.sandboxMode ?? 'workspace-write');
364
413
  if (input.model)
365
414
  baseFlags.push('--model', input.model);
366
- const effort = codexEffort(input.effortLevel);
415
+ const effort = codexEffort(input.effortLevel, input.supportedEfforts);
367
416
  if (effort) {
368
- baseFlags.push('-c', `reasoning.effort=${JSON.stringify(effort)}`);
417
+ // `model_reasoning_effort` is the key codex reads. The `reasoning.effort`
418
+ // this sent before was ignored ("session-flags: `reasoning` is ignored",
419
+ // codex 0.164), so every codex run went out at its model's default.
420
+ baseFlags.push('-c', `model_reasoning_effort=${JSON.stringify(effort)}`);
369
421
  }
370
422
  const args = input.resumeThreadId
371
423
  ? ['exec', ...baseFlags, 'resume', input.resumeThreadId, input.prompt]
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- // Executes runtime_commands issued from Entity Studio: install a harness
3
- // (npm i -g) or log one in (PTY-bridged OAuth / API key). Woken by the
4
- // heartbeat's pending_commands hint — no always-on poller. One command
5
- // runs at a time; while one is live we poll its row every ~2s so the
6
- // login conversation (URL out, code back) feels interactive.
7
- //
8
- // Truth contract: login "success" is only reported after a capabilities
9
- // re-probe says authed=true. The CLI's exit code is a hint, the probe is
10
- // the verdict.
11
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
3
  if (k2 === undefined) k2 = k;
13
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -44,6 +35,16 @@ var __importStar = (this && this.__importStar) || (function () {
44
35
  Object.defineProperty(exports, "__esModule", { value: true });
45
36
  exports.setRestartHook = setRestartHook;
46
37
  exports.wake = wake;
38
+ const entitybox_desktop_admission_1 = require("./entitybox-desktop-admission");
39
+ // Executes runtime_commands issued from Entity Studio: install a harness
40
+ // (npm i -g) or log one in (PTY-bridged OAuth / API key). Woken by the
41
+ // heartbeat's pending_commands hint — no always-on poller. One command
42
+ // runs at a time; while one is live we poll its row every ~2s so the
43
+ // login conversation (URL out, code back) feels interactive.
44
+ //
45
+ // Truth contract: login "success" is only reported after a capabilities
46
+ // re-probe says authed=true. The CLI's exit code is a hint, the probe is
47
+ // the verdict.
47
48
  const child_process_1 = require("child_process");
48
49
  const crypto = __importStar(require("crypto"));
49
50
  const fs = __importStar(require("fs"));
@@ -653,6 +654,16 @@ async function runSetupScript(provider, row, onLog) {
653
654
  await (0, creds_1.execInDesktop)(provider, row, ['bash', '/conf/setup.sh'], onLog);
654
655
  }
655
656
  async function runDesktopCommand(cmd) {
657
+ if (!['desktop_create', 'desktop_rebuild', 'desktop_start'].includes(cmd.kind))
658
+ return runDesktopCommandUncoordinated(cmd);
659
+ try {
660
+ await (0, entitybox_desktop_admission_1.entityboxWithDesktopStart)(() => runDesktopCommandUncoordinated(cmd));
661
+ }
662
+ catch {
663
+ await update(cmd.id, 'failed', undefined, 'Desktop admission is unavailable. Try again when the runtime is ready.');
664
+ }
665
+ }
666
+ async function runDesktopCommandUncoordinated(cmd) {
656
667
  if (!cmd.desktop_id) {
657
668
  await update(cmd.id, 'failed', undefined, 'no desktop on command');
658
669
  return;
@@ -677,6 +688,10 @@ async function runDesktopCommand(cmd) {
677
688
  await update(cmd.id, 'failed', undefined, 'desktop row not found');
678
689
  return;
679
690
  }
691
+ if (row.entitybox_managed === true) {
692
+ await update(cmd.id, 'failed', undefined, 'This computer is managed by EntityBox');
693
+ return;
694
+ }
680
695
  let log = '';
681
696
  const onLog = (chunk) => {
682
697
  log = (log + chunk).slice(-LOG_TAIL_CHARS);
@@ -687,6 +702,13 @@ async function runDesktopCommand(cmd) {
687
702
  switch (cmd.kind) {
688
703
  case 'desktop_create':
689
704
  case 'desktop_rebuild': {
705
+ if ((0, entitybox_desktop_admission_1.entityboxAdmissionEnabled)()) {
706
+ const room = await (0, manager_1.desktopStartAllowed)(await (0, manager_1.listDesktops)(), row.id);
707
+ if (!room.allowed) {
708
+ await update(cmd.id, 'failed', undefined, room.reason);
709
+ return;
710
+ }
711
+ }
690
712
  if (cmd.kind === 'desktop_rebuild') {
691
713
  onLog('removing existing container\n');
692
714
  await provider.remove(row);
@@ -42,6 +42,8 @@ class CliProvider {
42
42
  return { id: this.id, version: this.version, running: true };
43
43
  }
44
44
  async create(row, opts, onLog) {
45
+ if (row.entitybox_managed === true)
46
+ throw new Error('This computer is managed by EntityBox');
45
47
  // Pull first so the slow, chatty image download streams into the command
46
48
  // payload as its own phase rather than hiding inside `run`.
47
49
  onLog(`pulling ${row.image}\n`);
@@ -2,7 +2,7 @@ import { DesktopRow } from './spec';
2
2
  export type Action = 'none' | 'start' | 'mark_failed' | 'mark_stopped' | 'mark_running';
3
3
  /** Pure: given what the row claims and what the engine reports, what now?
4
4
  * `actual` is null when no such container exists. */
5
- export declare function reconcileAction(row: Pick<DesktopRow, 'status' | 'autostart'>, actual: {
5
+ export declare function reconcileAction(row: Pick<DesktopRow, 'status' | 'autostart' | 'entitybox_managed'>, actual: {
6
6
  running: boolean;
7
7
  } | null): Action;
8
8
  export declare function listDesktops(): Promise<DesktopRow[]>;
@@ -38,6 +38,7 @@ export declare function ensureDirs(desktopId: string): {
38
38
  workDir: string;
39
39
  };
40
40
  export declare const DEFAULT_MAX_CONCURRENT_DESKTOPS = 2;
41
+ export declare function entityboxDesktopLimitKnown(): boolean;
41
42
  export declare function clampMaxDesktops(value: unknown): number;
42
43
  /** Pushed in by the heartbeat. Lowering it never stops a running desktop —
43
44
  * killing someone's live session to satisfy a number they just typed would