@addai/node 0.30.12 → 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.
Files changed (143) hide show
  1. package/dist/auto-update.d.ts +3 -0
  2. package/dist/auto-update.js +15 -0
  3. package/dist/capabilities.d.ts +31 -6
  4. package/dist/capabilities.js +48 -26
  5. package/dist/claude-models.d.ts +71 -0
  6. package/dist/claude-models.js +330 -0
  7. package/dist/claude-print.d.ts +15 -0
  8. package/dist/claude-print.js +16 -0
  9. package/dist/codex-models.d.ts +81 -0
  10. package/dist/codex-models.js +385 -0
  11. package/dist/codex-spawn.d.ts +14 -8
  12. package/dist/codex-spawn.js +30 -10
  13. package/dist/command-runner.js +31 -9
  14. package/dist/desktop/docker.js +2 -0
  15. package/dist/desktop/manager.d.ts +2 -1
  16. package/dist/desktop/manager.js +51 -1
  17. package/dist/desktop/provider.d.ts +1 -1
  18. package/dist/desktop/provider.js +2 -0
  19. package/dist/desktop/relay-client.js +7 -0
  20. package/dist/desktop/spec.d.ts +2 -0
  21. package/dist/direct/aios-bridge.d.ts +13 -0
  22. package/dist/direct/aios-bridge.js +177 -0
  23. package/dist/direct/aios-mcp.d.ts +2 -0
  24. package/dist/direct/aios-mcp.js +132 -0
  25. package/dist/direct/aios-tools.d.ts +351 -0
  26. package/dist/direct/aios-tools.js +120 -0
  27. package/dist/direct/board-chat-provider.d.ts +64 -0
  28. package/dist/direct/board-chat-provider.js +151 -0
  29. package/dist/direct/board-chat.d.ts +40 -0
  30. package/dist/direct/board-chat.js +414 -0
  31. package/dist/direct/config.d.ts +2 -0
  32. package/dist/direct/config.js +60 -0
  33. package/dist/direct/launch.d.ts +14 -0
  34. package/dist/direct/launch.js +121 -0
  35. package/dist/direct/protocol.d.ts +46 -0
  36. package/dist/direct/protocol.js +84 -0
  37. package/dist/direct/relay-main.d.ts +1 -0
  38. package/dist/direct/relay-main.js +21 -0
  39. package/dist/direct/relay.d.ts +26 -0
  40. package/dist/direct/relay.js +334 -0
  41. package/dist/direct/sessions.d.ts +61 -0
  42. package/dist/direct/sessions.js +368 -0
  43. package/dist/direct/supervisor.d.ts +9 -0
  44. package/dist/direct/supervisor.js +137 -0
  45. package/dist/direct/terminal.d.ts +1 -0
  46. package/dist/direct/terminal.js +106 -0
  47. package/dist/direct/worker.d.ts +1 -0
  48. package/dist/direct/worker.js +278 -0
  49. package/dist/entitybox/container-action.cjs +85 -0
  50. package/dist/entitybox/container-watchdog.cjs +70 -0
  51. package/dist/entitybox/model-entry.d.ts +3 -0
  52. package/dist/entitybox/model-entry.js +233 -0
  53. package/dist/entitybox/model-mcp-proxy.d.ts +3 -0
  54. package/dist/entitybox/model-mcp-proxy.js +85 -0
  55. package/dist/entitybox-application-boundary.d.ts +59 -0
  56. package/dist/entitybox-application-boundary.js +161 -0
  57. package/dist/entitybox-application-guard-core.d.ts +44 -0
  58. package/dist/entitybox-application-guard-core.js +164 -0
  59. package/dist/entitybox-application-guard-process.d.ts +1 -0
  60. package/dist/entitybox-application-guard-process.js +83 -0
  61. package/dist/entitybox-application-guard.d.ts +46 -0
  62. package/dist/entitybox-application-guard.js +239 -0
  63. package/dist/entitybox-capacity-health-host.d.ts +12 -0
  64. package/dist/entitybox-capacity-health-host.js +80 -0
  65. package/dist/entitybox-capacity-health-worker.d.ts +22 -0
  66. package/dist/entitybox-capacity-health-worker.js +168 -0
  67. package/dist/entitybox-capacity-host.d.ts +17 -0
  68. package/dist/entitybox-capacity-host.js +155 -0
  69. package/dist/entitybox-capacity-owner-process.d.ts +1 -0
  70. package/dist/entitybox-capacity-owner-process.js +32 -0
  71. package/dist/entitybox-capacity-owner.d.ts +16 -0
  72. package/dist/entitybox-capacity-owner.js +90 -0
  73. package/dist/entitybox-capacity-programs.d.ts +7 -0
  74. package/dist/entitybox-capacity-programs.js +49 -0
  75. package/dist/entitybox-capacity-provisioner.d.ts +82 -0
  76. package/dist/entitybox-capacity-provisioner.js +404 -0
  77. package/dist/entitybox-capacity-publication.d.ts +29 -0
  78. package/dist/entitybox-capacity-publication.js +199 -0
  79. package/dist/entitybox-capacity-registration.d.ts +36 -0
  80. package/dist/entitybox-capacity-registration.js +75 -0
  81. package/dist/entitybox-capacity-worker.d.ts +29 -0
  82. package/dist/entitybox-capacity-worker.js +113 -0
  83. package/dist/entitybox-computer-task.d.ts +69 -0
  84. package/dist/entitybox-computer-task.js +176 -0
  85. package/dist/entitybox-controller-recovery.d.ts +40 -0
  86. package/dist/entitybox-controller-recovery.js +156 -0
  87. package/dist/entitybox-desktop-action-server.d.ts +34 -0
  88. package/dist/entitybox-desktop-action-server.js +176 -0
  89. package/dist/entitybox-desktop-admission.d.ts +32 -0
  90. package/dist/entitybox-desktop-admission.js +79 -0
  91. package/dist/entitybox-display-binding.d.ts +13 -0
  92. package/dist/entitybox-display-binding.js +31 -0
  93. package/dist/entitybox-execution-container.d.ts +82 -0
  94. package/dist/entitybox-execution-container.js +450 -0
  95. package/dist/entitybox-execution-host.d.ts +40 -0
  96. package/dist/entitybox-execution-host.js +168 -0
  97. package/dist/entitybox-execution-lease.d.ts +81 -0
  98. package/dist/entitybox-execution-lease.js +332 -0
  99. package/dist/entitybox-file-inputs.d.ts +51 -0
  100. package/dist/entitybox-file-inputs.js +182 -0
  101. package/dist/entitybox-file-session.d.ts +38 -0
  102. package/dist/entitybox-file-session.js +82 -0
  103. package/dist/entitybox-host-process.d.ts +12 -0
  104. package/dist/entitybox-host-process.js +120 -0
  105. package/dist/entitybox-install.d.ts +10 -0
  106. package/dist/entitybox-install.js +40 -0
  107. package/dist/entitybox-mcp-broker.d.ts +77 -0
  108. package/dist/entitybox-mcp-broker.js +420 -0
  109. package/dist/entitybox-mcp-contract.d.ts +10 -0
  110. package/dist/entitybox-mcp-contract.js +24 -0
  111. package/dist/entitybox-mcp-guard.d.ts +3 -0
  112. package/dist/entitybox-mcp-guard.js +167 -0
  113. package/dist/entitybox-model-artifact.d.ts +9 -0
  114. package/dist/entitybox-model-artifact.js +83 -0
  115. package/dist/entitybox-native-services.d.ts +33 -0
  116. package/dist/entitybox-native-services.js +172 -0
  117. package/dist/entitybox-operation.d.ts +4 -0
  118. package/dist/entitybox-operation.js +34 -0
  119. package/dist/entitybox-provider-broker.d.ts +41 -0
  120. package/dist/entitybox-provider-broker.js +149 -0
  121. package/dist/entitybox-queue-request.d.ts +32 -0
  122. package/dist/entitybox-queue-request.js +141 -0
  123. package/dist/entitybox-recovery-host.d.ts +12 -0
  124. package/dist/entitybox-recovery-host.js +80 -0
  125. package/dist/entitybox-recovery-store.d.ts +33 -0
  126. package/dist/entitybox-recovery-store.js +150 -0
  127. package/dist/entitybox-recovery-worker.d.ts +30 -0
  128. package/dist/entitybox-recovery-worker.js +134 -0
  129. package/dist/entitybox-vault-provider.d.ts +27 -0
  130. package/dist/entitybox-vault-provider.js +127 -0
  131. package/dist/harness-registry.js +3 -1
  132. package/dist/index.js +72 -38
  133. package/dist/install.d.ts +14 -2
  134. package/dist/install.js +115 -10
  135. package/dist/model-cache.d.ts +38 -0
  136. package/dist/model-cache.js +137 -0
  137. package/dist/request-pump.d.ts +3 -0
  138. package/dist/request-pump.js +21 -7
  139. package/dist/session-runner.d.ts +8 -1
  140. package/dist/session-runner.js +48 -3
  141. package/dist/supabase-client.js +4 -1
  142. package/package.json +5 -1
  143. package/scripts/copy-assets.js +4 -2
