@addai/node 0.30.6 → 0.30.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/capabilities.js +35 -6
- package/dist/claude-print.js +8 -1
- package/dist/claude-spawn.js +9 -2
- package/dist/claude-system-prompt.d.ts +18 -0
- package/dist/claude-system-prompt.js +110 -0
- package/dist/desktop/relay-client.js +62 -15
- package/dist/win.d.ts +6 -0
- package/dist/win.js +61 -6
- package/package.json +62 -62
package/dist/capabilities.js
CHANGED
|
@@ -92,16 +92,45 @@ function runCmd(cmd, args, timeoutMs = 5000) {
|
|
|
92
92
|
}
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
-
// Curated model lists baked into the daemon. The CLIs
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
95
|
+
// Curated model lists baked into the daemon. The CLIs still expose no
|
|
96
|
+
// non-interactive "list models" command, so this stays a constant — but
|
|
97
|
+
// WHAT goes in it is what decides whether the UI's dropdown rots.
|
|
98
|
+
//
|
|
99
|
+
// Do not "fix" this by probing the CLI. Checked against Claude Code
|
|
100
|
+
// 2.1.266: there is no `claude models` subcommand and no `claude config
|
|
101
|
+
// list`. Both strings are read as a PROMPT — the CLI starts a billed
|
|
102
|
+
// session and answers in prose. Probing that on every heartbeat would
|
|
103
|
+
// spend money several times a minute and still return nothing parseable.
|
|
104
|
+
//
|
|
105
|
+
// `claude --help` documents the real contract instead: --model takes
|
|
106
|
+
// "an alias for the latest model (e.g. 'fable', 'opus', or 'sonnet') or
|
|
107
|
+
// a model's full name (e.g. 'claude-fable-5')".
|
|
108
|
+
//
|
|
109
|
+
// So the ALIASES lead the list. They are the only entries that cannot go
|
|
110
|
+
// stale — the CLI resolves each to whatever is current in its tier — and
|
|
111
|
+
// staleness is exactly what went wrong before: this list was still
|
|
112
|
+
// offering claude-opus-4-1 and claude-sonnet-4-5 long after they were
|
|
113
|
+
// superseded, and no amount of heartbeating fixes a wrong constant.
|
|
114
|
+
// Pinned ids follow, newest first, for anyone who must hold one model.
|
|
115
|
+
//
|
|
116
|
+
// Availability is plan-gated (these nodes sign in with claude.ai
|
|
117
|
+
// subscriptions, not API keys), so a listed model can still be refused
|
|
118
|
+
// at spawn time — the CLI stays the source of truth for validity, the
|
|
119
|
+
// same way model-filter.ts already assumes.
|
|
99
120
|
const CLAUDE_MODELS = [
|
|
121
|
+
// Aliases — always the latest in their tier.
|
|
122
|
+
'opus',
|
|
123
|
+
'sonnet',
|
|
124
|
+
'haiku',
|
|
125
|
+
'fable',
|
|
126
|
+
// Pinned ids, newest first.
|
|
127
|
+
'claude-opus-5',
|
|
128
|
+
'claude-sonnet-5',
|
|
129
|
+
'claude-fable-5-1',
|
|
130
|
+
'claude-opus-4-8',
|
|
100
131
|
'claude-opus-4-7',
|
|
101
132
|
'claude-sonnet-4-6',
|
|
102
133
|
'claude-haiku-4-5',
|
|
103
|
-
'claude-opus-4-1',
|
|
104
|
-
'claude-sonnet-4-5',
|
|
105
134
|
];
|
|
106
135
|
const CODEX_MODELS = [
|
|
107
136
|
'gpt-5',
|
package/dist/claude-print.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.spawnClaudePrint = spawnClaudePrint;
|
|
|
11
11
|
const child_process_1 = require("child_process");
|
|
12
12
|
const claude_binary_1 = require("./claude-binary");
|
|
13
13
|
const win_1 = require("./win");
|
|
14
|
+
const claude_system_prompt_1 = require("./claude-system-prompt");
|
|
14
15
|
/**
|
|
15
16
|
* The text to emit for one flushed block, given what came before.
|
|
16
17
|
*
|
|
@@ -174,9 +175,13 @@ function spawnClaudePrint(input) {
|
|
|
174
175
|
// its node script instead (identity on POSIX). It can throw; so can
|
|
175
176
|
// spawn() itself (synchronously, on win32). Surface either as a normal
|
|
176
177
|
// rejected `done` so callers keep their single error path.
|
|
178
|
+
// An entity system prompt of tens of thousands of characters does not fit
|
|
179
|
+
// on a Windows command line — see claude-system-prompt.ts. No-op when it
|
|
180
|
+
// fits, which is every case that works today.
|
|
181
|
+
const sysPrompt = (0, claude_system_prompt_1.offloadLongSystemPrompt)(bin, args);
|
|
177
182
|
let proc;
|
|
178
183
|
try {
|
|
179
|
-
const inv = (0, win_1.resolveCliInvocation)(bin, args);
|
|
184
|
+
const inv = (0, win_1.resolveCliInvocation)(bin, sysPrompt.args);
|
|
180
185
|
proc = (0, child_process_1.spawn)(inv.file, inv.args, {
|
|
181
186
|
cwd: input.workingDirectory,
|
|
182
187
|
env: { ...process.env, TERM: 'dumb' },
|
|
@@ -187,6 +192,7 @@ function spawnClaudePrint(input) {
|
|
|
187
192
|
});
|
|
188
193
|
}
|
|
189
194
|
catch (err) {
|
|
195
|
+
sysPrompt.cleanup();
|
|
190
196
|
const done = Promise.reject(err instanceof Error ? err : new Error(String(err)));
|
|
191
197
|
// Mark handled now — the caller awaits `done` a few ticks later, and an
|
|
192
198
|
// already-rejected promise with no handler would fire unhandledRejection.
|
|
@@ -332,6 +338,7 @@ function spawnClaudePrint(input) {
|
|
|
332
338
|
const done = new Promise((resolve, reject) => {
|
|
333
339
|
proc.once('error', reject);
|
|
334
340
|
proc.once('exit', (code) => {
|
|
341
|
+
sysPrompt.cleanup();
|
|
335
342
|
flushThinking();
|
|
336
343
|
flushText();
|
|
337
344
|
const trimmed = stderrBuf.trim();
|
package/dist/claude-spawn.js
CHANGED
|
@@ -47,6 +47,7 @@ const path = __importStar(require("path"));
|
|
|
47
47
|
const crypto_1 = require("crypto");
|
|
48
48
|
const claude_binary_1 = require("./claude-binary");
|
|
49
49
|
const win_1 = require("./win");
|
|
50
|
+
const claude_system_prompt_1 = require("./claude-system-prompt");
|
|
50
51
|
const mcp_config_1 = require("./mcp-config");
|
|
51
52
|
const paths_1 = require("./paths");
|
|
52
53
|
function resolveMcpFilter(workingDirectory, enabled) {
|
|
@@ -150,6 +151,11 @@ function spawnClaudeForRuntime(input) {
|
|
|
150
151
|
...entityFlags,
|
|
151
152
|
...claudeArgs,
|
|
152
153
|
];
|
|
154
|
+
// Same overflow as claude-print: a long --append-system-prompt does not fit
|
|
155
|
+
// on a Windows command line. Applied before the branch so both the ConPTY
|
|
156
|
+
// and the POSIX shell form get the shortened argv; cleaned up in onExit
|
|
157
|
+
// beside the temp MCP config.
|
|
158
|
+
const sysPrompt = (0, claude_system_prompt_1.offloadLongSystemPrompt)(claudeBin, effectiveArgs);
|
|
153
159
|
let shell;
|
|
154
160
|
let shellArgs;
|
|
155
161
|
if (isWin) {
|
|
@@ -160,14 +166,14 @@ function spawnClaudeForRuntime(input) {
|
|
|
160
166
|
// node script (run via process.execPath) or passes an .exe straight
|
|
161
167
|
// through; either target parses argv via CommandLineToArgvW, matching
|
|
162
168
|
// node-pty's quoting. ConPTY handles both fine.
|
|
163
|
-
const inv = (0, win_1.resolveCliInvocation)(claudeBin,
|
|
169
|
+
const inv = (0, win_1.resolveCliInvocation)(claudeBin, sysPrompt.args);
|
|
164
170
|
shell = inv.file;
|
|
165
171
|
shellArgs = inv.args;
|
|
166
172
|
}
|
|
167
173
|
else {
|
|
168
174
|
// `exec "$@"` passes positional args straight through without shell escaping.
|
|
169
175
|
shell = process.env.SHELL || '/bin/bash';
|
|
170
|
-
shellArgs = ['-l', '-c', 'exec "$@"', '_', claudeBin, ...
|
|
176
|
+
shellArgs = ['-l', '-c', 'exec "$@"', '_', claudeBin, ...sysPrompt.args];
|
|
171
177
|
}
|
|
172
178
|
const proc = pty.spawn(shell, shellArgs, {
|
|
173
179
|
name: 'xterm-256color',
|
|
@@ -196,6 +202,7 @@ function spawnClaudeForRuntime(input) {
|
|
|
196
202
|
l(exitCode);
|
|
197
203
|
}
|
|
198
204
|
catch { }
|
|
205
|
+
sysPrompt.cleanup();
|
|
199
206
|
// best-effort cleanup of any temp MCP config
|
|
200
207
|
if (mcpConfigPath) {
|
|
201
208
|
try {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Rough rendered length of `file` + `args` as one command line. Counts two
|
|
2
|
+
* quotes and a separating space per argument — the shape a renderer uses for
|
|
3
|
+
* anything that might contain whitespace. */
|
|
4
|
+
export declare function commandLineLength(file: string, args: string[]): number;
|
|
5
|
+
export interface OffloadResult {
|
|
6
|
+
args: string[];
|
|
7
|
+
/** Removes the temp file, if one was written. Safe to call more than once. */
|
|
8
|
+
cleanup: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Swap an oversized inline system prompt for a file-backed one.
|
|
12
|
+
*
|
|
13
|
+
* Returns the args unchanged (and a no-op cleanup) whenever the command line
|
|
14
|
+
* already fits, whenever there is no --append-system-prompt to move, or when
|
|
15
|
+
* the temp file cannot be written — in that last case the spawn is left to
|
|
16
|
+
* fail with its own error rather than a worse one invented here.
|
|
17
|
+
*/
|
|
18
|
+
export declare function offloadLongSystemPrompt(file: string, args: string[], isWindows?: boolean): OffloadResult;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// A long --append-system-prompt does not fit on a Windows command line.
|
|
3
|
+
//
|
|
4
|
+
// CreateProcess caps the entire command line at 32,767 characters, and an
|
|
5
|
+
// entity's system prompt is routinely tens of thousands — Hannah's was 31,004,
|
|
6
|
+
// which with the user prompt and the rest of the flags overflowed, and every
|
|
7
|
+
// run died with a bare `spawn ENAMETOOLONG` and no hint of which argument was
|
|
8
|
+
// to blame. POSIX is nowhere near its limit at these sizes (ARG_MAX is ~256KB
|
|
9
|
+
// on macOS, ~2MB on Linux), which is why this only ever bit on Windows — and
|
|
10
|
+
// only once claude could be spawned there at all.
|
|
11
|
+
//
|
|
12
|
+
// claude takes the same text from a file, so when the rendered command line
|
|
13
|
+
// would not fit, the prompt is written to a temp file and the flag becomes
|
|
14
|
+
// --append-system-prompt-file. ONLY when it would not fit: the inline form is
|
|
15
|
+
// what every working install uses today and an older claude may not know the
|
|
16
|
+
// -file variant, so nothing that currently works changes shape.
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.commandLineLength = commandLineLength;
|
|
52
|
+
exports.offloadLongSystemPrompt = offloadLongSystemPrompt;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const os = __importStar(require("os"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const crypto_1 = require("crypto");
|
|
57
|
+
const INLINE_FLAG = '--append-system-prompt';
|
|
58
|
+
const FILE_FLAG = '--append-system-prompt-file';
|
|
59
|
+
/** Head-room under the real 32,767 limit. Both node and node-pty escape quotes
|
|
60
|
+
* and backslashes when they render argv into a command line, so what actually
|
|
61
|
+
* reaches CreateProcess is longer than the sum of the arguments. */
|
|
62
|
+
const WINDOWS_BUDGET = 30_000;
|
|
63
|
+
/** POSIX only needs catching if a prompt has gone genuinely pathological. */
|
|
64
|
+
const POSIX_BUDGET = 120_000;
|
|
65
|
+
/** Rough rendered length of `file` + `args` as one command line. Counts two
|
|
66
|
+
* quotes and a separating space per argument — the shape a renderer uses for
|
|
67
|
+
* anything that might contain whitespace. */
|
|
68
|
+
function commandLineLength(file, args) {
|
|
69
|
+
return args.reduce((n, a) => n + a.length + 3, file.length + 3);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Swap an oversized inline system prompt for a file-backed one.
|
|
73
|
+
*
|
|
74
|
+
* Returns the args unchanged (and a no-op cleanup) whenever the command line
|
|
75
|
+
* already fits, whenever there is no --append-system-prompt to move, or when
|
|
76
|
+
* the temp file cannot be written — in that last case the spawn is left to
|
|
77
|
+
* fail with its own error rather than a worse one invented here.
|
|
78
|
+
*/
|
|
79
|
+
function offloadLongSystemPrompt(file, args, isWindows = process.platform === 'win32') {
|
|
80
|
+
const noop = { args, cleanup: () => { } };
|
|
81
|
+
const budget = isWindows ? WINDOWS_BUDGET : POSIX_BUDGET;
|
|
82
|
+
if (commandLineLength(file, args) <= budget)
|
|
83
|
+
return noop;
|
|
84
|
+
const i = args.indexOf(INLINE_FLAG);
|
|
85
|
+
if (i < 0 || i + 1 >= args.length)
|
|
86
|
+
return noop;
|
|
87
|
+
const promptPath = path.join(os.tmpdir(), `ainode-sysprompt-${(0, crypto_1.randomUUID)()}.txt`);
|
|
88
|
+
try {
|
|
89
|
+
fs.writeFileSync(promptPath, args[i + 1], 'utf8');
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return noop;
|
|
93
|
+
}
|
|
94
|
+
const next = args.slice();
|
|
95
|
+
next[i] = FILE_FLAG;
|
|
96
|
+
next[i + 1] = promptPath;
|
|
97
|
+
let removed = false;
|
|
98
|
+
return {
|
|
99
|
+
args: next,
|
|
100
|
+
cleanup: () => {
|
|
101
|
+
if (removed)
|
|
102
|
+
return;
|
|
103
|
+
removed = true;
|
|
104
|
+
try {
|
|
105
|
+
fs.unlinkSync(promptPath);
|
|
106
|
+
}
|
|
107
|
+
catch { /* swept from tmp anyway */ }
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -71,13 +71,25 @@ const IDLE_CHECK_MS = 60_000;
|
|
|
71
71
|
* leaves a half-open socket that never fires 'close', so without this the
|
|
72
72
|
* client sits holding a dead handle and never reconnects. */
|
|
73
73
|
const PING_INTERVAL_MS = 30_000;
|
|
74
|
+
/** How often to ask the relay which deploy is answering. Cloud Run keeps a
|
|
75
|
+
* retiring instance alive until its in-flight connections end — for this
|
|
76
|
+
* socket, the hourly cut — so after a relay deploy this node sat attached to
|
|
77
|
+
* the OLD instance, pongs and all, while every viewer landed on the new one
|
|
78
|
+
* and was told the machine was offline. The relay's greeting names the
|
|
79
|
+
* instance this socket reached; /health is answered by the live one. When
|
|
80
|
+
* the two differ, move. */
|
|
81
|
+
const REVISION_CHECK_MS = 60_000;
|
|
82
|
+
const RELAY_HTTP_URL = RELAY_URL.replace(/^ws/, 'http');
|
|
74
83
|
let ws = null;
|
|
75
84
|
let stopped = true;
|
|
76
85
|
let backoff = RECONNECT_MIN_MS;
|
|
77
86
|
let idleTimer = null;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
/** Silences the CURRENT socket's timers; set when a socket opens so
|
|
88
|
+
* stopRelayClient can reach them. Each socket owns its own timers and miss
|
|
89
|
+
* count: a socket closing late used to clear the module-wide timer, which by
|
|
90
|
+
* then belonged to its replacement — leaving the live socket with no ping
|
|
91
|
+
* loop and no way to notice a dead link. */
|
|
92
|
+
let clearCurrentTimers = null;
|
|
81
93
|
/** viewerId -> that viewer's OWN TCP socket to the desktop's RFB port.
|
|
82
94
|
* Keyed per viewer, not per desktop: RFB is a stateful 1:1 protocol, so two
|
|
83
95
|
* people watching the same screen each need their own connection and their
|
|
@@ -157,16 +169,51 @@ async function connect() {
|
|
|
157
169
|
}
|
|
158
170
|
const sock = new ws_1.default(`${RELAY_URL}/node?token=${encodeURIComponent(token)}`);
|
|
159
171
|
ws = sock;
|
|
172
|
+
// Which deploy of the relay this socket reached, from its greeting. Null
|
|
173
|
+
// until the relay says — an older relay never does, and then there is
|
|
174
|
+
// nothing to compare and the check stays quiet.
|
|
175
|
+
let attachedRevision = null;
|
|
176
|
+
/** Set on every pong; cleared when a ping goes out. Two misses = dead. */
|
|
177
|
+
let missedPongs = 0;
|
|
178
|
+
let pingTimer = null;
|
|
179
|
+
let revisionTimer = null;
|
|
180
|
+
const clearTimers = () => {
|
|
181
|
+
if (pingTimer) {
|
|
182
|
+
clearInterval(pingTimer);
|
|
183
|
+
pingTimer = null;
|
|
184
|
+
}
|
|
185
|
+
if (revisionTimer) {
|
|
186
|
+
clearInterval(revisionTimer);
|
|
187
|
+
revisionTimer = null;
|
|
188
|
+
}
|
|
189
|
+
if (clearCurrentTimers === clearTimers)
|
|
190
|
+
clearCurrentTimers = null;
|
|
191
|
+
};
|
|
192
|
+
const checkRevision = async () => {
|
|
193
|
+
if (!attachedRevision || sock.readyState !== ws_1.default.OPEN)
|
|
194
|
+
return;
|
|
195
|
+
let live;
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch(`${RELAY_HTTP_URL}/health`, { signal: AbortSignal.timeout(8_000) });
|
|
198
|
+
live = (await res.json()).revision;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return;
|
|
202
|
+
} // could not ask; the ping loop still guards the link
|
|
203
|
+
if (live && live !== attachedRevision && sock.readyState === ws_1.default.OPEN) {
|
|
204
|
+
console.warn(`[relay] the relay moved from ${attachedRevision} to ${live} — reconnecting`);
|
|
205
|
+
sock.terminate(); // 'close' → retry → redial, onto the live instance
|
|
206
|
+
}
|
|
207
|
+
};
|
|
160
208
|
sock.on('open', () => {
|
|
161
209
|
backoff = RECONNECT_MIN_MS;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
clearInterval(liveTimer);
|
|
210
|
+
clearCurrentTimers?.();
|
|
211
|
+
clearCurrentTimers = clearTimers;
|
|
165
212
|
// A suspended Mac comes back with a socket that looks open and is not.
|
|
166
213
|
// Ping until two go unanswered, then tear it down so the normal backoff
|
|
167
214
|
// path reconnects - the daemon's own sleep detector reports the same
|
|
168
215
|
// suspends this is guarding against.
|
|
169
|
-
|
|
216
|
+
pingTimer = setInterval(() => {
|
|
170
217
|
if (sock.readyState !== ws_1.default.OPEN) {
|
|
171
218
|
sock.terminate();
|
|
172
219
|
return;
|
|
@@ -184,6 +231,7 @@ async function connect() {
|
|
|
184
231
|
sock.terminate();
|
|
185
232
|
}
|
|
186
233
|
}, PING_INTERVAL_MS);
|
|
234
|
+
revisionTimer = setInterval(() => { void checkRevision(); }, REVISION_CHECK_MS);
|
|
187
235
|
});
|
|
188
236
|
sock.on('pong', () => { missedPongs = 0; });
|
|
189
237
|
sock.on('message', async (raw) => {
|
|
@@ -208,6 +256,11 @@ async function connect() {
|
|
|
208
256
|
sock.send(JSON.stringify({ type: 'gone', viewerId, reason }));
|
|
209
257
|
}
|
|
210
258
|
};
|
|
259
|
+
if (msg.type === 'hello') {
|
|
260
|
+
attachedRevision = msg.revision ?? null;
|
|
261
|
+
console.log(`[relay] connected — relay revision ${attachedRevision ?? '?'}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
211
264
|
if (msg.type === 'attach') {
|
|
212
265
|
// Claim the viewer BEFORE the await, so a packet arriving during the
|
|
213
266
|
// lookup is held rather than treated as arriving after a lost bridge.
|
|
@@ -312,10 +365,7 @@ async function connect() {
|
|
|
312
365
|
}
|
|
313
366
|
});
|
|
314
367
|
const retry = () => {
|
|
315
|
-
|
|
316
|
-
clearInterval(liveTimer);
|
|
317
|
-
liveTimer = null;
|
|
318
|
-
}
|
|
368
|
+
clearTimers(); // only ever this socket's own
|
|
319
369
|
if (ws !== sock)
|
|
320
370
|
return; // superseded by a newer socket
|
|
321
371
|
dropBridges();
|
|
@@ -338,10 +388,7 @@ function stopRelayClient() {
|
|
|
338
388
|
clearTimeout(idleTimer);
|
|
339
389
|
idleTimer = null;
|
|
340
390
|
}
|
|
341
|
-
|
|
342
|
-
clearInterval(liveTimer);
|
|
343
|
-
liveTimer = null;
|
|
344
|
-
}
|
|
391
|
+
clearCurrentTimers?.();
|
|
345
392
|
dropBridges();
|
|
346
393
|
try {
|
|
347
394
|
ws?.close();
|
package/dist/win.d.ts
CHANGED
|
@@ -34,6 +34,11 @@ export declare function resolveCliInvocation(bin: string, args: string[]): CliIn
|
|
|
34
34
|
* pnpm/yarn-classic write `"%~dp0\<rel>.js"` (batch parameter expansion —
|
|
35
35
|
* note: no trailing `%`). */
|
|
36
36
|
declare function resolveNpmShimScript(shimPath: string): string | null;
|
|
37
|
+
/** Parse a .cmd shim for a `%~dp0`-relative NATIVE executable, for packages
|
|
38
|
+
* that ship one instead of a node script. Same two dialects and the same
|
|
39
|
+
* on-disk check as resolveNpmShimScript; deliberately a separate function so
|
|
40
|
+
* the npm path the auto-updater depends on is untouched by this. */
|
|
41
|
+
declare function resolveShimExecutable(shimPath: string): string | null;
|
|
37
42
|
/**
|
|
38
43
|
* Kill an agent child process AND everything it spawned.
|
|
39
44
|
*
|
|
@@ -99,5 +104,6 @@ export declare function linkDir(real: string, dest: string): void;
|
|
|
99
104
|
* worth exercising on any platform, not just on a Windows box. */
|
|
100
105
|
export declare const __testables: {
|
|
101
106
|
resolveNpmShimScript: typeof resolveNpmShimScript;
|
|
107
|
+
resolveShimExecutable: typeof resolveShimExecutable;
|
|
102
108
|
};
|
|
103
109
|
export {};
|
package/dist/win.js
CHANGED
|
@@ -203,11 +203,24 @@ function resolveCliInvocation(bin, args) {
|
|
|
203
203
|
}
|
|
204
204
|
if (/\.(cmd|bat)$/i.test(target)) {
|
|
205
205
|
const script = resolveNpmShimScript(target);
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
if (script)
|
|
207
|
+
return { file: process.execPath, args: [script, ...args] };
|
|
208
|
+
// Not every npm shim wraps a node script. A package may ship a native
|
|
209
|
+
// Windows binary and have its shim exec THAT:
|
|
210
|
+
//
|
|
211
|
+
// "%dp0%\\node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe" %*
|
|
212
|
+
//
|
|
213
|
+
// which is what @anthropic-ai/claude-code does now. There is no .js to
|
|
214
|
+
// find, so this threw — and the capabilities probe swallows the throw and
|
|
215
|
+
// reports the harness `available: false`. On a machine where `claude
|
|
216
|
+
// --version` worked in every shell, claude was simply missing from the
|
|
217
|
+
// picker. The old message's advice ('install the native Windows build')
|
|
218
|
+
// was already satisfied: the .exe sits inside the package the shim names.
|
|
219
|
+
const exe = resolveShimExecutable(target);
|
|
220
|
+
if (exe)
|
|
221
|
+
return { file: exe, args };
|
|
222
|
+
throw new Error(`cannot spawn npm shim safely: ${target} — it wraps neither a node script ` +
|
|
223
|
+
`nor an executable this can find. Reinstall the CLI via npm.`);
|
|
211
224
|
}
|
|
212
225
|
return { file: target, args };
|
|
213
226
|
}
|
|
@@ -245,6 +258,28 @@ function resolveNpmShimScript(shimPath) {
|
|
|
245
258
|
const re = /%(?:~dp0|dp0%)[\\/]([^"\s]+\.(?:m|c)?js)/gi;
|
|
246
259
|
for (const m of body.matchAll(re))
|
|
247
260
|
candidates.push(m[1]);
|
|
261
|
+
// ⚠️ The CLI entry point first — NOT the file's order.
|
|
262
|
+
//
|
|
263
|
+
// npm's own npm.cmd names two %~dp0 scripts, and the helper comes first:
|
|
264
|
+
//
|
|
265
|
+
// SET "NPM_PREFIX_JS=%~dp0\node_modules\npm\bin\npm-prefix.js"
|
|
266
|
+
// SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js"
|
|
267
|
+
// ...
|
|
268
|
+
// "%NODE_EXE%" "%NPM_CLI_JS%" %*
|
|
269
|
+
//
|
|
270
|
+
// Only that last line runs, and it runs NPM_CLI_JS. Taking the first that
|
|
271
|
+
// exists therefore spawned npm-prefix.js — and the reason this hid for so
|
|
272
|
+
// long is that doing so does not FAIL: it exits 0 and prints the npm prefix
|
|
273
|
+
// path. npmLatestVersion saw a clean exit whose output was not a version,
|
|
274
|
+
// returned null, and the auto-updater logged "could not read the latest
|
|
275
|
+
// published version" on every tick. A Windows node then sat for ever on the
|
|
276
|
+
// version it was installed with — the machine this was found on was still
|
|
277
|
+
// running 0.30.3 while 0.30.6 was published.
|
|
278
|
+
//
|
|
279
|
+
// Every package bin shim names exactly one script and is unaffected: the
|
|
280
|
+
// sort is stable, so with nothing to promote nothing moves.
|
|
281
|
+
const isCliEntry = (rel) => /(^|[\\/])(npm|npx)-cli\.(m|c)?js$/i.test(rel);
|
|
282
|
+
candidates.sort((a, b) => Number(isCliEntry(b)) - Number(isCliEntry(a)));
|
|
248
283
|
for (const rel of candidates) {
|
|
249
284
|
// Shims always write backslashes. Split on either separator and re-join
|
|
250
285
|
// with the platform's, so the same parsing is exercisable off Windows —
|
|
@@ -256,6 +291,26 @@ function resolveNpmShimScript(shimPath) {
|
|
|
256
291
|
}
|
|
257
292
|
return null;
|
|
258
293
|
}
|
|
294
|
+
/** Parse a .cmd shim for a `%~dp0`-relative NATIVE executable, for packages
|
|
295
|
+
* that ship one instead of a node script. Same two dialects and the same
|
|
296
|
+
* on-disk check as resolveNpmShimScript; deliberately a separate function so
|
|
297
|
+
* the npm path the auto-updater depends on is untouched by this. */
|
|
298
|
+
function resolveShimExecutable(shimPath) {
|
|
299
|
+
let body;
|
|
300
|
+
try {
|
|
301
|
+
body = fs.readFileSync(shimPath, 'utf8');
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
const re = /%(?:~dp0|dp0%)[\\/]([^"\s]+\.exe)/gi;
|
|
307
|
+
for (const m of body.matchAll(re)) {
|
|
308
|
+
const exe = path.join(path.dirname(shimPath), ...m[1].split(/[\\/]+/));
|
|
309
|
+
if (fs.existsSync(exe))
|
|
310
|
+
return exe;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
259
314
|
/** How long a POSIX agent gets to honour SIGINT before we stop asking. */
|
|
260
315
|
const KILL_ESCALATE_MS = 3000;
|
|
261
316
|
/** One `ps` snapshot as (pid, ppid) pairs. Empty on any failure — callers
|
|
@@ -444,4 +499,4 @@ function linkDir(real, dest) {
|
|
|
444
499
|
}
|
|
445
500
|
/** Exposed for tests only: the shim parsing is pure file-shape logic and is
|
|
446
501
|
* worth exercising on any platform, not just on a Windows box. */
|
|
447
|
-
exports.__testables = { resolveNpmShimScript };
|
|
502
|
+
exports.__testables = { resolveNpmShimScript, resolveShimExecutable };
|
package/package.json
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@addai/node",
|
|
3
|
-
"version": "0.30.
|
|
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
|
-
"license": "MIT",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"addai",
|
|
8
|
-
"entity-studio",
|
|
9
|
-
"claude",
|
|
10
|
-
"codex",
|
|
11
|
-
"kimi",
|
|
12
|
-
"gemini",
|
|
13
|
-
"agent",
|
|
14
|
-
"daemon",
|
|
15
|
-
"supabase",
|
|
16
|
-
"mcp"
|
|
17
|
-
],
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/just-AddAi/addai-entity-runtime.git"
|
|
21
|
-
},
|
|
22
|
-
"homepage": "https://github.com/just-AddAi/addai-entity-runtime#readme",
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/just-AddAi/addai-entity-runtime/issues"
|
|
25
|
-
},
|
|
26
|
-
"type": "commonjs",
|
|
27
|
-
"main": "dist/index.js",
|
|
28
|
-
"bin": {
|
|
29
|
-
"ainode": "dist/cli.js"
|
|
30
|
-
},
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"dist",
|
|
36
|
-
"scripts",
|
|
37
|
-
"README.md",
|
|
38
|
-
"assets"
|
|
39
|
-
],
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "tsc -p tsconfig.json && node scripts/copy-assets.js",
|
|
42
|
-
"start": "node dist/cli.js",
|
|
43
|
-
"dev": "tsc -p tsconfig.json && node dist/cli.js",
|
|
44
|
-
"test": "npm run build && node --test test/*.test.mjs",
|
|
45
|
-
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
46
|
-
"postinstall": "node scripts/fix-pty-helper.js",
|
|
47
|
-
"prepublishOnly": "npm run build"
|
|
48
|
-
},
|
|
49
|
-
"engines": {
|
|
50
|
-
"node": ">=18"
|
|
51
|
-
},
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"@addai/node-flows": "^1.0.0",
|
|
54
|
-
"node-pty": "^1.1.0",
|
|
55
|
-
"ws": "^8.21.3"
|
|
56
|
-
},
|
|
57
|
-
"devDependencies": {
|
|
58
|
-
"@types/node": "^20.0.0",
|
|
59
|
-
"@types/ws": "^8.18.1",
|
|
60
|
-
"typescript": "^5.6.0"
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@addai/node",
|
|
3
|
+
"version": "0.30.9",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"addai",
|
|
8
|
+
"entity-studio",
|
|
9
|
+
"claude",
|
|
10
|
+
"codex",
|
|
11
|
+
"kimi",
|
|
12
|
+
"gemini",
|
|
13
|
+
"agent",
|
|
14
|
+
"daemon",
|
|
15
|
+
"supabase",
|
|
16
|
+
"mcp"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/just-AddAi/addai-entity-runtime.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/just-AddAi/addai-entity-runtime#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/just-AddAi/addai-entity-runtime/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"main": "dist/index.js",
|
|
28
|
+
"bin": {
|
|
29
|
+
"ainode": "dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"scripts",
|
|
37
|
+
"README.md",
|
|
38
|
+
"assets"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-assets.js",
|
|
42
|
+
"start": "node dist/cli.js",
|
|
43
|
+
"dev": "tsc -p tsconfig.json && node dist/cli.js",
|
|
44
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
45
|
+
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
46
|
+
"postinstall": "node scripts/fix-pty-helper.js",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@addai/node-flows": "^1.0.0",
|
|
54
|
+
"node-pty": "^1.1.0",
|
|
55
|
+
"ws": "^8.21.3"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^20.0.0",
|
|
59
|
+
"@types/ws": "^8.18.1",
|
|
60
|
+
"typescript": "^5.6.0"
|
|
61
|
+
}
|
|
62
|
+
}
|