atris 3.15.30 → 3.15.31
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/ax +21 -479
- package/commands/aeo.js +13 -377
- package/commands/computer.js +176 -1
- package/commands/gm.js +4 -2
- package/commands/play.js +4 -2
- package/commands/xp.js +14 -1
- package/package.json +1 -1
package/ax
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require('fs');
|
|
4
3
|
const http = require('http');
|
|
5
|
-
const https = require('https');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const path = require('path');
|
|
8
4
|
const readline = require('readline');
|
|
9
|
-
const { loadCredentials } = require('./utils/auth');
|
|
10
5
|
|
|
11
6
|
const EXIT_WORDS = new Set(['exit', 'quit', ':q']);
|
|
12
7
|
const BACKEND = {
|
|
@@ -14,39 +9,6 @@ const BACKEND = {
|
|
|
14
9
|
port: 8000,
|
|
15
10
|
path: '/api/atris2/turn'
|
|
16
11
|
};
|
|
17
|
-
const DEFAULT_BACKEND_BASE = `http://${BACKEND.host}:${BACKEND.port}`;
|
|
18
|
-
const CONNECTION_STATUS_PATH = '/api/integrations/status';
|
|
19
|
-
const CONNECTION_CAPABILITIES_PATH = '/api/atris2/connection-capabilities';
|
|
20
|
-
const ATRIS2_CONNECTION_STATUS_PATH = '/api/atris2/connection-status';
|
|
21
|
-
const CONNECTOR_NAMES = {
|
|
22
|
-
gmail: 'Gmail',
|
|
23
|
-
google_calendar: 'Google Calendar',
|
|
24
|
-
google_drive: 'Google Drive',
|
|
25
|
-
google_docs: 'Google Docs',
|
|
26
|
-
github: 'GitHub',
|
|
27
|
-
slack: 'Slack',
|
|
28
|
-
linear: 'Linear',
|
|
29
|
-
notion: 'Notion',
|
|
30
|
-
hubspot: 'HubSpot',
|
|
31
|
-
twitter: 'Twitter',
|
|
32
|
-
microsoft: 'Microsoft',
|
|
33
|
-
discord: 'Discord',
|
|
34
|
-
jira: 'Jira',
|
|
35
|
-
linkedin: 'LinkedIn',
|
|
36
|
-
whatsapp: 'WhatsApp',
|
|
37
|
-
salesforce: 'Salesforce'
|
|
38
|
-
};
|
|
39
|
-
const CONNECTOR_SCOPES = {
|
|
40
|
-
gmail: ['mail'],
|
|
41
|
-
google_calendar: ['calendar'],
|
|
42
|
-
google_drive: ['drive'],
|
|
43
|
-
google_docs: ['docs'],
|
|
44
|
-
github: ['repos', 'issues', 'pull_requests'],
|
|
45
|
-
slack: ['messages'],
|
|
46
|
-
linear: ['issues'],
|
|
47
|
-
notion: ['pages', 'databases'],
|
|
48
|
-
hubspot: ['crm']
|
|
49
|
-
};
|
|
50
12
|
const ANSI = {
|
|
51
13
|
reset: '\x1b[0m',
|
|
52
14
|
bold: '\x1b[1m',
|
|
@@ -88,54 +50,37 @@ function formatUsage() {
|
|
|
88
50
|
'ax - Atris 2 local coding agent',
|
|
89
51
|
'',
|
|
90
52
|
'Usage:',
|
|
91
|
-
' ax [--pro|--fast]
|
|
92
|
-
' ax [--pro|--fast]
|
|
53
|
+
' ax [--pro|--fast] <message>',
|
|
54
|
+
' ax [--pro|--fast] --chat',
|
|
93
55
|
' ax [--pro|--fast] --doctor',
|
|
94
|
-
' ax [--fast] --benchmark',
|
|
95
56
|
'',
|
|
96
57
|
'Modes:',
|
|
97
58
|
' --pro local workspace agent, deeper tool loop',
|
|
98
59
|
' --fast local workspace agent, faster low-latency turns',
|
|
99
|
-
' --local force local workspace tools',
|
|
100
|
-
' --cloud force authenticated cloud connectors/chat',
|
|
101
60
|
'',
|
|
102
61
|
'Examples:',
|
|
103
62
|
' ax --pro find the config file and explain it',
|
|
104
63
|
' ax --fast what files are here',
|
|
105
|
-
' ax --fast what is on my calendar today',
|
|
106
64
|
' ax --pro --chat',
|
|
107
65
|
].join('\n');
|
|
108
66
|
}
|
|
109
67
|
|
|
110
|
-
function backendBaseUrl() {
|
|
111
|
-
return (process.env.AX_BACKEND_URL
|
|
112
|
-
|| process.env.OBELISK_LOCAL_ATRIS2_BACKEND_URL
|
|
113
|
-
|| process.env.OBELISK_ATRIS2_BACKEND_URL
|
|
114
|
-
|| DEFAULT_BACKEND_BASE).replace(/\/$/, '');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
68
|
function backendUrl() {
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function backendPathUrl(pathname) {
|
|
122
|
-
return new URL(pathname, backendBaseUrl()).toString();
|
|
69
|
+
return `http://${BACKEND.host}:${BACKEND.port}${BACKEND.path}`;
|
|
123
70
|
}
|
|
124
71
|
|
|
125
72
|
function buildRunProfile(options = {}) {
|
|
126
73
|
const mode = options.mode === 'fast' ? 'fast' : 'pro';
|
|
127
74
|
const cwd = options.cwd || process.cwd();
|
|
128
|
-
const
|
|
129
|
-
const payload = buildPayload(options.message || 'doctor', { mode, cwd, route });
|
|
75
|
+
const payload = buildPayload(options.message || 'doctor', { mode, cwd });
|
|
130
76
|
return {
|
|
131
77
|
endpoint: backendUrl(),
|
|
132
78
|
mode,
|
|
133
|
-
route,
|
|
134
79
|
model: payload.model,
|
|
135
|
-
workspace_path: payload.workspace_path
|
|
80
|
+
workspace_path: payload.workspace_path,
|
|
136
81
|
max_turns: payload.max_turns,
|
|
137
82
|
streaming: true,
|
|
138
|
-
runtime:
|
|
83
|
+
runtime: 'local workspace',
|
|
139
84
|
reasoning: mode === 'pro'
|
|
140
85
|
? 'backend reports run row; Pro workspace tool loop uses API default medium'
|
|
141
86
|
: 'backend reports run row; Fast workspace tool loop uses provider default'
|
|
@@ -146,7 +91,6 @@ function formatRunProfile(profile, options = {}) {
|
|
|
146
91
|
const rows = [
|
|
147
92
|
['mode', `${profile.mode} (${profile.model})`],
|
|
148
93
|
['endpoint', profile.endpoint],
|
|
149
|
-
['route', profile.route || 'auto'],
|
|
150
94
|
['workspace', formatPathSubject(profile.workspace_path, options)],
|
|
151
95
|
['turns', String(profile.max_turns)],
|
|
152
96
|
['streaming', profile.streaming ? 'yes' : 'no'],
|
|
@@ -156,196 +100,6 @@ function formatRunProfile(profile, options = {}) {
|
|
|
156
100
|
return rows.map(([label, value]) => formatAuxRow(label, value, options)).join('\n');
|
|
157
101
|
}
|
|
158
102
|
|
|
159
|
-
function canonicalConnectorId(value) {
|
|
160
|
-
return String(value || '').trim().toLowerCase().replace(/-/g, '_');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function connectorDisplayName(id) {
|
|
164
|
-
const key = canonicalConnectorId(id);
|
|
165
|
-
if (CONNECTOR_NAMES[key]) return CONNECTOR_NAMES[key];
|
|
166
|
-
return key.split('_').map(part => part ? part[0].toUpperCase() + part.slice(1) : '').join(' ');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function authToken() {
|
|
170
|
-
if (process.env.OBELISK_AUTH_TOKEN) return process.env.OBELISK_AUTH_TOKEN;
|
|
171
|
-
if (process.env.ATRIS_TOKEN) return process.env.ATRIS_TOKEN;
|
|
172
|
-
try {
|
|
173
|
-
const creds = loadCredentials();
|
|
174
|
-
return creds && creds.token ? creds.token : '';
|
|
175
|
-
} catch (_) {
|
|
176
|
-
return '';
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function authUserId() {
|
|
181
|
-
if (process.env.OBELISK_USER_ID) return process.env.OBELISK_USER_ID;
|
|
182
|
-
if (process.env.ATRIS_USER_ID) return process.env.ATRIS_USER_ID;
|
|
183
|
-
try {
|
|
184
|
-
const creds = loadCredentials();
|
|
185
|
-
return creds && creds.user_id ? creds.user_id : '';
|
|
186
|
-
} catch (_) {
|
|
187
|
-
return '';
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function isLoopbackBackend() {
|
|
192
|
-
try {
|
|
193
|
-
const parsed = new URL(backendBaseUrl());
|
|
194
|
-
return ['127.0.0.1', 'localhost', '::1'].includes(parsed.hostname);
|
|
195
|
-
} catch (_) {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function requestJson(pathname, { token = authToken(), timeoutMs = 6000 } = {}) {
|
|
201
|
-
const url = backendPathUrl(pathname);
|
|
202
|
-
const parsed = new URL(url);
|
|
203
|
-
const transport = parsed.protocol === 'https:' ? https : http;
|
|
204
|
-
return new Promise((resolve) => {
|
|
205
|
-
const req = transport.request({
|
|
206
|
-
method: 'GET',
|
|
207
|
-
hostname: parsed.hostname,
|
|
208
|
-
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
209
|
-
path: `${parsed.pathname}${parsed.search}`,
|
|
210
|
-
headers: {
|
|
211
|
-
Accept: 'application/json',
|
|
212
|
-
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
213
|
-
}
|
|
214
|
-
}, (res) => {
|
|
215
|
-
const chunks = [];
|
|
216
|
-
res.on('data', chunk => chunks.push(chunk));
|
|
217
|
-
res.on('end', () => {
|
|
218
|
-
const text = Buffer.concat(chunks).toString('utf8');
|
|
219
|
-
try {
|
|
220
|
-
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, data: text ? JSON.parse(text) : null, text });
|
|
221
|
-
} catch (_) {
|
|
222
|
-
resolve({ ok: false, status: res.statusCode, data: null, text });
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
req.on('error', error => resolve({ ok: false, status: 0, data: null, text: '', error: error.message }));
|
|
227
|
-
req.setTimeout(timeoutMs, () => {
|
|
228
|
-
req.destroy();
|
|
229
|
-
resolve({ ok: false, status: 0, data: null, text: '', error: `timeout after ${timeoutMs}ms` });
|
|
230
|
-
});
|
|
231
|
-
req.end();
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function defaultAuthority(id) {
|
|
236
|
-
const key = canonicalConnectorId(id);
|
|
237
|
-
if (key === 'gmail') return { list_messages: 'read_only', get_message: 'read_only', send_message: 'approval_required' };
|
|
238
|
-
if (key === 'google_calendar') return { list_events: 'read_only', get_event: 'read_only', create_event: 'approval_required' };
|
|
239
|
-
if (key === 'google_drive') return { list_files: 'read_only', search_files: 'read_only', download_file: 'read_only', upload_file: 'approval_required' };
|
|
240
|
-
if (key === 'google_docs') return { list_documents: 'read_only', get_document: 'read_only', create_document: 'approval_required' };
|
|
241
|
-
if (key === 'github') return { list_repos: 'read_only', get_repo: 'read_only', list_issues: 'read_only', create_issue: 'approval_required' };
|
|
242
|
-
if (key === 'slack') return { list_channels: 'read_only', search_messages: 'read_only', post_message: 'approval_required' };
|
|
243
|
-
if (key === 'linear') return { list_issues: 'read_only', get_issue: 'read_only', create_issue: 'approval_required' };
|
|
244
|
-
if (key === 'notion') return { search: 'read_only', get_page: 'read_only', create_page: 'approval_required' };
|
|
245
|
-
if (key === 'hubspot') return { list_contacts: 'read_only', search_contacts: 'read_only', create_contact: 'approval_required' };
|
|
246
|
-
return { inspect_status: 'read_only' };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function connectionFromStatus(id, connected, contractConnector = null) {
|
|
250
|
-
const key = canonicalConnectorId(id);
|
|
251
|
-
const authority = contractConnector && contractConnector.authority && typeof contractConnector.authority === 'object'
|
|
252
|
-
? contractConnector.authority
|
|
253
|
-
: defaultAuthority(key);
|
|
254
|
-
return {
|
|
255
|
-
id: key,
|
|
256
|
-
name: connectorDisplayName(key),
|
|
257
|
-
connected: connected === true,
|
|
258
|
-
local: false,
|
|
259
|
-
actions: Object.keys(authority),
|
|
260
|
-
scopes: CONNECTOR_SCOPES[key] || [],
|
|
261
|
-
authority,
|
|
262
|
-
source: 'ax_integrations_status'
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function statusConnected(statuses, id) {
|
|
267
|
-
const keys = [id, id.replace(/_/g, '-'), canonicalConnectorId(id)];
|
|
268
|
-
for (const key of keys) {
|
|
269
|
-
const value = statuses[key];
|
|
270
|
-
if (value === true) return true;
|
|
271
|
-
if (value && typeof value === 'object') {
|
|
272
|
-
if (value.connected === true) return true;
|
|
273
|
-
if (String(value.status || '').toLowerCase() === 'connected') return true;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function buildConnectionContext(options = {}) {
|
|
280
|
-
const token = options.token || authToken();
|
|
281
|
-
const localStatusUserId = isLoopbackBackend() ? authUserId() : '';
|
|
282
|
-
const statusPath = localStatusUserId
|
|
283
|
-
? `${ATRIS2_CONNECTION_STATUS_PATH}?connection_user_id=${encodeURIComponent(localStatusUserId)}`
|
|
284
|
-
: CONNECTION_STATUS_PATH;
|
|
285
|
-
const [statusRes, contractRes] = await Promise.all([
|
|
286
|
-
localStatusUserId || token ? requestJson(statusPath, { token: localStatusUserId ? '' : token }) : Promise.resolve({ ok: false, data: null }),
|
|
287
|
-
requestJson(CONNECTION_CAPABILITIES_PATH, { token: '' })
|
|
288
|
-
]);
|
|
289
|
-
const statusData = statusRes.ok && statusRes.data && typeof statusRes.data === 'object' ? statusRes.data : {};
|
|
290
|
-
const statuses = statusData.statuses && typeof statusData.statuses === 'object' ? statusData.statuses : statusData;
|
|
291
|
-
const contractConnectors = new Map();
|
|
292
|
-
if (contractRes.ok && contractRes.data && Array.isArray(contractRes.data.connectors)) {
|
|
293
|
-
for (const connector of contractRes.data.connectors) {
|
|
294
|
-
const key = canonicalConnectorId(connector.id);
|
|
295
|
-
if (key) contractConnectors.set(key, connector);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
const ids = new Set([
|
|
299
|
-
...Object.keys(CONNECTOR_NAMES),
|
|
300
|
-
...Object.keys(statuses).map(canonicalConnectorId),
|
|
301
|
-
...contractConnectors.keys(),
|
|
302
|
-
]);
|
|
303
|
-
const connections = [...ids].sort().map(id => connectionFromStatus(
|
|
304
|
-
id,
|
|
305
|
-
statusConnected(statuses, id),
|
|
306
|
-
contractConnectors.get(id)
|
|
307
|
-
));
|
|
308
|
-
connections.push({
|
|
309
|
-
id: 'computer.local',
|
|
310
|
-
name: 'This Mac workspace',
|
|
311
|
-
connected: options.localWorkspace === true,
|
|
312
|
-
local: true,
|
|
313
|
-
status: options.localWorkspace === true ? 'available' : 'cloud_only',
|
|
314
|
-
actions: ['inspect_status', 'use_local_workspace'],
|
|
315
|
-
scopes: ['computer', 'workspace'],
|
|
316
|
-
authority: { inspect_status: 'read_only', use_local_workspace: 'read_only' },
|
|
317
|
-
source: 'ax'
|
|
318
|
-
});
|
|
319
|
-
return {
|
|
320
|
-
schema: 'atris.connection_capabilities.v1',
|
|
321
|
-
source: 'ax',
|
|
322
|
-
stale: false,
|
|
323
|
-
capability_contract_source: contractRes.ok ? 'backend' : 'fallback',
|
|
324
|
-
capability_contract_schema: contractRes.data && contractRes.data.schema,
|
|
325
|
-
capability_contract_connectors: contractConnectors.size || undefined,
|
|
326
|
-
connections
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function mentionsConnector(message) {
|
|
331
|
-
return /\b(gmail|email|mail|inbox|calendar|events?|meetings?|schedule|google drive|drive|docs?|sheets?|github|slack|linear|notion|hubspot|integrations?|connectors?|connections?|connected apps?|connected tools?)\b/i.test(message || '');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function connectorWriteIntent(message) {
|
|
335
|
-
return mentionsConnector(message) && /\b(send|post|dm|message|reply|draft|compose|schedule|book|create|update|delete|archive|move|share|comment|invite)\b/i.test(message || '');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function workspaceIntent(message) {
|
|
339
|
-
return /\b(files?|folders?|repo|workspace|project|directory|tree|read|open|inspect|search|grep|find|locate|where|edit|write|change|modify|patch|fix|test|tests?|build|src|source|code|diff|git|backend|frontend|atris task|atris xp|xp game|career xp|agentxp|todo|map)\b/i.test(message || '');
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function resolveRoute(message, options = {}) {
|
|
343
|
-
if (options.route === 'local' || options.forceLocal) return 'local';
|
|
344
|
-
if (options.route === 'cloud' || options.forceCloud) return 'cloud';
|
|
345
|
-
if (mentionsConnector(message) && !workspaceIntent(message)) return 'cloud';
|
|
346
|
-
return 'local';
|
|
347
|
-
}
|
|
348
|
-
|
|
349
103
|
function formatPrompt() {
|
|
350
104
|
return '› ';
|
|
351
105
|
}
|
|
@@ -588,29 +342,13 @@ function buildMessage(message, history = []) {
|
|
|
588
342
|
|
|
589
343
|
function buildPayload(message, options = {}) {
|
|
590
344
|
const mode = options.mode === 'fast' ? 'fast' : 'pro';
|
|
591
|
-
|
|
592
|
-
const local = route !== 'cloud';
|
|
593
|
-
const payload = {
|
|
345
|
+
return {
|
|
594
346
|
message: buildMessage(message, options.history || []),
|
|
347
|
+
workspace_path: options.cwd || process.cwd(),
|
|
595
348
|
model: modelForMode(mode),
|
|
596
|
-
max_turns:
|
|
349
|
+
max_turns: mode === 'pro' ? 14 : 6,
|
|
597
350
|
verify_command: 'true'
|
|
598
351
|
};
|
|
599
|
-
if (local) {
|
|
600
|
-
payload.workspace_path = options.cwd || process.cwd();
|
|
601
|
-
}
|
|
602
|
-
if (options.connectionContext) {
|
|
603
|
-
payload.connection_context = options.connectionContext;
|
|
604
|
-
}
|
|
605
|
-
if (!local && options.connectionUserId) {
|
|
606
|
-
payload.connection_user_id = options.connectionUserId;
|
|
607
|
-
}
|
|
608
|
-
if (!local && connectorWriteIntent(message)) {
|
|
609
|
-
payload.allow_external_actions = true;
|
|
610
|
-
payload.cleanup_external_actions = true;
|
|
611
|
-
payload.max_turns = mode === 'pro' ? 4 : 2;
|
|
612
|
-
}
|
|
613
|
-
return payload;
|
|
614
352
|
}
|
|
615
353
|
|
|
616
354
|
function handleEvent(event, state, output) {
|
|
@@ -663,24 +401,11 @@ function handleEvent(event, state, output) {
|
|
|
663
401
|
}
|
|
664
402
|
}
|
|
665
403
|
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
const local = route !== 'cloud';
|
|
669
|
-
const token = authToken();
|
|
670
|
-
const shouldSendConnectionContext = options.connectionContext
|
|
671
|
-
|| route === 'cloud'
|
|
672
|
-
|| mentionsConnector(message)
|
|
673
|
-
|| /\b(can you use|can you access|connected|connections?|integrations?|tools?|capabilities)\b/i.test(message || '');
|
|
674
|
-
const connectionContext = options.connectionContext || (shouldSendConnectionContext
|
|
675
|
-
? await buildConnectionContext({ token, localWorkspace: local })
|
|
676
|
-
: null);
|
|
677
|
-
const connectionUserId = !local && isLoopbackBackend() ? authUserId() : '';
|
|
678
|
-
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId });
|
|
404
|
+
function postTurn(message, options = {}) {
|
|
405
|
+
const payload = buildPayload(message, options);
|
|
679
406
|
const postData = JSON.stringify(payload);
|
|
680
407
|
const output = options.output || process.stdout;
|
|
681
408
|
const timeoutMs = payload.model === 'atris:pro' ? 180000 : 60000;
|
|
682
|
-
const turnUrl = new URL(backendUrl());
|
|
683
|
-
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
684
409
|
const state = {
|
|
685
410
|
events: [],
|
|
686
411
|
errors: [],
|
|
@@ -710,16 +435,15 @@ async function postTurn(message, options = {}) {
|
|
|
710
435
|
else resolve(value);
|
|
711
436
|
};
|
|
712
437
|
|
|
713
|
-
const req =
|
|
714
|
-
hostname:
|
|
715
|
-
port:
|
|
716
|
-
path:
|
|
438
|
+
const req = http.request({
|
|
439
|
+
hostname: BACKEND.host,
|
|
440
|
+
port: BACKEND.port,
|
|
441
|
+
path: BACKEND.path,
|
|
717
442
|
method: 'POST',
|
|
718
443
|
headers: {
|
|
719
444
|
'Content-Type': 'application/json',
|
|
720
445
|
'Content-Length': Buffer.byteLength(postData),
|
|
721
|
-
Accept: 'text/event-stream'
|
|
722
|
-
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
446
|
+
Accept: 'text/event-stream'
|
|
723
447
|
}
|
|
724
448
|
}, (res) => {
|
|
725
449
|
res.setEncoding('utf8');
|
|
@@ -830,174 +554,7 @@ async function chat(options = {}) {
|
|
|
830
554
|
function printBackendHint() {
|
|
831
555
|
console.log('');
|
|
832
556
|
console.log('Start backend:');
|
|
833
|
-
console.log(`cd /Users/keshavrao/arena/atrisos-backend/backend &&
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
function bufferedOutput() {
|
|
837
|
-
let text = '';
|
|
838
|
-
return {
|
|
839
|
-
isTTY: false,
|
|
840
|
-
write(chunk) {
|
|
841
|
-
text += String(chunk || '');
|
|
842
|
-
return true;
|
|
843
|
-
},
|
|
844
|
-
text() {
|
|
845
|
-
return text;
|
|
846
|
-
}
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function receiptFromState(state) {
|
|
851
|
-
const event = [...(state.events || [])].reverse().find(item => item && item.type === 'receipt' && item.receipt);
|
|
852
|
-
return event ? event.receipt : null;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
function toolEventsFromState(state) {
|
|
856
|
-
const receipt = receiptFromState(state);
|
|
857
|
-
if (receipt && Array.isArray(receipt.tool_events)) return receipt.tool_events;
|
|
858
|
-
return [];
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
function assertBenchmark(condition, message) {
|
|
862
|
-
if (!condition) throw new Error(message);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
function toolEventText(events) {
|
|
866
|
-
return JSON.stringify(events || []);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
async function runBenchmarkCase(label, fn, results, output) {
|
|
870
|
-
const startedAt = Date.now();
|
|
871
|
-
output.write(`- ${label} ... `);
|
|
872
|
-
try {
|
|
873
|
-
const detail = await fn();
|
|
874
|
-
const duration = formatDuration(Date.now() - startedAt);
|
|
875
|
-
output.write(`ok (${duration})${detail ? ` - ${detail}` : ''}\n`);
|
|
876
|
-
results.push({ label, ok: true, duration_ms: Date.now() - startedAt, detail });
|
|
877
|
-
} catch (error) {
|
|
878
|
-
const duration = formatDuration(Date.now() - startedAt);
|
|
879
|
-
output.write(`fail (${duration}) - ${error.message}\n`);
|
|
880
|
-
results.push({ label, ok: false, duration_ms: Date.now() - startedAt, error: error.message });
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
async function runBenchmark(options = {}) {
|
|
885
|
-
const mode = options.mode === 'pro' ? 'pro' : 'fast';
|
|
886
|
-
const output = options.output || process.stdout;
|
|
887
|
-
const results = [];
|
|
888
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ax-atris2-benchmark-'));
|
|
889
|
-
const localOut = () => bufferedOutput();
|
|
890
|
-
|
|
891
|
-
output.write(`Atris 2 ${mode === 'fast' ? 'Fast' : 'Pro'} benchmark\n`);
|
|
892
|
-
output.write(`${backendUrl()}\n\n`);
|
|
893
|
-
|
|
894
|
-
try {
|
|
895
|
-
await runBenchmarkCase('search through files', async () => {
|
|
896
|
-
const workspace = path.join(tempRoot, 'search');
|
|
897
|
-
fs.mkdirSync(path.join(workspace, 'src', 'deep'), { recursive: true });
|
|
898
|
-
fs.writeFileSync(path.join(workspace, 'package.json'), '{"name":"ax-search-bench"}\n');
|
|
899
|
-
fs.writeFileSync(path.join(workspace, 'src', 'deep', 'needle.txt'), 'AX_SEARCH_SENTINEL=found-here\n');
|
|
900
|
-
const sink = localOut();
|
|
901
|
-
const state = await postTurn(
|
|
902
|
-
'Use local tools to search for AX_SEARCH_SENTINEL and answer with the exact file path. Do not edit.',
|
|
903
|
-
{ mode, cwd: workspace, route: 'local', output: sink, showProgress: false }
|
|
904
|
-
);
|
|
905
|
-
const combined = `${state.output}\n${toolEventText(toolEventsFromState(state))}`;
|
|
906
|
-
assertBenchmark(/src\/deep\/needle\.txt/.test(combined), 'missing proof path src/deep/needle.txt');
|
|
907
|
-
assertBenchmark(/AX_SEARCH_SENTINEL/.test(combined), 'missing sentinel proof');
|
|
908
|
-
return 'found src/deep/needle.txt';
|
|
909
|
-
}, results, output);
|
|
910
|
-
|
|
911
|
-
await runBenchmarkCase('modify files', async () => {
|
|
912
|
-
const workspace = path.join(tempRoot, 'edit');
|
|
913
|
-
fs.mkdirSync(path.join(workspace, 'src'), { recursive: true });
|
|
914
|
-
fs.writeFileSync(path.join(workspace, 'package.json'), '{"name":"ax-edit-bench"}\n');
|
|
915
|
-
const target = path.join(workspace, 'src', 'bench-target.txt');
|
|
916
|
-
fs.writeFileSync(target, 'status=AX_BEFORE\n');
|
|
917
|
-
const sink = localOut();
|
|
918
|
-
const state = await postTurn(
|
|
919
|
-
'Use local tools to edit src/bench-target.txt. Replace AX_BEFORE with AX_AFTER, then report the changed file. Do not create any other files.',
|
|
920
|
-
{ mode, cwd: workspace, route: 'local', output: sink, showProgress: false }
|
|
921
|
-
);
|
|
922
|
-
const text = fs.readFileSync(target, 'utf8');
|
|
923
|
-
assertBenchmark(text.includes('AX_AFTER'), 'src/bench-target.txt was not modified');
|
|
924
|
-
const combined = `${state.output}\n${toolEventText(toolEventsFromState(state))}`;
|
|
925
|
-
assertBenchmark(/bench-target\.txt/.test(combined), 'missing changed-file proof in response or receipt');
|
|
926
|
-
return 'edited src/bench-target.txt';
|
|
927
|
-
}, results, output);
|
|
928
|
-
|
|
929
|
-
await runBenchmarkCase('use Atris workspace state', async () => {
|
|
930
|
-
const cwd = options.cwd || process.cwd();
|
|
931
|
-
assertBenchmark(fs.existsSync(path.join(cwd, 'atris')), 'current workspace has no atris/ directory');
|
|
932
|
-
const sink = localOut();
|
|
933
|
-
const state = await postTurn(
|
|
934
|
-
'Use Atris local tools to inspect current task or map state. Report one concrete Atris file or task source you used. Do not edit files.',
|
|
935
|
-
{ mode, cwd, route: 'local', output: sink, showProgress: false }
|
|
936
|
-
);
|
|
937
|
-
const combined = `${state.output}\n${toolEventText(toolEventsFromState(state))}`;
|
|
938
|
-
assertBenchmark(/atris\/(TODO|MAP|atris)\.md|local_task_op|local_map_op|Task|Map/.test(combined), 'missing Atris task/map proof');
|
|
939
|
-
return 'read Atris task/map surface';
|
|
940
|
-
}, results, output);
|
|
941
|
-
|
|
942
|
-
await runBenchmarkCase('know XP system', async () => {
|
|
943
|
-
const cwd = options.cwd || process.cwd();
|
|
944
|
-
assertBenchmark(fs.existsSync(path.join(cwd, 'atris')), 'current workspace has no atris/ directory');
|
|
945
|
-
const sink = localOut();
|
|
946
|
-
const state = await postTurn(
|
|
947
|
-
'Use the local RewardRubric/Atris tools or workspace files to explain the Career XP reward system in this workspace. Mention proof, review/acceptance, and reward/XP. Cite the exact tool or file you used. Do not edit files.',
|
|
948
|
-
{ mode, cwd, route: 'local', output: sink, showProgress: false }
|
|
949
|
-
);
|
|
950
|
-
const combined = `${state.output}\n${toolEventText(toolEventsFromState(state))}`;
|
|
951
|
-
assertBenchmark(/xp|reward|rubric/i.test(combined), 'missing XP/reward/rubric proof');
|
|
952
|
-
assertBenchmark(/proof|review|accept|approval/i.test(combined), 'missing proof/review/acceptance explanation');
|
|
953
|
-
assertBenchmark(/RewardRubric|local_reward_rubric_op|atris\/|main\.js|taskBoard/i.test(combined), 'missing cited XP source or tool');
|
|
954
|
-
return 'explained XP reward loop';
|
|
955
|
-
}, results, output);
|
|
956
|
-
|
|
957
|
-
await runBenchmarkCase('list connections', async () => {
|
|
958
|
-
const context = await buildConnectionContext({ token: authToken(), localWorkspace: false });
|
|
959
|
-
const connected = (context.connections || []).filter(row => row.connected);
|
|
960
|
-
assertBenchmark((context.connections || []).length >= 5, 'connection capability context is missing');
|
|
961
|
-
const sink = localOut();
|
|
962
|
-
const state = await postTurn(
|
|
963
|
-
'Which Atris integrations are connected for me? Include Google Calendar status. Answer from live connection context.',
|
|
964
|
-
{ mode, route: 'cloud', output: sink, showProgress: false, connectionContext: context }
|
|
965
|
-
);
|
|
966
|
-
const receipt = receiptFromState(state);
|
|
967
|
-
assertBenchmark(receipt && receipt.connection_context, 'receipt missing connection_context');
|
|
968
|
-
const answer = `${state.output}\n${receipt && receipt.final ? receipt.final : ''}`;
|
|
969
|
-
assertBenchmark(/calendar|gmail|github|slack|drive|integration|connected/i.test(answer), 'answer did not discuss integrations');
|
|
970
|
-
return `${connected.length} connected reported`;
|
|
971
|
-
}, results, output);
|
|
972
|
-
|
|
973
|
-
await runBenchmarkCase('read calendar', async () => {
|
|
974
|
-
const sink = localOut();
|
|
975
|
-
const state = await postTurn(
|
|
976
|
-
'What is on my calendar today? Use the connected Google Calendar read path and be concise.',
|
|
977
|
-
{ mode, route: 'cloud', output: sink, showProgress: false }
|
|
978
|
-
);
|
|
979
|
-
const events = toolEventsFromState(state);
|
|
980
|
-
const text = toolEventText(events);
|
|
981
|
-
assertBenchmark(/google_calendar/.test(text), 'calendar tool event missing');
|
|
982
|
-
assertBenchmark(!/"status":"not_connected"/.test(text), 'Google Calendar is not connected for this account');
|
|
983
|
-
assertBenchmark(/Calendar|event|No calendar events|meeting/i.test(state.output), 'calendar answer missing');
|
|
984
|
-
return 'calendar read path responded';
|
|
985
|
-
}, results, output);
|
|
986
|
-
} finally {
|
|
987
|
-
try {
|
|
988
|
-
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
989
|
-
} catch (_) {}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
const failed = results.filter(result => !result.ok);
|
|
993
|
-
output.write('\n');
|
|
994
|
-
output.write(failed.length ? `Benchmark failed: ${failed.length}/${results.length} failed\n` : `Benchmark passed: ${results.length}/${results.length}\n`);
|
|
995
|
-
if (failed.length) {
|
|
996
|
-
const error = new Error(failed.map(result => `${result.label}: ${result.error}`).join('; '));
|
|
997
|
-
error.results = results;
|
|
998
|
-
throw error;
|
|
999
|
-
}
|
|
1000
|
-
return results;
|
|
557
|
+
console.log(`cd /Users/keshavrao/arena/atrisos-backend/backend && uvicorn main:app --host ${BACKEND.host} --port ${BACKEND.port}`);
|
|
1001
558
|
}
|
|
1002
559
|
|
|
1003
560
|
async function main() {
|
|
@@ -1009,36 +566,27 @@ async function main() {
|
|
|
1009
566
|
|
|
1010
567
|
const mode = args.includes('--fast') ? 'fast' : 'pro';
|
|
1011
568
|
const doctor = args.includes('--doctor');
|
|
1012
|
-
const benchmark = args.includes('--benchmark');
|
|
1013
|
-
const forceCloud = args.includes('--cloud');
|
|
1014
|
-
const forceLocal = args.includes('--local');
|
|
1015
|
-
const route = forceCloud ? 'cloud' : forceLocal ? 'local' : 'auto';
|
|
1016
569
|
const prompt = args
|
|
1017
|
-
.filter(arg => !['--fast', '--pro', '--chat', '--doctor', '--
|
|
570
|
+
.filter(arg => !['--fast', '--pro', '--chat', '--doctor', '--help', '-h'].includes(arg))
|
|
1018
571
|
.join(' ')
|
|
1019
572
|
.trim();
|
|
1020
573
|
|
|
1021
574
|
try {
|
|
1022
|
-
if (benchmark) {
|
|
1023
|
-
await runBenchmark({ mode, cwd: process.cwd(), output: process.stdout });
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
575
|
if (doctor) {
|
|
1028
576
|
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }));
|
|
1029
577
|
console.log('');
|
|
1030
|
-
console.log(formatRunProfile(buildRunProfile({ mode, cwd: process.cwd()
|
|
578
|
+
console.log(formatRunProfile(buildRunProfile({ mode, cwd: process.cwd() }), process.stdout));
|
|
1031
579
|
return;
|
|
1032
580
|
}
|
|
1033
581
|
|
|
1034
582
|
if (!prompt || args.includes('--chat')) {
|
|
1035
|
-
await chat({ mode, cwd: process.cwd()
|
|
583
|
+
await chat({ mode, cwd: process.cwd() });
|
|
1036
584
|
return;
|
|
1037
585
|
}
|
|
1038
586
|
|
|
1039
587
|
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }));
|
|
1040
588
|
console.log('');
|
|
1041
|
-
const result = await postTurn(prompt, { mode, cwd: process.cwd()
|
|
589
|
+
const result = await postTurn(prompt, { mode, cwd: process.cwd() });
|
|
1042
590
|
console.log('');
|
|
1043
591
|
console.log(formatDoneLine(result.durationMs));
|
|
1044
592
|
} catch (error) {
|
|
@@ -1053,12 +601,8 @@ if (require.main === module) {
|
|
|
1053
601
|
}
|
|
1054
602
|
|
|
1055
603
|
module.exports = {
|
|
1056
|
-
authToken,
|
|
1057
|
-
authUserId,
|
|
1058
|
-
backendBaseUrl,
|
|
1059
604
|
backendUrl,
|
|
1060
605
|
buildPayload,
|
|
1061
|
-
buildConnectionContext,
|
|
1062
606
|
buildRunProfile,
|
|
1063
607
|
chat,
|
|
1064
608
|
createProgressReporter,
|
|
@@ -1076,8 +620,6 @@ module.exports = {
|
|
|
1076
620
|
modelForMode,
|
|
1077
621
|
parseSseBlock,
|
|
1078
622
|
postTurn,
|
|
1079
|
-
resolveRoute,
|
|
1080
|
-
runBenchmark,
|
|
1081
623
|
summarizeToolInput,
|
|
1082
624
|
summarizeToolResult
|
|
1083
625
|
};
|