atris 3.30.8 → 3.31.0
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/AGENTS.md +16 -3
- package/FOR_AGENTS.md +81 -0
- package/README.md +4 -2
- package/atris/atris.md +51 -19
- package/atris/skills/README.md +1 -0
- package/atris/skills/blocks/SKILL.md +134 -0
- package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
- package/atris/skills/youtube/SKILL.md +31 -11
- package/atris.md +7 -0
- package/ax +367 -93
- package/bin/atris.js +317 -155
- package/commands/autoland.js +319 -0
- package/commands/autopilot.js +94 -4
- package/commands/business.js +1 -1
- package/commands/clean.js +72 -9
- package/commands/codex-goal.js +72 -22
- package/commands/computer.js +48 -3
- package/commands/gm.js +1 -1
- package/commands/harvest.js +179 -0
- package/commands/init.js +1 -1
- package/commands/land.js +442 -0
- package/commands/loop-front.js +122 -4
- package/commands/member.js +519 -19
- package/commands/mission.js +3659 -282
- package/commands/play.js +1 -1
- package/commands/pulse.js +65 -7
- package/commands/run.js +3 -3
- package/commands/strings.js +301 -0
- package/commands/task.js +575 -102
- package/commands/truth.js +170 -0
- package/commands/xp.js +32 -8
- package/commands/youtube.js +72 -5
- package/decks/README.md +6 -12
- package/lib/auto-accept-certified.js +10 -0
- package/lib/autoland.js +283 -0
- package/lib/context-gatherer.js +0 -8
- package/lib/mission-artifact.js +504 -0
- package/lib/mission-room.js +846 -0
- package/lib/mission-runtime-loop.js +320 -0
- package/lib/next-moves.js +212 -6
- package/lib/pulse.js +74 -1
- package/lib/runner-command.js +20 -8
- package/lib/runs-prune.js +242 -0
- package/lib/task-proof.js +1 -1
- package/package.json +3 -3
- package/decks/atris-seed-pitch-v3.json +0 -118
- package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
- package/decks/atris-seed-pitch-v5.json +0 -109
- package/decks/atris-seed-pitch-v6.json +0 -137
- package/decks/atris-seed-pitch-v7.json +0 -133
- package/decks/mark-pincus-narrative.json +0 -102
- package/decks/mark-pincus-sourcery.json +0 -94
- package/decks/yash-applied-compute-detailed.json +0 -150
- package/decks/yash-applied-compute-generalist.json +0 -82
- package/decks/yash-applied-compute-narrative.json +0 -54
- package/lib/ax-chat-input.js +0 -164
- package/lib/ax-goal.js +0 -307
- package/lib/ax-prefs.js +0 -63
- package/lib/ax-shimmer.js +0 -63
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;
|
|
@@ -1017,6 +1165,51 @@ function formatApprovalTime(value) {
|
|
|
1017
1165
|
return `${months[Number(month) - 1] || month} ${Number(day)}, ${year} at ${hour12}:${minute} ${ampm}`;
|
|
1018
1166
|
}
|
|
1019
1167
|
|
|
1168
|
+
function previewText(value, limit = 80) {
|
|
1169
|
+
return String(value || '').replace(/\s+/g, ' ').trim().slice(0, limit);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function previewContact(value) {
|
|
1173
|
+
if (!value) return '';
|
|
1174
|
+
if (typeof value === 'string') return previewText(value, 120);
|
|
1175
|
+
if (typeof value !== 'object') return previewText(value, 120);
|
|
1176
|
+
return previewText(value.email || value.address || value.value || value.name || '', 120);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function previewContacts(value) {
|
|
1180
|
+
const values = Array.isArray(value)
|
|
1181
|
+
? value
|
|
1182
|
+
: String(value || '').includes(',')
|
|
1183
|
+
? String(value).split(',')
|
|
1184
|
+
: [value];
|
|
1185
|
+
return values
|
|
1186
|
+
.map(previewContact)
|
|
1187
|
+
.filter(Boolean)
|
|
1188
|
+
.slice(0, 3)
|
|
1189
|
+
.join(', ');
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function gmailApprovalDetails(receipt, request, approvalEvent = null) {
|
|
1193
|
+
const payload = request && request.payload && typeof request.payload === 'object' ? request.payload : {};
|
|
1194
|
+
const taskPreview = receipt && receipt.task_preview && typeof receipt.task_preview === 'object' ? receipt.task_preview : {};
|
|
1195
|
+
const slots = taskPreview.slots && typeof taskPreview.slots === 'object' ? taskPreview.slots : {};
|
|
1196
|
+
const preview = approvalEvent && approvalEvent.preview && typeof approvalEvent.preview === 'object' ? approvalEvent.preview : {};
|
|
1197
|
+
return {
|
|
1198
|
+
to: previewContacts(payload.to || payload.recipient || payload.recipients || payload.email || slots.to || slots.recipient || preview.to),
|
|
1199
|
+
subject: previewText(payload.subject || payload.title || slots.subject || preview.subject, 80),
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function isGmailApproval(request, approvalEvent = null, accept = {}) {
|
|
1204
|
+
const connector = canonicalConnectorId((request && request.connector) || (approvalEvent && approvalEvent.tool));
|
|
1205
|
+
const action = String(
|
|
1206
|
+
(request && (request.executor_action_type || request.action_type || request.action))
|
|
1207
|
+
|| accept.task
|
|
1208
|
+
|| ''
|
|
1209
|
+
).toLowerCase().replace(/_/g, ' ');
|
|
1210
|
+
return connector === 'gmail' && /\b(send|reply|compose)\b/.test(action);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1020
1213
|
function formatApprovalReceipt(receipt) {
|
|
1021
1214
|
if (!receipt || typeof receipt !== 'object') return null;
|
|
1022
1215
|
const events = Array.isArray(receipt.tool_events) ? receipt.tool_events : [];
|
|
@@ -1034,6 +1227,15 @@ function formatApprovalReceipt(receipt) {
|
|
|
1034
1227
|
return `Approval needed before creation: calendar event "${String(title).slice(0, 80)}"${when ? ` ${when}` : ''}`;
|
|
1035
1228
|
}
|
|
1036
1229
|
|
|
1230
|
+
if (isGmailApproval(request, approvalEvent, accept)) {
|
|
1231
|
+
const details = gmailApprovalDetails(receipt, request, approvalEvent);
|
|
1232
|
+
const parts = [
|
|
1233
|
+
details.to ? `to ${details.to}` : '',
|
|
1234
|
+
details.subject ? `subject "${details.subject}"` : '',
|
|
1235
|
+
].filter(Boolean);
|
|
1236
|
+
return `Approval needed before sending Gmail${parts.length ? `: ${parts.join(' ')}` : ''} (body hidden until approved)`;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1037
1239
|
const connector = String(request.connector || approvalEvent.tool || 'connector').replace(/_/g, ' ');
|
|
1038
1240
|
const action = String(request.action || actionType || 'action').replace(/_/g, ' ');
|
|
1039
1241
|
return `Approval needed before ${action}: ${connector}`;
|
|
@@ -1062,6 +1264,16 @@ function formatTaskPreviewPlan(receipt) {
|
|
|
1062
1264
|
if (!taskPreview) return null;
|
|
1063
1265
|
const task = String(taskPreview.task || '').trim();
|
|
1064
1266
|
const slots = taskPreview.slots && typeof taskPreview.slots === 'object' ? taskPreview.slots : {};
|
|
1267
|
+
const approvalEvent = taskPreviewApprovalEvent(receipt);
|
|
1268
|
+
const request = approvalEvent && approvalEvent.approval_request;
|
|
1269
|
+
if (isGmailApproval(request, approvalEvent, receipt.task_accept_receipt || {}) || task === 'gmail.send_message') {
|
|
1270
|
+
const details = gmailApprovalDetails(receipt, request || { payload: slots }, approvalEvent);
|
|
1271
|
+
return [
|
|
1272
|
+
'send Gmail',
|
|
1273
|
+
details.to ? `to ${details.to}` : '',
|
|
1274
|
+
details.subject ? `subject "${details.subject}"` : '',
|
|
1275
|
+
].filter(Boolean).join(' ');
|
|
1276
|
+
}
|
|
1065
1277
|
if (task === 'calendar.create_event') {
|
|
1066
1278
|
const title = taskPreviewTitle(receipt);
|
|
1067
1279
|
const start = slots.start || (receipt.task_accept_receipt && receipt.task_accept_receipt.start);
|
|
@@ -1345,11 +1557,11 @@ function creditsFromApprovalExecution(response) {
|
|
|
1345
1557
|
return Number.isFinite(credits) && credits > 0 ? credits : null;
|
|
1346
1558
|
}
|
|
1347
1559
|
|
|
1348
|
-
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken()
|
|
1560
|
+
async function postApprovalExecution(approvalRequest, { model = 'atris:fast', token = authToken() } = {}) {
|
|
1349
1561
|
return postJson(APPROVAL_EXECUTE_PATH, {
|
|
1350
1562
|
model,
|
|
1351
1563
|
approval_request: approvalRequest,
|
|
1352
|
-
}, { token
|
|
1564
|
+
}, { token });
|
|
1353
1565
|
}
|
|
1354
1566
|
|
|
1355
1567
|
async function executeApprovalRequest(approvalRequest, {
|
|
@@ -1564,12 +1776,30 @@ function buildMessage(message, history = []) {
|
|
|
1564
1776
|
return String(message || '').trim();
|
|
1565
1777
|
}
|
|
1566
1778
|
|
|
1779
|
+
function createTurnId() {
|
|
1780
|
+
return `axt_${Date.now().toString(36)}_${crypto.randomBytes(4).toString('hex')}`;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
function connectorTurnPolicy(message, options = {}) {
|
|
1784
|
+
if (!connectorWriteIntent(message)) return null;
|
|
1785
|
+
const policy = {
|
|
1786
|
+
schema: 'ax.connector_turn.v1',
|
|
1787
|
+
scope: 'current_turn_only',
|
|
1788
|
+
policy: 'preview_then_explicit_approval',
|
|
1789
|
+
writes_require_approval: true,
|
|
1790
|
+
};
|
|
1791
|
+
if (options.turnId) policy.turn_id = String(options.turnId);
|
|
1792
|
+
if (options.conversationId) policy.conversation_id = String(options.conversationId);
|
|
1793
|
+
return policy;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1567
1796
|
function buildPayload(message, options = {}) {
|
|
1568
1797
|
const mode = normalizeMode(options.mode);
|
|
1569
1798
|
const route = options.business ? 'local' : resolveRoute(message, options);
|
|
1570
1799
|
const local = route !== 'cloud';
|
|
1571
1800
|
const verifyCommand = String(options.verify || '').trim();
|
|
1572
1801
|
const previousMessages = structuredHistory(options.history || []);
|
|
1802
|
+
const turnId = String(options.turnId || '').trim();
|
|
1573
1803
|
const payload = {
|
|
1574
1804
|
message: buildMessage(message, options.history || []),
|
|
1575
1805
|
model: modelForMode(mode),
|
|
@@ -1582,6 +1812,14 @@ function buildPayload(message, options = {}) {
|
|
|
1582
1812
|
if (options.conversationId) {
|
|
1583
1813
|
payload.conversation_id = String(options.conversationId);
|
|
1584
1814
|
}
|
|
1815
|
+
if (turnId) {
|
|
1816
|
+
payload.turn_id = turnId;
|
|
1817
|
+
}
|
|
1818
|
+
const connectorPolicy = !local ? connectorTurnPolicy(message, { ...options, turnId }) : null;
|
|
1819
|
+
if (connectorPolicy) {
|
|
1820
|
+
payload.connector_turn = connectorPolicy;
|
|
1821
|
+
payload.external_action_policy = 'preview_then_explicit_approval';
|
|
1822
|
+
}
|
|
1585
1823
|
if (options.business) {
|
|
1586
1824
|
// Business cloud workspace: the model loop stays on the backend and every
|
|
1587
1825
|
// file/bash tool call relays here, executing on the business EC2 via the
|
|
@@ -1732,23 +1970,25 @@ function handleEvent(event, state, output) {
|
|
|
1732
1970
|
async function postTurn(message, options = {}) {
|
|
1733
1971
|
const route = options.business ? 'local' : resolveRoute(message, options);
|
|
1734
1972
|
const local = route !== 'cloud';
|
|
1973
|
+
const endpointRoute = options.business ? 'cloud' : route;
|
|
1735
1974
|
const token = authToken();
|
|
1975
|
+
const turnId = options.turnId || createTurnId();
|
|
1736
1976
|
const shouldSendConnectionContext = options.connectionContext
|
|
1737
1977
|
|| route === 'cloud'
|
|
1738
1978
|
|| mentionsConnector(message)
|
|
1739
1979
|
|| /\b(can you use|can you access|connected|connections?|integrations?|tools?|capabilities)\b/i.test(message || '');
|
|
1740
1980
|
const connectionContext = options.connectionContext || (shouldSendConnectionContext
|
|
1741
|
-
? await buildConnectionContext({ token, localWorkspace: local
|
|
1981
|
+
? await buildConnectionContext({ token, localWorkspace: local })
|
|
1742
1982
|
: null);
|
|
1743
|
-
const connectionUserId = !local && isLoopbackBackend({ route }) ? authUserId() : '';
|
|
1744
|
-
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId });
|
|
1983
|
+
const connectionUserId = !local && isLoopbackBackend({ route: endpointRoute }) ? authUserId() : '';
|
|
1984
|
+
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId, turnId });
|
|
1745
1985
|
const postData = JSON.stringify(payload);
|
|
1746
1986
|
const output = options.output || process.stdout;
|
|
1747
1987
|
// Relayed business turns wait on EC2 terminal calls (up to 60s each) with no
|
|
1748
1988
|
// SSE traffic in between, so the socket-idle timeout needs more headroom.
|
|
1749
1989
|
const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
|
|
1750
1990
|
const timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
|
|
1751
|
-
const turnUrl = new URL(backendUrl({ route }));
|
|
1991
|
+
const turnUrl = new URL(backendUrl({ route: endpointRoute }));
|
|
1752
1992
|
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
1753
1993
|
const state = {
|
|
1754
1994
|
events: [],
|
|
@@ -1769,7 +2009,7 @@ async function postTurn(message, options = {}) {
|
|
|
1769
2009
|
relay: options.business ? {
|
|
1770
2010
|
chain: Promise.resolve(),
|
|
1771
2011
|
execute: options.business.executor,
|
|
1772
|
-
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl())
|
|
2012
|
+
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl({ route: 'cloud' }))
|
|
1773
2013
|
} : null
|
|
1774
2014
|
};
|
|
1775
2015
|
|
|
@@ -1989,23 +2229,27 @@ async function chat(options = {}) {
|
|
|
1989
2229
|
|
|
1990
2230
|
output.write(`${formatHeader({ mode, cwd, chat: true }, output)}\n\n`);
|
|
1991
2231
|
if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
const
|
|
1995
|
-
|
|
2232
|
+
maybeWriteAtrisGoalBanner(output, cwd);
|
|
2233
|
+
|
|
2234
|
+
const runtimePreflight = new Set();
|
|
2235
|
+
const ensureRuntimeReady = async (routeForTurn = 'cloud') => {
|
|
2236
|
+
const endpointRoute = routeForTurn === 'local' ? 'local' : 'cloud';
|
|
2237
|
+
const key = `${normalizeMode(mode)}:${endpointRoute}`;
|
|
2238
|
+
if (runtimePreflight.has(key)) return true;
|
|
2239
|
+
if (options.skipRuntimePreflight || options.turnFunction || options.business || normalizeMode(mode) === 'code-fast') {
|
|
2240
|
+
runtimePreflight.add(key);
|
|
1996
2241
|
return true;
|
|
1997
2242
|
}
|
|
1998
|
-
if (runtimePreflight.localReady) return true;
|
|
1999
2243
|
const startedAt = Date.now();
|
|
2000
2244
|
const health = options.runtimeHealth
|
|
2001
|
-
? await Promise.resolve(options.runtimeHealth({ mode }))
|
|
2002
|
-
: await buildRuntimeHealth({ timeoutMs: options.preflightTimeoutMs || 1200, route:
|
|
2245
|
+
? await Promise.resolve(options.runtimeHealth({ mode, route: endpointRoute }))
|
|
2246
|
+
: await buildRuntimeHealth({ timeoutMs: options.preflightTimeoutMs || 1200, route: endpointRoute });
|
|
2003
2247
|
if (runtimeReadyForChat(health, mode)) {
|
|
2004
|
-
runtimePreflight.
|
|
2248
|
+
runtimePreflight.add(key);
|
|
2005
2249
|
return true;
|
|
2006
2250
|
}
|
|
2007
2251
|
output.write(`${formatRuntimeHealth(health, output)}\n\n`);
|
|
2008
|
-
output.write(`${formatBackendHint({
|
|
2252
|
+
output.write(`${formatBackendHint({ route: endpointRoute })}\n`);
|
|
2009
2253
|
output.write(`${formatDoneLine(Date.now() - startedAt)}\n\n`);
|
|
2010
2254
|
return false;
|
|
2011
2255
|
};
|
|
@@ -2035,6 +2279,26 @@ async function chat(options = {}) {
|
|
|
2035
2279
|
|
|
2036
2280
|
if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
|
|
2037
2281
|
output.write('\n');
|
|
2282
|
+
|
|
2283
|
+
const missionIntent = missionRunIntentFromMessage(trimmed);
|
|
2284
|
+
if (missionIntent) {
|
|
2285
|
+
const result = await runLocalMission(missionIntent, {
|
|
2286
|
+
cwd,
|
|
2287
|
+
onProgress: (line) => {
|
|
2288
|
+
if (/^pursuing /.test(line)) output.write('Pursuing goal...\n');
|
|
2289
|
+
else if (/^still_running /.test(line)) {
|
|
2290
|
+
const seconds = Number(String(line).split(/\s+/)[1]);
|
|
2291
|
+
output.write(`Still pursuing... ${formatSeconds(seconds)}\n`);
|
|
2292
|
+
} else if (/^landing /.test(line)) output.write('Landing goal...\n');
|
|
2293
|
+
else if (/^\[tick /.test(line)) output.write(`${line}\n`);
|
|
2294
|
+
},
|
|
2295
|
+
});
|
|
2296
|
+
output.write(`${result.text || 'Mission command failed.'}\n\n`);
|
|
2297
|
+
history.push({ role: 'user', content: trimmed });
|
|
2298
|
+
history.push({ role: 'assistant', content: result.text || 'Mission command failed.' });
|
|
2299
|
+
return false;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2038
2302
|
const pendingApprovalRequest = latestPendingApprovalRequest(history);
|
|
2039
2303
|
if (pendingApprovalRequest && normalizeMode(mode) !== 'code-fast' && messageCancelsPendingApproval(trimmed)) {
|
|
2040
2304
|
const startedAt = Date.now();
|
|
@@ -2091,13 +2355,13 @@ async function chat(options = {}) {
|
|
|
2091
2355
|
}
|
|
2092
2356
|
|
|
2093
2357
|
const pendingTaskPreview = latestPendingTaskPreview(history);
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
if (!(await ensureRuntimeReady(route, decision))) return false;
|
|
2358
|
+
const route = pendingTaskPreview ? 'cloud' : options.route;
|
|
2359
|
+
maybeWriteAtrisGoalBanner(output, cwd);
|
|
2360
|
+
const turnRoute = options.business ? 'cloud' : resolveRoute(trimmed, { route });
|
|
2361
|
+
if (!(await ensureRuntimeReady(turnRoute))) return false;
|
|
2099
2362
|
const turnFunction = options.turnFunction || turnFunctionForMode(mode);
|
|
2100
|
-
const
|
|
2363
|
+
const turnId = options.turnId || createTurnId();
|
|
2364
|
+
const result = await turnFunction(trimmed, { mode, cwd, history, output, route, business: options.business, verify: options.verify, conversationId, turnId });
|
|
2101
2365
|
if (result.output && !result.output.endsWith('\n')) output.write('\n');
|
|
2102
2366
|
const approvalExecution = normalizeMode(mode) === 'code-fast'
|
|
2103
2367
|
? null
|
|
@@ -2158,16 +2422,14 @@ function printBackendHint(options = {}) {
|
|
|
2158
2422
|
console.log(formatBackendHint(options));
|
|
2159
2423
|
}
|
|
2160
2424
|
|
|
2161
|
-
function backendStartCommand(
|
|
2162
|
-
|
|
2163
|
-
return 'Start your AtrisOS backend for local workspace, or retry with --cloud for hosted cloud scratch.';
|
|
2425
|
+
function backendStartCommand() {
|
|
2426
|
+
return `cd ${process.env.HOME}/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
2427
|
}
|
|
2165
2428
|
|
|
2166
2429
|
function formatBackendHint(options = {}) {
|
|
2167
|
-
const
|
|
2168
|
-
if (
|
|
2169
|
-
|
|
2170
|
-
return lines.join('\n');
|
|
2430
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
2431
|
+
if (route === 'local') return `Start backend:\n${backendStartCommand()}`;
|
|
2432
|
+
return 'Atris cloud did not respond. Check login/network, then rerun ax --doctor.';
|
|
2171
2433
|
}
|
|
2172
2434
|
|
|
2173
2435
|
function bufferedOutput() {
|
|
@@ -2270,7 +2532,7 @@ async function runSelfTest(options = {}) {
|
|
|
2270
2532
|
assertSelfTest(!/atris:|fireworks|glm|secret|token/i.test(text), 'doctor output leaked provider/debug text');
|
|
2271
2533
|
}, results, output);
|
|
2272
2534
|
|
|
2273
|
-
await runSelfTestCase('chat
|
|
2535
|
+
await runSelfTestCase('chat cloud offline preflight', async () => {
|
|
2274
2536
|
const previousAuto = process.env.AX_AUTO_LOG;
|
|
2275
2537
|
process.env.AX_AUTO_LOG = '0';
|
|
2276
2538
|
const chunks = [];
|
|
@@ -2285,7 +2547,6 @@ async function runSelfTest(options = {}) {
|
|
|
2285
2547
|
try {
|
|
2286
2548
|
await chat({
|
|
2287
2549
|
mode: 'fast',
|
|
2288
|
-
route: 'local',
|
|
2289
2550
|
cwd: os.tmpdir(),
|
|
2290
2551
|
input: Readable.from(['hi\n', 'exit\n']),
|
|
2291
2552
|
output: chatOutput,
|
|
@@ -2306,9 +2567,8 @@ async function runSelfTest(options = {}) {
|
|
|
2306
2567
|
const text = chunks.join('');
|
|
2307
2568
|
assertSelfTest(healthCalls === 1, 'offline preflight repeated unexpectedly');
|
|
2308
2569
|
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');
|
|
2570
|
+
assertSelfTest(/Atris cloud unavailable/.test(text), 'cloud offline guidance missing');
|
|
2571
|
+
assertSelfTest(!/Start backend:|uvicorn main:app/.test(text), 'cloud default showed local backend instructions');
|
|
2312
2572
|
assertSelfTest(!/secret-token/.test(text), 'offline preflight leaked error detail');
|
|
2313
2573
|
}, results, output);
|
|
2314
2574
|
|
|
@@ -2947,6 +3207,24 @@ async function main() {
|
|
|
2947
3207
|
.join(' ')
|
|
2948
3208
|
.trim();
|
|
2949
3209
|
|
|
3210
|
+
const missionIntent = missionRunIntentFromMessage(prompt);
|
|
3211
|
+
if (missionIntent) {
|
|
3212
|
+
const result = await runLocalMission(missionIntent, {
|
|
3213
|
+
cwd: process.cwd(),
|
|
3214
|
+
onProgress: (line) => {
|
|
3215
|
+
if (/^pursuing /.test(line)) console.log('Pursuing goal...');
|
|
3216
|
+
else if (/^still_running /.test(line)) {
|
|
3217
|
+
const seconds = Number(String(line).split(/\s+/)[1]);
|
|
3218
|
+
console.log(`Still pursuing... ${formatSeconds(seconds)}`);
|
|
3219
|
+
} else if (/^landing /.test(line)) console.log('Landing goal...');
|
|
3220
|
+
else if (/^\[tick /.test(line)) console.log(line);
|
|
3221
|
+
},
|
|
3222
|
+
});
|
|
3223
|
+
console.log(result.text || 'Mission command failed.');
|
|
3224
|
+
if (!result.ok) process.exit(result.status || 1);
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
2950
3228
|
let business = null;
|
|
2951
3229
|
if (businessSlug) {
|
|
2952
3230
|
if (normalizeMode(mode) === 'code-fast') {
|
|
@@ -2994,7 +3272,7 @@ async function main() {
|
|
|
2994
3272
|
console.log('');
|
|
2995
3273
|
console.log(formatRunProfile(buildRunProfile({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route }), process.stdout));
|
|
2996
3274
|
console.log('');
|
|
2997
|
-
console.log(formatRuntimeHealth(await buildRuntimeHealth(), process.stdout));
|
|
3275
|
+
console.log(formatRuntimeHealth(await buildRuntimeHealth({ route: route === 'auto' ? undefined : route }), process.stdout));
|
|
2998
3276
|
return;
|
|
2999
3277
|
}
|
|
3000
3278
|
|
|
@@ -3021,6 +3299,7 @@ async function main() {
|
|
|
3021
3299
|
|
|
3022
3300
|
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }, process.stdout));
|
|
3023
3301
|
console.log('');
|
|
3302
|
+
maybeWriteAtrisGoalBanner(process.stdout, process.cwd());
|
|
3024
3303
|
const result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
|
|
3025
3304
|
const receipt = receiptFromState(result);
|
|
3026
3305
|
const approvalRequest = normalizeMode(mode) === 'code-fast' ? null : approvalRequestFromReceipt(receipt);
|
|
@@ -3032,11 +3311,7 @@ async function main() {
|
|
|
3032
3311
|
console.log(formatDoneLine(result.durationMs, creditsFromState(result)));
|
|
3033
3312
|
} catch (error) {
|
|
3034
3313
|
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' });
|
|
3314
|
+
printBackendHint({ route: route === 'auto' ? undefined : route });
|
|
3040
3315
|
process.exit(1);
|
|
3041
3316
|
}
|
|
3042
3317
|
}
|
|
@@ -3106,7 +3381,6 @@ module.exports = {
|
|
|
3106
3381
|
renderStreamingMarkdown,
|
|
3107
3382
|
renderTerminalMarkdown,
|
|
3108
3383
|
resolveRoute,
|
|
3109
|
-
shouldPreflightRuntime,
|
|
3110
3384
|
runBenchmark,
|
|
3111
3385
|
runSelfTest,
|
|
3112
3386
|
runtimeReadyForChat,
|