atris 3.30.8 → 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 +273 -91
- 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;
|
|
@@ -598,15 +612,7 @@ function connectorWriteIntent(message) {
|
|
|
598
612
|
}
|
|
599
613
|
|
|
600
614
|
function workspaceIntent(message) {
|
|
601
|
-
return
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function workspaceIntentReason(message) {
|
|
605
|
-
const text = String(message || '');
|
|
606
|
-
if (/\b(files?|folders?|repo|workspace|project|directory|tree|src|source|code|diff|git|backend|frontend|atris task|atris xp|xp game|career xp|agentxp|todo|map|tests?)\b/i.test(text)) return 'prompt mentions workspace, file, code, or repo terms';
|
|
607
|
-
if (/(^|\s|["'`])(?:\.{0,2}\/)?[\w.-]+\/[\w./-]+|[\w.-]+\.(?:js|jsx|ts|tsx|mjs|cjs|json|md|py|rb|go|rs|java|css|scss|html|yml|yaml|toml|env|txt)\b/i.test(text)) return 'prompt names a file path or filename';
|
|
608
|
-
if (/\b(read|open|inspect|search|grep|find|locate|where|edit|write|change|modify|patch|fix|test|build|refactor)\b.*\b(this|file|folder|repo|workspace|project|directory|module|component|package|readme|source|code|branch|suite|build|bug)\b/i.test(text)) return 'prompt asks to operate on workspace material';
|
|
609
|
-
return '';
|
|
615
|
+
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 || '');
|
|
610
616
|
}
|
|
611
617
|
|
|
612
618
|
function githubWorkspaceIntent(message) {
|
|
@@ -616,22 +622,9 @@ function githubWorkspaceIntent(message) {
|
|
|
616
622
|
}
|
|
617
623
|
|
|
618
624
|
function resolveRoute(message, options = {}) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
function routeDecision(message, options = {}) {
|
|
623
|
-
if (options.route === 'local' || options.forceLocal) return { route: 'local', reason: 'explicit --local' };
|
|
624
|
-
if (options.route === 'cloud' || options.forceCloud) return { route: 'cloud', reason: 'explicit --cloud' };
|
|
625
|
-
if (githubWorkspaceIntent(message)) return { route: 'local', reason: 'prompt asks for GitHub repo mutation' };
|
|
626
|
-
const reason = workspaceIntentReason(message);
|
|
627
|
-
if (mentionsConnector(message) && !reason) return { route: 'cloud', reason: 'connector request uses hosted Atris' };
|
|
628
|
-
if (reason) return { route: 'local', reason };
|
|
629
|
-
return { route: 'cloud', reason: 'default hosted chat' };
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function shouldPreflightRuntime(route, options = {}, mode = 'fast') {
|
|
633
|
-
if (options.skipRuntimePreflight || options.turnFunction || options.business || normalizeMode(mode) === 'code-fast') return false;
|
|
634
|
-
return route !== 'cloud';
|
|
625
|
+
if (options.route === 'local' || options.forceLocal) return 'local';
|
|
626
|
+
if (options.route === 'cloud' || options.forceCloud) return 'cloud';
|
|
627
|
+
return 'cloud';
|
|
635
628
|
}
|
|
636
629
|
|
|
637
630
|
function normalizeMode(mode) {
|
|
@@ -705,6 +698,161 @@ function formatDoneLine(ms, credits) {
|
|
|
705
698
|
return `${base} —`;
|
|
706
699
|
}
|
|
707
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
|
+
|
|
708
856
|
function useColor(options = {}) {
|
|
709
857
|
if (options.color === false) return false;
|
|
710
858
|
if (process.env.NO_COLOR) return false;
|
|
@@ -1345,11 +1493,11 @@ function creditsFromApprovalExecution(response) {
|
|
|
1345
1493
|
return Number.isFinite(credits) && credits > 0 ? credits : null;
|
|
1346
1494
|
}
|
|
1347
1495
|
|
|
1348
|
-
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken()
|
|
1496
|
+
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken() } = {}) {
|
|
1349
1497
|
return postJson(APPROVAL_EXECUTE_PATH, {
|
|
1350
1498
|
model,
|
|
1351
1499
|
approval_request: approvalRequest,
|
|
1352
|
-
}, { token
|
|
1500
|
+
}, { token });
|
|
1353
1501
|
}
|
|
1354
1502
|
|
|
1355
1503
|
async function executeApprovalRequest(approvalRequest, {
|
|
@@ -1732,15 +1880,16 @@ function handleEvent(event, state, output) {
|
|
|
1732
1880
|
async function postTurn(message, options = {}) {
|
|
1733
1881
|
const route = options.business ? 'local' : resolveRoute(message, options);
|
|
1734
1882
|
const local = route !== 'cloud';
|
|
1883
|
+
const endpointRoute = options.business ? 'cloud' : route;
|
|
1735
1884
|
const token = authToken();
|
|
1736
1885
|
const shouldSendConnectionContext = options.connectionContext
|
|
1737
1886
|
|| route === 'cloud'
|
|
1738
1887
|
|| mentionsConnector(message)
|
|
1739
1888
|
|| /\b(can you use|can you access|connected|connections?|integrations?|tools?|capabilities)\b/i.test(message || '');
|
|
1740
1889
|
const connectionContext = options.connectionContext || (shouldSendConnectionContext
|
|
1741
|
-
? await buildConnectionContext({ token, localWorkspace: local
|
|
1890
|
+
? await buildConnectionContext({ token, localWorkspace: local })
|
|
1742
1891
|
: null);
|
|
1743
|
-
const connectionUserId = !local && isLoopbackBackend({ route }) ? authUserId() : '';
|
|
1892
|
+
const connectionUserId = !local && isLoopbackBackend({ route: endpointRoute }) ? authUserId() : '';
|
|
1744
1893
|
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId });
|
|
1745
1894
|
const postData = JSON.stringify(payload);
|
|
1746
1895
|
const output = options.output || process.stdout;
|
|
@@ -1748,7 +1897,7 @@ async function postTurn(message, options = {}) {
|
|
|
1748
1897
|
// SSE traffic in between, so the socket-idle timeout needs more headroom.
|
|
1749
1898
|
const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
|
|
1750
1899
|
const timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
|
|
1751
|
-
const turnUrl = new URL(backendUrl({ route }));
|
|
1900
|
+
const turnUrl = new URL(backendUrl({ route: endpointRoute }));
|
|
1752
1901
|
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
1753
1902
|
const state = {
|
|
1754
1903
|
events: [],
|
|
@@ -1769,7 +1918,7 @@ async function postTurn(message, options = {}) {
|
|
|
1769
1918
|
relay: options.business ? {
|
|
1770
1919
|
chain: Promise.resolve(),
|
|
1771
1920
|
execute: options.business.executor,
|
|
1772
|
-
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl())
|
|
1921
|
+
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl({ route: 'cloud' }))
|
|
1773
1922
|
} : null
|
|
1774
1923
|
};
|
|
1775
1924
|
|
|
@@ -1989,23 +2138,27 @@ async function chat(options = {}) {
|
|
|
1989
2138
|
|
|
1990
2139
|
output.write(`${formatHeader({ mode, cwd, chat: true }, output)}\n\n`);
|
|
1991
2140
|
if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const
|
|
1995
|
-
|
|
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);
|
|
1996
2150
|
return true;
|
|
1997
2151
|
}
|
|
1998
|
-
if (runtimePreflight.localReady) return true;
|
|
1999
2152
|
const startedAt = Date.now();
|
|
2000
2153
|
const health = options.runtimeHealth
|
|
2001
|
-
? await Promise.resolve(options.runtimeHealth({ mode }))
|
|
2002
|
-
: 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 });
|
|
2003
2156
|
if (runtimeReadyForChat(health, mode)) {
|
|
2004
|
-
runtimePreflight.
|
|
2157
|
+
runtimePreflight.add(key);
|
|
2005
2158
|
return true;
|
|
2006
2159
|
}
|
|
2007
2160
|
output.write(`${formatRuntimeHealth(health, output)}\n\n`);
|
|
2008
|
-
output.write(`${formatBackendHint({
|
|
2161
|
+
output.write(`${formatBackendHint({ route: endpointRoute })}\n`);
|
|
2009
2162
|
output.write(`${formatDoneLine(Date.now() - startedAt)}\n\n`);
|
|
2010
2163
|
return false;
|
|
2011
2164
|
};
|
|
@@ -2035,6 +2188,26 @@ async function chat(options = {}) {
|
|
|
2035
2188
|
|
|
2036
2189
|
if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
|
|
2037
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
|
+
|
|
2038
2211
|
const pendingApprovalRequest = latestPendingApprovalRequest(history);
|
|
2039
2212
|
if (pendingApprovalRequest && normalizeMode(mode) !== 'code-fast' && messageCancelsPendingApproval(trimmed)) {
|
|
2040
2213
|
const startedAt = Date.now();
|
|
@@ -2091,11 +2264,10 @@ async function chat(options = {}) {
|
|
|
2091
2264
|
}
|
|
2092
2265
|
|
|
2093
2266
|
const pendingTaskPreview = latestPendingTaskPreview(history);
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
if (!(await ensureRuntimeReady(route, decision))) return false;
|
|
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;
|
|
2099
2271
|
const turnFunction = options.turnFunction || turnFunctionForMode(mode);
|
|
2100
2272
|
const result = await turnFunction(trimmed, { mode, cwd, history, output, route, business: options.business, verify: options.verify, conversationId });
|
|
2101
2273
|
if (result.output && !result.output.endsWith('\n')) output.write('\n');
|
|
@@ -2158,16 +2330,14 @@ function printBackendHint(options = {}) {
|
|
|
2158
2330
|
console.log(formatBackendHint(options));
|
|
2159
2331
|
}
|
|
2160
2332
|
|
|
2161
|
-
function backendStartCommand(
|
|
2162
|
-
|
|
2163
|
-
return 'Start your AtrisOS backend for local workspace, or retry with --cloud for hosted cloud scratch.';
|
|
2333
|
+
function backendStartCommand() {
|
|
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}`;
|
|
2164
2335
|
}
|
|
2165
2336
|
|
|
2166
2337
|
function formatBackendHint(options = {}) {
|
|
2167
|
-
const
|
|
2168
|
-
if (
|
|
2169
|
-
|
|
2170
|
-
return lines.join('\n');
|
|
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.';
|
|
2171
2341
|
}
|
|
2172
2342
|
|
|
2173
2343
|
function bufferedOutput() {
|
|
@@ -2270,7 +2440,7 @@ async function runSelfTest(options = {}) {
|
|
|
2270
2440
|
assertSelfTest(!/atris:|fireworks|glm|secret|token/i.test(text), 'doctor output leaked provider/debug text');
|
|
2271
2441
|
}, results, output);
|
|
2272
2442
|
|
|
2273
|
-
await runSelfTestCase('chat
|
|
2443
|
+
await runSelfTestCase('chat cloud offline preflight', async () => {
|
|
2274
2444
|
const previousAuto = process.env.AX_AUTO_LOG;
|
|
2275
2445
|
process.env.AX_AUTO_LOG = '0';
|
|
2276
2446
|
const chunks = [];
|
|
@@ -2285,7 +2455,6 @@ async function runSelfTest(options = {}) {
|
|
|
2285
2455
|
try {
|
|
2286
2456
|
await chat({
|
|
2287
2457
|
mode: 'fast',
|
|
2288
|
-
route: 'local',
|
|
2289
2458
|
cwd: os.tmpdir(),
|
|
2290
2459
|
input: Readable.from(['hi\n', 'exit\n']),
|
|
2291
2460
|
output: chatOutput,
|
|
@@ -2306,9 +2475,8 @@ async function runSelfTest(options = {}) {
|
|
|
2306
2475
|
const text = chunks.join('');
|
|
2307
2476
|
assertSelfTest(healthCalls === 1, 'offline preflight repeated unexpectedly');
|
|
2308
2477
|
assertSelfTest(/backend\s+offline/.test(text), 'offline backend status missing');
|
|
2309
|
-
assertSelfTest(/
|
|
2310
|
-
assertSelfTest(
|
|
2311
|
-
assertSelfTest(!/\/Users\/keshavrao/.test(text), 'local backend hint leaked a developer path');
|
|
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');
|
|
2312
2480
|
assertSelfTest(!/secret-token/.test(text), 'offline preflight leaked error detail');
|
|
2313
2481
|
}, results, output);
|
|
2314
2482
|
|
|
@@ -2947,6 +3115,24 @@ async function main() {
|
|
|
2947
3115
|
.join(' ')
|
|
2948
3116
|
.trim();
|
|
2949
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
|
+
|
|
2950
3136
|
let business = null;
|
|
2951
3137
|
if (businessSlug) {
|
|
2952
3138
|
if (normalizeMode(mode) === 'code-fast') {
|
|
@@ -2994,7 +3180,7 @@ async function main() {
|
|
|
2994
3180
|
console.log('');
|
|
2995
3181
|
console.log(formatRunProfile(buildRunProfile({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route }), process.stdout));
|
|
2996
3182
|
console.log('');
|
|
2997
|
-
console.log(formatRuntimeHealth(await buildRuntimeHealth(), process.stdout));
|
|
3183
|
+
console.log(formatRuntimeHealth(await buildRuntimeHealth({ route: route === 'auto' ? undefined : route }), process.stdout));
|
|
2998
3184
|
return;
|
|
2999
3185
|
}
|
|
3000
3186
|
|
|
@@ -3021,6 +3207,7 @@ async function main() {
|
|
|
3021
3207
|
|
|
3022
3208
|
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }, process.stdout));
|
|
3023
3209
|
console.log('');
|
|
3210
|
+
maybeWriteAtrisGoalBanner(process.stdout, process.cwd());
|
|
3024
3211
|
const result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
|
|
3025
3212
|
const receipt = receiptFromState(result);
|
|
3026
3213
|
const approvalRequest = normalizeMode(mode) === 'code-fast' ? null : approvalRequestFromReceipt(receipt);
|
|
@@ -3032,11 +3219,7 @@ async function main() {
|
|
|
3032
3219
|
console.log(formatDoneLine(result.durationMs, creditsFromState(result)));
|
|
3033
3220
|
} catch (error) {
|
|
3034
3221
|
console.error(`x ${error.message}`);
|
|
3035
|
-
|
|
3036
|
-
? routeDecision(prompt, { route: route === 'auto' ? undefined : route })
|
|
3037
|
-
: { route: forceLocal ? 'local' : 'cloud', reason: forceLocal ? 'explicit --local' : 'default hosted chat' };
|
|
3038
|
-
const shouldShowBackendHint = decision.route === 'local';
|
|
3039
|
-
if (shouldShowBackendHint) printBackendHint({ routeReason: decision.reason, explicitLocal: forceLocal || decision.reason === 'explicit --local' });
|
|
3222
|
+
printBackendHint({ route: route === 'auto' ? undefined : route });
|
|
3040
3223
|
process.exit(1);
|
|
3041
3224
|
}
|
|
3042
3225
|
}
|
|
@@ -3106,7 +3289,6 @@ module.exports = {
|
|
|
3106
3289
|
renderStreamingMarkdown,
|
|
3107
3290
|
renderTerminalMarkdown,
|
|
3108
3291
|
resolveRoute,
|
|
3109
|
-
shouldPreflightRuntime,
|
|
3110
3292
|
runBenchmark,
|
|
3111
3293
|
runSelfTest,
|
|
3112
3294
|
runtimeReadyForChat,
|