atris 3.30.7 → 3.30.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/ax +271 -65
- package/bin/atris.js +190 -1
- package/commands/mission.js +349 -12
- package/lib/mission-runtime-loop.js +320 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/ax
CHANGED
|
@@ -9,6 +9,7 @@ const readline = require('readline');
|
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
const { Readable } = require('stream');
|
|
11
11
|
const { loadCredentials } = require('./utils/auth');
|
|
12
|
+
const missionRuntime = require('./lib/mission-runtime-loop');
|
|
12
13
|
|
|
13
14
|
const EXIT_WORDS = new Set(['exit', 'quit', ':q']);
|
|
14
15
|
const BACKEND = {
|
|
@@ -16,8 +17,8 @@ const BACKEND = {
|
|
|
16
17
|
port: 8000,
|
|
17
18
|
path: '/api/atris2/turn'
|
|
18
19
|
};
|
|
19
|
-
const DEFAULT_BACKEND_BASE = `http://${BACKEND.host}:${BACKEND.port}`;
|
|
20
20
|
const CLOUD_BACKEND_BASE = 'https://api.atris.ai';
|
|
21
|
+
const LOCAL_BACKEND_BASE = `http://${BACKEND.host}:${BACKEND.port}`;
|
|
21
22
|
const CODE_FAST = {
|
|
22
23
|
path: '/api/cursor/turn',
|
|
23
24
|
model: 'composer-2-5-fast',
|
|
@@ -125,11 +126,11 @@ function formatUsage() {
|
|
|
125
126
|
' ax [--max|--fast] --benchmark',
|
|
126
127
|
'',
|
|
127
128
|
'Modes:',
|
|
128
|
-
' --max highest reasoning, slowest turns',
|
|
129
|
-
' --pro deeper tool loop',
|
|
130
|
-
' --fast faster low-latency turns',
|
|
129
|
+
' --max hosted Atris 2, highest reasoning, slowest turns',
|
|
130
|
+
' --pro hosted Atris 2, deeper tool loop',
|
|
131
|
+
' --fast hosted Atris 2, faster low-latency turns',
|
|
131
132
|
' --code-fast Atris Code Fast public lane',
|
|
132
|
-
' --local
|
|
133
|
+
' --local opt into local backend/workspace tools',
|
|
133
134
|
' --cloud force authenticated cloud connectors/chat',
|
|
134
135
|
' --business <slug> run tools on that business cloud workspace (EC2)',
|
|
135
136
|
' --verify <cmd> gate the turn on this command passing (default: no verifier)',
|
|
@@ -211,11 +212,12 @@ function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', out
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
function backendBaseUrl(options = {}) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
216
|
+
if (process.env.AX_BACKEND_URL) return process.env.AX_BACKEND_URL.replace(/\/$/, '');
|
|
217
|
+
if (route === 'local') {
|
|
218
|
+
return (process.env.OBELISK_LOCAL_ATRIS2_BACKEND_URL || LOCAL_BACKEND_BASE).replace(/\/$/, '');
|
|
219
|
+
}
|
|
220
|
+
return (process.env.OBELISK_ATRIS2_BACKEND_URL || process.env.ATRIS_API_BASE || CLOUD_BACKEND_BASE).replace(/\/$/, '');
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
function backendUrl(options = {}) {
|
|
@@ -268,9 +270,13 @@ function buildRunProfile(options = {}) {
|
|
|
268
270
|
reasoning: 'Composer 2.5 fast lane; charges 10 credits per public turn'
|
|
269
271
|
};
|
|
270
272
|
}
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const
|
|
273
|
+
const route = resolveRoute(options.message || 'doctor', options);
|
|
274
|
+
const payload = buildPayload(options.message || 'doctor', { mode, cwd, route });
|
|
275
|
+
const reasoningByMode = mode === 'max'
|
|
276
|
+
? 'Max workspace tool loop uses high reasoning effort'
|
|
277
|
+
: mode === 'pro'
|
|
278
|
+
? 'Pro workspace tool loop uses API default medium'
|
|
279
|
+
: 'Fast workspace tool loop uses provider default';
|
|
274
280
|
return {
|
|
275
281
|
endpoint: backendUrl({ route }),
|
|
276
282
|
mode,
|
|
@@ -278,13 +284,11 @@ function buildRunProfile(options = {}) {
|
|
|
278
284
|
model: payload.model,
|
|
279
285
|
workspace_path: payload.workspace_path || 'cloud',
|
|
280
286
|
max_turns: payload.max_turns,
|
|
287
|
+
member_slug: 'ax',
|
|
288
|
+
bypass_permissions: false,
|
|
281
289
|
streaming: true,
|
|
282
290
|
runtime: route === 'cloud' ? 'authenticated cloud connectors/chat' : 'local workspace',
|
|
283
|
-
reasoning:
|
|
284
|
-
? 'backend reports run row; Max workspace tool loop uses high reasoning effort'
|
|
285
|
-
: mode === 'pro'
|
|
286
|
-
? 'backend reports run row; Pro workspace tool loop uses API default medium'
|
|
287
|
-
: 'backend reports run row; Fast workspace tool loop uses provider default'
|
|
291
|
+
reasoning: `${route === 'cloud' ? 'Atris cloud service' : 'local backend'}; ${reasoningByMode}`
|
|
288
292
|
};
|
|
289
293
|
}
|
|
290
294
|
|
|
@@ -303,18 +307,23 @@ function formatRunProfile(profile, options = {}) {
|
|
|
303
307
|
}
|
|
304
308
|
|
|
305
309
|
async function buildRuntimeHealth(options = {}) {
|
|
310
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
306
311
|
const healthRes = options.healthRes
|
|
307
312
|
? await Promise.resolve(options.healthRes)
|
|
308
|
-
: await requestJson(ATRIS2_HEALTH_PATH, { token: '', timeoutMs: options.timeoutMs || 1500, route
|
|
313
|
+
: await requestJson(ATRIS2_HEALTH_PATH, { token: route === 'cloud' ? authToken() : '', timeoutMs: options.timeoutMs || 1500, route });
|
|
309
314
|
const data = healthRes.ok && healthRes.data && typeof healthRes.data === 'object' ? healthRes.data : {};
|
|
310
315
|
const models = Array.isArray(data.models) ? data.models : [];
|
|
311
316
|
const fast = models.find(row => row && row.id === 'atris:fast') || null;
|
|
317
|
+
const status = Number(healthRes.status || 0);
|
|
318
|
+
const authRequired = status === 401 || status === 403;
|
|
312
319
|
return {
|
|
313
320
|
schema: 'ax.runtime_health.v1',
|
|
321
|
+
route,
|
|
314
322
|
backend: {
|
|
315
323
|
ready: Boolean(healthRes.ok && data.ready),
|
|
316
|
-
reachable: Boolean(healthRes.ok),
|
|
317
|
-
|
|
324
|
+
reachable: Boolean(healthRes.ok || status > 0),
|
|
325
|
+
auth_required: authRequired,
|
|
326
|
+
status,
|
|
318
327
|
error: healthRes.error || '',
|
|
319
328
|
},
|
|
320
329
|
fast: {
|
|
@@ -331,14 +340,19 @@ async function buildRuntimeHealth(options = {}) {
|
|
|
331
340
|
function formatRuntimeHealth(health, options = {}) {
|
|
332
341
|
const backendReady = health && health.backend && health.backend.ready;
|
|
333
342
|
const backendReachable = health && health.backend && health.backend.reachable;
|
|
343
|
+
const authRequired = health && health.backend && health.backend.auth_required;
|
|
334
344
|
const fastReady = health && health.fast && health.fast.ready;
|
|
335
345
|
const permissionReady = health && health.permissions && health.permissions.ready;
|
|
336
346
|
const rows = [
|
|
337
|
-
['backend', backendReady ? 'ready' : backendReachable ? 'not ready' : 'offline'],
|
|
347
|
+
['backend', authRequired ? 'auth required' : backendReady ? 'ready' : backendReachable ? 'not ready' : 'offline'],
|
|
338
348
|
['fast', fastReady ? 'ready' : 'not ready'],
|
|
339
|
-
['approvals', permissionReady ? 'ready' : 'offline'],
|
|
349
|
+
['approvals', authRequired ? 'auth required' : permissionReady ? 'ready' : 'offline'],
|
|
340
350
|
];
|
|
341
|
-
if (
|
|
351
|
+
if (authRequired) {
|
|
352
|
+
rows.push(['fix', 'run atris login, then rerun ax --doctor']);
|
|
353
|
+
} else if (!backendReachable) {
|
|
354
|
+
rows.push(['fix', health?.route === 'local' ? 'start local backend, then rerun ax --doctor' : 'Atris cloud unavailable; retry or run ax --doctor']);
|
|
355
|
+
}
|
|
342
356
|
return rows.map(([label, value]) => formatAuxRow(label, value, options)).join('\n');
|
|
343
357
|
}
|
|
344
358
|
|
|
@@ -527,7 +541,7 @@ function cachedIntegrationStatus(options = {}) {
|
|
|
527
541
|
|
|
528
542
|
async function buildConnectionContext(options = {}) {
|
|
529
543
|
const token = options.token || authToken();
|
|
530
|
-
const localStatusUserId = isLoopbackBackend(
|
|
544
|
+
const localStatusUserId = isLoopbackBackend() ? authUserId() : '';
|
|
531
545
|
const statusPath = localStatusUserId
|
|
532
546
|
? `${ATRIS2_CONNECTION_STATUS_PATH}?connection_user_id=${encodeURIComponent(localStatusUserId)}`
|
|
533
547
|
: CONNECTION_STATUS_PATH;
|
|
@@ -535,9 +549,9 @@ async function buildConnectionContext(options = {}) {
|
|
|
535
549
|
options.statusRes
|
|
536
550
|
? Promise.resolve(options.statusRes)
|
|
537
551
|
: localStatusUserId || token
|
|
538
|
-
? requestJson(statusPath, { token: localStatusUserId ? '' : token
|
|
552
|
+
? requestJson(statusPath, { token: localStatusUserId ? '' : token })
|
|
539
553
|
: Promise.resolve({ ok: false, data: null }),
|
|
540
|
-
options.contractRes ? Promise.resolve(options.contractRes) : requestJson(CONNECTION_CAPABILITIES_PATH, { token: ''
|
|
554
|
+
options.contractRes ? Promise.resolve(options.contractRes) : requestJson(CONNECTION_CAPABILITIES_PATH, { token: '' })
|
|
541
555
|
]);
|
|
542
556
|
const statusData = statusRes.ok && statusRes.data && typeof statusRes.data === 'object' ? statusRes.data : {};
|
|
543
557
|
const backendStatuses = statusData.statuses && typeof statusData.statuses === 'object' ? statusData.statuses : statusData;
|
|
@@ -610,17 +624,9 @@ function githubWorkspaceIntent(message) {
|
|
|
610
624
|
function resolveRoute(message, options = {}) {
|
|
611
625
|
if (options.route === 'local' || options.forceLocal) return 'local';
|
|
612
626
|
if (options.route === 'cloud' || options.forceCloud) return 'cloud';
|
|
613
|
-
if (githubWorkspaceIntent(message)) return 'local';
|
|
614
|
-
if (mentionsConnector(message) && !workspaceIntent(message)) return 'cloud';
|
|
615
|
-
if (workspaceIntent(message)) return 'local';
|
|
616
627
|
return 'cloud';
|
|
617
628
|
}
|
|
618
629
|
|
|
619
|
-
function shouldPreflightRuntime(route, options = {}, mode = 'fast') {
|
|
620
|
-
if (options.skipRuntimePreflight || options.turnFunction || options.business || normalizeMode(mode) === 'code-fast') return false;
|
|
621
|
-
return route !== 'cloud';
|
|
622
|
-
}
|
|
623
|
-
|
|
624
630
|
function normalizeMode(mode) {
|
|
625
631
|
if (mode === 'code-fast' || mode === 'code') return 'code-fast';
|
|
626
632
|
if (mode === 'max') return 'max';
|
|
@@ -692,6 +698,161 @@ function formatDoneLine(ms, credits) {
|
|
|
692
698
|
return `${base} —`;
|
|
693
699
|
}
|
|
694
700
|
|
|
701
|
+
function stripMissionQuotes(value) {
|
|
702
|
+
const text = String(value || '').trim();
|
|
703
|
+
if (text.length >= 2) {
|
|
704
|
+
const first = text[0];
|
|
705
|
+
const last = text[text.length - 1];
|
|
706
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
707
|
+
return text.slice(1, -1).trim();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return text;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function extractMissionFlag(text, flagName) {
|
|
714
|
+
const source = String(text || '');
|
|
715
|
+
const inline = new RegExp(`(^|\\s)${flagName}=([^\\s]+)(?=\\s|$)`).exec(source);
|
|
716
|
+
if (inline) {
|
|
717
|
+
return {
|
|
718
|
+
value: inline[2],
|
|
719
|
+
text: `${source.slice(0, inline.index)}${inline[1] || ''}${source.slice(inline.index + inline[0].length)}`.replace(/\s+/g, ' ').trim(),
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
const split = new RegExp(`(^|\\s)${flagName}\\s+([^\\s]+)(?=\\s|$)`).exec(source);
|
|
723
|
+
if (split) {
|
|
724
|
+
return {
|
|
725
|
+
value: split[2],
|
|
726
|
+
text: `${source.slice(0, split.index)}${split[1] || ''}${source.slice(split.index + split[0].length)}`.replace(/\s+/g, ' ').trim(),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
return { value: null, text: source.trim() };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function missionRunIntentFromMessage(message) {
|
|
733
|
+
return missionRuntime.missionRunIntentFromMessage(message);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function loadMissionRecord(cwd, missionId) {
|
|
737
|
+
if (!missionId) return null;
|
|
738
|
+
try {
|
|
739
|
+
const statePath = path.join(cwd || process.cwd(), '.atris', 'state', 'missions.jsonl');
|
|
740
|
+
if (!fs.existsSync(statePath)) return null;
|
|
741
|
+
const lines = fs.readFileSync(statePath, 'utf8').split('\n').filter(Boolean);
|
|
742
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
743
|
+
let record;
|
|
744
|
+
try {
|
|
745
|
+
record = JSON.parse(lines[i]);
|
|
746
|
+
} catch (_) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const id = record && (record.id || record.mission_id);
|
|
750
|
+
if (id === missionId) return record;
|
|
751
|
+
}
|
|
752
|
+
} catch (_) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function currentAtrisGoal(cwd = process.cwd()) {
|
|
759
|
+
try {
|
|
760
|
+
const statePath = path.join(cwd, '.atris', 'state', 'atris_goal.json');
|
|
761
|
+
if (!fs.existsSync(statePath)) return null;
|
|
762
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
763
|
+
const goal = state && state.goal;
|
|
764
|
+
if (!goal || !goal.objective) return null;
|
|
765
|
+
if (String(goal.runner || '').trim().toLowerCase() === 'codex_goal') return null;
|
|
766
|
+
const mission = loadMissionRecord(cwd, goal.mission_id);
|
|
767
|
+
return {
|
|
768
|
+
...goal,
|
|
769
|
+
mission_status: mission?.status || goal.mission_status,
|
|
770
|
+
created_at: goal.created_at || mission?.created_at || null,
|
|
771
|
+
updated_at: goal.updated_at || mission?.updated_at || null,
|
|
772
|
+
completed_at: goal.completed_at || mission?.completed_at || null,
|
|
773
|
+
next_command: goal.next_command || mission?.next_action || null,
|
|
774
|
+
};
|
|
775
|
+
} catch (_) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function goalElapsedSeconds(goal) {
|
|
781
|
+
const started = Date.parse(goal?.created_at || '');
|
|
782
|
+
if (!Number.isFinite(started)) return null;
|
|
783
|
+
const ended = Date.parse(goal?.completed_at || '') || Date.now();
|
|
784
|
+
return Math.max(0, Math.floor((ended - started) / 1000));
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function goalAchieved(goal) {
|
|
788
|
+
return ['complete', 'completed', 'achieved'].includes(String(goal?.mission_status || goal?.status || '').toLowerCase());
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function formatAtrisGoal(goal) {
|
|
792
|
+
if (!goal) return '';
|
|
793
|
+
const objective = String(goal.objective || '').length > 92
|
|
794
|
+
? `${String(goal.objective).slice(0, 89)}...`
|
|
795
|
+
: String(goal.objective || '');
|
|
796
|
+
const elapsed = goalElapsedSeconds(goal);
|
|
797
|
+
const missionParts = [
|
|
798
|
+
goal.mission_id || '?',
|
|
799
|
+
goal.mission_status || '?',
|
|
800
|
+
goal.runner || 'mission',
|
|
801
|
+
];
|
|
802
|
+
if (elapsed !== null) missionParts.push(`elapsed ${formatSeconds(elapsed)}`);
|
|
803
|
+
missionParts.push(`achieved ${goalAchieved(goal) ? 'yes' : 'no'}`);
|
|
804
|
+
return [
|
|
805
|
+
`Atris goal: ${objective}`,
|
|
806
|
+
`Mission: ${missionParts.join(' · ')}`,
|
|
807
|
+
goal.next_command ? `Next: ${goal.next_command}` : '',
|
|
808
|
+
].filter(Boolean).join('\n');
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function writeAtrisGoalBanner(output, cwd = process.cwd()) {
|
|
812
|
+
const text = formatAtrisGoal(currentAtrisGoal(cwd));
|
|
813
|
+
if (!text) return false;
|
|
814
|
+
output.write(`${text}\n\n`);
|
|
815
|
+
return true;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function maybeWriteAtrisGoalBanner(output, cwd = process.cwd()) {
|
|
819
|
+
if (process.env.AX_SHOW_GOAL_BANNER !== '1') return false;
|
|
820
|
+
return writeAtrisGoalBanner(output, cwd);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function formatMissionStartReceipt(payload) {
|
|
824
|
+
const mission = payload?.mission || {};
|
|
825
|
+
const goal = payload?.atris_goal_state?.goal || {};
|
|
826
|
+
const receiptGoal = {
|
|
827
|
+
...goal,
|
|
828
|
+
objective: goal.objective || mission.objective,
|
|
829
|
+
mission_id: goal.mission_id || mission.id,
|
|
830
|
+
mission_status: goal.mission_status || mission.status,
|
|
831
|
+
runner: goal.runner || mission.runner,
|
|
832
|
+
created_at: goal.created_at || mission.created_at,
|
|
833
|
+
updated_at: goal.updated_at || mission.updated_at,
|
|
834
|
+
completed_at: goal.completed_at || mission.completed_at,
|
|
835
|
+
next_command: goal.next_command || payload?.next_command || mission.next_action,
|
|
836
|
+
};
|
|
837
|
+
const elapsed = goalElapsedSeconds(receiptGoal);
|
|
838
|
+
return [
|
|
839
|
+
'Atris mission started',
|
|
840
|
+
`Goal: ${receiptGoal.objective || '?'}`,
|
|
841
|
+
`Mission: ${receiptGoal.mission_id || '?'} · ${receiptGoal.mission_status || '?'} · ${receiptGoal.runner || 'atris2'}`,
|
|
842
|
+
`Elapsed: ${elapsed === null ? '0s' : formatSeconds(elapsed)}`,
|
|
843
|
+
`Achieved: ${goalAchieved(receiptGoal) ? 'yes' : 'no'}`,
|
|
844
|
+
receiptGoal.next_command ? `Next: ${receiptGoal.next_command}` : '',
|
|
845
|
+
].filter(Boolean).join('\n');
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function runLocalMission(intent, options = {}) {
|
|
849
|
+
return missionRuntime.runRuntimeMissionLoop(intent, {
|
|
850
|
+
cwd: options.cwd || process.cwd(),
|
|
851
|
+
cliPath: path.join(__dirname, 'bin', 'atris.js'),
|
|
852
|
+
onProgress: options.onProgress,
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
695
856
|
function useColor(options = {}) {
|
|
696
857
|
if (options.color === false) return false;
|
|
697
858
|
if (process.env.NO_COLOR) return false;
|
|
@@ -1332,11 +1493,11 @@ function creditsFromApprovalExecution(response) {
|
|
|
1332
1493
|
return Number.isFinite(credits) && credits > 0 ? credits : null;
|
|
1333
1494
|
}
|
|
1334
1495
|
|
|
1335
|
-
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken()
|
|
1496
|
+
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken() } = {}) {
|
|
1336
1497
|
return postJson(APPROVAL_EXECUTE_PATH, {
|
|
1337
1498
|
model,
|
|
1338
1499
|
approval_request: approvalRequest,
|
|
1339
|
-
}, { token
|
|
1500
|
+
}, { token });
|
|
1340
1501
|
}
|
|
1341
1502
|
|
|
1342
1503
|
async function executeApprovalRequest(approvalRequest, {
|
|
@@ -1719,15 +1880,16 @@ function handleEvent(event, state, output) {
|
|
|
1719
1880
|
async function postTurn(message, options = {}) {
|
|
1720
1881
|
const route = options.business ? 'local' : resolveRoute(message, options);
|
|
1721
1882
|
const local = route !== 'cloud';
|
|
1883
|
+
const endpointRoute = options.business ? 'cloud' : route;
|
|
1722
1884
|
const token = authToken();
|
|
1723
1885
|
const shouldSendConnectionContext = options.connectionContext
|
|
1724
1886
|
|| route === 'cloud'
|
|
1725
1887
|
|| mentionsConnector(message)
|
|
1726
1888
|
|| /\b(can you use|can you access|connected|connections?|integrations?|tools?|capabilities)\b/i.test(message || '');
|
|
1727
1889
|
const connectionContext = options.connectionContext || (shouldSendConnectionContext
|
|
1728
|
-
? await buildConnectionContext({ token, localWorkspace: local
|
|
1890
|
+
? await buildConnectionContext({ token, localWorkspace: local })
|
|
1729
1891
|
: null);
|
|
1730
|
-
const connectionUserId = !local && isLoopbackBackend({ route }) ? authUserId() : '';
|
|
1892
|
+
const connectionUserId = !local && isLoopbackBackend({ route: endpointRoute }) ? authUserId() : '';
|
|
1731
1893
|
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId });
|
|
1732
1894
|
const postData = JSON.stringify(payload);
|
|
1733
1895
|
const output = options.output || process.stdout;
|
|
@@ -1735,7 +1897,7 @@ async function postTurn(message, options = {}) {
|
|
|
1735
1897
|
// SSE traffic in between, so the socket-idle timeout needs more headroom.
|
|
1736
1898
|
const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
|
|
1737
1899
|
const timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
|
|
1738
|
-
const turnUrl = new URL(backendUrl({ route }));
|
|
1900
|
+
const turnUrl = new URL(backendUrl({ route: endpointRoute }));
|
|
1739
1901
|
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
1740
1902
|
const state = {
|
|
1741
1903
|
events: [],
|
|
@@ -1756,7 +1918,7 @@ async function postTurn(message, options = {}) {
|
|
|
1756
1918
|
relay: options.business ? {
|
|
1757
1919
|
chain: Promise.resolve(),
|
|
1758
1920
|
execute: options.business.executor,
|
|
1759
|
-
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl())
|
|
1921
|
+
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl({ route: 'cloud' }))
|
|
1760
1922
|
} : null
|
|
1761
1923
|
};
|
|
1762
1924
|
|
|
@@ -1976,23 +2138,27 @@ async function chat(options = {}) {
|
|
|
1976
2138
|
|
|
1977
2139
|
output.write(`${formatHeader({ mode, cwd, chat: true }, output)}\n\n`);
|
|
1978
2140
|
if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
const
|
|
1982
|
-
|
|
2141
|
+
maybeWriteAtrisGoalBanner(output, cwd);
|
|
2142
|
+
|
|
2143
|
+
const runtimePreflight = new Set();
|
|
2144
|
+
const ensureRuntimeReady = async (routeForTurn = 'cloud') => {
|
|
2145
|
+
const endpointRoute = routeForTurn === 'local' ? 'local' : 'cloud';
|
|
2146
|
+
const key = `${normalizeMode(mode)}:${endpointRoute}`;
|
|
2147
|
+
if (runtimePreflight.has(key)) return true;
|
|
2148
|
+
if (options.skipRuntimePreflight || options.turnFunction || options.business || normalizeMode(mode) === 'code-fast') {
|
|
2149
|
+
runtimePreflight.add(key);
|
|
1983
2150
|
return true;
|
|
1984
2151
|
}
|
|
1985
|
-
if (runtimePreflight.localReady) return true;
|
|
1986
2152
|
const startedAt = Date.now();
|
|
1987
2153
|
const health = options.runtimeHealth
|
|
1988
|
-
? await Promise.resolve(options.runtimeHealth({ mode }))
|
|
1989
|
-
: await buildRuntimeHealth({ timeoutMs: options.preflightTimeoutMs || 1200, route:
|
|
2154
|
+
? await Promise.resolve(options.runtimeHealth({ mode, route: endpointRoute }))
|
|
2155
|
+
: await buildRuntimeHealth({ timeoutMs: options.preflightTimeoutMs || 1200, route: endpointRoute });
|
|
1990
2156
|
if (runtimeReadyForChat(health, mode)) {
|
|
1991
|
-
runtimePreflight.
|
|
2157
|
+
runtimePreflight.add(key);
|
|
1992
2158
|
return true;
|
|
1993
2159
|
}
|
|
1994
2160
|
output.write(`${formatRuntimeHealth(health, output)}\n\n`);
|
|
1995
|
-
output.write(`${formatBackendHint()}\n`);
|
|
2161
|
+
output.write(`${formatBackendHint({ route: endpointRoute })}\n`);
|
|
1996
2162
|
output.write(`${formatDoneLine(Date.now() - startedAt)}\n\n`);
|
|
1997
2163
|
return false;
|
|
1998
2164
|
};
|
|
@@ -2022,6 +2188,26 @@ async function chat(options = {}) {
|
|
|
2022
2188
|
|
|
2023
2189
|
if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
|
|
2024
2190
|
output.write('\n');
|
|
2191
|
+
|
|
2192
|
+
const missionIntent = missionRunIntentFromMessage(trimmed);
|
|
2193
|
+
if (missionIntent) {
|
|
2194
|
+
const result = await runLocalMission(missionIntent, {
|
|
2195
|
+
cwd,
|
|
2196
|
+
onProgress: (line) => {
|
|
2197
|
+
if (/^pursuing /.test(line)) output.write('Pursuing goal...\n');
|
|
2198
|
+
else if (/^still_running /.test(line)) {
|
|
2199
|
+
const seconds = Number(String(line).split(/\s+/)[1]);
|
|
2200
|
+
output.write(`Still pursuing... ${formatSeconds(seconds)}\n`);
|
|
2201
|
+
} else if (/^landing /.test(line)) output.write('Landing goal...\n');
|
|
2202
|
+
else if (/^\[tick /.test(line)) output.write(`${line}\n`);
|
|
2203
|
+
},
|
|
2204
|
+
});
|
|
2205
|
+
output.write(`${result.text || 'Mission command failed.'}\n\n`);
|
|
2206
|
+
history.push({ role: 'user', content: trimmed });
|
|
2207
|
+
history.push({ role: 'assistant', content: result.text || 'Mission command failed.' });
|
|
2208
|
+
return false;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2025
2211
|
const pendingApprovalRequest = latestPendingApprovalRequest(history);
|
|
2026
2212
|
if (pendingApprovalRequest && normalizeMode(mode) !== 'code-fast' && messageCancelsPendingApproval(trimmed)) {
|
|
2027
2213
|
const startedAt = Date.now();
|
|
@@ -2078,8 +2264,10 @@ async function chat(options = {}) {
|
|
|
2078
2264
|
}
|
|
2079
2265
|
|
|
2080
2266
|
const pendingTaskPreview = latestPendingTaskPreview(history);
|
|
2081
|
-
const route = pendingTaskPreview ? 'cloud' :
|
|
2082
|
-
|
|
2267
|
+
const route = pendingTaskPreview ? 'cloud' : options.route;
|
|
2268
|
+
maybeWriteAtrisGoalBanner(output, cwd);
|
|
2269
|
+
const turnRoute = options.business ? 'cloud' : resolveRoute(trimmed, { route });
|
|
2270
|
+
if (!(await ensureRuntimeReady(turnRoute))) return false;
|
|
2083
2271
|
const turnFunction = options.turnFunction || turnFunctionForMode(mode);
|
|
2084
2272
|
const result = await turnFunction(trimmed, { mode, cwd, history, output, route, business: options.business, verify: options.verify, conversationId });
|
|
2085
2273
|
if (result.output && !result.output.endsWith('\n')) output.write('\n');
|
|
@@ -2137,17 +2325,19 @@ async function chat(options = {}) {
|
|
|
2137
2325
|
if (logger) logger.close(0);
|
|
2138
2326
|
}
|
|
2139
2327
|
|
|
2140
|
-
function printBackendHint() {
|
|
2328
|
+
function printBackendHint(options = {}) {
|
|
2141
2329
|
console.log('');
|
|
2142
|
-
console.log(formatBackendHint());
|
|
2330
|
+
console.log(formatBackendHint(options));
|
|
2143
2331
|
}
|
|
2144
2332
|
|
|
2145
2333
|
function backendStartCommand() {
|
|
2146
|
-
return
|
|
2334
|
+
return `cd /Users/keshavrao/arena/atrisos-backend/backend && ATRIS2_ALLOW_LOCAL_WORKSPACE=1 ENVIRONMENT=development ENV=development ../venv/bin/uvicorn main:app --host ${BACKEND.host} --port ${BACKEND.port}`;
|
|
2147
2335
|
}
|
|
2148
2336
|
|
|
2149
|
-
function formatBackendHint() {
|
|
2150
|
-
|
|
2337
|
+
function formatBackendHint(options = {}) {
|
|
2338
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
2339
|
+
if (route === 'local') return `Start backend:\n${backendStartCommand()}`;
|
|
2340
|
+
return 'Atris cloud did not respond. Check login/network, then rerun ax --doctor.';
|
|
2151
2341
|
}
|
|
2152
2342
|
|
|
2153
2343
|
function bufferedOutput() {
|
|
@@ -2250,7 +2440,7 @@ async function runSelfTest(options = {}) {
|
|
|
2250
2440
|
assertSelfTest(!/atris:|fireworks|glm|secret|token/i.test(text), 'doctor output leaked provider/debug text');
|
|
2251
2441
|
}, results, output);
|
|
2252
2442
|
|
|
2253
|
-
await runSelfTestCase('chat
|
|
2443
|
+
await runSelfTestCase('chat cloud offline preflight', async () => {
|
|
2254
2444
|
const previousAuto = process.env.AX_AUTO_LOG;
|
|
2255
2445
|
process.env.AX_AUTO_LOG = '0';
|
|
2256
2446
|
const chunks = [];
|
|
@@ -2265,7 +2455,6 @@ async function runSelfTest(options = {}) {
|
|
|
2265
2455
|
try {
|
|
2266
2456
|
await chat({
|
|
2267
2457
|
mode: 'fast',
|
|
2268
|
-
route: 'local',
|
|
2269
2458
|
cwd: os.tmpdir(),
|
|
2270
2459
|
input: Readable.from(['hi\n', 'exit\n']),
|
|
2271
2460
|
output: chatOutput,
|
|
@@ -2286,8 +2475,8 @@ async function runSelfTest(options = {}) {
|
|
|
2286
2475
|
const text = chunks.join('');
|
|
2287
2476
|
assertSelfTest(healthCalls === 1, 'offline preflight repeated unexpectedly');
|
|
2288
2477
|
assertSelfTest(/backend\s+offline/.test(text), 'offline backend status missing');
|
|
2289
|
-
assertSelfTest(/
|
|
2290
|
-
assertSelfTest(
|
|
2478
|
+
assertSelfTest(/Atris cloud unavailable/.test(text), 'cloud offline guidance missing');
|
|
2479
|
+
assertSelfTest(!/Start backend:|uvicorn main:app/.test(text), 'cloud default showed local backend instructions');
|
|
2291
2480
|
assertSelfTest(!/secret-token/.test(text), 'offline preflight leaked error detail');
|
|
2292
2481
|
}, results, output);
|
|
2293
2482
|
|
|
@@ -2926,6 +3115,24 @@ async function main() {
|
|
|
2926
3115
|
.join(' ')
|
|
2927
3116
|
.trim();
|
|
2928
3117
|
|
|
3118
|
+
const missionIntent = missionRunIntentFromMessage(prompt);
|
|
3119
|
+
if (missionIntent) {
|
|
3120
|
+
const result = await runLocalMission(missionIntent, {
|
|
3121
|
+
cwd: process.cwd(),
|
|
3122
|
+
onProgress: (line) => {
|
|
3123
|
+
if (/^pursuing /.test(line)) console.log('Pursuing goal...');
|
|
3124
|
+
else if (/^still_running /.test(line)) {
|
|
3125
|
+
const seconds = Number(String(line).split(/\s+/)[1]);
|
|
3126
|
+
console.log(`Still pursuing... ${formatSeconds(seconds)}`);
|
|
3127
|
+
} else if (/^landing /.test(line)) console.log('Landing goal...');
|
|
3128
|
+
else if (/^\[tick /.test(line)) console.log(line);
|
|
3129
|
+
},
|
|
3130
|
+
});
|
|
3131
|
+
console.log(result.text || 'Mission command failed.');
|
|
3132
|
+
if (!result.ok) process.exit(result.status || 1);
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
2929
3136
|
let business = null;
|
|
2930
3137
|
if (businessSlug) {
|
|
2931
3138
|
if (normalizeMode(mode) === 'code-fast') {
|
|
@@ -2973,7 +3180,7 @@ async function main() {
|
|
|
2973
3180
|
console.log('');
|
|
2974
3181
|
console.log(formatRunProfile(buildRunProfile({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route }), process.stdout));
|
|
2975
3182
|
console.log('');
|
|
2976
|
-
console.log(formatRuntimeHealth(await buildRuntimeHealth(), process.stdout));
|
|
3183
|
+
console.log(formatRuntimeHealth(await buildRuntimeHealth({ route: route === 'auto' ? undefined : route }), process.stdout));
|
|
2977
3184
|
return;
|
|
2978
3185
|
}
|
|
2979
3186
|
|
|
@@ -3000,6 +3207,7 @@ async function main() {
|
|
|
3000
3207
|
|
|
3001
3208
|
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }, process.stdout));
|
|
3002
3209
|
console.log('');
|
|
3210
|
+
maybeWriteAtrisGoalBanner(process.stdout, process.cwd());
|
|
3003
3211
|
const result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
|
|
3004
3212
|
const receipt = receiptFromState(result);
|
|
3005
3213
|
const approvalRequest = normalizeMode(mode) === 'code-fast' ? null : approvalRequestFromReceipt(receipt);
|
|
@@ -3011,8 +3219,7 @@ async function main() {
|
|
|
3011
3219
|
console.log(formatDoneLine(result.durationMs, creditsFromState(result)));
|
|
3012
3220
|
} catch (error) {
|
|
3013
3221
|
console.error(`x ${error.message}`);
|
|
3014
|
-
|
|
3015
|
-
if (shouldShowBackendHint) printBackendHint();
|
|
3222
|
+
printBackendHint({ route: route === 'auto' ? undefined : route });
|
|
3016
3223
|
process.exit(1);
|
|
3017
3224
|
}
|
|
3018
3225
|
}
|
|
@@ -3082,7 +3289,6 @@ module.exports = {
|
|
|
3082
3289
|
renderStreamingMarkdown,
|
|
3083
3290
|
renderTerminalMarkdown,
|
|
3084
3291
|
resolveRoute,
|
|
3085
|
-
shouldPreflightRuntime,
|
|
3086
3292
|
runBenchmark,
|
|
3087
3293
|
runSelfTest,
|
|
3088
3294
|
runtimeReadyForChat,
|