@addai/node 0.30.16-msp.4 → 0.30.16-msp.6
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/claude-print.d.ts +4 -0
- package/dist/claude-print.js +10 -1
- package/dist/codex-models.d.ts +2 -0
- package/dist/codex-models.js +5 -0
- package/dist/model-selector.d.ts +8 -1
- package/dist/model-selector.js +36 -6
- package/dist/session-runner.d.ts +1 -1
- package/dist/session-runner.js +47 -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/claude-print.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ export interface ClaudePrintInput {
|
|
|
14
14
|
appendSystemPrompt?: string;
|
|
15
15
|
/** Restrict to a specific set of tools (claude only). null/undefined = inherit. */
|
|
16
16
|
allowedTools?: string[];
|
|
17
|
+
/** How long to wait for apps before the first model call. Unset = the CLI's default. */
|
|
18
|
+
mcpStartupWaitMs?: number;
|
|
17
19
|
}
|
|
20
|
+
/** The child's environment. The wait is only set when asked for, so everyone else keeps the CLI's default. */
|
|
21
|
+
export declare function claudePrintEnv(mcpStartupWaitMs?: number, base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
18
22
|
export type ClaudePrintInputBlock = {
|
|
19
23
|
type: 'text';
|
|
20
24
|
text: string;
|
package/dist/claude-print.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// our normalised RuntimeEvent shape, and resolves when the process exits.
|
|
6
6
|
// No PTY needed — Claude detects non-TTY and runs headless.
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.claudePrintEnv = claudePrintEnv;
|
|
8
9
|
exports.claudePrintInputMessage = claudePrintInputMessage;
|
|
9
10
|
exports.blockDelta = blockDelta;
|
|
10
11
|
exports.lineToEvents = lineToEvents;
|
|
@@ -14,6 +15,14 @@ const claude_binary_1 = require("./claude-binary");
|
|
|
14
15
|
const win_1 = require("./win");
|
|
15
16
|
const claude_system_prompt_1 = require("./claude-system-prompt");
|
|
16
17
|
const prompt_transport_1 = require("./prompt-transport");
|
|
18
|
+
/** The child's environment. The wait is only set when asked for, so everyone else keeps the CLI's default. */
|
|
19
|
+
function claudePrintEnv(mcpStartupWaitMs, base = process.env) {
|
|
20
|
+
return {
|
|
21
|
+
...base,
|
|
22
|
+
TERM: 'dumb',
|
|
23
|
+
...(mcpStartupWaitMs != null ? { CLAUDE_CODE_MCP_STARTUP_WAIT_MS: String(mcpStartupWaitMs) } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
17
26
|
/** Stream JSON reaches stdin only; files never become command-line arguments. */
|
|
18
27
|
function claudePrintInputMessage(prompt, blocks) {
|
|
19
28
|
if (!Array.isArray(blocks) || blocks.length < 1 || blocks.length > 12 || !blocks.every(b => b && (b.type === 'text' && typeof b.text === 'string' || ['image', 'document'].includes(b.type) && 'source' in b && b.source?.type === 'base64' && typeof b.source.data === 'string' && /^[A-Za-z0-9+/]*={0,2}$/.test(b.source.data)
|
|
@@ -204,7 +213,7 @@ function spawnClaudePrint(input) {
|
|
|
204
213
|
const inv = (0, win_1.resolveCliInvocation)(bin, transport.args);
|
|
205
214
|
proc = (0, child_process_1.spawn)(inv.file, inv.args, {
|
|
206
215
|
cwd: input.workingDirectory,
|
|
207
|
-
env:
|
|
216
|
+
env: claudePrintEnv(input.mcpStartupWaitMs),
|
|
208
217
|
stdio: (0, prompt_transport_1.stdioFor)(transport.stdin),
|
|
209
218
|
// No console window. This is headless work on somebody's machine —
|
|
210
219
|
// on Windows a spawn without this flashes a terminal every run.
|
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;
|
|
@@ -29,6 +30,9 @@ export interface MspConfig {
|
|
|
29
30
|
silence_minutes: number;
|
|
30
31
|
backstop_hours: number;
|
|
31
32
|
};
|
|
33
|
+
/** How long a Claude turn waits for its apps before the first model call.
|
|
34
|
+
* At 0 they keep connecting in the background. null leaves the CLI's default. */
|
|
35
|
+
mcp_startup_wait_ms: number | null;
|
|
32
36
|
/** A few sentences on how the entity talks, for the line. */
|
|
33
37
|
voice: string;
|
|
34
38
|
}
|
|
@@ -70,6 +74,7 @@ export interface MspDecision {
|
|
|
70
74
|
/** Jev says it approves or restarts work, so it stays on the chat's tier. */
|
|
71
75
|
go_ahead: boolean;
|
|
72
76
|
max_run: MspConfig['max_run'];
|
|
77
|
+
mcp_startup_wait_ms: MspConfig['mcp_startup_wait_ms'];
|
|
73
78
|
fan_out: boolean;
|
|
74
79
|
}
|
|
75
80
|
/**
|
|
@@ -77,7 +82,7 @@ export interface MspDecision {
|
|
|
77
82
|
* the shape, but the node must not trust it: a typo in a tier must cost that
|
|
78
83
|
* tier its override, not the turn.
|
|
79
84
|
*/
|
|
80
|
-
export declare function parseConfig(raw: unknown): MspConfig | null;
|
|
85
|
+
export declare function parseConfig(raw: unknown, agent?: MspAgent): MspConfig | null;
|
|
81
86
|
/**
|
|
82
87
|
* The person's own words for this turn. The chat dispatcher wraps the message
|
|
83
88
|
* in a bracketed briefing ("[Direct message · … ]\n\n<message>"); the selector
|
|
@@ -186,6 +191,8 @@ export declare function decide(opts: {
|
|
|
186
191
|
entityName: string;
|
|
187
192
|
entityModel: string | null;
|
|
188
193
|
entityEffort: string | null;
|
|
194
|
+
/** Which tiers this turn runs on. Claude's when not said. */
|
|
195
|
+
agent?: MspAgent;
|
|
189
196
|
run?: SelectorRunner;
|
|
190
197
|
/** Required for an entity on Jev; without it the turn falls back. */
|
|
191
198
|
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 = {
|
|
@@ -105,8 +112,18 @@ const DEFAULT_CONFIG = {
|
|
|
105
112
|
announce: 'always',
|
|
106
113
|
overrides: { 'think hard': 'max', 'ultrathink': 'max', 'quick one': 'quick' },
|
|
107
114
|
max_run: { silence_minutes: 15, backstop_hours: 6 },
|
|
115
|
+
mcp_startup_wait_ms: null,
|
|
108
116
|
voice: '',
|
|
109
117
|
};
|
|
118
|
+
// Luna is codex's fast one, Terra its balanced one, Sol its coding one and Astra
|
|
119
|
+
// its most capable. A tier naming a model the node's codex doesn't list runs on
|
|
120
|
+
// the entity's own model instead (session-runner's applyModelSelection).
|
|
121
|
+
const DEFAULT_CODEX_TIERS = {
|
|
122
|
+
quick: { model: 'gpt-6-luna', effort: 'low' },
|
|
123
|
+
standard: { model: 'gpt-6.1-terra', effort: 'medium' },
|
|
124
|
+
deep: { model: 'gpt-6.1-sol', effort: 'high' },
|
|
125
|
+
max: { model: 'gpt-6-astra', effort: 'max', fan_out: true },
|
|
126
|
+
};
|
|
110
127
|
const EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
111
128
|
const isTier = (t) => typeof t === 'string' && exports.TIER_ORDER.includes(t);
|
|
112
129
|
const rank = (t) => exports.TIER_ORDER.indexOf(t);
|
|
@@ -117,18 +134,21 @@ const num = (v, lo, hi, dflt) => (typeof v === 'number' && Number.isFinite(v) ?
|
|
|
117
134
|
* the shape, but the node must not trust it: a typo in a tier must cost that
|
|
118
135
|
* tier its override, not the turn.
|
|
119
136
|
*/
|
|
120
|
-
function parseConfig(raw) {
|
|
137
|
+
function parseConfig(raw, agent = 'claude') {
|
|
121
138
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
122
139
|
return null;
|
|
123
140
|
const r = raw;
|
|
124
141
|
if (r.enabled !== true)
|
|
125
142
|
return null;
|
|
126
|
-
|
|
143
|
+
// The tiers this turn runs on. A Claude model in codex_tiers, or the other way
|
|
144
|
+
// round, costs that tier its override like any other typo.
|
|
145
|
+
const tiers = { ...(agent === 'codex' ? DEFAULT_CODEX_TIERS : DEFAULT_CONFIG.tiers) };
|
|
146
|
+
const rawTiers = agent === 'codex' ? r.codex_tiers : r.tiers;
|
|
127
147
|
for (const t of exports.TIER_ORDER) {
|
|
128
|
-
const spec =
|
|
148
|
+
const spec = rawTiers?.[t];
|
|
129
149
|
const model = str(spec?.model, 80);
|
|
130
150
|
const effort = str(spec?.effort, 10);
|
|
131
|
-
if (model &&
|
|
151
|
+
if (model && (0, model_filter_1.modelForAgent)(agent, model) && EFFORTS.has(effort)) {
|
|
132
152
|
tiers[t] = { model, effort, ...(spec?.fan_out === true ? { fan_out: true } : {}) };
|
|
133
153
|
}
|
|
134
154
|
}
|
|
@@ -166,6 +186,9 @@ function parseConfig(raw) {
|
|
|
166
186
|
silence_minutes: num(r.max_run?.silence_minutes, 1, 120, DEFAULT_CONFIG.max_run.silence_minutes),
|
|
167
187
|
backstop_hours: num(r.max_run?.backstop_hours, 0.25, 24, DEFAULT_CONFIG.max_run.backstop_hours),
|
|
168
188
|
},
|
|
189
|
+
mcp_startup_wait_ms: typeof r.mcp_startup_wait_ms === 'number'
|
|
190
|
+
? Math.round(num(r.mcp_startup_wait_ms, 0, 60_000, 0))
|
|
191
|
+
: null,
|
|
169
192
|
voice: str(r.voice, 600),
|
|
170
193
|
};
|
|
171
194
|
}
|
|
@@ -280,6 +303,9 @@ const label = (model) => {
|
|
|
280
303
|
for (const name of ['fable', 'opus', 'sonnet', 'haiku'])
|
|
281
304
|
if (m.includes(name))
|
|
282
305
|
return name[0].toUpperCase() + name.slice(1);
|
|
306
|
+
// gpt-6.1-sol reads GPT-6.1-Sol, as codex names it.
|
|
307
|
+
if (m.startsWith('gpt-'))
|
|
308
|
+
return model.split('-').map((p, i) => (i === 0 ? 'GPT' : p.charAt(0).toUpperCase() + p.slice(1))).join('-');
|
|
283
309
|
return model;
|
|
284
310
|
};
|
|
285
311
|
const PLAIN_OPENER = {
|
|
@@ -493,7 +519,7 @@ function freshLast(last, now) {
|
|
|
493
519
|
* a fallback decision, and the turn runs.
|
|
494
520
|
*/
|
|
495
521
|
async function decide(opts) {
|
|
496
|
-
const cfg = parseConfig(opts.config);
|
|
522
|
+
const cfg = parseConfig(opts.config, opts.agent);
|
|
497
523
|
if (!cfg)
|
|
498
524
|
return null;
|
|
499
525
|
const last = freshLast(opts.last, opts.now ?? Date.now());
|
|
@@ -525,6 +551,7 @@ async function decide(opts) {
|
|
|
525
551
|
continues: false,
|
|
526
552
|
go_ahead: false,
|
|
527
553
|
max_run: cfg.max_run,
|
|
554
|
+
mcp_startup_wait_ms: cfg.mcp_startup_wait_ms,
|
|
528
555
|
fan_out: t ? cfg.tiers[t].fan_out === true : false,
|
|
529
556
|
};
|
|
530
557
|
};
|
|
@@ -570,6 +597,7 @@ async function decide(opts) {
|
|
|
570
597
|
continues,
|
|
571
598
|
go_ahead: goAhead,
|
|
572
599
|
max_run: cfg.max_run,
|
|
600
|
+
mcp_startup_wait_ms: cfg.mcp_startup_wait_ms,
|
|
573
601
|
fan_out: cfg.tiers[s.tier].fan_out === true,
|
|
574
602
|
};
|
|
575
603
|
if (!shouldAnnounce(cfg, last, decision))
|
|
@@ -621,6 +649,7 @@ async function decide(opts) {
|
|
|
621
649
|
continues: false,
|
|
622
650
|
go_ahead: false,
|
|
623
651
|
max_run: cfg.max_run,
|
|
652
|
+
mcp_startup_wait_ms: cfg.mcp_startup_wait_ms,
|
|
624
653
|
fan_out: cfg.tiers[s.tier].fan_out === true,
|
|
625
654
|
};
|
|
626
655
|
if (!shouldAnnounce(cfg, last, decision))
|
|
@@ -631,7 +660,8 @@ async function decide(opts) {
|
|
|
631
660
|
function mspApplies(req) {
|
|
632
661
|
if (process.env.ADDAI_MSP === '0')
|
|
633
662
|
return false;
|
|
634
|
-
|
|
663
|
+
// Codex only has the one way to run. Claude's TUI runs can't change model here.
|
|
664
|
+
if (req.agent !== 'codex' && (req.agent !== 'claude' || req.mode !== 'print'))
|
|
635
665
|
return false;
|
|
636
666
|
return req.issued_via === 'chatflows' || req.issued_via === 'chat';
|
|
637
667
|
}
|
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)({
|
|
@@ -1685,6 +1700,7 @@ async function runClaudePrint(req, cwd, installed) {
|
|
|
1685
1700
|
resumeSessionId: req.resume_session_id ?? undefined,
|
|
1686
1701
|
appendSystemPrompt: (0, time_context_1.appendTimeContext)(req.system_prompt),
|
|
1687
1702
|
allowedTools: req.allowed_tools ?? undefined,
|
|
1703
|
+
mcpStartupWaitMs: msp?.mcp_startup_wait_ms ?? undefined,
|
|
1688
1704
|
});
|
|
1689
1705
|
}
|
|
1690
1706
|
catch (err) {
|
|
@@ -1811,6 +1827,7 @@ async function runCodex(req, cwd, installed) {
|
|
|
1811
1827
|
void emit(req.id, 'session_dropped_missing_rollout', { sessionId: req.resume_session_id });
|
|
1812
1828
|
req.resume_session_id = null;
|
|
1813
1829
|
}
|
|
1830
|
+
const msp = await applyModelSelection(req);
|
|
1814
1831
|
let handle;
|
|
1815
1832
|
try {
|
|
1816
1833
|
const codexModel = (0, model_filter_1.modelForAgent)('codex', req.model);
|
|
@@ -1849,11 +1866,15 @@ async function runCodex(req, cwd, installed) {
|
|
|
1849
1866
|
// call looks identical to a hang. Give it grok's leash: standard window
|
|
1850
1867
|
// before any output (a wedged spawn/resume trips fast), 3x after activity
|
|
1851
1868
|
// has been seen (silent tool work is expected, not a hang). Raw stdout/
|
|
1852
|
-
// stderr bytes count as liveness via onActivity.
|
|
1869
|
+
// stderr bytes count as liveness via onActivity. An MSP run can lengthen
|
|
1870
|
+
// that leash with max_run.silence_minutes but not shorten it, because silence
|
|
1871
|
+
// here can be a tool working. It is stopped rather than retried (below).
|
|
1853
1872
|
const sawActivity = { current: false };
|
|
1854
|
-
const
|
|
1873
|
+
const silenced = { current: false };
|
|
1874
|
+
const quietMs = () => Math.max(config_1.HANG_WATCHDOG_IDLE_MS * 3, (msp?.max_run.silence_minutes ?? 0) * 60_000);
|
|
1875
|
+
const hang = armHangWatchdog(req, lastError, () => { silenced.current = true; handle.kill(); }, {
|
|
1855
1876
|
sawActivity: () => sawActivity.current,
|
|
1856
|
-
idleMsFn: () => (sawActivity.current ?
|
|
1877
|
+
idleMsFn: () => (sawActivity.current ? quietMs() : config_1.HANG_WATCHDOG_IDLE_MS),
|
|
1857
1878
|
});
|
|
1858
1879
|
handle.onActivity(() => { sawActivity.current = true; hang.touch(); });
|
|
1859
1880
|
handle.onEvent(ev => {
|
|
@@ -1869,7 +1890,7 @@ async function runCodex(req, cwd, installed) {
|
|
|
1869
1890
|
});
|
|
1870
1891
|
// Node-configured execution timeout (null/0 = unlimited). On timeout we
|
|
1871
1892
|
// kill the agent; the completion below treats it as complete-with-partial.
|
|
1872
|
-
const nodeTimeout = armNodeTimeout(req, () => handle.kill());
|
|
1893
|
+
const nodeTimeout = armNodeTimeout(req, () => handle.kill(), msp ? msp.max_run.backstop_hours * 3600 : undefined);
|
|
1873
1894
|
let exitCode;
|
|
1874
1895
|
try {
|
|
1875
1896
|
exitCode = await handle.done;
|
|
@@ -1889,6 +1910,14 @@ async function runCodex(req, cwd, installed) {
|
|
|
1889
1910
|
nodeTimeout.clear();
|
|
1890
1911
|
watcher.stop();
|
|
1891
1912
|
hang.stop();
|
|
1913
|
+
// An MSP run that worked and then went quiet ends with what it has, and says
|
|
1914
|
+
// so, instead of being retried from the top. As in runClaudePrint.
|
|
1915
|
+
const mspSilenced = !!msp && silenced.current && sawActivity.current;
|
|
1916
|
+
if (mspSilenced) {
|
|
1917
|
+
await emit(req.id, 'assistant_text', {
|
|
1918
|
+
delta: `\n\n_I went quiet for ${Math.round(quietMs() / 60_000)} minutes, so I was stopped here._`,
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1892
1921
|
if (handle.threadId)
|
|
1893
1922
|
await setStatus(req.id, { spawnedSessionId: handle.threadId });
|
|
1894
1923
|
void emit(req.id, 'session_end', { reason: 'codex_exit', exitCode });
|
|
@@ -1896,7 +1925,7 @@ async function runCodex(req, cwd, installed) {
|
|
|
1896
1925
|
if (currentStatus !== 'canceled') {
|
|
1897
1926
|
// A timed-out run completes-with-partial: the node asked for a cap, not
|
|
1898
1927
|
// a failure, so surface whatever the agent produced.
|
|
1899
|
-
const cleanExit = exitCode === 0 || nodeTimeout.timedOut();
|
|
1928
|
+
const cleanExit = exitCode === 0 || nodeTimeout.timedOut() || mspSilenced;
|
|
1900
1929
|
await finalizeTerminal(req, {
|
|
1901
1930
|
clean: cleanExit,
|
|
1902
1931
|
errorCode: cleanExit ? undefined : (lastError.current?.code ?? `exit_${exitCode}`),
|
|
@@ -2305,8 +2334,8 @@ async function runRequest(req, entitybox) {
|
|
|
2305
2334
|
return;
|
|
2306
2335
|
}
|
|
2307
2336
|
// 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.
|
|
2337
|
+
// cwd/install/memory setup below instead of after it. Claude print and Codex
|
|
2338
|
+
// chat turns only; whether it is on for this entity is the server's answer.
|
|
2310
2339
|
// Nothing in here may stop the run, so even the gate is guarded.
|
|
2311
2340
|
try {
|
|
2312
2341
|
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.6",
|
|
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": [
|