@addai/node 0.30.16-msp.4 → 0.30.16-msp.5
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/capabilities.d.ts +3 -1
- package/dist/capabilities.js +1 -1
- package/dist/codex-models.d.ts +2 -0
- package/dist/codex-models.js +5 -0
- package/dist/model-selector.d.ts +4 -1
- package/dist/model-selector.js +29 -6
- package/dist/session-runner.d.ts +1 -1
- package/dist/session-runner.js +46 -18
- package/package.json +1 -1
package/dist/capabilities.d.ts
CHANGED
|
@@ -84,10 +84,12 @@ interface CapabilitiesShape {
|
|
|
84
84
|
/** Present when this build has the Model Selector Protocol, so Studio can
|
|
85
85
|
* say which nodes would ignore an entity's selector setting instead of
|
|
86
86
|
* guessing from a version number. enabled is false under ADDAI_MSP=0.
|
|
87
|
-
* jev is true on builds that can use Jev as the selector
|
|
87
|
+
* jev is true on builds that can use Jev as the selector, codex on builds
|
|
88
|
+
* that pick for Codex turns too. */
|
|
88
89
|
model_selector?: {
|
|
89
90
|
enabled: boolean;
|
|
90
91
|
jev?: boolean;
|
|
92
|
+
codex?: boolean;
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
/** What the heartbeat says about claude's models. `models` keeps its old
|
package/dist/capabilities.js
CHANGED
|
@@ -510,7 +510,7 @@ async function probeCapabilities() {
|
|
|
510
510
|
autostart: (0, autostart_1.status)(),
|
|
511
511
|
container_engine: containerEngine,
|
|
512
512
|
flows: await probeFlows(),
|
|
513
|
-
model_selector: { enabled: process.env.ADDAI_MSP !== '0', jev: true },
|
|
513
|
+
model_selector: { enabled: process.env.ADDAI_MSP !== '0', jev: true, codex: true },
|
|
514
514
|
auto_update: (0, auto_update_1.autoUpdateState)(),
|
|
515
515
|
machine: (0, machine_metrics_1.sampleMachine)(),
|
|
516
516
|
claude: deco('claude', claude),
|
package/dist/codex-models.d.ts
CHANGED
|
@@ -79,3 +79,5 @@ export declare function codexModelsFor(bin: string, key: string): CodexModelRepo
|
|
|
79
79
|
* model is not in its list.
|
|
80
80
|
*/
|
|
81
81
|
export declare function codexModelEfforts(model?: string | null, report?: CodexModelReport | null): string[] | null;
|
|
82
|
+
/** False only when codex has listed its models and this isn't one of them. */
|
|
83
|
+
export declare function codexModelListed(model: string, report?: CodexModelReport | null): boolean;
|
package/dist/codex-models.js
CHANGED
|
@@ -71,6 +71,7 @@ exports.probeCodexModels = probeCodexModels;
|
|
|
71
71
|
exports.createCodexModelCache = createCodexModelCache;
|
|
72
72
|
exports.codexModelsFor = codexModelsFor;
|
|
73
73
|
exports.codexModelEfforts = codexModelEfforts;
|
|
74
|
+
exports.codexModelListed = codexModelListed;
|
|
74
75
|
const child_process_1 = require("child_process");
|
|
75
76
|
const fs = __importStar(require("fs"));
|
|
76
77
|
const os = __importStar(require("os"));
|
|
@@ -383,3 +384,7 @@ function codexModelEfforts(model, report = sharedCache.last()) {
|
|
|
383
384
|
const entry = model ? report.catalog.find(e => e.id === model) : report.catalog.find(e => e.default);
|
|
384
385
|
return entry?.efforts?.length ? entry.efforts : null;
|
|
385
386
|
}
|
|
387
|
+
/** False only when codex has listed its models and this isn't one of them. */
|
|
388
|
+
function codexModelListed(model, report = sharedCache.last()) {
|
|
389
|
+
return !report || report.catalog.some(e => e.id === model);
|
|
390
|
+
}
|
package/dist/model-selector.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const TIER_ORDER: readonly ["quick", "standard", "deep", "max"];
|
|
2
2
|
export type TierName = typeof TIER_ORDER[number];
|
|
3
|
+
export type MspAgent = 'claude' | 'codex';
|
|
3
4
|
export interface TierSpec {
|
|
4
5
|
model: string;
|
|
5
6
|
effort: string;
|
|
@@ -77,7 +78,7 @@ export interface MspDecision {
|
|
|
77
78
|
* the shape, but the node must not trust it: a typo in a tier must cost that
|
|
78
79
|
* tier its override, not the turn.
|
|
79
80
|
*/
|
|
80
|
-
export declare function parseConfig(raw: unknown): MspConfig | null;
|
|
81
|
+
export declare function parseConfig(raw: unknown, agent?: MspAgent): MspConfig | null;
|
|
81
82
|
/**
|
|
82
83
|
* The person's own words for this turn. The chat dispatcher wraps the message
|
|
83
84
|
* in a bracketed briefing ("[Direct message · … ]\n\n<message>"); the selector
|
|
@@ -186,6 +187,8 @@ export declare function decide(opts: {
|
|
|
186
187
|
entityName: string;
|
|
187
188
|
entityModel: string | null;
|
|
188
189
|
entityEffort: string | null;
|
|
190
|
+
/** Which tiers this turn runs on. Claude's when not said. */
|
|
191
|
+
agent?: MspAgent;
|
|
189
192
|
run?: SelectorRunner;
|
|
190
193
|
/** Required for an entity on Jev; without it the turn falls back. */
|
|
191
194
|
runJev?: JevRunner;
|
package/dist/model-selector.js
CHANGED
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
// the server: the node asks the entity-msp-select edge function, which reads
|
|
30
30
|
// the entity's chosen Vault key. Only the new message goes, never the chat, and
|
|
31
31
|
// a message with override words isn't sent at all.
|
|
32
|
+
//
|
|
33
|
+
// Claude and Codex turns both come through here. The picking is the same; each
|
|
34
|
+
// has its own models for the tiers (tiers and codex_tiers in the setting), so an
|
|
35
|
+
// entity with both in its chain stays right on either. Claude as the selector
|
|
36
|
+
// needs a Claude login on the node. On a Codex-only node Jev is what can pick,
|
|
37
|
+
// and otherwise the turn falls back and runs as it would have.
|
|
32
38
|
// Plan: addai-entity-studio docs/model-selector-protocol-plan.md.
|
|
33
39
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
34
40
|
if (k2 === undefined) k2 = k;
|
|
@@ -87,6 +93,7 @@ const os = __importStar(require("os"));
|
|
|
87
93
|
const path = __importStar(require("path"));
|
|
88
94
|
const claude_binary_1 = require("./claude-binary");
|
|
89
95
|
const config_1 = require("./config");
|
|
96
|
+
const model_filter_1 = require("./model-filter");
|
|
90
97
|
const win_1 = require("./win");
|
|
91
98
|
exports.TIER_ORDER = ['quick', 'standard', 'deep', 'max'];
|
|
92
99
|
const DEFAULT_CONFIG = {
|
|
@@ -107,6 +114,15 @@ const DEFAULT_CONFIG = {
|
|
|
107
114
|
max_run: { silence_minutes: 15, backstop_hours: 6 },
|
|
108
115
|
voice: '',
|
|
109
116
|
};
|
|
117
|
+
// Luna is codex's fast one, Terra its balanced one, Sol its coding one and Astra
|
|
118
|
+
// its most capable. A tier naming a model the node's codex doesn't list runs on
|
|
119
|
+
// the entity's own model instead (session-runner's applyModelSelection).
|
|
120
|
+
const DEFAULT_CODEX_TIERS = {
|
|
121
|
+
quick: { model: 'gpt-6-luna', effort: 'low' },
|
|
122
|
+
standard: { model: 'gpt-6.1-terra', effort: 'medium' },
|
|
123
|
+
deep: { model: 'gpt-6.1-sol', effort: 'high' },
|
|
124
|
+
max: { model: 'gpt-6-astra', effort: 'max', fan_out: true },
|
|
125
|
+
};
|
|
110
126
|
const EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
111
127
|
const isTier = (t) => typeof t === 'string' && exports.TIER_ORDER.includes(t);
|
|
112
128
|
const rank = (t) => exports.TIER_ORDER.indexOf(t);
|
|
@@ -117,18 +133,21 @@ const num = (v, lo, hi, dflt) => (typeof v === 'number' && Number.isFinite(v) ?
|
|
|
117
133
|
* the shape, but the node must not trust it: a typo in a tier must cost that
|
|
118
134
|
* tier its override, not the turn.
|
|
119
135
|
*/
|
|
120
|
-
function parseConfig(raw) {
|
|
136
|
+
function parseConfig(raw, agent = 'claude') {
|
|
121
137
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
122
138
|
return null;
|
|
123
139
|
const r = raw;
|
|
124
140
|
if (r.enabled !== true)
|
|
125
141
|
return null;
|
|
126
|
-
|
|
142
|
+
// The tiers this turn runs on. A Claude model in codex_tiers, or the other way
|
|
143
|
+
// round, costs that tier its override like any other typo.
|
|
144
|
+
const tiers = { ...(agent === 'codex' ? DEFAULT_CODEX_TIERS : DEFAULT_CONFIG.tiers) };
|
|
145
|
+
const rawTiers = agent === 'codex' ? r.codex_tiers : r.tiers;
|
|
127
146
|
for (const t of exports.TIER_ORDER) {
|
|
128
|
-
const spec =
|
|
147
|
+
const spec = rawTiers?.[t];
|
|
129
148
|
const model = str(spec?.model, 80);
|
|
130
149
|
const effort = str(spec?.effort, 10);
|
|
131
|
-
if (model &&
|
|
150
|
+
if (model && (0, model_filter_1.modelForAgent)(agent, model) && EFFORTS.has(effort)) {
|
|
132
151
|
tiers[t] = { model, effort, ...(spec?.fan_out === true ? { fan_out: true } : {}) };
|
|
133
152
|
}
|
|
134
153
|
}
|
|
@@ -280,6 +299,9 @@ const label = (model) => {
|
|
|
280
299
|
for (const name of ['fable', 'opus', 'sonnet', 'haiku'])
|
|
281
300
|
if (m.includes(name))
|
|
282
301
|
return name[0].toUpperCase() + name.slice(1);
|
|
302
|
+
// gpt-6.1-sol reads GPT-6.1-Sol, as codex names it.
|
|
303
|
+
if (m.startsWith('gpt-'))
|
|
304
|
+
return model.split('-').map((p, i) => (i === 0 ? 'GPT' : p.charAt(0).toUpperCase() + p.slice(1))).join('-');
|
|
283
305
|
return model;
|
|
284
306
|
};
|
|
285
307
|
const PLAIN_OPENER = {
|
|
@@ -493,7 +515,7 @@ function freshLast(last, now) {
|
|
|
493
515
|
* a fallback decision, and the turn runs.
|
|
494
516
|
*/
|
|
495
517
|
async function decide(opts) {
|
|
496
|
-
const cfg = parseConfig(opts.config);
|
|
518
|
+
const cfg = parseConfig(opts.config, opts.agent);
|
|
497
519
|
if (!cfg)
|
|
498
520
|
return null;
|
|
499
521
|
const last = freshLast(opts.last, opts.now ?? Date.now());
|
|
@@ -631,7 +653,8 @@ async function decide(opts) {
|
|
|
631
653
|
function mspApplies(req) {
|
|
632
654
|
if (process.env.ADDAI_MSP === '0')
|
|
633
655
|
return false;
|
|
634
|
-
|
|
656
|
+
// Codex only has the one way to run. Claude's TUI runs can't change model here.
|
|
657
|
+
if (req.agent !== 'codex' && (req.agent !== 'claude' || req.mode !== 'print'))
|
|
635
658
|
return false;
|
|
636
659
|
return req.issued_via === 'chatflows' || req.issued_via === 'chat';
|
|
637
660
|
}
|
package/dist/session-runner.d.ts
CHANGED
|
@@ -124,7 +124,7 @@ export interface RuntimeRequestRow {
|
|
|
124
124
|
memoryV2Flagged?: boolean;
|
|
125
125
|
/** Model Selector Protocol — NOT a DB column. Started by runRequest() as
|
|
126
126
|
* soon as the run is accepted, so the selector's second or so overlaps the
|
|
127
|
-
* cwd/install/memory-pack setup; awaited by
|
|
127
|
+
* cwd/install/memory-pack setup; awaited by applyModelSelection() just before
|
|
128
128
|
* the spawn. Resolves null for every entity without MSP switched on, and
|
|
129
129
|
* is undefined for runs it can't apply to. See model-selector.ts. */
|
|
130
130
|
mspDecision?: Promise<MspDecision | null>;
|
package/dist/session-runner.js
CHANGED
|
@@ -1600,6 +1600,7 @@ async function startModelSelection(req) {
|
|
|
1600
1600
|
entityName: setting.entity_name || 'the assistant',
|
|
1601
1601
|
entityModel: req.model,
|
|
1602
1602
|
entityEffort: req.effort,
|
|
1603
|
+
agent: req.agent === 'codex' ? 'codex' : 'claude',
|
|
1603
1604
|
runJev: (0, model_selector_1.askJev)(token(), req.id),
|
|
1604
1605
|
});
|
|
1605
1606
|
}
|
|
@@ -1630,6 +1631,30 @@ function publishModelSelection(req, d, changed) {
|
|
|
1630
1631
|
p_decision: { tier: d.tier, model: d.model, effort: d.effort },
|
|
1631
1632
|
}).catch(err => console.error(`[msp] announce failed [reqId=${req.id}]: ${err.message}`));
|
|
1632
1633
|
}
|
|
1634
|
+
/** This turn's model and effort, when MSP is on. Written onto req so a guardrail
|
|
1635
|
+
* retry turn stays on the same model. Call it just before the spawn. */
|
|
1636
|
+
async function applyModelSelection(req) {
|
|
1637
|
+
const msp = req.mspDecision ? await req.mspDecision : null;
|
|
1638
|
+
if (!msp)
|
|
1639
|
+
return null;
|
|
1640
|
+
// A ChatGPT login refuses a model codex doesn't list, which would fail the
|
|
1641
|
+
// turn. The node's list can differ from the node the tiers were chosen on.
|
|
1642
|
+
if (req.agent === 'codex' && msp.model && msp.model !== req.model && !(0, codex_models_1.codexModelListed)(msp.model)) {
|
|
1643
|
+
console.log(`[msp] ${msp.model} is not on this node's codex; running the entity's own model [reqId=${req.id}]`);
|
|
1644
|
+
msp.fallback_reason = msp.selector_error = `model_not_on_node: ${msp.model}`;
|
|
1645
|
+
msp.fell_back = true;
|
|
1646
|
+
msp.line = null;
|
|
1647
|
+
msp.model = req.model;
|
|
1648
|
+
msp.effort = req.effort;
|
|
1649
|
+
}
|
|
1650
|
+
const changed = (!!msp.model && msp.model !== req.model) || (!!msp.effort && msp.effort !== req.effort);
|
|
1651
|
+
if (msp.model)
|
|
1652
|
+
req.model = msp.model;
|
|
1653
|
+
if (msp.effort)
|
|
1654
|
+
req.effort = msp.effort;
|
|
1655
|
+
publishModelSelection(req, msp, changed);
|
|
1656
|
+
return msp;
|
|
1657
|
+
}
|
|
1633
1658
|
// ── claude --print ─────────────────────────────────────────────────────────
|
|
1634
1659
|
async function runClaudePrint(req, cwd, installed) {
|
|
1635
1660
|
if (!req.prompt) {
|
|
@@ -1661,17 +1686,7 @@ async function runClaudePrint(req, cwd, installed) {
|
|
|
1661
1686
|
abort.abort();
|
|
1662
1687
|
}
|
|
1663
1688
|
}
|
|
1664
|
-
|
|
1665
|
-
// Written onto req so a guardrail retry turn stays on the same model.
|
|
1666
|
-
const msp = req.mspDecision ? await req.mspDecision : null;
|
|
1667
|
-
if (msp) {
|
|
1668
|
-
const changed = (!!msp.model && msp.model !== req.model) || (!!msp.effort && msp.effort !== req.effort);
|
|
1669
|
-
if (msp.model)
|
|
1670
|
-
req.model = msp.model;
|
|
1671
|
-
if (msp.effort)
|
|
1672
|
-
req.effort = msp.effort;
|
|
1673
|
-
publishModelSelection(req, msp, changed);
|
|
1674
|
-
}
|
|
1689
|
+
const msp = await applyModelSelection(req);
|
|
1675
1690
|
let handle;
|
|
1676
1691
|
try {
|
|
1677
1692
|
handle = (0, claude_print_1.spawnClaudePrint)({
|
|
@@ -1811,6 +1826,7 @@ async function runCodex(req, cwd, installed) {
|
|
|
1811
1826
|
void emit(req.id, 'session_dropped_missing_rollout', { sessionId: req.resume_session_id });
|
|
1812
1827
|
req.resume_session_id = null;
|
|
1813
1828
|
}
|
|
1829
|
+
const msp = await applyModelSelection(req);
|
|
1814
1830
|
let handle;
|
|
1815
1831
|
try {
|
|
1816
1832
|
const codexModel = (0, model_filter_1.modelForAgent)('codex', req.model);
|
|
@@ -1849,11 +1865,15 @@ async function runCodex(req, cwd, installed) {
|
|
|
1849
1865
|
// call looks identical to a hang. Give it grok's leash: standard window
|
|
1850
1866
|
// before any output (a wedged spawn/resume trips fast), 3x after activity
|
|
1851
1867
|
// has been seen (silent tool work is expected, not a hang). Raw stdout/
|
|
1852
|
-
// stderr bytes count as liveness via onActivity.
|
|
1868
|
+
// stderr bytes count as liveness via onActivity. An MSP run can lengthen
|
|
1869
|
+
// that leash with max_run.silence_minutes but not shorten it, because silence
|
|
1870
|
+
// here can be a tool working. It is stopped rather than retried (below).
|
|
1853
1871
|
const sawActivity = { current: false };
|
|
1854
|
-
const
|
|
1872
|
+
const silenced = { current: false };
|
|
1873
|
+
const quietMs = () => Math.max(config_1.HANG_WATCHDOG_IDLE_MS * 3, (msp?.max_run.silence_minutes ?? 0) * 60_000);
|
|
1874
|
+
const hang = armHangWatchdog(req, lastError, () => { silenced.current = true; handle.kill(); }, {
|
|
1855
1875
|
sawActivity: () => sawActivity.current,
|
|
1856
|
-
idleMsFn: () => (sawActivity.current ?
|
|
1876
|
+
idleMsFn: () => (sawActivity.current ? quietMs() : config_1.HANG_WATCHDOG_IDLE_MS),
|
|
1857
1877
|
});
|
|
1858
1878
|
handle.onActivity(() => { sawActivity.current = true; hang.touch(); });
|
|
1859
1879
|
handle.onEvent(ev => {
|
|
@@ -1869,7 +1889,7 @@ async function runCodex(req, cwd, installed) {
|
|
|
1869
1889
|
});
|
|
1870
1890
|
// Node-configured execution timeout (null/0 = unlimited). On timeout we
|
|
1871
1891
|
// kill the agent; the completion below treats it as complete-with-partial.
|
|
1872
|
-
const nodeTimeout = armNodeTimeout(req, () => handle.kill());
|
|
1892
|
+
const nodeTimeout = armNodeTimeout(req, () => handle.kill(), msp ? msp.max_run.backstop_hours * 3600 : undefined);
|
|
1873
1893
|
let exitCode;
|
|
1874
1894
|
try {
|
|
1875
1895
|
exitCode = await handle.done;
|
|
@@ -1889,6 +1909,14 @@ async function runCodex(req, cwd, installed) {
|
|
|
1889
1909
|
nodeTimeout.clear();
|
|
1890
1910
|
watcher.stop();
|
|
1891
1911
|
hang.stop();
|
|
1912
|
+
// An MSP run that worked and then went quiet ends with what it has, and says
|
|
1913
|
+
// so, instead of being retried from the top. As in runClaudePrint.
|
|
1914
|
+
const mspSilenced = !!msp && silenced.current && sawActivity.current;
|
|
1915
|
+
if (mspSilenced) {
|
|
1916
|
+
await emit(req.id, 'assistant_text', {
|
|
1917
|
+
delta: `\n\n_I went quiet for ${Math.round(quietMs() / 60_000)} minutes, so I was stopped here._`,
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1892
1920
|
if (handle.threadId)
|
|
1893
1921
|
await setStatus(req.id, { spawnedSessionId: handle.threadId });
|
|
1894
1922
|
void emit(req.id, 'session_end', { reason: 'codex_exit', exitCode });
|
|
@@ -1896,7 +1924,7 @@ async function runCodex(req, cwd, installed) {
|
|
|
1896
1924
|
if (currentStatus !== 'canceled') {
|
|
1897
1925
|
// A timed-out run completes-with-partial: the node asked for a cap, not
|
|
1898
1926
|
// a failure, so surface whatever the agent produced.
|
|
1899
|
-
const cleanExit = exitCode === 0 || nodeTimeout.timedOut();
|
|
1927
|
+
const cleanExit = exitCode === 0 || nodeTimeout.timedOut() || mspSilenced;
|
|
1900
1928
|
await finalizeTerminal(req, {
|
|
1901
1929
|
clean: cleanExit,
|
|
1902
1930
|
errorCode: cleanExit ? undefined : (lastError.current?.code ?? `exit_${exitCode}`),
|
|
@@ -2305,8 +2333,8 @@ async function runRequest(req, entitybox) {
|
|
|
2305
2333
|
return;
|
|
2306
2334
|
}
|
|
2307
2335
|
// Model Selector Protocol: start now, so the selector runs alongside the
|
|
2308
|
-
// cwd/install/memory setup below instead of after it. Claude print
|
|
2309
|
-
// turns only; whether it is on for this entity is the server's answer.
|
|
2336
|
+
// cwd/install/memory setup below instead of after it. Claude print and Codex
|
|
2337
|
+
// chat turns only; whether it is on for this entity is the server's answer.
|
|
2310
2338
|
// Nothing in here may stop the run, so even the gate is guarded.
|
|
2311
2339
|
try {
|
|
2312
2340
|
if ((0, model_selector_1.mspApplies)(req))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@addai/node",
|
|
3
|
-
"version": "0.30.16-msp.
|
|
3
|
+
"version": "0.30.16-msp.5",
|
|
4
4
|
"description": "Daemon that pairs a machine with your +Ai account and runs Claude / Codex / Kimi / Gemini agents on its behalf. Reachable via Supabase from Vault, Entity Studio, or any other +Ai surface.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|