@addai/node 0.30.16-msp.8 → 0.30.16-msp.9
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/claude-print.d.ts +10 -0
- package/dist/claude-print.js +42 -9
- package/dist/session-runner.d.ts +13 -1
- package/dist/session-runner.js +73 -19
- package/package.json +1 -1
package/dist/claude-print.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export interface ClaudePrintInput {
|
|
|
16
16
|
allowedTools?: string[];
|
|
17
17
|
/** Write the CLI's debug log here, to time each app's connection. */
|
|
18
18
|
debugFile?: string;
|
|
19
|
+
/** Start now and take the prompt later through sendPrompt, so the CLI's
|
|
20
|
+
* start-up overlaps whatever the prompt is still waiting on. */
|
|
21
|
+
deferPrompt?: boolean;
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
21
24
|
* findClaudeBinary for the spawn path. The lookup runs `where` or a login shell
|
|
@@ -51,6 +54,8 @@ export type ClaudePrintInputBlock = {
|
|
|
51
54
|
};
|
|
52
55
|
};
|
|
53
56
|
/** Stream JSON reaches stdin only; files never become command-line arguments. */
|
|
57
|
+
/** The one stdin line a run started with deferPrompt gets. */
|
|
58
|
+
export declare function claudePrintPromptMessage(prompt: string): string;
|
|
54
59
|
export declare function claudePrintInputMessage(prompt: string, blocks: readonly ClaudePrintInputBlock[]): string;
|
|
55
60
|
/**
|
|
56
61
|
* The text to emit for one flushed block, given what came before.
|
|
@@ -73,6 +78,11 @@ export interface ClaudePrintHandle {
|
|
|
73
78
|
onEvent(cb: (e: RuntimeEvent) => void): void;
|
|
74
79
|
/** Resolves with exit code; rejects on spawn error. */
|
|
75
80
|
done: Promise<number>;
|
|
81
|
+
/** deferPrompt only. Closes stdin behind the message, so from here the run
|
|
82
|
+
* ends the way any other run does: one turn, then the process exits. */
|
|
83
|
+
sendPrompt?(prompt: string): void;
|
|
84
|
+
/** True once the process has gone, so a deferred start can be passed over. */
|
|
85
|
+
hasExited(): boolean;
|
|
76
86
|
}
|
|
77
87
|
/**
|
|
78
88
|
* Parse one stream-json line into 0+ RuntimeEvents.
|
package/dist/claude-print.js
CHANGED
|
@@ -40,6 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.cachedClaudeBinary = cachedClaudeBinary;
|
|
42
42
|
exports.parseMcpTimings = parseMcpTimings;
|
|
43
|
+
exports.claudePrintPromptMessage = claudePrintPromptMessage;
|
|
43
44
|
exports.claudePrintInputMessage = claudePrintInputMessage;
|
|
44
45
|
exports.blockDelta = blockDelta;
|
|
45
46
|
exports.lineToEvents = lineToEvents;
|
|
@@ -104,6 +105,10 @@ function parseMcpTimings(log) {
|
|
|
104
105
|
};
|
|
105
106
|
}
|
|
106
107
|
/** Stream JSON reaches stdin only; files never become command-line arguments. */
|
|
108
|
+
/** The one stdin line a run started with deferPrompt gets. */
|
|
109
|
+
function claudePrintPromptMessage(prompt) {
|
|
110
|
+
return JSON.stringify({ type: 'user', session_id: '', parent_tool_use_id: null, message: { role: 'user', content: [{ type: 'text', text: prompt }] } }) + '\n';
|
|
111
|
+
}
|
|
107
112
|
function claudePrintInputMessage(prompt, blocks) {
|
|
108
113
|
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)
|
|
109
114
|
&& (b.type === 'document' ? b.source.media_type === 'application/pdf' : ['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(b.source.media_type)))))
|
|
@@ -241,7 +246,7 @@ function spawnClaudePrint(input) {
|
|
|
241
246
|
args.push('--include-partial-messages');
|
|
242
247
|
if (input.debugFile)
|
|
243
248
|
args.push('--debug-file', input.debugFile);
|
|
244
|
-
if (input.inputBlocks)
|
|
249
|
+
if (input.inputBlocks || input.deferPrompt)
|
|
245
250
|
args.push('--input-format', 'stream-json');
|
|
246
251
|
if (input.permissionMode === 'bypassPermissions') {
|
|
247
252
|
args.push('--dangerously-skip-permissions');
|
|
@@ -308,15 +313,28 @@ function spawnClaudePrint(input) {
|
|
|
308
313
|
// Mark handled now — the caller awaits `done` a few ticks later, and an
|
|
309
314
|
// already-rejected promise with no handler would fire unhandledRejection.
|
|
310
315
|
done.catch(() => { });
|
|
311
|
-
return { pid: undefined, sessionId: null, kill: () => { }, onEvent: () => { }, done };
|
|
316
|
+
return { pid: undefined, sessionId: null, kill: () => { }, onEvent: () => { }, done, hasExited: () => true };
|
|
312
317
|
}
|
|
313
|
-
|
|
318
|
+
// A child that dies before its deferred prompt turns the later write into
|
|
319
|
+
// EPIPE; its exit reports that, same as feedStdin.
|
|
320
|
+
if (input.deferPrompt)
|
|
321
|
+
proc.stdin?.on('error', () => { });
|
|
322
|
+
else
|
|
323
|
+
(0, prompt_transport_1.feedStdin)(proc, transport.stdin);
|
|
314
324
|
const listeners = [];
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
325
|
+
// A deferred start can say something before the run has attached to it.
|
|
326
|
+
const unheard = [];
|
|
327
|
+
const emit = (e) => {
|
|
328
|
+
if (input.deferPrompt && listeners.length === 0) {
|
|
329
|
+
unheard.push(e);
|
|
330
|
+
return;
|
|
318
331
|
}
|
|
319
|
-
|
|
332
|
+
for (const l of listeners)
|
|
333
|
+
try {
|
|
334
|
+
l(e);
|
|
335
|
+
}
|
|
336
|
+
catch { }
|
|
337
|
+
};
|
|
320
338
|
let sessionId = null;
|
|
321
339
|
let buffer = '';
|
|
322
340
|
// Text-delta coalescer (partial-stream mode). Token deltas arrive many times
|
|
@@ -447,9 +465,11 @@ function spawnClaudePrint(input) {
|
|
|
447
465
|
proc.stderr?.on('data', (chunk) => {
|
|
448
466
|
stderrBuf += chunk.toString('utf8');
|
|
449
467
|
});
|
|
468
|
+
let exited = false;
|
|
450
469
|
const done = new Promise((resolve, reject) => {
|
|
451
|
-
proc.once('error', reject);
|
|
470
|
+
proc.once('error', (err) => { exited = true; reject(err); });
|
|
452
471
|
proc.once('exit', (code) => {
|
|
472
|
+
exited = true;
|
|
453
473
|
sysPrompt.cleanup();
|
|
454
474
|
flushThinking();
|
|
455
475
|
flushText();
|
|
@@ -460,11 +480,24 @@ function spawnClaudePrint(input) {
|
|
|
460
480
|
resolve(code ?? -1);
|
|
461
481
|
});
|
|
462
482
|
});
|
|
483
|
+
// Nothing awaits a deferred start's done until its prompt is ready, and a
|
|
484
|
+
// rejection nobody has handled yet would take the whole daemon down.
|
|
485
|
+
if (input.deferPrompt)
|
|
486
|
+
done.catch(() => { });
|
|
463
487
|
return {
|
|
464
488
|
pid: proc.pid,
|
|
465
489
|
get sessionId() { return sessionId; },
|
|
466
490
|
kill: () => { (0, win_1.killProcessTree)(proc); },
|
|
467
|
-
onEvent: (cb) => {
|
|
491
|
+
onEvent: (cb) => {
|
|
492
|
+
listeners.push(cb);
|
|
493
|
+
for (const e of unheard.splice(0))
|
|
494
|
+
try {
|
|
495
|
+
cb(e);
|
|
496
|
+
}
|
|
497
|
+
catch { }
|
|
498
|
+
},
|
|
468
499
|
done,
|
|
500
|
+
hasExited: () => exited,
|
|
501
|
+
...(input.deferPrompt ? { sendPrompt: (prompt) => { proc.stdin?.end(claudePrintPromptMessage(prompt), 'utf8'); } } : {}),
|
|
469
502
|
};
|
|
470
503
|
}
|
package/dist/session-runner.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ClaudePrintHandle } from './claude-print';
|
|
1
2
|
import { type EntityboxQueueServices } from './entitybox-queue-request';
|
|
2
3
|
import { type InstallResult } from './install';
|
|
3
4
|
import { fetchMemoryPack } from './memory-pack';
|
|
@@ -130,6 +131,14 @@ export interface RuntimeRequestRow {
|
|
|
130
131
|
* the spawn. Resolves null for every entity without MSP switched on, and
|
|
131
132
|
* is undefined for runs it can't apply to. See model-selector.ts. */
|
|
132
133
|
mspDecision?: Promise<MspDecision | null>;
|
|
134
|
+
/** NOT a DB column. What applyModelSelection decided: it publishes, so it
|
|
135
|
+
* runs once however many steps ask. */
|
|
136
|
+
mspApplied?: Promise<MspDecision | null>;
|
|
137
|
+
/** NOT a DB column. A CLI startClaudeEarly has running, waiting for its prompt. */
|
|
138
|
+
earlyStart?: {
|
|
139
|
+
handle: ClaudePrintHandle;
|
|
140
|
+
at: number;
|
|
141
|
+
};
|
|
133
142
|
}
|
|
134
143
|
export declare function notifyFlowsIfFlowRequest(requestId: string): Promise<void>;
|
|
135
144
|
export declare function notifyWidgetstudioIfWidgetRequest(requestId: string): Promise<void>;
|
|
@@ -145,6 +154,9 @@ export declare function notifySelfImproveIfEnabled(requestId: string): Promise<v
|
|
|
145
154
|
*
|
|
146
155
|
* Exported for tests. */
|
|
147
156
|
export declare function resumeSessionIsVoid(workingDirectory: string | null | undefined, resumeSessionId: string | null | undefined, cwdSubstituted: boolean): boolean;
|
|
157
|
+
/** The selector's early_start setting, on a turn it can work for. A new session
|
|
158
|
+
* carries its memories in the system prompt, so that can't start before they land. */
|
|
159
|
+
export declare function earlyStartWanted(req: Pick<RuntimeRequestRow, 'agent' | 'mode' | 'prompt' | 'resume_session_id' | 'entitybox_file_request'>, config: unknown): boolean;
|
|
148
160
|
type MemoryPack = Awaited<ReturnType<typeof fetchMemoryPack>>;
|
|
149
161
|
/**
|
|
150
162
|
* Setup for a turn with the model selector on. It does what the block in
|
|
@@ -153,6 +165,6 @@ type MemoryPack = Awaited<ReturnType<typeof fetchMemoryPack>>;
|
|
|
153
165
|
* turn. Claude reuses a resumed session's first system prompt, so its memory
|
|
154
166
|
* pack and pointer there are only read again after a compaction.
|
|
155
167
|
*/
|
|
156
|
-
export declare function prepareFastSetup(req: RuntimeRequestRow, cwd: string, installed: InstallResult, pack: Promise<MemoryPack>, startedAt: number): Promise<void>;
|
|
168
|
+
export declare function prepareFastSetup(req: RuntimeRequestRow, cwd: string, installed: InstallResult, pack: Promise<MemoryPack>, startedAt: number, onSystemPromptReady?: () => Promise<void>): Promise<void>;
|
|
157
169
|
export declare function runRequest(req: RuntimeRequestRow, entitybox?: EntityboxQueueServices): Promise<void>;
|
|
158
170
|
export {};
|
package/dist/session-runner.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.notifyFlowsIfFlowRequest = notifyFlowsIfFlowRequest;
|
|
|
43
43
|
exports.notifyWidgetstudioIfWidgetRequest = notifyWidgetstudioIfWidgetRequest;
|
|
44
44
|
exports.notifySelfImproveIfEnabled = notifySelfImproveIfEnabled;
|
|
45
45
|
exports.resumeSessionIsVoid = resumeSessionIsVoid;
|
|
46
|
+
exports.earlyStartWanted = earlyStartWanted;
|
|
46
47
|
exports.prepareFastSetup = prepareFastSetup;
|
|
47
48
|
exports.runRequest = runRequest;
|
|
48
49
|
const fs = __importStar(require("fs"));
|
|
@@ -1647,7 +1648,10 @@ function publishModelSelection(req, d, changed) {
|
|
|
1647
1648
|
}
|
|
1648
1649
|
/** This turn's model and effort, when MSP is on. Written onto req so a guardrail
|
|
1649
1650
|
* retry turn stays on the same model. Call it just before the spawn. */
|
|
1650
|
-
|
|
1651
|
+
function applyModelSelection(req) {
|
|
1652
|
+
return req.mspApplied ??= selectModelOnce(req);
|
|
1653
|
+
}
|
|
1654
|
+
async function selectModelOnce(req) {
|
|
1651
1655
|
const msp = req.mspDecision ? await req.mspDecision : null;
|
|
1652
1656
|
if (!msp)
|
|
1653
1657
|
return null;
|
|
@@ -1681,12 +1685,55 @@ function reportMcpTimings(req, file) {
|
|
|
1681
1685
|
catch { /* best-effort */ }
|
|
1682
1686
|
}
|
|
1683
1687
|
// ── claude --print ─────────────────────────────────────────────────────────
|
|
1688
|
+
function mcpDebugFileFor(req, msp) {
|
|
1689
|
+
return msp?.mcp_timing ? path.join(os.tmpdir(), `ainode-mcp-${req.id}.log`) : null;
|
|
1690
|
+
}
|
|
1691
|
+
function claudePrintInputFor(req, cwd, installed, msp) {
|
|
1692
|
+
const debugFile = mcpDebugFileFor(req, msp);
|
|
1693
|
+
return {
|
|
1694
|
+
workingDirectory: cwd,
|
|
1695
|
+
prompt: req.prompt ?? '',
|
|
1696
|
+
permissionMode: req.agent === 'claude-bypass' ? 'bypassPermissions' : (req.permission_mode ?? undefined),
|
|
1697
|
+
effortLevel: req.effort ?? undefined,
|
|
1698
|
+
model: (0, model_filter_1.modelForAgent)('claude', req.model),
|
|
1699
|
+
mcpConfigPath: installed.mcpConfigPath ?? undefined,
|
|
1700
|
+
resumeSessionId: req.resume_session_id ?? undefined,
|
|
1701
|
+
appendSystemPrompt: msp ? (0, time_context_1.appendChatTimeContext)(req.system_prompt) : (0, time_context_1.appendTimeContext)(req.system_prompt),
|
|
1702
|
+
allowedTools: req.allowed_tools ?? undefined,
|
|
1703
|
+
...(debugFile ? { debugFile } : {}),
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
/** The selector's early_start setting, on a turn it can work for. A new session
|
|
1707
|
+
* carries its memories in the system prompt, so that can't start before they land. */
|
|
1708
|
+
function earlyStartWanted(req, config) {
|
|
1709
|
+
if (process.env.ADDAI_EARLY_START === '0')
|
|
1710
|
+
return false;
|
|
1711
|
+
if (config?.early_start !== true)
|
|
1712
|
+
return false;
|
|
1713
|
+
return req.agent === 'claude' && req.mode === 'print' && !!req.prompt && !!req.resume_session_id
|
|
1714
|
+
&& req.entitybox_file_request !== true;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Start the CLI while the memory lookup is still running. Its start-up doesn't
|
|
1718
|
+
* need the message, so the two can overlap instead of one waiting for the other.
|
|
1719
|
+
* It's still one process for one turn, gone when the turn ends, so nothing stays
|
|
1720
|
+
* running on the node. If this fails, runClaudePrint spawns the normal way.
|
|
1721
|
+
*/
|
|
1722
|
+
async function startClaudeEarly(req, cwd, installed) {
|
|
1723
|
+
try {
|
|
1724
|
+
const msp = await applyModelSelection(req);
|
|
1725
|
+
const handle = (0, claude_print_1.spawnClaudePrint)({ ...claudePrintInputFor(req, cwd, installed, msp), prompt: '', deferPrompt: true });
|
|
1726
|
+
req.earlyStart = { handle, at: Date.now() };
|
|
1727
|
+
}
|
|
1728
|
+
catch (err) {
|
|
1729
|
+
console.error(`[setup] early start failed [reqId=${req.id}]: ${err.message}`);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1684
1732
|
async function runClaudePrint(req, cwd, installed) {
|
|
1685
1733
|
if (!req.prompt) {
|
|
1686
1734
|
await setStatus(req.id, { status: 'failed', errorCode: 'no_prompt', errorMessage: 'print mode requires a prompt' });
|
|
1687
1735
|
return;
|
|
1688
1736
|
}
|
|
1689
|
-
const permissionMode = req.agent === 'claude-bypass' ? 'bypassPermissions' : (req.permission_mode ?? undefined);
|
|
1690
1737
|
// Floor for "which subagent transcripts belong to this attempt" — see
|
|
1691
1738
|
// subagent-usage.ts. Taken before the spawn so nothing this run writes
|
|
1692
1739
|
// can fall outside the window.
|
|
@@ -1712,22 +1759,24 @@ async function runClaudePrint(req, cwd, installed) {
|
|
|
1712
1759
|
}
|
|
1713
1760
|
}
|
|
1714
1761
|
const msp = await applyModelSelection(req);
|
|
1715
|
-
const mcpDebugFile =
|
|
1762
|
+
const mcpDebugFile = mcpDebugFileFor(req, msp);
|
|
1763
|
+
// A CLI that started early and has already gone (a session it can't resume, a
|
|
1764
|
+
// bad flag) is passed over, so the normal spawn gives this turn its usual chance.
|
|
1765
|
+
const early = req.earlyStart && !req.earlyStart.handle.hasExited() ? req.earlyStart : null;
|
|
1766
|
+
req.earlyStart = undefined;
|
|
1716
1767
|
let handle;
|
|
1717
1768
|
try {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
...(mcpDebugFile ? { debugFile: mcpDebugFile } : {}),
|
|
1730
|
-
});
|
|
1769
|
+
if (early) {
|
|
1770
|
+
handle = early.handle;
|
|
1771
|
+
handle.sendPrompt?.(req.prompt);
|
|
1772
|
+
void emit(req.id, 'early_start', { lead_ms: Date.now() - early.at });
|
|
1773
|
+
}
|
|
1774
|
+
else {
|
|
1775
|
+
handle = (0, claude_print_1.spawnClaudePrint)({
|
|
1776
|
+
...claudePrintInputFor(req, cwd, installed, msp),
|
|
1777
|
+
...(inputBlocks ? { inputBlocks } : {}),
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1731
1780
|
}
|
|
1732
1781
|
catch (err) {
|
|
1733
1782
|
await failRequest(req, 'spawn_failed', err.message);
|
|
@@ -2393,7 +2442,7 @@ function launchAppsDirectly(cwd, installed) {
|
|
|
2393
2442
|
* turn. Claude reuses a resumed session's first system prompt, so its memory
|
|
2394
2443
|
* pack and pointer there are only read again after a compaction.
|
|
2395
2444
|
*/
|
|
2396
|
-
async function prepareFastSetup(req, cwd, installed, pack, startedAt) {
|
|
2445
|
+
async function prepareFastSetup(req, cwd, installed, pack, startedAt, onSystemPromptReady) {
|
|
2397
2446
|
const resumedClaude = req.agent !== 'codex' && !!req.resume_session_id;
|
|
2398
2447
|
const timing = { pack_ms: null };
|
|
2399
2448
|
try {
|
|
@@ -2452,6 +2501,8 @@ async function prepareFastSetup(req, cwd, installed, pack, startedAt) {
|
|
|
2452
2501
|
let recall = 'system_prompt';
|
|
2453
2502
|
if (resumedClaude) {
|
|
2454
2503
|
const decision = await req.mspDecision;
|
|
2504
|
+
// Only the message changes from here on, and that's what the pack is for.
|
|
2505
|
+
await onSystemPromptReady?.();
|
|
2455
2506
|
const found = await (decision?.picked === 'quick'
|
|
2456
2507
|
? Promise.race([pack, new Promise(done => setTimeout(() => done(null), QUICK_RECALL_WAIT_MS))])
|
|
2457
2508
|
: pack);
|
|
@@ -2603,10 +2654,11 @@ async function runRequest(req, entitybox) {
|
|
|
2603
2654
|
skills_installed: installed.skillSlugs,
|
|
2604
2655
|
});
|
|
2605
2656
|
}
|
|
2606
|
-
const
|
|
2657
|
+
const fastSetting = earlyPack ? await mspSetting : null;
|
|
2658
|
+
const fast = !!fastSetting;
|
|
2607
2659
|
if (fast) {
|
|
2608
2660
|
try {
|
|
2609
|
-
await prepareFastSetup(req, cwd, installed, earlyPack, startedAt);
|
|
2661
|
+
await prepareFastSetup(req, cwd, installed, earlyPack, startedAt, earlyStartWanted(req, fastSetting?.config) ? () => startClaudeEarly(req, cwd, installed) : undefined);
|
|
2610
2662
|
}
|
|
2611
2663
|
catch (err) {
|
|
2612
2664
|
console.error(`[setup] fast setup failed [reqId=${req.id}]: ${err.message}`);
|
|
@@ -2700,6 +2752,8 @@ async function runRequest(req, entitybox) {
|
|
|
2700
2752
|
}
|
|
2701
2753
|
}
|
|
2702
2754
|
finally {
|
|
2755
|
+
// An early start nothing went on to use. The turn is over, so it must not outlive it.
|
|
2756
|
+
req.earlyStart?.handle.kill();
|
|
2703
2757
|
// Best-effort cleanup of installed files. If the request crashed
|
|
2704
2758
|
// mid-flight we may leak files; next install run for the same cwd
|
|
2705
2759
|
// will overwrite them.
|
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.9",
|
|
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": [
|