@addai/node 0.4.0
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/README.md +96 -0
- package/dist/agent-auth.d.ts +9 -0
- package/dist/agent-auth.js +96 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.js +164 -0
- package/dist/capabilities.d.ts +64 -0
- package/dist/capabilities.js +406 -0
- package/dist/claude-binary.d.ts +16 -0
- package/dist/claude-binary.js +145 -0
- package/dist/claude-config.d.ts +11 -0
- package/dist/claude-config.js +144 -0
- package/dist/claude-print.d.ts +35 -0
- package/dist/claude-print.js +312 -0
- package/dist/claude-spawn.d.ts +40 -0
- package/dist/claude-spawn.js +220 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/codex-binary.d.ts +4 -0
- package/dist/codex-binary.js +70 -0
- package/dist/codex-spawn.d.ts +47 -0
- package/dist/codex-spawn.js +320 -0
- package/dist/command-runner.d.ts +9 -0
- package/dist/command-runner.js +592 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +38 -0
- package/dist/control-server.d.ts +5 -0
- package/dist/control-server.js +292 -0
- package/dist/diskguard.d.ts +96 -0
- package/dist/diskguard.js +435 -0
- package/dist/error-classify.d.ts +20 -0
- package/dist/error-classify.js +159 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.js +97 -0
- package/dist/gemini-binary.d.ts +4 -0
- package/dist/gemini-binary.js +64 -0
- package/dist/gemini-spawn.d.ts +52 -0
- package/dist/gemini-spawn.js +369 -0
- package/dist/git-identity.d.ts +18 -0
- package/dist/git-identity.js +104 -0
- package/dist/grok-binary.d.ts +4 -0
- package/dist/grok-binary.js +70 -0
- package/dist/grok-spawn.d.ts +41 -0
- package/dist/grok-spawn.js +495 -0
- package/dist/guardrails.d.ts +32 -0
- package/dist/guardrails.js +125 -0
- package/dist/hang-watchdog.d.ts +17 -0
- package/dist/hang-watchdog.js +48 -0
- package/dist/harness-registry.d.ts +55 -0
- package/dist/harness-registry.js +180 -0
- package/dist/heartbeat.d.ts +13 -0
- package/dist/heartbeat.js +95 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +440 -0
- package/dist/install.d.ts +63 -0
- package/dist/install.js +467 -0
- package/dist/jsonl-tail.d.ts +20 -0
- package/dist/jsonl-tail.js +192 -0
- package/dist/kimi-binary.d.ts +4 -0
- package/dist/kimi-binary.js +68 -0
- package/dist/kimi-spawn.d.ts +47 -0
- package/dist/kimi-spawn.js +319 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +169 -0
- package/dist/mcp-config.d.ts +23 -0
- package/dist/mcp-config.js +133 -0
- package/dist/memory-capture.d.ts +20 -0
- package/dist/memory-capture.js +45 -0
- package/dist/memory-pack.d.ts +34 -0
- package/dist/memory-pack.js +171 -0
- package/dist/model-filter.d.ts +4 -0
- package/dist/model-filter.js +34 -0
- package/dist/node-health.d.ts +18 -0
- package/dist/node-health.js +74 -0
- package/dist/pairing.d.ts +20 -0
- package/dist/pairing.js +129 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +48 -0
- package/dist/precompact-hook.d.ts +27 -0
- package/dist/precompact-hook.js +136 -0
- package/dist/projects.d.ts +25 -0
- package/dist/projects.js +255 -0
- package/dist/prompt-keystrokes.d.ts +3 -0
- package/dist/prompt-keystrokes.js +56 -0
- package/dist/pty-helper.d.ts +26 -0
- package/dist/pty-helper.js +156 -0
- package/dist/request-pump.d.ts +12 -0
- package/dist/request-pump.js +249 -0
- package/dist/run-retry.d.ts +28 -0
- package/dist/run-retry.js +81 -0
- package/dist/self-update.d.ts +75 -0
- package/dist/self-update.js +192 -0
- package/dist/session-runner.d.ts +130 -0
- package/dist/session-runner.js +2271 -0
- package/dist/sleep-detector.d.ts +11 -0
- package/dist/sleep-detector.js +32 -0
- package/dist/state-migrate.d.ts +8 -0
- package/dist/state-migrate.js +91 -0
- package/dist/store.d.ts +5 -0
- package/dist/store.js +82 -0
- package/dist/supabase-client.d.ts +15 -0
- package/dist/supabase-client.js +119 -0
- package/dist/time-context.d.ts +10 -0
- package/dist/time-context.js +71 -0
- package/dist/tokens.d.ts +4 -0
- package/dist/tokens.js +33 -0
- package/dist/tui/app.d.ts +35 -0
- package/dist/tui/app.js +82 -0
- package/dist/tui/console-capture.d.ts +13 -0
- package/dist/tui/console-capture.js +62 -0
- package/dist/tui/dashboard.d.ts +42 -0
- package/dist/tui/dashboard.js +205 -0
- package/dist/tui/data.d.ts +57 -0
- package/dist/tui/data.js +62 -0
- package/dist/tui/harnesses.d.ts +16 -0
- package/dist/tui/harnesses.js +354 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.js +116 -0
- package/dist/tui/requests.d.ts +18 -0
- package/dist/tui/requests.js +127 -0
- package/dist/tui/run.d.ts +13 -0
- package/dist/tui/run.js +178 -0
- package/dist/tui/transcript.d.ts +39 -0
- package/dist/tui/transcript.js +234 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +3 -0
- package/dist/win.d.ts +67 -0
- package/dist/win.js +317 -0
- package/package.json +60 -0
- package/scripts/fix-pty-helper.js +28 -0
- package/scripts/precompact-capture.js +292 -0
- package/scripts/smoke-test.sh +74 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Spawn a Claude Code session in a pseudoterminal. The runtime never
|
|
3
|
+
// renders a TUI, so we only need:
|
|
4
|
+
// - the PTY (node-pty) so Claude thinks it has a terminal
|
|
5
|
+
// - onData callback so the request runner can observe startup output
|
|
6
|
+
// and inject Enter to submit the initial prompt
|
|
7
|
+
// - onExit so sessions clean up
|
|
8
|
+
// - write() so callers can send prompts
|
|
9
|
+
// - the JSONL path on disk so the tail can stream clean events
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.spawnClaudeForRuntime = spawnClaudeForRuntime;
|
|
45
|
+
const pty = __importStar(require("node-pty"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const crypto_1 = require("crypto");
|
|
48
|
+
const claude_binary_1 = require("./claude-binary");
|
|
49
|
+
const win_1 = require("./win");
|
|
50
|
+
const mcp_config_1 = require("./mcp-config");
|
|
51
|
+
const paths_1 = require("./paths");
|
|
52
|
+
function resolveMcpFilter(workingDirectory, enabled) {
|
|
53
|
+
if (!enabled)
|
|
54
|
+
return null;
|
|
55
|
+
const all = (0, mcp_config_1.listMcps)(workingDirectory).map(s => s.name);
|
|
56
|
+
// If the request lists every installed server, no filter needed.
|
|
57
|
+
if (enabled.length === all.length && enabled.every(n => all.includes(n)))
|
|
58
|
+
return null;
|
|
59
|
+
return enabled;
|
|
60
|
+
}
|
|
61
|
+
function spawnClaudeForRuntime(input) {
|
|
62
|
+
const ptyId = (0, crypto_1.randomUUID)();
|
|
63
|
+
const sessionId = (0, crypto_1.randomUUID)();
|
|
64
|
+
const claudeBin = (0, claude_binary_1.findClaudeBinary)();
|
|
65
|
+
// No claude on this runtime — fail THIS request cleanly rather than
|
|
66
|
+
// spawning a bare 'claude' that ENOENTs and crashes the daemon. The
|
|
67
|
+
// caller (runClaudeTui) wraps this in try/catch → per-request 'spawn_failed'.
|
|
68
|
+
if (!claudeBin) {
|
|
69
|
+
throw new Error('claude CLI not found on this runtime — install it from Studio → Runtimes → Harnesses, ' +
|
|
70
|
+
'or run `npm i -g @anthropic-ai/claude-code`.');
|
|
71
|
+
}
|
|
72
|
+
const isWin = process.platform === 'win32';
|
|
73
|
+
// Build CLI flags.
|
|
74
|
+
const permissionFlags = [];
|
|
75
|
+
if (input.permissionMode === 'bypassPermissions') {
|
|
76
|
+
permissionFlags.push('--dangerously-skip-permissions');
|
|
77
|
+
}
|
|
78
|
+
else if (input.permissionMode) {
|
|
79
|
+
permissionFlags.push('--permission-mode', input.permissionMode);
|
|
80
|
+
}
|
|
81
|
+
const effortFlags = input.effortLevel ? ['--effort', input.effortLevel] : [];
|
|
82
|
+
// Must include 'user' so the pre-written `~/.claude/settings.json` with
|
|
83
|
+
// `skipDangerousModePermissionPrompt: true` is honored. Without user
|
|
84
|
+
// scope, Claude re-prompts the "WARNING: Bypass Permissions" dialog on
|
|
85
|
+
// every spawn → the TUI hangs forever waiting for confirmation → the
|
|
86
|
+
// daemon sees `ready` then `transcript_slow` 30s later and nothing
|
|
87
|
+
// else (the smoking-gun pattern we were chasing).
|
|
88
|
+
//
|
|
89
|
+
// We exclude 'local' on purpose: ~/.claude/settings.local.json can
|
|
90
|
+
// declare its own mcpServers that would leak through --strict-mcp-config.
|
|
91
|
+
// 'user' (settings.json only) is safe — mcpServers in user settings are
|
|
92
|
+
// still overridden by our --mcp-config + --strict-mcp-config flags.
|
|
93
|
+
const settingSources = ['--setting-sources', 'user,project'];
|
|
94
|
+
const mcpFlags = [];
|
|
95
|
+
let mcpConfigPath = null;
|
|
96
|
+
if (input.mcpConfigPathOverride) {
|
|
97
|
+
// install.ts already wrote a complete .mcp.json from the registry;
|
|
98
|
+
// skip the local-catalog filter entirely.
|
|
99
|
+
mcpConfigPath = input.mcpConfigPathOverride;
|
|
100
|
+
mcpFlags.push('--mcp-config', mcpConfigPath, '--strict-mcp-config');
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const filter = resolveMcpFilter(input.workingDirectory, input.enabledMcps);
|
|
104
|
+
if (filter !== null) {
|
|
105
|
+
try {
|
|
106
|
+
mcpConfigPath = (0, mcp_config_1.writeSessionMcpConfig)(ptyId, input.workingDirectory, filter);
|
|
107
|
+
if (mcpConfigPath) {
|
|
108
|
+
mcpFlags.push('--mcp-config', mcpConfigPath, '--strict-mcp-config');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.error('[runtime] failed to write session MCP config', err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// --resume <id> attaches to an existing transcript; otherwise we
|
|
117
|
+
// mint a fresh session-id so the JSONL path is predictable for tailing.
|
|
118
|
+
const effectiveSessionId = input.resumeSessionId || sessionId;
|
|
119
|
+
const entityFlags = [];
|
|
120
|
+
if (input.appendSystemPrompt && input.appendSystemPrompt.trim().length > 0) {
|
|
121
|
+
entityFlags.push('--append-system-prompt', input.appendSystemPrompt);
|
|
122
|
+
}
|
|
123
|
+
if (Array.isArray(input.allowedTools) && input.allowedTools.length > 0) {
|
|
124
|
+
// Mirror claude-print: skip the flag entirely for an empty array.
|
|
125
|
+
// An empty `--allowed-tools ` value would hand claude an EMPTY allowlist
|
|
126
|
+
// (no tools at all) instead of inheriting its defaults — tool-blocking
|
|
127
|
+
// the entity. Empty array = "inherit defaults", same as undefined.
|
|
128
|
+
entityFlags.push('--allowed-tools', input.allowedTools.join(','));
|
|
129
|
+
}
|
|
130
|
+
// Model override — without this, claude uses its CLI default regardless
|
|
131
|
+
// of what the user picked in the composer.
|
|
132
|
+
const modelFlags = (input.model && input.model.trim())
|
|
133
|
+
? ['--model', input.model.trim()]
|
|
134
|
+
: [];
|
|
135
|
+
// We never pass the prompt as argv anymore. Claude's TUI behavior with
|
|
136
|
+
// `--resume <id> <prompt>` was concatenating turn 2's prompt onto the
|
|
137
|
+
// previous turn instead of submitting it as a fresh user message. The
|
|
138
|
+
// daemon now types the prompt directly into the PTY after the TUI has
|
|
139
|
+
// settled — same input path for first-turn and continuation-turn, so
|
|
140
|
+
// both are reliable.
|
|
141
|
+
const claudeArgs = input.resumeSessionId
|
|
142
|
+
? ['--resume', input.resumeSessionId]
|
|
143
|
+
: ['--session-id', sessionId];
|
|
144
|
+
const effectiveArgs = [
|
|
145
|
+
...permissionFlags,
|
|
146
|
+
...effortFlags,
|
|
147
|
+
...modelFlags,
|
|
148
|
+
...settingSources,
|
|
149
|
+
...mcpFlags,
|
|
150
|
+
...entityFlags,
|
|
151
|
+
...claudeArgs,
|
|
152
|
+
];
|
|
153
|
+
let shell;
|
|
154
|
+
let shellArgs;
|
|
155
|
+
if (isWin) {
|
|
156
|
+
// NO cmd.exe: node-pty joins argv with CommandLineToArgvW quoting, which
|
|
157
|
+
// cmd.exe re-parses by its own rules — paths with spaces break, and
|
|
158
|
+
// --append-system-prompt text (quotes/%/newlines) would be interpreted
|
|
159
|
+
// as shell syntax. resolveCliInvocation unwraps an npm .cmd shim to its
|
|
160
|
+
// node script (run via process.execPath) or passes an .exe straight
|
|
161
|
+
// through; either target parses argv via CommandLineToArgvW, matching
|
|
162
|
+
// node-pty's quoting. ConPTY handles both fine.
|
|
163
|
+
const inv = (0, win_1.resolveCliInvocation)(claudeBin, effectiveArgs);
|
|
164
|
+
shell = inv.file;
|
|
165
|
+
shellArgs = inv.args;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// `exec "$@"` passes positional args straight through without shell escaping.
|
|
169
|
+
shell = process.env.SHELL || '/bin/bash';
|
|
170
|
+
shellArgs = ['-l', '-c', 'exec "$@"', '_', claudeBin, ...effectiveArgs];
|
|
171
|
+
}
|
|
172
|
+
const proc = pty.spawn(shell, shellArgs, {
|
|
173
|
+
name: 'xterm-256color',
|
|
174
|
+
cols: 120,
|
|
175
|
+
rows: 30,
|
|
176
|
+
cwd: input.workingDirectory,
|
|
177
|
+
env: {
|
|
178
|
+
...process.env,
|
|
179
|
+
TERM: 'xterm-256color',
|
|
180
|
+
COLORTERM: 'truecolor',
|
|
181
|
+
...(input.extraEnv ?? {}),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
const dataListeners = [];
|
|
185
|
+
proc.onData((data) => {
|
|
186
|
+
for (const l of dataListeners)
|
|
187
|
+
try {
|
|
188
|
+
l(data);
|
|
189
|
+
}
|
|
190
|
+
catch { }
|
|
191
|
+
});
|
|
192
|
+
const exitListeners = [];
|
|
193
|
+
proc.onExit(({ exitCode }) => {
|
|
194
|
+
for (const l of exitListeners)
|
|
195
|
+
try {
|
|
196
|
+
l(exitCode);
|
|
197
|
+
}
|
|
198
|
+
catch { }
|
|
199
|
+
// best-effort cleanup of any temp MCP config
|
|
200
|
+
if (mcpConfigPath) {
|
|
201
|
+
try {
|
|
202
|
+
require('fs').unlinkSync(mcpConfigPath);
|
|
203
|
+
}
|
|
204
|
+
catch { }
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
const jsonlPath = path.join(paths_1.CLAUDE_PROJECTS_DIR, (0, claude_binary_1.encodeProjectPath)(input.workingDirectory), `${effectiveSessionId}.jsonl`);
|
|
208
|
+
return {
|
|
209
|
+
ptyId,
|
|
210
|
+
sessionId: effectiveSessionId,
|
|
211
|
+
jsonlPath,
|
|
212
|
+
write: (data) => proc.write(data),
|
|
213
|
+
kill: () => { try {
|
|
214
|
+
proc.kill();
|
|
215
|
+
}
|
|
216
|
+
catch { } },
|
|
217
|
+
onData: cb => { dataListeners.push(cb); },
|
|
218
|
+
onExit: cb => { exitListeners.push(cb); },
|
|
219
|
+
};
|
|
220
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// ainode CLI — the +Ai Node console.
|
|
4
|
+
//
|
|
5
|
+
// ainode run this node: daemon + live dashboard
|
|
6
|
+
// ainode harnesses interactive harness manager (install / login)
|
|
7
|
+
// ainode unpair --yes disconnect from +Ai, cancel in-flight work
|
|
8
|
+
// ainode version print version
|
|
9
|
+
//
|
|
10
|
+
// With no args on a terminal, the daemon starts and the dashboard takes
|
|
11
|
+
// over the screen. Piped or headless (nohup/launchd) it prints plain log
|
|
12
|
+
// lines instead, so the daemon log never fills with escape sequences. If a
|
|
13
|
+
// daemon is already running, this attaches to it read-only rather than
|
|
14
|
+
// refusing to start.
|
|
15
|
+
//
|
|
16
|
+
// Unpair reaches Supabase via the daemon token in ~/.ainode/state.json and
|
|
17
|
+
// does not require the daemon to be running.
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
const lockfile_1 = require("./lockfile");
|
|
20
|
+
const index_1 = require("./index");
|
|
21
|
+
const store_1 = require("./store");
|
|
22
|
+
const supabase_client_1 = require("./supabase-client");
|
|
23
|
+
const harnesses_1 = require("./tui/harnesses");
|
|
24
|
+
const run_1 = require("./tui/run");
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
function token() {
|
|
27
|
+
const p = (0, store_1.readPairing)();
|
|
28
|
+
if (!p) {
|
|
29
|
+
console.error('Not paired. Run `ainode` (no args) to start the pairing flow.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return p.daemonToken;
|
|
33
|
+
}
|
|
34
|
+
async function cmdUnpair() {
|
|
35
|
+
if (!(0, store_1.isPaired)()) {
|
|
36
|
+
console.log('Not paired — nothing to do.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const confirm = args.includes('--yes') || args.includes('-y');
|
|
40
|
+
if (!confirm) {
|
|
41
|
+
console.log('This will disconnect the runtime from +Ai and cancel any in-flight requests.');
|
|
42
|
+
console.log('Re-run with --yes to confirm.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const r = await (0, supabase_client_1.rpc)('runtime_self_unpair', { p_token: token() });
|
|
47
|
+
if (r?.cancelled_requests) {
|
|
48
|
+
console.log(`Cancelled ${r.cancelled_requests} in-flight request(s).`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error(`server unpair failed (continuing with local cleanup): ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
(0, store_1.clearPairing)();
|
|
55
|
+
console.log('Unpaired. Run `ainode` to pair again.');
|
|
56
|
+
}
|
|
57
|
+
async function main() {
|
|
58
|
+
const cmd = args[0];
|
|
59
|
+
if (!cmd || cmd === 'run' || cmd === 'start' || cmd === '--run') {
|
|
60
|
+
const tui = (0, run_1.shouldRenderTui)({
|
|
61
|
+
isTTY: Boolean(process.stdout.isTTY),
|
|
62
|
+
stdinTTY: Boolean(process.stdin.isTTY),
|
|
63
|
+
noTui: process.env.AINODE_NO_TUI === '1',
|
|
64
|
+
});
|
|
65
|
+
// A daemon may already own this node. Rather than refusing, attach
|
|
66
|
+
// read-only so "is it running?" is answerable from any terminal.
|
|
67
|
+
//
|
|
68
|
+
// Only when it is genuinely still running, though. A crashed daemon
|
|
69
|
+
// leaves its lockfile behind, and treating that as an owner sent every
|
|
70
|
+
// later `ainode` into the viewer and returned before start() — so a node
|
|
71
|
+
// could never come back from a crash without someone deleting the file
|
|
72
|
+
// by hand. Same liveness check acquireLockfile() uses.
|
|
73
|
+
const existing = (0, lockfile_1.readLockfile)();
|
|
74
|
+
if (existing && (0, lockfile_1.processAlive)(existing.pid) && tui) {
|
|
75
|
+
await (0, run_1.runDashboard)({
|
|
76
|
+
viewerMode: true,
|
|
77
|
+
pid: existing.pid,
|
|
78
|
+
startedAt: existing.startedAt ?? null,
|
|
79
|
+
version: index_1.VERSION,
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const runtime = await (0, index_1.start)();
|
|
84
|
+
if (!tui) {
|
|
85
|
+
console.log(`ainode v${index_1.VERSION} running (pid ${runtime.pid}, port ${runtime.port})`);
|
|
86
|
+
if (!(0, store_1.isPaired)())
|
|
87
|
+
console.log('Not paired yet — pairing flow comes online in the next build.');
|
|
88
|
+
await new Promise(() => { });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
await (0, run_1.runDashboard)({
|
|
92
|
+
viewerMode: false,
|
|
93
|
+
pid: runtime.pid,
|
|
94
|
+
startedAt: Date.now(),
|
|
95
|
+
version: index_1.VERSION,
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
switch (cmd) {
|
|
100
|
+
case 'harnesses':
|
|
101
|
+
await (0, harnesses_1.runHarnessesTui)();
|
|
102
|
+
return;
|
|
103
|
+
case 'unpair':
|
|
104
|
+
await cmdUnpair();
|
|
105
|
+
return;
|
|
106
|
+
case 'version':
|
|
107
|
+
case '--version':
|
|
108
|
+
case '-v':
|
|
109
|
+
console.log(index_1.VERSION);
|
|
110
|
+
return;
|
|
111
|
+
case 'help':
|
|
112
|
+
case '--help':
|
|
113
|
+
case '-h':
|
|
114
|
+
console.log(`ainode v${index_1.VERSION}
|
|
115
|
+
|
|
116
|
+
usage:
|
|
117
|
+
ainode run this +Ai Node — daemon + live dashboard
|
|
118
|
+
ainode harnesses install / log in agent harnesses (interactive)
|
|
119
|
+
ainode unpair --yes disconnect from +Ai
|
|
120
|
+
ainode version print version
|
|
121
|
+
|
|
122
|
+
Piped or headless output falls back to plain log lines. Set AINODE_NO_TUI=1
|
|
123
|
+
to force that on a terminal.
|
|
124
|
+
|
|
125
|
+
Pair the node by running it with no args and following the prompt
|
|
126
|
+
to vault.add.ai/entity/connect/<CODE>.
|
|
127
|
+
`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
console.error(`unknown command: ${cmd}`);
|
|
131
|
+
console.error("try 'ainode help'");
|
|
132
|
+
process.exit(2);
|
|
133
|
+
}
|
|
134
|
+
main().catch(err => {
|
|
135
|
+
console.error(err.message || err);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Locate the `codex` binary (OpenAI codex-cli) on the host.
|
|
3
|
+
// `npm install -g @openai/codex-cli` puts it on PATH as `codex`
|
|
4
|
+
// (a .cmd shim under %APPDATA%\npm on Windows).
|
|
5
|
+
//
|
|
6
|
+
// Before this existed, codex-spawn.ts called `spawn('codex', ...)` bare —
|
|
7
|
+
// which fails with ENOENT when the daemon's PATH (especially under
|
|
8
|
+
// launchd / desktop-app spawn) doesn't include npm-global's bin dir.
|
|
9
|
+
// The ENOENT rejected the spawn promise BEFORE session_end could be
|
|
10
|
+
// emitted, leaving the request stuck at 'running' on the DB.
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.resetCache = resetCache;
|
|
46
|
+
exports.findCodexBinary = findCodexBinary;
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
49
|
+
const win_1 = require("./win");
|
|
50
|
+
const POSIX_CANDIDATES = [
|
|
51
|
+
'/usr/local/bin/codex',
|
|
52
|
+
'/opt/homebrew/bin/codex',
|
|
53
|
+
path.join(os.homedir(), '.npm-global/bin/codex'),
|
|
54
|
+
];
|
|
55
|
+
const WIN32_CANDIDATES = [
|
|
56
|
+
path.join(os.homedir(), '.npm-global', 'codex.cmd'),
|
|
57
|
+
];
|
|
58
|
+
/** Forget the cached lookup — call after an install so a fresh probe
|
|
59
|
+
* can see a binary that was missing at daemon start. */
|
|
60
|
+
function resetCache() { cached = undefined; }
|
|
61
|
+
let cached;
|
|
62
|
+
function findCodexBinary() {
|
|
63
|
+
if (cached !== undefined)
|
|
64
|
+
return cached;
|
|
65
|
+
cached = (0, win_1.findCliBinary)('codex', {
|
|
66
|
+
posixCandidates: POSIX_CANDIDATES,
|
|
67
|
+
win32Candidates: WIN32_CANDIDATES,
|
|
68
|
+
});
|
|
69
|
+
return cached;
|
|
70
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RuntimeEvent, RuntimeEffortLevel } from './types';
|
|
2
|
+
/** What we pass into codex for one MCP server. Shape matches the
|
|
3
|
+
* install.ts ResolvedMcp env-resolved structure. */
|
|
4
|
+
export interface CodexMcpServer {
|
|
5
|
+
slug: string;
|
|
6
|
+
command: string;
|
|
7
|
+
args?: string[] | null;
|
|
8
|
+
env?: Record<string, string> | null;
|
|
9
|
+
}
|
|
10
|
+
export interface CodexInput {
|
|
11
|
+
workingDirectory: string;
|
|
12
|
+
prompt: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
effortLevel?: RuntimeEffortLevel;
|
|
15
|
+
/** Sandbox for normal non-bypass runs. Defaults to workspace-write. */
|
|
16
|
+
sandboxMode?: 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
17
|
+
/** Skip Codex approvals and sandboxing. Codex's "yolo" equivalent. */
|
|
18
|
+
bypassPermissions?: boolean;
|
|
19
|
+
/** If false, codex requires the workingDirectory to be a git repo. */
|
|
20
|
+
skipGitRepoCheck?: boolean;
|
|
21
|
+
/** Existing codex thread id to resume via `codex exec resume <id>`. */
|
|
22
|
+
resumeThreadId?: string;
|
|
23
|
+
/** When set, codex runs against a sandboxed CODEX_HOME that contains
|
|
24
|
+
* ONLY these MCP servers (host's ~/.codex/config.toml is ignored).
|
|
25
|
+
* Mirrors the claude --strict-mcp-config behavior. */
|
|
26
|
+
mcpServers?: CodexMcpServer[];
|
|
27
|
+
}
|
|
28
|
+
export interface CodexHandle {
|
|
29
|
+
pid: number | undefined;
|
|
30
|
+
threadId: string | null;
|
|
31
|
+
kill(): void;
|
|
32
|
+
onEvent(cb: (e: RuntimeEvent) => void): void;
|
|
33
|
+
/** Fires on every raw stdout/stderr chunk. codex emits no per-tool progress
|
|
34
|
+
* events during a long `command_execution`, so raw bytes are the liveness
|
|
35
|
+
* signal the hang-watchdog uses to avoid killing a busy agent. */
|
|
36
|
+
onActivity(cb: () => void): void;
|
|
37
|
+
done: Promise<number>;
|
|
38
|
+
}
|
|
39
|
+
/** Clamp our effort ladder to a value codex's CLI accepts. codex only knows
|
|
40
|
+
* `minimal|low|medium|high` and FAIL-FASTS (exit 2, dead-letter) on anything
|
|
41
|
+
* else — but RuntimeEffortLevel also carries `xhigh`/`max`, and a ladder
|
|
42
|
+
* failover (e.g. claude(max) → codex) forwards req.effort across agents
|
|
43
|
+
* unchanged. Map the extra-high tiers down to `high`; drop anything
|
|
44
|
+
* unrecognised so we emit no `-c reasoning.effort` at all rather than a bad
|
|
45
|
+
* one. Mirrors grokEffort() in grok-spawn.ts. */
|
|
46
|
+
export declare function codexEffort(level?: RuntimeEffortLevel): string | null;
|
|
47
|
+
export declare function spawnCodex(input: CodexInput): CodexHandle;
|