@addai/node 0.30.13 → 0.30.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auto-update.d.ts +3 -0
- package/dist/auto-update.js +15 -0
- package/dist/capabilities.d.ts +17 -3
- package/dist/capabilities.js +18 -8
- package/dist/claude-models.d.ts +4 -12
- package/dist/claude-models.js +12 -88
- package/dist/claude-print.d.ts +15 -0
- package/dist/claude-print.js +16 -0
- package/dist/codex-models.d.ts +81 -0
- package/dist/codex-models.js +385 -0
- package/dist/codex-spawn.d.ts +14 -8
- package/dist/codex-spawn.js +30 -10
- package/dist/command-runner.js +31 -9
- package/dist/desktop/docker.js +2 -0
- package/dist/desktop/manager.d.ts +2 -1
- package/dist/desktop/manager.js +51 -1
- package/dist/desktop/provider.d.ts +1 -1
- package/dist/desktop/provider.js +2 -0
- package/dist/desktop/relay-client.js +7 -0
- package/dist/desktop/spec.d.ts +2 -0
- package/dist/direct/aios-bridge.d.ts +13 -0
- package/dist/direct/aios-bridge.js +177 -0
- package/dist/direct/aios-mcp.d.ts +2 -0
- package/dist/direct/aios-mcp.js +132 -0
- package/dist/direct/aios-tools.d.ts +351 -0
- package/dist/direct/aios-tools.js +120 -0
- package/dist/direct/board-chat-provider.d.ts +64 -0
- package/dist/direct/board-chat-provider.js +151 -0
- package/dist/direct/board-chat.d.ts +40 -0
- package/dist/direct/board-chat.js +414 -0
- package/dist/direct/config.d.ts +2 -0
- package/dist/direct/config.js +60 -0
- package/dist/direct/launch.d.ts +14 -0
- package/dist/direct/launch.js +121 -0
- package/dist/direct/protocol.d.ts +46 -0
- package/dist/direct/protocol.js +84 -0
- package/dist/direct/relay-main.d.ts +1 -0
- package/dist/direct/relay-main.js +21 -0
- package/dist/direct/relay.d.ts +26 -0
- package/dist/direct/relay.js +334 -0
- package/dist/direct/sessions.d.ts +61 -0
- package/dist/direct/sessions.js +368 -0
- package/dist/direct/supervisor.d.ts +9 -0
- package/dist/direct/supervisor.js +137 -0
- package/dist/direct/terminal.d.ts +1 -0
- package/dist/direct/terminal.js +106 -0
- package/dist/direct/worker.d.ts +1 -0
- package/dist/direct/worker.js +278 -0
- package/dist/entitybox/container-action.cjs +85 -0
- package/dist/entitybox/container-watchdog.cjs +70 -0
- package/dist/entitybox/model-entry.d.ts +3 -0
- package/dist/entitybox/model-entry.js +233 -0
- package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
- package/dist/entitybox/model-mcp-proxy.js +85 -0
- package/dist/entitybox-application-boundary.d.ts +59 -0
- package/dist/entitybox-application-boundary.js +161 -0
- package/dist/entitybox-application-guard-core.d.ts +44 -0
- package/dist/entitybox-application-guard-core.js +164 -0
- package/dist/entitybox-application-guard-process.d.ts +1 -0
- package/dist/entitybox-application-guard-process.js +83 -0
- package/dist/entitybox-application-guard.d.ts +46 -0
- package/dist/entitybox-application-guard.js +239 -0
- package/dist/entitybox-capacity-health-host.d.ts +12 -0
- package/dist/entitybox-capacity-health-host.js +80 -0
- package/dist/entitybox-capacity-health-worker.d.ts +22 -0
- package/dist/entitybox-capacity-health-worker.js +168 -0
- package/dist/entitybox-capacity-host.d.ts +17 -0
- package/dist/entitybox-capacity-host.js +155 -0
- package/dist/entitybox-capacity-owner-process.d.ts +1 -0
- package/dist/entitybox-capacity-owner-process.js +32 -0
- package/dist/entitybox-capacity-owner.d.ts +16 -0
- package/dist/entitybox-capacity-owner.js +90 -0
- package/dist/entitybox-capacity-programs.d.ts +7 -0
- package/dist/entitybox-capacity-programs.js +49 -0
- package/dist/entitybox-capacity-provisioner.d.ts +82 -0
- package/dist/entitybox-capacity-provisioner.js +404 -0
- package/dist/entitybox-capacity-publication.d.ts +29 -0
- package/dist/entitybox-capacity-publication.js +199 -0
- package/dist/entitybox-capacity-registration.d.ts +36 -0
- package/dist/entitybox-capacity-registration.js +75 -0
- package/dist/entitybox-capacity-worker.d.ts +29 -0
- package/dist/entitybox-capacity-worker.js +113 -0
- package/dist/entitybox-computer-task.d.ts +69 -0
- package/dist/entitybox-computer-task.js +176 -0
- package/dist/entitybox-controller-recovery.d.ts +40 -0
- package/dist/entitybox-controller-recovery.js +156 -0
- package/dist/entitybox-desktop-action-server.d.ts +34 -0
- package/dist/entitybox-desktop-action-server.js +176 -0
- package/dist/entitybox-desktop-admission.d.ts +32 -0
- package/dist/entitybox-desktop-admission.js +79 -0
- package/dist/entitybox-display-binding.d.ts +13 -0
- package/dist/entitybox-display-binding.js +31 -0
- package/dist/entitybox-execution-container.d.ts +82 -0
- package/dist/entitybox-execution-container.js +450 -0
- package/dist/entitybox-execution-host.d.ts +40 -0
- package/dist/entitybox-execution-host.js +168 -0
- package/dist/entitybox-execution-lease.d.ts +81 -0
- package/dist/entitybox-execution-lease.js +332 -0
- package/dist/entitybox-file-inputs.d.ts +51 -0
- package/dist/entitybox-file-inputs.js +182 -0
- package/dist/entitybox-file-session.d.ts +38 -0
- package/dist/entitybox-file-session.js +82 -0
- package/dist/entitybox-host-process.d.ts +12 -0
- package/dist/entitybox-host-process.js +120 -0
- package/dist/entitybox-install.d.ts +10 -0
- package/dist/entitybox-install.js +40 -0
- package/dist/entitybox-mcp-broker.d.ts +77 -0
- package/dist/entitybox-mcp-broker.js +420 -0
- package/dist/entitybox-mcp-contract.d.ts +10 -0
- package/dist/entitybox-mcp-contract.js +24 -0
- package/dist/entitybox-mcp-guard.d.ts +3 -0
- package/dist/entitybox-mcp-guard.js +167 -0
- package/dist/entitybox-model-artifact.d.ts +9 -0
- package/dist/entitybox-model-artifact.js +83 -0
- package/dist/entitybox-native-services.d.ts +33 -0
- package/dist/entitybox-native-services.js +172 -0
- package/dist/entitybox-operation.d.ts +4 -0
- package/dist/entitybox-operation.js +34 -0
- package/dist/entitybox-provider-broker.d.ts +41 -0
- package/dist/entitybox-provider-broker.js +149 -0
- package/dist/entitybox-queue-request.d.ts +32 -0
- package/dist/entitybox-queue-request.js +141 -0
- package/dist/entitybox-recovery-host.d.ts +12 -0
- package/dist/entitybox-recovery-host.js +80 -0
- package/dist/entitybox-recovery-store.d.ts +33 -0
- package/dist/entitybox-recovery-store.js +150 -0
- package/dist/entitybox-recovery-worker.d.ts +30 -0
- package/dist/entitybox-recovery-worker.js +134 -0
- package/dist/entitybox-vault-provider.d.ts +27 -0
- package/dist/entitybox-vault-provider.js +127 -0
- package/dist/harness-registry.js +3 -1
- package/dist/index.js +72 -38
- package/dist/install.d.ts +14 -2
- package/dist/install.js +115 -10
- package/dist/model-cache.d.ts +38 -0
- package/dist/model-cache.js +137 -0
- package/dist/request-pump.d.ts +3 -0
- package/dist/request-pump.js +21 -7
- package/dist/session-runner.d.ts +8 -1
- package/dist/session-runner.js +48 -3
- package/dist/supabase-client.js +4 -1
- package/package.json +5 -1
- 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
|
+
}
|
package/dist/codex-spawn.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -73,12 +76,15 @@ export interface CodexHandle {
|
|
|
73
76
|
onActivity(cb: () => void): void;
|
|
74
77
|
done: Promise<number>;
|
|
75
78
|
}
|
|
76
|
-
/** Clamp our effort ladder to a value codex
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
79
|
+
/** Clamp our effort ladder to a value this codex accepts. A codex that does
|
|
80
|
+
* not know a level FAIL-FASTS (exit 2, dead-letter), and a ladder failover
|
|
81
|
+
* (e.g. claude(max) → codex) forwards req.effort across agents unchanged.
|
|
82
|
+
*
|
|
83
|
+
* `supported` is what codex listed for the model about to run
|
|
84
|
+
* (codexModelEfforts): the level asked for if the model takes it, else the
|
|
85
|
+
* highest it takes below that. Without a list — an older codex, or one that
|
|
86
|
+
* has not answered yet — only `minimal|low|medium|high` is safe, so xhigh
|
|
87
|
+
* and max become high, as they always did. Anything unrecognised emits no
|
|
88
|
+
* effort at all rather than a bad one. Mirrors grokEffort() in grok-spawn.ts. */
|
|
89
|
+
export declare function codexEffort(level?: RuntimeEffortLevel, supported?: readonly string[] | null): string | null;
|
|
84
90
|
export declare function spawnCodex(input: CodexInput): CodexHandle;
|
package/dist/codex-spawn.js
CHANGED
|
@@ -328,14 +328,31 @@ function lineToEvents(line) {
|
|
|
328
328
|
// new "codex:" namespace which adds zero value.
|
|
329
329
|
return [{ type: `claude:codex.${type || 'unknown'}`, raw: line }];
|
|
330
330
|
}
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
|
|
331
|
+
/** codex's effort levels, lowest first. */
|
|
332
|
+
const CODEX_EFFORT_LADDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
333
|
+
/** Clamp our effort ladder to a value this codex accepts. A codex that does
|
|
334
|
+
* not know a level FAIL-FASTS (exit 2, dead-letter), and a ladder failover
|
|
335
|
+
* (e.g. claude(max) → codex) forwards req.effort across agents unchanged.
|
|
336
|
+
*
|
|
337
|
+
* `supported` is what codex listed for the model about to run
|
|
338
|
+
* (codexModelEfforts): the level asked for if the model takes it, else the
|
|
339
|
+
* highest it takes below that. Without a list — an older codex, or one that
|
|
340
|
+
* has not answered yet — only `minimal|low|medium|high` is safe, so xhigh
|
|
341
|
+
* and max become high, as they always did. Anything unrecognised emits no
|
|
342
|
+
* effort at all rather than a bad one. Mirrors grokEffort() in grok-spawn.ts. */
|
|
343
|
+
function codexEffort(level, supported) {
|
|
344
|
+
const want = CODEX_EFFORT_LADDER.indexOf(level ?? '');
|
|
345
|
+
if (want < 0)
|
|
346
|
+
return null;
|
|
347
|
+
if (supported?.length) {
|
|
348
|
+
let best = -1;
|
|
349
|
+
for (const s of supported) {
|
|
350
|
+
const i = CODEX_EFFORT_LADDER.indexOf(s);
|
|
351
|
+
if (i >= 0 && i <= want && i > best)
|
|
352
|
+
best = i;
|
|
353
|
+
}
|
|
354
|
+
return best >= 0 ? CODEX_EFFORT_LADDER[best] : null;
|
|
355
|
+
}
|
|
339
356
|
switch (level) {
|
|
340
357
|
case 'low': return 'low';
|
|
341
358
|
case 'medium': return 'medium';
|
|
@@ -363,9 +380,12 @@ function spawnCodex(input) {
|
|
|
363
380
|
baseFlags.push('--sandbox', input.sandboxMode ?? 'workspace-write');
|
|
364
381
|
if (input.model)
|
|
365
382
|
baseFlags.push('--model', input.model);
|
|
366
|
-
const effort = codexEffort(input.effortLevel);
|
|
383
|
+
const effort = codexEffort(input.effortLevel, input.supportedEfforts);
|
|
367
384
|
if (effort) {
|
|
368
|
-
|
|
385
|
+
// `model_reasoning_effort` is the key codex reads. The `reasoning.effort`
|
|
386
|
+
// this sent before was ignored ("session-flags: `reasoning` is ignored",
|
|
387
|
+
// codex 0.164), so every codex run went out at its model's default.
|
|
388
|
+
baseFlags.push('-c', `model_reasoning_effort=${JSON.stringify(effort)}`);
|
|
369
389
|
}
|
|
370
390
|
const args = input.resumeThreadId
|
|
371
391
|
? ['exec', ...baseFlags, 'resume', input.resumeThreadId, input.prompt]
|
package/dist/command-runner.js
CHANGED
|
@@ -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);
|
package/dist/desktop/docker.js
CHANGED
|
@@ -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
|