@addai/node 0.30.13 → 0.30.14
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/auto-update.d.ts +3 -0
- package/dist/auto-update.js +15 -0
- package/dist/capabilities.d.ts +17 -3
- package/dist/capabilities.js +18 -8
- package/dist/claude-models.d.ts +4 -12
- package/dist/claude-models.js +12 -88
- package/dist/claude-print.d.ts +15 -0
- package/dist/claude-print.js +16 -0
- package/dist/codex-models.d.ts +81 -0
- package/dist/codex-models.js +385 -0
- package/dist/codex-spawn.d.ts +14 -8
- package/dist/codex-spawn.js +30 -10
- package/dist/command-runner.js +31 -9
- package/dist/desktop/docker.js +2 -0
- package/dist/desktop/manager.d.ts +2 -1
- package/dist/desktop/manager.js +51 -1
- package/dist/desktop/provider.d.ts +1 -1
- package/dist/desktop/provider.js +2 -0
- package/dist/desktop/relay-client.js +7 -0
- package/dist/desktop/spec.d.ts +2 -0
- package/dist/direct/aios-bridge.d.ts +13 -0
- package/dist/direct/aios-bridge.js +177 -0
- package/dist/direct/aios-mcp.d.ts +2 -0
- package/dist/direct/aios-mcp.js +132 -0
- package/dist/direct/aios-tools.d.ts +351 -0
- package/dist/direct/aios-tools.js +120 -0
- package/dist/direct/board-chat-provider.d.ts +64 -0
- package/dist/direct/board-chat-provider.js +151 -0
- package/dist/direct/board-chat.d.ts +40 -0
- package/dist/direct/board-chat.js +414 -0
- package/dist/direct/config.d.ts +2 -0
- package/dist/direct/config.js +60 -0
- package/dist/direct/launch.d.ts +14 -0
- package/dist/direct/launch.js +121 -0
- package/dist/direct/protocol.d.ts +46 -0
- package/dist/direct/protocol.js +84 -0
- package/dist/direct/relay-main.d.ts +1 -0
- package/dist/direct/relay-main.js +21 -0
- package/dist/direct/relay.d.ts +26 -0
- package/dist/direct/relay.js +334 -0
- package/dist/direct/sessions.d.ts +61 -0
- package/dist/direct/sessions.js +368 -0
- package/dist/direct/supervisor.d.ts +9 -0
- package/dist/direct/supervisor.js +137 -0
- package/dist/direct/terminal.d.ts +1 -0
- package/dist/direct/terminal.js +106 -0
- package/dist/direct/worker.d.ts +1 -0
- package/dist/direct/worker.js +278 -0
- package/dist/entitybox/container-action.cjs +85 -0
- package/dist/entitybox/container-watchdog.cjs +70 -0
- package/dist/entitybox/model-entry.d.ts +3 -0
- package/dist/entitybox/model-entry.js +233 -0
- package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
- package/dist/entitybox/model-mcp-proxy.js +85 -0
- package/dist/entitybox-application-boundary.d.ts +59 -0
- package/dist/entitybox-application-boundary.js +161 -0
- package/dist/entitybox-application-guard-core.d.ts +44 -0
- package/dist/entitybox-application-guard-core.js +164 -0
- package/dist/entitybox-application-guard-process.d.ts +1 -0
- package/dist/entitybox-application-guard-process.js +83 -0
- package/dist/entitybox-application-guard.d.ts +46 -0
- package/dist/entitybox-application-guard.js +239 -0
- package/dist/entitybox-capacity-health-host.d.ts +12 -0
- package/dist/entitybox-capacity-health-host.js +80 -0
- package/dist/entitybox-capacity-health-worker.d.ts +22 -0
- package/dist/entitybox-capacity-health-worker.js +168 -0
- package/dist/entitybox-capacity-host.d.ts +17 -0
- package/dist/entitybox-capacity-host.js +155 -0
- package/dist/entitybox-capacity-owner-process.d.ts +1 -0
- package/dist/entitybox-capacity-owner-process.js +32 -0
- package/dist/entitybox-capacity-owner.d.ts +16 -0
- package/dist/entitybox-capacity-owner.js +90 -0
- package/dist/entitybox-capacity-programs.d.ts +7 -0
- package/dist/entitybox-capacity-programs.js +49 -0
- package/dist/entitybox-capacity-provisioner.d.ts +82 -0
- package/dist/entitybox-capacity-provisioner.js +404 -0
- package/dist/entitybox-capacity-publication.d.ts +29 -0
- package/dist/entitybox-capacity-publication.js +199 -0
- package/dist/entitybox-capacity-registration.d.ts +36 -0
- package/dist/entitybox-capacity-registration.js +75 -0
- package/dist/entitybox-capacity-worker.d.ts +29 -0
- package/dist/entitybox-capacity-worker.js +113 -0
- package/dist/entitybox-computer-task.d.ts +69 -0
- package/dist/entitybox-computer-task.js +176 -0
- package/dist/entitybox-controller-recovery.d.ts +40 -0
- package/dist/entitybox-controller-recovery.js +156 -0
- package/dist/entitybox-desktop-action-server.d.ts +34 -0
- package/dist/entitybox-desktop-action-server.js +176 -0
- package/dist/entitybox-desktop-admission.d.ts +32 -0
- package/dist/entitybox-desktop-admission.js +79 -0
- package/dist/entitybox-display-binding.d.ts +13 -0
- package/dist/entitybox-display-binding.js +31 -0
- package/dist/entitybox-execution-container.d.ts +82 -0
- package/dist/entitybox-execution-container.js +450 -0
- package/dist/entitybox-execution-host.d.ts +40 -0
- package/dist/entitybox-execution-host.js +168 -0
- package/dist/entitybox-execution-lease.d.ts +81 -0
- package/dist/entitybox-execution-lease.js +332 -0
- package/dist/entitybox-file-inputs.d.ts +51 -0
- package/dist/entitybox-file-inputs.js +182 -0
- package/dist/entitybox-file-session.d.ts +38 -0
- package/dist/entitybox-file-session.js +82 -0
- package/dist/entitybox-host-process.d.ts +12 -0
- package/dist/entitybox-host-process.js +120 -0
- package/dist/entitybox-install.d.ts +10 -0
- package/dist/entitybox-install.js +40 -0
- package/dist/entitybox-mcp-broker.d.ts +77 -0
- package/dist/entitybox-mcp-broker.js +420 -0
- package/dist/entitybox-mcp-contract.d.ts +10 -0
- package/dist/entitybox-mcp-contract.js +24 -0
- package/dist/entitybox-mcp-guard.d.ts +3 -0
- package/dist/entitybox-mcp-guard.js +167 -0
- package/dist/entitybox-model-artifact.d.ts +9 -0
- package/dist/entitybox-model-artifact.js +83 -0
- package/dist/entitybox-native-services.d.ts +33 -0
- package/dist/entitybox-native-services.js +172 -0
- package/dist/entitybox-operation.d.ts +4 -0
- package/dist/entitybox-operation.js +34 -0
- package/dist/entitybox-provider-broker.d.ts +41 -0
- package/dist/entitybox-provider-broker.js +149 -0
- package/dist/entitybox-queue-request.d.ts +32 -0
- package/dist/entitybox-queue-request.js +141 -0
- package/dist/entitybox-recovery-host.d.ts +12 -0
- package/dist/entitybox-recovery-host.js +80 -0
- package/dist/entitybox-recovery-store.d.ts +33 -0
- package/dist/entitybox-recovery-store.js +150 -0
- package/dist/entitybox-recovery-worker.d.ts +30 -0
- package/dist/entitybox-recovery-worker.js +134 -0
- package/dist/entitybox-vault-provider.d.ts +27 -0
- package/dist/entitybox-vault-provider.js +127 -0
- package/dist/harness-registry.js +3 -1
- package/dist/index.js +72 -38
- package/dist/install.d.ts +14 -2
- package/dist/install.js +115 -10
- package/dist/model-cache.d.ts +38 -0
- package/dist/model-cache.js +137 -0
- package/dist/request-pump.d.ts +3 -0
- package/dist/request-pump.js +21 -7
- package/dist/session-runner.d.ts +8 -1
- package/dist/session-runner.js +48 -3
- package/dist/supabase-client.js +4 -1
- package/package.json +5 -1
- package/scripts/copy-assets.js +4 -2
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DirectSessions = void 0;
|
|
37
|
+
exports.isWithin = isWithin;
|
|
38
|
+
const fs = __importStar(require("node:fs"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const headless_1 = require("@xterm/headless");
|
|
42
|
+
const addon_serialize_1 = require("@xterm/addon-serialize");
|
|
43
|
+
const protocol_1 = require("./protocol");
|
|
44
|
+
const aios_tools_1 = require("./aios-tools");
|
|
45
|
+
function isWithin(root, folder) {
|
|
46
|
+
const rel = path.relative(root, folder);
|
|
47
|
+
return rel === '' || (!path.isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${path.sep}`));
|
|
48
|
+
}
|
|
49
|
+
class DirectSessions {
|
|
50
|
+
deps;
|
|
51
|
+
records = new Map();
|
|
52
|
+
live = new Map();
|
|
53
|
+
roots;
|
|
54
|
+
shuttingDown = false;
|
|
55
|
+
// Operations are serialized by the worker; callers must not concurrently create/resume.
|
|
56
|
+
constructor(deps) {
|
|
57
|
+
this.deps = deps;
|
|
58
|
+
this.roots = deps.roots.map(root => fs.realpathSync(root));
|
|
59
|
+
if (!this.roots.length || this.roots.some(root => !fs.statSync(root).isDirectory()))
|
|
60
|
+
throw new Error('Configure at least one project root.');
|
|
61
|
+
fs.mkdirSync(deps.directory, { recursive: true, mode: 0o700 });
|
|
62
|
+
for (const name of fs.readdirSync(deps.directory)) {
|
|
63
|
+
if (!name.endsWith('.json') || !protocol_1.UUID.test(name.slice(0, -5)))
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
const file = path.join(deps.directory, name);
|
|
67
|
+
if (fs.statSync(file).size > 16_384)
|
|
68
|
+
continue;
|
|
69
|
+
const r = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
70
|
+
if (r.id !== name.slice(0, -5) || !protocol_1.UUID.test(r.ownerId) || !['claude', 'codex'].includes(r.provider) ||
|
|
71
|
+
typeof r.cwd !== 'string' || typeof r.title !== 'string')
|
|
72
|
+
continue;
|
|
73
|
+
if (r.provider === 'claude' && !protocol_1.UUID.test(r.nativeSessionId ?? ''))
|
|
74
|
+
continue;
|
|
75
|
+
if (r.status === 'running') {
|
|
76
|
+
r.status = 'exited';
|
|
77
|
+
r.reason = 'The node restarted. Resume to continue.';
|
|
78
|
+
this.save(r);
|
|
79
|
+
}
|
|
80
|
+
this.records.set(r.id, r);
|
|
81
|
+
}
|
|
82
|
+
catch { /* A damaged record must not prevent other sessions or the node starting. */ }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
get count() { return this.live.size; }
|
|
86
|
+
save(r) {
|
|
87
|
+
const target = path.join(this.deps.directory, `${r.id}.json`);
|
|
88
|
+
const tmp = `${target}.tmp`;
|
|
89
|
+
fs.writeFileSync(tmp, JSON.stringify(r), { mode: 0o600 });
|
|
90
|
+
fs.renameSync(tmp, target);
|
|
91
|
+
}
|
|
92
|
+
folder(raw) {
|
|
93
|
+
if (!path.isAbsolute(raw))
|
|
94
|
+
throw new Error('Use an absolute project path on the paired machine.');
|
|
95
|
+
let folder;
|
|
96
|
+
try {
|
|
97
|
+
folder = fs.realpathSync(raw);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new Error('That project folder does not exist on this machine.');
|
|
101
|
+
}
|
|
102
|
+
if (!fs.statSync(folder).isDirectory() || !this.roots.some(root => isWithin(root, folder))) {
|
|
103
|
+
throw new Error('That folder is outside the project roots enabled on this machine.');
|
|
104
|
+
}
|
|
105
|
+
for (const live of this.live.values()) {
|
|
106
|
+
if (isWithin(live.record.cwd, folder) || isWithin(folder, live.record.cwd)) {
|
|
107
|
+
throw new Error('A direct session already uses this folder. Reopen it or choose a separate working copy.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return folder;
|
|
111
|
+
}
|
|
112
|
+
owned(id, ownerId) {
|
|
113
|
+
const r = this.records.get(id);
|
|
114
|
+
if (!r || r.ownerId !== ownerId)
|
|
115
|
+
throw new Error('Session not found.');
|
|
116
|
+
return r;
|
|
117
|
+
}
|
|
118
|
+
controlled(id, ownerId, viewerId) {
|
|
119
|
+
this.owned(id, ownerId);
|
|
120
|
+
const live = this.live.get(id);
|
|
121
|
+
if (!live || live.stopping)
|
|
122
|
+
throw new Error('This session is no longer running.');
|
|
123
|
+
if (live.viewer !== viewerId)
|
|
124
|
+
throw new Error('Connect to this session before controlling it.');
|
|
125
|
+
return live;
|
|
126
|
+
}
|
|
127
|
+
start(r, cols, rows, resume) {
|
|
128
|
+
if (this.shuttingDown)
|
|
129
|
+
throw new Error('The node is shutting down.');
|
|
130
|
+
if (this.deps.canStart && !this.deps.canStart())
|
|
131
|
+
throw new Error('This machine has reached its coding-session limit.');
|
|
132
|
+
if (this.live.size >= (this.deps.maxActive ?? 2))
|
|
133
|
+
throw new Error('This machine has reached its direct-session limit.');
|
|
134
|
+
if (!this.deps.providers()[r.provider])
|
|
135
|
+
throw new Error(`${r.provider === 'claude' ? 'Claude Code' : 'Codex'} is not installed on this machine.`);
|
|
136
|
+
r.cwd = this.folder(r.cwd);
|
|
137
|
+
r.status = 'running';
|
|
138
|
+
r.updatedAt = new Date().toISOString();
|
|
139
|
+
delete r.exitCode;
|
|
140
|
+
delete r.reason;
|
|
141
|
+
this.save(r);
|
|
142
|
+
const terminal = new headless_1.Terminal({ cols, rows, scrollback: 1000, allowProposedApi: true });
|
|
143
|
+
const serializer = new addon_serialize_1.SerializeAddon();
|
|
144
|
+
terminal.loadAddon(serializer);
|
|
145
|
+
let proc;
|
|
146
|
+
try {
|
|
147
|
+
proc = this.deps.spawn(r, { cols, rows }, resume, (name, args) => this.callDesktop(r.id, name, args));
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
terminal.dispose();
|
|
151
|
+
r.status = 'exited';
|
|
152
|
+
r.reason = 'The program could not start.';
|
|
153
|
+
this.save(r);
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
const live = { record: r, proc, terminal, serializer, viewer: null, seq: 0, pendingBytes: 0, stopping: false, unacked: new Map(), unackedBytes: 0, attachmentId: null, aios: false, calls: new Map() };
|
|
157
|
+
this.live.set(r.id, live);
|
|
158
|
+
this.deps.onCount?.(this.count);
|
|
159
|
+
proc.onData(data => {
|
|
160
|
+
// Bound the parser queue as well as the serialized screen. The PTY pauses until caught up.
|
|
161
|
+
live.pendingBytes += data.length;
|
|
162
|
+
if (live.pendingBytes > 1024 * 1024)
|
|
163
|
+
proc.pause?.();
|
|
164
|
+
terminal.write(data, () => {
|
|
165
|
+
live.pendingBytes -= data.length;
|
|
166
|
+
if (live.pendingBytes < 256 * 1024 && live.unackedBytes < 256 * 1024)
|
|
167
|
+
proc.resume?.();
|
|
168
|
+
if (this.live.get(r.id) !== live)
|
|
169
|
+
return;
|
|
170
|
+
// Each frame fits comfortably within the relay's payload limit.
|
|
171
|
+
for (let i = 0; i < data.length; i += 16_384) {
|
|
172
|
+
const chunk = data.slice(i, i + 16_384);
|
|
173
|
+
live.seq++;
|
|
174
|
+
if (live.viewer) {
|
|
175
|
+
live.unacked.set(live.seq, chunk.length);
|
|
176
|
+
live.unackedBytes += chunk.length;
|
|
177
|
+
this.deps.emit(live.viewer, { type: 'output', sessionId: r.id, seq: live.seq, data: chunk });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (live.unackedBytes > 256 * 1024)
|
|
181
|
+
proc.pause?.();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
proc.onExit(({ exitCode }) => {
|
|
185
|
+
const wasStopped = live.stopping;
|
|
186
|
+
live.stopping = true;
|
|
187
|
+
this.cancelCalls(live);
|
|
188
|
+
// Flush pending output before disposing the screen or emitting the exit event.
|
|
189
|
+
terminal.write('', () => {
|
|
190
|
+
if (this.live.get(r.id) !== live)
|
|
191
|
+
return;
|
|
192
|
+
r.status = 'exited';
|
|
193
|
+
r.exitCode = exitCode;
|
|
194
|
+
r.updatedAt = new Date().toISOString();
|
|
195
|
+
r.reason = wasStopped ? 'Stopped.' : 'The program exited.';
|
|
196
|
+
try {
|
|
197
|
+
this.save(r);
|
|
198
|
+
}
|
|
199
|
+
catch { /* Keep shutdown independent of disk health. */ }
|
|
200
|
+
this.live.delete(r.id);
|
|
201
|
+
this.deps.onCount?.(this.count);
|
|
202
|
+
if (live.viewer)
|
|
203
|
+
this.deps.emit(live.viewer, { type: 'exit', session: { ...r } });
|
|
204
|
+
terminal.dispose();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async request(viewerId, ownerId, value) {
|
|
209
|
+
if (!protocol_1.UUID.test(ownerId) || !protocol_1.UUID.test(viewerId))
|
|
210
|
+
throw new Error('Invalid viewer.');
|
|
211
|
+
const q = (0, protocol_1.parseRequest)(value);
|
|
212
|
+
if (q.op === 'describe')
|
|
213
|
+
return { roots: this.roots, providers: this.deps.providers(), maxActive: this.deps.maxActive ?? 2, aios: aios_tools_1.AIOS_VERSION, aiosTools: aios_tools_1.AIOS_TOOLS.map(tool => tool.name) };
|
|
214
|
+
if (q.op === 'list')
|
|
215
|
+
return [...this.records.values()].filter(r => r.ownerId === ownerId).map(r => ({ ...r })).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
216
|
+
const id = q.sessionId;
|
|
217
|
+
if (q.op === 'create') {
|
|
218
|
+
// A retry with the same session ID must not spawn a second program.
|
|
219
|
+
if (this.records.has(id)) {
|
|
220
|
+
const prior = this.owned(id, ownerId);
|
|
221
|
+
if (prior.provider !== q.provider || prior.cwd !== fs.realpathSync(q.cwd))
|
|
222
|
+
throw new Error('Session ID already exists.');
|
|
223
|
+
return { ...prior };
|
|
224
|
+
}
|
|
225
|
+
if ([...this.records.values()].filter(r => r.ownerId === ownerId).length >= 500)
|
|
226
|
+
throw new Error('The local session history is full.');
|
|
227
|
+
const now = new Date().toISOString();
|
|
228
|
+
const r = { id, ownerId, provider: q.provider, cwd: q.cwd, title: q.title?.trim() || path.basename(q.cwd), createdAt: now, updatedAt: now, status: 'exited' };
|
|
229
|
+
if (r.provider === 'claude')
|
|
230
|
+
r.nativeSessionId = (0, node_crypto_1.randomUUID)();
|
|
231
|
+
if (q.aios === true)
|
|
232
|
+
r.aios = true;
|
|
233
|
+
this.start(r, q.cols, q.rows, false);
|
|
234
|
+
this.records.set(id, r);
|
|
235
|
+
return { ...r };
|
|
236
|
+
}
|
|
237
|
+
const r = this.owned(id, ownerId);
|
|
238
|
+
if (q.op === 'resume') {
|
|
239
|
+
if (!this.live.has(id))
|
|
240
|
+
this.start(r, q.cols, q.rows, true);
|
|
241
|
+
return { ...r };
|
|
242
|
+
}
|
|
243
|
+
if (q.op === 'attach') {
|
|
244
|
+
const live = this.live.get(id);
|
|
245
|
+
if (!live || live.stopping)
|
|
246
|
+
throw new Error('This session has ended. Resume it to continue.');
|
|
247
|
+
if (live.viewer && live.viewer !== viewerId)
|
|
248
|
+
throw new Error('This session is open in another window. Close that connection first.');
|
|
249
|
+
// Detach this viewer from a previous session; only one input target per connection.
|
|
250
|
+
this.detach(viewerId);
|
|
251
|
+
await new Promise(resolve => live.terminal.write('', resolve));
|
|
252
|
+
if (this.live.get(id) !== live)
|
|
253
|
+
throw new Error('This session has ended.');
|
|
254
|
+
live.viewer = viewerId;
|
|
255
|
+
live.attachmentId = (0, node_crypto_1.randomUUID)();
|
|
256
|
+
live.aios = r.aios === true && q.aios === true;
|
|
257
|
+
live.terminal.resize(q.cols, q.rows);
|
|
258
|
+
live.proc.resize(q.cols, q.rows);
|
|
259
|
+
return { session: { ...r }, seq: live.seq, screen: live.serializer.serialize({ scrollback: 1000 }), attachmentId: live.attachmentId };
|
|
260
|
+
}
|
|
261
|
+
if (q.op === 'detach') {
|
|
262
|
+
if (this.live.get(id)?.viewer === viewerId)
|
|
263
|
+
this.detach(viewerId);
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
const live = this.controlled(id, ownerId, viewerId);
|
|
267
|
+
if (q.op === 'aios-result' || q.op === 'aios-access') {
|
|
268
|
+
if (q.attachmentId !== live.attachmentId)
|
|
269
|
+
throw new Error('The +Ai connection changed.');
|
|
270
|
+
if (q.op === 'aios-access') {
|
|
271
|
+
live.aios = r.aios === true && q.aios === true;
|
|
272
|
+
if (!live.aios)
|
|
273
|
+
this.cancelCalls(live);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
const pending = live.calls.get(q.callId);
|
|
277
|
+
if (!pending)
|
|
278
|
+
throw new Error('This +Ai call is no longer pending.');
|
|
279
|
+
live.calls.delete(q.callId);
|
|
280
|
+
clearTimeout(pending.timer);
|
|
281
|
+
if (q.ok)
|
|
282
|
+
pending.resolve(q.result);
|
|
283
|
+
else
|
|
284
|
+
pending.reject(new Error(q.error || '+Ai request failed.'));
|
|
285
|
+
}
|
|
286
|
+
return {};
|
|
287
|
+
}
|
|
288
|
+
if (q.op === 'ack') {
|
|
289
|
+
if (q.seq > live.seq)
|
|
290
|
+
throw new Error('Invalid output acknowledgment.');
|
|
291
|
+
for (const [seq, bytes] of live.unacked)
|
|
292
|
+
if (seq <= q.seq) {
|
|
293
|
+
live.unackedBytes -= bytes;
|
|
294
|
+
live.unacked.delete(seq);
|
|
295
|
+
}
|
|
296
|
+
if (live.unackedBytes < 64 * 1024 && live.pendingBytes < 256 * 1024)
|
|
297
|
+
live.proc.resume?.();
|
|
298
|
+
}
|
|
299
|
+
if (q.op === 'input')
|
|
300
|
+
live.proc.write(q.data);
|
|
301
|
+
if (q.op === 'resize') {
|
|
302
|
+
live.terminal.resize(q.cols, q.rows);
|
|
303
|
+
live.proc.resize(q.cols, q.rows);
|
|
304
|
+
}
|
|
305
|
+
if (q.op === 'stop') {
|
|
306
|
+
live.stopping = true;
|
|
307
|
+
this.cancelCalls(live);
|
|
308
|
+
live.proc.kill();
|
|
309
|
+
}
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
312
|
+
detach(viewerId) {
|
|
313
|
+
for (const live of this.live.values())
|
|
314
|
+
if (live.viewer === viewerId) {
|
|
315
|
+
this.cancelCalls(live);
|
|
316
|
+
live.attachmentId = null;
|
|
317
|
+
live.aios = false;
|
|
318
|
+
live.viewer = null;
|
|
319
|
+
live.unacked.clear();
|
|
320
|
+
live.unackedBytes = 0;
|
|
321
|
+
if (live.pendingBytes < 256 * 1024)
|
|
322
|
+
live.proc.resume?.();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
detachAll() { for (const live of this.live.values())
|
|
326
|
+
if (live.viewer)
|
|
327
|
+
this.detach(live.viewer); }
|
|
328
|
+
stop() {
|
|
329
|
+
this.shuttingDown = true;
|
|
330
|
+
for (const live of this.live.values()) {
|
|
331
|
+
live.stopping = true;
|
|
332
|
+
this.cancelCalls(live);
|
|
333
|
+
try {
|
|
334
|
+
live.proc.kill();
|
|
335
|
+
}
|
|
336
|
+
catch { /* Continue stopping the other direct sessions. */ }
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
cancelCalls(live) {
|
|
340
|
+
for (const pending of live.calls.values()) {
|
|
341
|
+
clearTimeout(pending.timer);
|
|
342
|
+
pending.reject(new Error('+Ai access paused or disconnected. Reopen CodeFlows to continue.'));
|
|
343
|
+
}
|
|
344
|
+
live.calls.clear();
|
|
345
|
+
}
|
|
346
|
+
async callDesktop(id, name, args) {
|
|
347
|
+
(0, aios_tools_1.validateDesktopCall)(name, args);
|
|
348
|
+
const live = this.live.get(id);
|
|
349
|
+
if (!live || live.stopping || !live.viewer || !live.aios || !live.attachmentId)
|
|
350
|
+
throw new Error('+Ai access is unavailable. Attach this session in CodeFlows and enable +Ai access.');
|
|
351
|
+
if (live.calls.size >= 8)
|
|
352
|
+
throw new Error('Too many pending +Ai calls.');
|
|
353
|
+
const callId = (0, node_crypto_1.randomUUID)(), expiresAt = Date.now() + aios_tools_1.AIOS_TIMEOUT;
|
|
354
|
+
return new Promise((resolve, reject) => {
|
|
355
|
+
const timer = setTimeout(() => { live.calls.delete(callId); reject(new Error('+Ai desktop did not respond. Check the board before retrying an action.')); }, aios_tools_1.AIOS_TIMEOUT);
|
|
356
|
+
live.calls.set(callId, { resolve, reject, timer });
|
|
357
|
+
try {
|
|
358
|
+
this.deps.emit(live.viewer, { type: 'aios-request', sessionId: id, attachmentId: live.attachmentId, callId, expiresAt, name, args });
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
live.calls.delete(callId);
|
|
363
|
+
reject(err);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
exports.DirectSessions = DirectSessions;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fork } from 'node:child_process';
|
|
2
|
+
export interface DirectService {
|
|
3
|
+
busy(): boolean;
|
|
4
|
+
quiesce(): Promise<boolean>;
|
|
5
|
+
release(): void;
|
|
6
|
+
stop(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/** The default branch creates no processes, timers, connections, folders or config. */
|
|
9
|
+
export declare function startDirectService(env?: NodeJS.ProcessEnv, spawn?: typeof fork, report?: (message: string) => void): DirectService;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.startDirectService = startDirectService;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const node_child_process_1 = require("node:child_process");
|
|
39
|
+
const protocol_1 = require("./protocol");
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
/** The default branch creates no processes, timers, connections, folders or config. */
|
|
42
|
+
function startDirectService(env = process.env, spawn = node_child_process_1.fork, report = message => console.error(`[direct-sessions] ${message}`)) {
|
|
43
|
+
const off = { busy: () => false, quiesce: async () => true, release: () => { }, stop: async () => { } };
|
|
44
|
+
if (env.AINODE_DIRECT_SESSIONS !== '1')
|
|
45
|
+
return off;
|
|
46
|
+
try {
|
|
47
|
+
(0, protocol_1.relayUrl)(env.AINODE_DIRECT_RELAY_URL ?? '', '/node');
|
|
48
|
+
const roots = JSON.parse(env.AINODE_DIRECT_ROOTS ?? '[]');
|
|
49
|
+
if (!Array.isArray(roots) || !roots.length || roots.some(r => typeof r !== 'string' || !path.isAbsolute(r))) {
|
|
50
|
+
throw new Error('Configure AINODE_DIRECT_ROOTS as a JSON array of absolute project paths.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
report(err.message);
|
|
55
|
+
return off;
|
|
56
|
+
}
|
|
57
|
+
let child;
|
|
58
|
+
try {
|
|
59
|
+
const options = {
|
|
60
|
+
env: { ...env }, stdio: ['ignore', 'ignore', 'ignore', 'ipc'], windowsHide: true,
|
|
61
|
+
};
|
|
62
|
+
child = spawn(path.join(__dirname, 'worker.js'), [], options);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
report('Could not start the optional session service. Existing workloads remain available.');
|
|
66
|
+
return off;
|
|
67
|
+
}
|
|
68
|
+
let count = -1; // Treat startup as busy until the child confirms its state.
|
|
69
|
+
let exited = false;
|
|
70
|
+
let leases = 0;
|
|
71
|
+
let pending;
|
|
72
|
+
const startup = setTimeout(() => { if (count === -1) {
|
|
73
|
+
report('The optional session service did not start in time.');
|
|
74
|
+
child.kill();
|
|
75
|
+
} }, 15_000);
|
|
76
|
+
const clearPending = (ok) => { if (pending) {
|
|
77
|
+
clearTimeout(pending.timer);
|
|
78
|
+
pending.resolve(ok);
|
|
79
|
+
pending = undefined;
|
|
80
|
+
} };
|
|
81
|
+
child.on('message', (value) => {
|
|
82
|
+
if (value?.type === 'direct-count' && Number.isInteger(value.count) && value.count >= 0) {
|
|
83
|
+
count = value.count;
|
|
84
|
+
clearTimeout(startup);
|
|
85
|
+
}
|
|
86
|
+
if (value?.type === 'direct-quiesced' && pending?.id === value.id) {
|
|
87
|
+
if (value.count === 0)
|
|
88
|
+
leases++;
|
|
89
|
+
clearPending(value.count === 0);
|
|
90
|
+
}
|
|
91
|
+
if (value?.type === 'direct-error')
|
|
92
|
+
report('The optional session service could not start. Check its project roots and relay configuration.');
|
|
93
|
+
});
|
|
94
|
+
child.on('error', () => { exited = true; count = 0; clearTimeout(startup); clearPending(false); report('The optional session service failed.'); });
|
|
95
|
+
child.on('exit', () => { exited = true; count = 0; clearTimeout(startup); clearPending(false); });
|
|
96
|
+
return {
|
|
97
|
+
busy: () => !exited && count !== 0,
|
|
98
|
+
async quiesce() {
|
|
99
|
+
if (exited)
|
|
100
|
+
return true;
|
|
101
|
+
if (leases > 0) {
|
|
102
|
+
leases++;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (count !== 0 || pending || !child.connected)
|
|
106
|
+
return false;
|
|
107
|
+
return new Promise(resolve => {
|
|
108
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
109
|
+
const timer = setTimeout(() => { clearPending(false); if (child.connected)
|
|
110
|
+
child.send({ type: 'direct-accept' }, () => { }); }, 10_000);
|
|
111
|
+
pending = { id, resolve, timer };
|
|
112
|
+
child.send({ type: 'direct-quiesce', id }, err => { if (err)
|
|
113
|
+
clearPending(false); });
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
release() {
|
|
117
|
+
if (leases > 0)
|
|
118
|
+
leases--;
|
|
119
|
+
if (!leases && child.connected)
|
|
120
|
+
child.send({ type: 'direct-accept' }, () => { });
|
|
121
|
+
},
|
|
122
|
+
async stop() {
|
|
123
|
+
clearTimeout(startup);
|
|
124
|
+
clearPending(false);
|
|
125
|
+
if (exited)
|
|
126
|
+
return;
|
|
127
|
+
await new Promise(resolve => {
|
|
128
|
+
const timer = setTimeout(() => { child.kill(); resolve(); }, 5000);
|
|
129
|
+
child.once('exit', () => { clearTimeout(timer); resolve(); });
|
|
130
|
+
if (child.connected)
|
|
131
|
+
child.send({ type: 'direct-stop' }, () => { });
|
|
132
|
+
else
|
|
133
|
+
child.kill();
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const pty = __importStar(require("node-pty"));
|
|
37
|
+
const win_1 = require("../win");
|
|
38
|
+
// A private per-terminal host. Its parent owns termination of the entire process
|
|
39
|
+
// tree; no native CLI, prompt or approval protocol is interpreted here.
|
|
40
|
+
let terminal;
|
|
41
|
+
let paused = false, sending = 0, ending = false;
|
|
42
|
+
function send(message, done) {
|
|
43
|
+
if (!process.connected || !process.send) {
|
|
44
|
+
done?.();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
process.send(message, () => done?.());
|
|
48
|
+
}
|
|
49
|
+
function end(exitCode) {
|
|
50
|
+
if (ending)
|
|
51
|
+
return;
|
|
52
|
+
ending = true;
|
|
53
|
+
send({ type: 'exit', exitCode }, () => process.exit(0));
|
|
54
|
+
}
|
|
55
|
+
function stopTree() {
|
|
56
|
+
if (ending)
|
|
57
|
+
return;
|
|
58
|
+
ending = true;
|
|
59
|
+
(0, win_1.killProcessTree)({ pid: process.pid, kill: signal => { process.kill(process.pid, signal); } });
|
|
60
|
+
}
|
|
61
|
+
// Keep the host alive through POSIX's polite phase so tree escalation can still
|
|
62
|
+
// find descendants that ignore SIGINT. Windows uses taskkill /T /F directly.
|
|
63
|
+
process.on('SIGINT', () => { });
|
|
64
|
+
process.on('SIGTERM', stopTree);
|
|
65
|
+
process.on('disconnect', stopTree);
|
|
66
|
+
process.on('message', (message) => {
|
|
67
|
+
if (ending)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
if (message?.type === 'start' && !terminal) {
|
|
71
|
+
terminal = pty.spawn(message.file, message.args, message.options);
|
|
72
|
+
terminal.onData(data => {
|
|
73
|
+
terminal.pause();
|
|
74
|
+
for (let i = 0; i < data.length; i += 16_384) {
|
|
75
|
+
sending++;
|
|
76
|
+
send({ type: 'data', data: data.slice(i, i + 16_384) }, () => {
|
|
77
|
+
sending--;
|
|
78
|
+
if (!sending && !paused && !ending)
|
|
79
|
+
terminal?.resume();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
terminal.onExit(({ exitCode }) => end(exitCode));
|
|
84
|
+
}
|
|
85
|
+
if (message?.type === 'input' && typeof message.data === 'string')
|
|
86
|
+
terminal?.write(message.data);
|
|
87
|
+
if (message?.type === 'resize')
|
|
88
|
+
terminal?.resize(message.cols, message.rows);
|
|
89
|
+
if (message?.type === 'pause') {
|
|
90
|
+
paused = true;
|
|
91
|
+
terminal?.pause();
|
|
92
|
+
}
|
|
93
|
+
if (message?.type === 'resume') {
|
|
94
|
+
paused = false;
|
|
95
|
+
if (!sending)
|
|
96
|
+
terminal?.resume();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (!terminal) {
|
|
101
|
+
send({ type: 'data', data: `\r\nThe terminal could not start: ${error instanceof Error ? error.message : String(error)}\r\n` });
|
|
102
|
+
end(1);
|
|
103
|
+
}
|
|
104
|
+
// A resize/input racing an exited terminal must not crash a different session.
|
|
105
|
+
}
|
|
106
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDirectWorker(): () => void;
|