@@ -0,0 +1,40 @@
1
+ import { type ChatProvider, type ProviderEvent } from './board-chat-provider';
2
+ export type BoardBinding = {
3
+ sessionId: string;
4
+ desktopId: string;
5
+ spaceId: string;
6
+ workspaceId: string;
7
+ label: string;
8
+ };
9
+ export declare const CHAT_RECONNECT_MS = 90000;
10
+ type Options = {
11
+ directory: string;
12
+ emit: (viewer: string, event: unknown) => void;
13
+ canStart: () => boolean;
14
+ onCount: () => void;
15
+ provider?: (cwd: string, emit: (event: ProviderEvent) => void, ended: (error: string) => void) => ChatProvider;
16
+ };
17
+ export declare function validateBinding(value: any): BoardBinding;
18
+ /** Owner-scoped durable conversations. No prompts or actions are replayed on reconnect. */
19
+ export declare class BoardChats {
20
+ private options;
21
+ private records;
22
+ constructor(options: Options);
23
+ get count(): number;
24
+ private save;
25
+ private emit;
26
+ private item;
27
+ private snapshot;
28
+ request(viewer: string, owner: string, input: unknown): unknown;
29
+ private run;
30
+ private event;
31
+ private armPending;
32
+ private resolve;
33
+ private finish;
34
+ private interrupt;
35
+ disconnect(viewer: string): void;
36
+ detach(viewer: string): void;
37
+ detachAll(): void;
38
+ stop(): void;
39
+ }
40
+ export {};
@@ -0,0 +1,414 @@
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.BoardChats = exports.CHAT_RECONNECT_MS = void 0;
37
+ exports.validateBinding = validateBinding;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const node_crypto_1 = require("node:crypto");
41
+ const protocol_1 = require("./protocol");
42
+ const board_chat_provider_1 = require("./board-chat-provider");
43
+ exports.CHAT_RECONNECT_MS = 90_000;
44
+ function validateBinding(value) {
45
+ if (!value || !protocol_1.UUID.test(value.sessionId ?? '') || !protocol_1.UUID.test(value.desktopId ?? '') || !protocol_1.UUID.test(value.workspaceId ?? '') ||
46
+ typeof value.spaceId !== 'string' || !value.spaceId || value.spaceId.length > 200 || /[\x00-\x1f]/.test(value.spaceId) ||
47
+ typeof value.label !== 'string' || value.label.length > 200)
48
+ throw new Error('Open a saved board before starting a chat.');
49
+ return { sessionId: value.sessionId, desktopId: value.desktopId, spaceId: value.spaceId, workspaceId: value.workspaceId, label: value.label };
50
+ }
51
+ const sameBoard = (a, b) => ['sessionId', 'desktopId', 'spaceId', 'workspaceId'].every(k => a[k] === b[k]);
52
+ const bounded = (value, size = 48_000) => JSON.stringify(value ?? null).slice(0, size);
53
+ /** Tool consent is an empty MCP form. Never treat a data-entry form as consent. */
54
+ function mcpConsent(params) {
55
+ const schema = params.requestedSchema;
56
+ if (params.mode !== 'form' || typeof params.serverName !== 'string' || !params.serverName || params.serverName.length > 200 ||
57
+ typeof params.message !== 'string' || !params.message.trim() || params.message.length > 8000 ||
58
+ !schema || schema.type !== 'object' || !schema.properties || typeof schema.properties !== 'object' || Array.isArray(schema.properties) ||
59
+ Object.keys(schema.properties).length ||
60
+ (schema.required !== undefined && (!Array.isArray(schema.required) || schema.required.length)) ||
61
+ Object.keys(schema).some(key => !['type', 'properties', 'required', 'title', 'description', 'additionalProperties'].includes(key)) ||
62
+ (schema.additionalProperties !== undefined && typeof schema.additionalProperties !== 'boolean'))
63
+ return null;
64
+ // Deliberately omit _meta: the browser cannot choose persistent/session approval.
65
+ return { serverName: params.serverName, message: params.message };
66
+ }
67
+ /** Owner-scoped durable conversations. No prompts or actions are replayed on reconnect. */
68
+ class BoardChats {
69
+ options;
70
+ records = new Map();
71
+ constructor(options) {
72
+ this.options = options;
73
+ fs.mkdirSync(options.directory, { recursive: true, mode: 0o700 });
74
+ for (const name of fs.readdirSync(options.directory).slice(0, 2000)) {
75
+ if (!protocol_1.UUID.test(name.replace(/\.json$/, '')) || !name.endsWith('.json'))
76
+ continue;
77
+ try {
78
+ const file = path.join(options.directory, name);
79
+ if (fs.statSync(file).size > 16 * 1024 * 1024)
80
+ continue;
81
+ const r = JSON.parse(fs.readFileSync(file, 'utf8'));
82
+ if (name !== `${r.id}.json` || !protocol_1.UUID.test(r.ownerId) || !Array.isArray(r.items) || !Array.isArray(r.sent) || !Number.isSafeInteger(r.seq))
83
+ continue;
84
+ validateBinding(r.binding);
85
+ // A worker restart never resumes a partially completed action automatically.
86
+ for (const item of r.items)
87
+ if (item.status === 'running')
88
+ item.status = 'interrupted';
89
+ this.records.set(r.id, { record: r, busy: false, pending: new Map() });
90
+ }
91
+ catch { /* Quarantine unreadable records by leaving their files untouched. */ }
92
+ }
93
+ }
94
+ get count() { return [...this.records.values()].filter(l => l.provider).length; }
95
+ save(l) {
96
+ l.record.updatedAt = new Date().toISOString();
97
+ const file = path.join(this.options.directory, `${l.record.id}.json`);
98
+ fs.writeFileSync(file + '.tmp', JSON.stringify(l.record), { mode: 0o600 });
99
+ fs.renameSync(file + '.tmp', file);
100
+ }
101
+ emit(l, event) {
102
+ if (l.viewer)
103
+ this.options.emit(l.viewer, { type: 'board-chat', sessionId: l.record.id, attachmentId: l.attachment, ...event });
104
+ }
105
+ item(l, id, kind, text, status, append = false) {
106
+ l.bytes ??= Buffer.byteLength(JSON.stringify(l.record));
107
+ let item = l.record.items.find(i => i.id === id);
108
+ const priorBytes = item ? Buffer.byteLength(JSON.stringify(item)) : 0;
109
+ if (!item) {
110
+ item = { id, kind, text: '', seq: ++l.record.seq };
111
+ l.record.items.push(item);
112
+ }
113
+ item.text = (append ? item.text + text : text).slice(0, 48_000);
114
+ item.status = status;
115
+ while (Buffer.byteLength(JSON.stringify(item)) > 48_000)
116
+ item.text = item.text.slice(0, Math.floor(item.text.length * .8));
117
+ l.bytes += Buffer.byteLength(JSON.stringify(item)) - priorBytes;
118
+ this.emit(l, { item: { ...item }, busy: l.busy });
119
+ }
120
+ snapshot(l, before = Infinity) {
121
+ const items = [];
122
+ let size = 0;
123
+ for (const item of l.record.items.filter(i => i.seq < before).reverse()) {
124
+ const bytes = Buffer.byteLength(JSON.stringify(item));
125
+ if (items.length && (size + bytes > 100_000 || items.length >= 60))
126
+ break;
127
+ items.unshift(item);
128
+ size += bytes;
129
+ }
130
+ return { id: l.record.id, binding: l.record.binding, attachmentId: l.attachment, busy: l.busy, items,
131
+ before: items[0]?.seq > 1 ? items[0].seq : null,
132
+ pending: [...l.pending.values()].map(({ timer, rpcId, ...p }) => p) };
133
+ }
134
+ request(viewer, owner, input) {
135
+ if (!protocol_1.UUID.test(viewer) || !protocol_1.UUID.test(owner))
136
+ throw new Error('Invalid chat owner.');
137
+ const r = (0, protocol_1.parseRequest)(input), chat = r.chat;
138
+ if (r.op !== 'chat')
139
+ throw new Error('Invalid chat request.');
140
+ if (chat.action === 'open') {
141
+ const binding = validateBinding(chat.binding);
142
+ let l = this.records.get(r.sessionId);
143
+ if (!l && !chat.fresh)
144
+ l = [...this.records.values()].filter(x => x.record.ownerId === owner && sameBoard(x.record.binding, binding)).sort((a, b) => b.record.updatedAt.localeCompare(a.record.updatedAt))[0];
145
+ if (!l) {
146
+ if ([...this.records.values()].filter(x => x.record.ownerId === owner).length >= 500)
147
+ throw new Error('This machine has reached its saved chat limit.');
148
+ l = { record: { id: r.sessionId, ownerId: owner, binding, items: [], sent: [], seq: 0, updatedAt: new Date().toISOString() }, busy: false, pending: new Map() };
149
+ this.save(l);
150
+ this.records.set(l.record.id, l);
151
+ }
152
+ if (l.record.ownerId !== owner || !sameBoard(l.record.binding, binding))
153
+ throw new Error('This conversation belongs to a different board or account.');
154
+ // Reopening the same conversation transfers its attachment, never its turn.
155
+ // This also covers a refresh racing the old socket's close notification.
156
+ for (const previous of this.records.values())
157
+ if (previous !== l && previous.viewer === viewer) {
158
+ this.interrupt(previous, 'You switched conversations. Send a message to continue this one.');
159
+ previous.viewer = undefined;
160
+ previous.attachment = undefined;
161
+ }
162
+ if (l.viewer && l.viewer !== viewer)
163
+ this.emit(l, { detached: true, busy: false, clearPending: true });
164
+ if (l.reconnectTimer)
165
+ clearTimeout(l.reconnectTimer);
166
+ l.reconnectTimer = undefined;
167
+ l.viewer = viewer;
168
+ l.attachment = (0, node_crypto_1.randomUUID)();
169
+ for (const pending of l.pending.values())
170
+ this.armPending(l, pending);
171
+ return this.snapshot(l);
172
+ }
173
+ const l = this.records.get(r.sessionId);
174
+ if (!l || l.record.ownerId !== owner || l.viewer !== viewer || l.attachment !== r.attachmentId)
175
+ throw new Error('This chat connection changed. Reopen it before continuing.');
176
+ if (chat.action === 'conversations')
177
+ return { conversations: [...this.records.values()]
178
+ .filter(x => x.record.ownerId === owner && sameBoard(x.record.binding, l.record.binding))
179
+ .sort((a, b) => b.record.updatedAt.localeCompare(a.record.updatedAt))
180
+ .map(x => ({ id: x.record.id, title: (x.record.items.find(i => i.kind === 'user')?.text || 'New conversation').slice(0, 40), updatedAt: x.record.updatedAt })) };
181
+ if (chat.action === 'history')
182
+ return this.snapshot(l, chat.before);
183
+ if (chat.action === 'detach') {
184
+ this.detach(viewer);
185
+ return { detached: true };
186
+ }
187
+ if (chat.action === 'cancel') {
188
+ this.interrupt(l);
189
+ return { cancelled: true };
190
+ }
191
+ if (chat.action === 'reply') {
192
+ const pending = l.pending.get(chat.callId);
193
+ if (!pending || pending.expiresAt <= Date.now())
194
+ throw new Error('This request has expired.');
195
+ this.resolve(l, pending, chat);
196
+ return { received: true };
197
+ }
198
+ if (chat.action === 'send') {
199
+ if (l.record.sent.includes(chat.messageId))
200
+ return { accepted: true, duplicate: true };
201
+ if (l.busy)
202
+ throw new Error('Wait for the current reply, or stop it first.');
203
+ if (!this.options.canStart())
204
+ throw new Error('Your machine is busy. Stop another coding session or try again shortly.');
205
+ if (l.record.items.length >= 1500 || l.record.sent.length >= 500 || (l.bytes ?? Buffer.byteLength(JSON.stringify(l.record))) >= 8 * 1024 * 1024)
206
+ throw new Error('Start a new conversation to continue. This chat remains saved.');
207
+ l.record.sent.push(chat.messageId);
208
+ l.busy = true;
209
+ this.item(l, chat.messageId, 'user', chat.text);
210
+ try {
211
+ this.save(l);
212
+ }
213
+ catch (error) {
214
+ l.busy = false;
215
+ throw error;
216
+ }
217
+ // Return immediately: tool answers and approvals must not wait behind a model turn.
218
+ void this.run(l, chat.text);
219
+ return { accepted: true };
220
+ }
221
+ throw new Error('Unknown chat action.');
222
+ }
223
+ async run(l, text) {
224
+ let provider;
225
+ try {
226
+ const cwd = path.join(this.options.directory, 'workspaces', l.record.id);
227
+ fs.mkdirSync(cwd, { recursive: true, mode: 0o700 });
228
+ provider = (this.options.provider || ((cwd, emit, ended) => new board_chat_provider_1.CodexBoardProvider(cwd, emit, ended)))(cwd, event => { if (l.provider === provider)
229
+ this.event(l, event); }, error => { if (l.provider === provider)
230
+ this.finish(l, error); });
231
+ l.provider = provider;
232
+ this.options.onCount();
233
+ const threadId = await provider.start(l.record.threadId);
234
+ if (l.provider !== provider || (!l.viewer && !l.reconnectTimer))
235
+ return;
236
+ l.record.threadId = threadId;
237
+ this.save(l);
238
+ await provider.send(threadId, text);
239
+ }
240
+ catch (error) {
241
+ if (!provider || l.provider === provider)
242
+ this.finish(l, error.message);
243
+ }
244
+ }
245
+ event(l, event) {
246
+ if (l.record.items.length >= 2000 || (l.bytes ?? 0) > 8 * 1024 * 1024) {
247
+ this.finish(l, 'This conversation is full. Start a new one to continue; its history remains saved.');
248
+ return;
249
+ }
250
+ const p = event.params || {};
251
+ if (event.id !== undefined) {
252
+ const mcp = event.method === 'mcpServer/elicitation/request';
253
+ const consent = mcp ? mcpConsent(p) : null;
254
+ if (mcp && (p.threadId !== l.record.threadId || (p.turnId && p.turnId !== l.turnId))) {
255
+ l.provider?.reply(event.id, { action: 'decline' });
256
+ return;
257
+ }
258
+ if (mcp && !consent) {
259
+ l.provider?.reply(event.id, { action: 'decline' });
260
+ this.item(l, (0, node_crypto_1.randomUUID)(), 'notice', 'A tool requested a form or sign-in interaction this chat cannot display yet. That request was declined; no permission was granted.');
261
+ return;
262
+ }
263
+ const kind = event.method === 'item/tool/call' && p.tool === 'aiboards' ? 'board-tool'
264
+ : ['item/commandExecution/requestApproval', 'item/fileChange/requestApproval'].includes(event.method) ? 'approval'
265
+ : mcp ? 'mcp-approval'
266
+ : event.method === 'item/tool/requestUserInput' ? 'question' : null;
267
+ if (!kind) {
268
+ this.finish(l, 'Codex requested an interaction this chat does not support yet. Continue in the native CodeFlows terminal.');
269
+ return;
270
+ }
271
+ if ((!l.viewer && !l.reconnectTimer) || l.pending.size >= 8) {
272
+ this.finish(l, 'Board access is paused or there are too many pending requests.');
273
+ return;
274
+ }
275
+ const id = (0, node_crypto_1.randomUUID)(), expiresAt = Date.now() + (kind === 'board-tool' ? 30_000 : 5 * 60_000);
276
+ const params = kind === 'board-tool' ? { arguments: p.arguments }
277
+ : kind === 'mcp-approval' ? consent
278
+ : kind === 'question' ? { questions: p.questions }
279
+ : { reason: p.reason, command: p.command, cwd: p.cwd, changes: p.changes || l.changes?.get(p.itemId), grantRoot: p.grantRoot };
280
+ if (Buffer.byteLength(JSON.stringify(params)) > 12_000) {
281
+ this.finish(l, 'A tool request was too large to review here. Continue in the native CodeFlows terminal.');
282
+ return;
283
+ }
284
+ const pending = { id, rpcId: event.id, kind, params, expiresAt };
285
+ l.pending.set(id, pending);
286
+ if (l.viewer)
287
+ this.armPending(l, pending);
288
+ this.emit(l, { pending: { id, kind, params, expiresAt } });
289
+ return;
290
+ }
291
+ if (event.method === 'serverRequest/resolved' && p.threadId === l.record.threadId) {
292
+ for (const pending of l.pending.values())
293
+ if (pending.rpcId === p.requestId) {
294
+ clearTimeout(pending.timer);
295
+ l.pending.delete(pending.id);
296
+ this.emit(l, { resolved: pending.id });
297
+ }
298
+ return;
299
+ }
300
+ if (event.method === 'turn/started') {
301
+ l.turnId = p.turn?.id;
302
+ this.emit(l, { busy: true });
303
+ }
304
+ const itemKey = (id) => `${p.turnId || l.turnId || 'starting'}:${id}`;
305
+ if (event.method === 'item/agentMessage/delta')
306
+ this.item(l, itemKey(p.itemId), 'assistant', p.delta || '', 'running', true);
307
+ if (['item/started', 'item/completed'].includes(event.method)) {
308
+ const item = p.item || {}, status = event.method === 'item/started' ? 'running' : 'done';
309
+ if (item.type === 'fileChange' && item.changes) {
310
+ l.changes ??= new Map();
311
+ l.changes.set(item.id, item.changes);
312
+ }
313
+ if (item.type === 'agentMessage')
314
+ this.item(l, itemKey(item.id), 'assistant', item.text || '', status);
315
+ if (['commandExecution', 'fileChange', 'mcpToolCall', 'dynamicToolCall', 'webSearch'].includes(item.type)) {
316
+ const label = item.type === 'commandExecution' ? item.command : item.type === 'mcpToolCall' ? `${item.server}: ${item.tool}` : item.type === 'dynamicToolCall' ? item.tool : item.type === 'webSearch' ? 'Searching the web' : 'Editing files';
317
+ this.item(l, itemKey(item.id), 'tool', String(label || item.type).slice(0, 4000), item.status === 'failed' ? 'failed' : status);
318
+ }
319
+ if (status === 'done')
320
+ this.save(l);
321
+ }
322
+ if (event.method === 'board/mcp')
323
+ this.emit(l, { mcp: p });
324
+ if (event.method === 'turn/completed')
325
+ this.finish(l, p.turn?.error?.message || (p.turn?.status === 'interrupted' ? 'Reply stopped.' : undefined));
326
+ if (event.method === 'error' && !p.willRetry)
327
+ this.finish(l, p.error?.message || 'Codex could not finish this reply.');
328
+ }
329
+ armPending(l, pending) {
330
+ clearTimeout(pending.timer);
331
+ pending.expiresAt = Date.now() + (pending.kind === 'board-tool' ? 30_000 : 5 * 60_000);
332
+ pending.timer = setTimeout(() => {
333
+ if (l.pending.has(pending.id))
334
+ this.resolve(l, pending, { ok: false, error: 'The request expired.', decision: 'decline' });
335
+ }, pending.expiresAt - Date.now());
336
+ }
337
+ resolve(l, p, answer) {
338
+ clearTimeout(p.timer);
339
+ l.pending.delete(p.id);
340
+ const result = p.kind === 'board-tool' ? { success: answer.ok === true, contentItems: [{ type: 'inputText', text: bounded(answer.ok ? answer.result : { error: answer.error || 'Board access is paused.' }) }] }
341
+ : p.kind === 'mcp-approval' ? answer.decision === 'accept' ? { action: 'accept', content: {} } : { action: 'decline' }
342
+ : p.kind === 'approval' ? { decision: answer.decision === 'accept' ? 'accept' : 'decline' }
343
+ : { answers: answer.answers || {} };
344
+ l.provider?.reply(p.rpcId, result);
345
+ this.emit(l, { resolved: p.id });
346
+ }
347
+ finish(l, error) {
348
+ if (l.reconnectTimer)
349
+ clearTimeout(l.reconnectTimer);
350
+ l.reconnectTimer = undefined;
351
+ const provider = l.provider;
352
+ l.provider = undefined;
353
+ l.busy = false;
354
+ l.turnId = undefined;
355
+ l.changes?.clear();
356
+ for (const p of l.pending.values())
357
+ clearTimeout(p.timer);
358
+ l.pending.clear();
359
+ provider?.close();
360
+ for (const item of l.record.items)
361
+ if (item.status === 'running')
362
+ item.status = error ? 'interrupted' : 'done';
363
+ if (error)
364
+ this.item(l, (0, node_crypto_1.randomUUID)(), 'notice', error.slice(0, 1000));
365
+ try {
366
+ this.save(l);
367
+ }
368
+ catch {
369
+ this.emit(l, { error: 'Conversation storage failed. Check free space on your machine.' });
370
+ }
371
+ this.emit(l, { busy: false, clearPending: true, interrupted: !!error });
372
+ this.options.onCount();
373
+ }
374
+ interrupt(l, reason = 'Reply stopped by you. Send a message to continue.') {
375
+ if (!l.busy && !l.provider)
376
+ return;
377
+ const provider = l.provider;
378
+ if (provider && l.turnId && l.record.threadId)
379
+ void provider.cancel(l.record.threadId, l.turnId).catch(() => { });
380
+ this.finish(l, reason);
381
+ }
382
+ // Network loss is not a user cancellation. Keep an accepted turn alive briefly;
383
+ // browser actions and approvals wait for a newly authenticated attachment.
384
+ disconnect(viewer) {
385
+ for (const l of this.records.values())
386
+ if (l.viewer === viewer) {
387
+ l.viewer = undefined;
388
+ l.attachment = undefined;
389
+ if (!l.busy && !l.provider)
390
+ continue;
391
+ for (const pending of l.pending.values()) {
392
+ clearTimeout(pending.timer);
393
+ pending.timer = undefined;
394
+ }
395
+ if (!l.reconnectTimer)
396
+ l.reconnectTimer = setTimeout(() => {
397
+ l.reconnectTimer = undefined;
398
+ this.interrupt(l, 'The board did not reconnect within 90 seconds, so this reply was stopped. Your work is saved; send a message to continue.');
399
+ }, exports.CHAT_RECONNECT_MS);
400
+ }
401
+ }
402
+ detach(viewer) { for (const l of this.records.values())
403
+ if (l.viewer === viewer) {
404
+ this.interrupt(l, 'Chat closed. Send a message to continue.');
405
+ l.viewer = undefined;
406
+ l.attachment = undefined;
407
+ } }
408
+ detachAll() { for (const l of this.records.values())
409
+ if (l.viewer)
410
+ this.disconnect(l.viewer); }
411
+ stop() { for (const l of this.records.values())
412
+ this.interrupt(l, 'Your machine stopped the chat service. Your work is saved; send a message to continue.'); }
413
+ }
414
+ exports.BoardChats = BoardChats;
@@ -0,0 +1,2 @@
1
+ /** Optional local configuration survives normal Node restarts. Explicit env wins. */
2
+ export declare function directEnvironment(env?: NodeJS.ProcessEnv, file?: string): NodeJS.ProcessEnv;
@@ -0,0 +1,60 @@
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.directEnvironment = directEnvironment;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const paths_1 = require("../paths");
40
+ /** Optional local configuration survives normal Node restarts. Explicit env wins. */
41
+ function directEnvironment(env = process.env, file = path.join(paths_1.RUNTIME_HOME, 'codeflows.json')) {
42
+ if (env.AINODE_DIRECT_SESSIONS !== undefined)
43
+ return env;
44
+ try {
45
+ if (fs.statSync(file).size > 16_384)
46
+ return env;
47
+ const config = JSON.parse(fs.readFileSync(file, 'utf8'));
48
+ if (config?.enabled !== true || typeof config.relayUrl !== 'string' || !Array.isArray(config.roots))
49
+ return env;
50
+ return {
51
+ ...env, AINODE_DIRECT_SESSIONS: '1',
52
+ AINODE_DIRECT_RELAY_URL: env.AINODE_DIRECT_RELAY_URL ?? config.relayUrl,
53
+ AINODE_DIRECT_ROOTS: env.AINODE_DIRECT_ROOTS ?? JSON.stringify(config.roots),
54
+ AINODE_DIRECT_MAX_SESSIONS: env.AINODE_DIRECT_MAX_SESSIONS ?? String(config.maxSessions ?? 2),
55
+ };
56
+ }
57
+ catch {
58
+ return env;
59
+ } // Missing or damaged optional settings cannot prevent Node startup.
60
+ }
@@ -0,0 +1,14 @@
1
+ import type * as pty from 'node-pty';
2
+ import type { SessionRecord, TerminalProcess } from './sessions';
3
+ import { type AiosLaunch } from './aios-bridge';
4
+ import type { DesktopCall } from './aios-tools';
5
+ export declare function directProviders(): {
6
+ claude: boolean;
7
+ codex: boolean;
8
+ };
9
+ export declare function directArgs(record: SessionRecord, resume: boolean, aios?: AiosLaunch): string[];
10
+ export declare function spawnDirect(record: SessionRecord, size: {
11
+ cols: number;
12
+ rows: number;
13
+ }, resume: boolean, desktop?: DesktopCall): TerminalProcess;
14
+ export declare function spawnDirectTerminal(file: string, args: string[], options: pty.IPtyForkOptions): TerminalProcess;
@@ -0,0 +1,121 @@
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.directProviders = directProviders;
37
+ exports.directArgs = directArgs;
38
+ exports.spawnDirect = spawnDirect;
39
+ exports.spawnDirectTerminal = spawnDirectTerminal;
40
+ const node_child_process_1 = require("node:child_process");
41
+ const node_events_1 = require("node:events");
42
+ const path = __importStar(require("node:path"));
43
+ const claude_binary_1 = require("../claude-binary");
44
+ const codex_binary_1 = require("../codex-binary");
45
+ const win_1 = require("../win");
46
+ const aios_bridge_1 = require("./aios-bridge");
47
+ function directProviders() { return { claude: !!(0, claude_binary_1.findClaudeBinary)(), codex: !!(0, codex_binary_1.findCodexBinary)() }; }
48
+ function directArgs(record, resume, aios) {
49
+ if (record.provider === 'claude') {
50
+ return ['--permission-mode', 'default', ...(resume ? ['--resume', record.nativeSessionId] : ['--session-id', record.nativeSessionId]), ...(aios ? ['--mcp-config', aios.claudeConfig] : [])];
51
+ }
52
+ // The native picker is deliberate. --last could resume an entity or another project's conversation.
53
+ const extra = aios ? Object.entries({ command: aios.command, args: aios.args, enabled: true, startup_timeout_sec: 10, tool_timeout_sec: 15 })
54
+ .flatMap(([key, value]) => ['-c', `mcp_servers.addai_codeflows.${key}=${JSON.stringify(value)}`]) : [];
55
+ return [...(resume ? ['resume'] : []), '--sandbox', 'workspace-write', '--ask-for-approval', 'on-request', ...extra];
56
+ }
57
+ function spawnDirect(record, size, resume, desktop) {
58
+ const bin = record.provider === 'claude' ? (0, claude_binary_1.findClaudeBinary)() : (0, codex_binary_1.findCodexBinary)();
59
+ if (!bin)
60
+ throw new Error('The selected program is not installed.');
61
+ const aios = record.aios === true && desktop ? (0, aios_bridge_1.createAiosBridge)(desktop) : undefined;
62
+ try {
63
+ const command = (0, win_1.resolveCliInvocation)(bin, directArgs(record, resume, aios));
64
+ const env = { ...process.env };
65
+ // Internal transport/config credentials are not agent inputs. Native provider credentials stay native.
66
+ for (const key of Object.keys(env))
67
+ if (key.startsWith('AINODE_DIRECT_') || key === 'SUPABASE_SERVICE_ROLE_KEY' || key === 'NODE_CHANNEL_FD')
68
+ delete env[key];
69
+ const proc = spawnDirectTerminal(command.file, command.args, {
70
+ name: 'xterm-256color', ...size, cwd: record.cwd,
71
+ env: { ...env, TERM: 'xterm-256color', COLORTERM: 'truecolor' },
72
+ });
73
+ proc.onExit(() => aios?.close());
74
+ return proc;
75
+ }
76
+ catch (err) {
77
+ aios?.close();
78
+ throw err;
79
+ }
80
+ }
81
+ function spawnDirectTerminal(file, args, options) {
82
+ // Each terminal owns a process tree. Stopping it bypasses node-pty's Windows
83
+ // console-list teardown race, and a finished terminal releases all host handles.
84
+ const hostOptions = {
85
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'], windowsHide: true, env: options.env,
86
+ };
87
+ const child = (0, node_child_process_1.fork)(path.join(__dirname, 'terminal.js'), [], hostOptions);
88
+ const events = new node_events_1.EventEmitter();
89
+ let ended = false, stopping = false, terminalExitCode;
90
+ const send = (message) => {
91
+ if (!ended && child.connected)
92
+ child.send(message, () => { });
93
+ };
94
+ child.on('message', (value) => {
95
+ if (value?.type === 'data' && typeof value.data === 'string')
96
+ events.emit('data', value.data);
97
+ if (value?.type === 'exit' && typeof value.exitCode === 'number')
98
+ terminalExitCode = value.exitCode;
99
+ });
100
+ const finish = (code) => {
101
+ if (ended)
102
+ return;
103
+ ended = true;
104
+ events.emit('exit', { exitCode: terminalExitCode ?? code });
105
+ };
106
+ child.on('error', () => finish(1));
107
+ child.on('exit', code => finish(code ?? 1));
108
+ send({ type: 'start', file, args, options });
109
+ return {
110
+ write: data => { if (!stopping)
111
+ send({ type: 'input', data }); },
112
+ resize: (cols, rows) => { if (!stopping)
113
+ send({ type: 'resize', cols, rows }); },
114
+ pause: () => send({ type: 'pause' }), resume: () => send({ type: 'resume' }),
115
+ kill: () => { if (!ended && !stopping) {
116
+ stopping = true;
117
+ (0, win_1.killProcessTree)(child);
118
+ } },
119
+ onData: cb => events.on('data', cb), onExit: cb => events.on('exit', cb),
120
+ };
121
+ }