atris 3.15.57 → 3.16.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 +2 -2
- package/GETTING_STARTED.md +1 -1
- package/PERSONA.md +4 -4
- package/README.md +11 -11
- package/atris/skills/copy-editor/SKILL.md +30 -4
- package/atris/skills/improve/SKILL.md +18 -20
- package/atris/wiki/concepts/agent-activation-contract.md +5 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +4 -4
- package/atris/wiki/index.md +1 -0
- package/ax +522 -73
- package/bin/atris.js +31 -30
- package/commands/align.js +0 -14
- package/commands/apps.js +102 -1
- package/commands/autopilot.js +197 -22
- package/commands/brain.js +219 -34
- package/commands/brainstorm.js +0 -829
- package/commands/computer.js +0 -60
- package/commands/improve.js +501 -0
- package/commands/integrations.js +233 -71
- package/commands/lesson.js +44 -0
- package/commands/member.js +4498 -226
- package/commands/mission.js +302 -27
- package/commands/now.js +89 -1
- package/commands/radar.js +181 -56
- package/commands/soul.js +0 -4
- package/commands/task.js +5582 -517
- package/commands/terminal.js +14 -10
- package/commands/wiki.js +87 -1
- package/commands/workflow.js +288 -73
- package/commands/worktree.js +52 -15
- package/commands/xp.js +6 -65
- package/lib/auto-accept-certified.js +294 -0
- package/lib/file-ops.js +0 -184
- package/lib/member-alive.js +232 -0
- package/lib/policy-lessons.js +280 -0
- package/lib/receipt-evidence.js +64 -0
- package/lib/state-detection.js +34 -0
- package/lib/task-db.js +568 -16
- package/lib/task-proof.js +43 -0
- package/package.json +1 -1
- package/utils/auth.js +13 -4
- package/commands/research.js +0 -52
- package/lib/section-merge.js +0 -196
package/ax
CHANGED
|
@@ -15,6 +15,11 @@ const BACKEND = {
|
|
|
15
15
|
path: '/api/atris2/turn'
|
|
16
16
|
};
|
|
17
17
|
const DEFAULT_BACKEND_BASE = `http://${BACKEND.host}:${BACKEND.port}`;
|
|
18
|
+
const CODE_FAST = {
|
|
19
|
+
path: '/api/cursor/turn',
|
|
20
|
+
model: 'composer-2-5-fast',
|
|
21
|
+
publicBase: 'https://api.atris.ai'
|
|
22
|
+
};
|
|
18
23
|
const CONNECTION_STATUS_PATH = '/api/integrations/status';
|
|
19
24
|
const CONNECTION_CAPABILITIES_PATH = '/api/atris2/connection-capabilities';
|
|
20
25
|
const ATRIS2_CONNECTION_STATUS_PATH = '/api/atris2/connection-status';
|
|
@@ -53,10 +58,19 @@ const ANSI = {
|
|
|
53
58
|
dim: '\x1b[2m',
|
|
54
59
|
muted: '\x1b[90m',
|
|
55
60
|
accent: '\x1b[36m',
|
|
56
|
-
ok: '\x1b[32m'
|
|
61
|
+
ok: '\x1b[32m',
|
|
62
|
+
magenta: '\x1b[35m'
|
|
57
63
|
};
|
|
58
64
|
|
|
65
|
+
const TIER_COLORS = { fast: '\x1b[32m', pro: '\x1b[36m', max: '\x1b[35m' };
|
|
66
|
+
|
|
67
|
+
function tierColor(mode) {
|
|
68
|
+
return TIER_COLORS[mode] || ANSI.accent;
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
function modelForMode(mode) {
|
|
72
|
+
if (mode === 'code-fast') return CODE_FAST.model;
|
|
73
|
+
if (mode === 'max') return 'atris:max';
|
|
60
74
|
return mode === 'fast' ? 'atris:fast' : 'atris:pro';
|
|
61
75
|
}
|
|
62
76
|
|
|
@@ -74,34 +88,48 @@ function formatSeconds(totalSeconds) {
|
|
|
74
88
|
return seconds ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
function
|
|
78
|
-
|
|
91
|
+
function tierLabel(mode) {
|
|
92
|
+
if (mode === 'code-fast') return 'Atris Code Fast';
|
|
93
|
+
if (mode === 'max') return 'Atris 2 Max';
|
|
94
|
+
if (mode === 'fast') return 'Atris 2 Fast';
|
|
95
|
+
return 'Atris 2 Pro';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatHeader({ mode = 'pro', cwd = process.cwd(), chat = false } = {}, options = {}) {
|
|
79
99
|
return [
|
|
80
|
-
`${
|
|
100
|
+
paint(`${tierLabel(mode)}${chat ? ' chat' : ''}`, [ANSI.bold, tierColor(normalizeMode(mode))], options),
|
|
81
101
|
cwd,
|
|
82
|
-
chat ? 'exit
|
|
102
|
+
chat ? paint('type / for the menu · /fast /pro /max swap tiers · exit to leave', [ANSI.muted], options) : '',
|
|
83
103
|
].filter(Boolean).join('\n');
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
function formatUsage() {
|
|
87
107
|
return [
|
|
88
|
-
'ax - Atris
|
|
108
|
+
'ax - Atris local/code agent',
|
|
89
109
|
'',
|
|
90
110
|
'Usage:',
|
|
91
|
-
' ax [--pro|--fast] [--local|--cloud] <message>',
|
|
92
|
-
' ax [--pro|--fast] [--local|--cloud] --chat',
|
|
93
|
-
' ax [--pro|--fast] --
|
|
94
|
-
' ax [--fast] --
|
|
111
|
+
' ax [--max|--pro|--fast|--code-fast] [--local|--cloud] <message>',
|
|
112
|
+
' ax [--max|--pro|--fast|--code-fast] [--local|--cloud] --chat',
|
|
113
|
+
' ax [--max|--pro|--fast] --business <slug> [<message>|--chat]',
|
|
114
|
+
' ax [--max|--pro|--fast|--code-fast] --doctor',
|
|
115
|
+
' ax [--max|--fast] --benchmark',
|
|
95
116
|
'',
|
|
96
117
|
'Modes:',
|
|
118
|
+
' --max local workspace agent, highest reasoning, slowest turns',
|
|
97
119
|
' --pro local workspace agent, deeper tool loop',
|
|
98
120
|
' --fast local workspace agent, faster low-latency turns',
|
|
121
|
+
' --code-fast Atris Code Fast public lane',
|
|
99
122
|
' --local force local workspace tools',
|
|
100
123
|
' --cloud force authenticated cloud connectors/chat',
|
|
124
|
+
' --business <slug> run tools on that business cloud workspace (EC2)',
|
|
125
|
+
' --verify <cmd> gate the turn on this command passing (default: no verifier)',
|
|
101
126
|
'',
|
|
102
127
|
'Examples:',
|
|
103
128
|
' ax --pro find the config file and explain it',
|
|
104
129
|
' ax --fast what files are here',
|
|
130
|
+
' ax --max refactor this module and verify the tests',
|
|
131
|
+
' ax --max --verify "npm test" fix the failing suite',
|
|
132
|
+
' ax --code-fast explain this error',
|
|
105
133
|
' ax --fast what is on my calendar today',
|
|
106
134
|
' ax --pro --chat',
|
|
107
135
|
].join('\n');
|
|
@@ -112,7 +140,9 @@ function timestampForFile(date = new Date()) {
|
|
|
112
140
|
}
|
|
113
141
|
|
|
114
142
|
function stripAnsi(value) {
|
|
115
|
-
return String(value || '')
|
|
143
|
+
return String(value || '')
|
|
144
|
+
.replace(/\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
|
|
145
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '');
|
|
116
146
|
}
|
|
117
147
|
|
|
118
148
|
function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', output = process.stdout } = {}) {
|
|
@@ -170,9 +200,48 @@ function backendPathUrl(pathname) {
|
|
|
170
200
|
return new URL(pathname, backendBaseUrl()).toString();
|
|
171
201
|
}
|
|
172
202
|
|
|
203
|
+
function codeFastBaseUrl() {
|
|
204
|
+
return (process.env.AX_CODE_FAST_BACKEND_URL
|
|
205
|
+
|| process.env.AX_CODE_FAST_API_BASE
|
|
206
|
+
|| process.env.ATRIS_API_BASE
|
|
207
|
+
|| CODE_FAST.publicBase).replace(/\/$/, '');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function codeFastUrl() {
|
|
211
|
+
return new URL(CODE_FAST.path, codeFastBaseUrl()).toString();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isLoopbackUrl(value) {
|
|
215
|
+
try {
|
|
216
|
+
const parsed = new URL(value);
|
|
217
|
+
return ['127.0.0.1', 'localhost', '::1'].includes(parsed.hostname);
|
|
218
|
+
} catch (_) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isCodeFastLocal(options = {}) {
|
|
224
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
225
|
+
return route === 'local' && isLoopbackUrl(options.codeFastBaseUrl || codeFastBaseUrl());
|
|
226
|
+
}
|
|
227
|
+
|
|
173
228
|
function buildRunProfile(options = {}) {
|
|
174
|
-
const mode = options.mode
|
|
229
|
+
const mode = normalizeMode(options.mode);
|
|
175
230
|
const cwd = options.cwd || process.cwd();
|
|
231
|
+
if (mode === 'code-fast') {
|
|
232
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
233
|
+
return {
|
|
234
|
+
endpoint: codeFastUrl(),
|
|
235
|
+
mode,
|
|
236
|
+
route,
|
|
237
|
+
model: CODE_FAST.model,
|
|
238
|
+
workspace_path: isCodeFastLocal(options) ? cwd : 'cloud scratch',
|
|
239
|
+
max_turns: 1,
|
|
240
|
+
streaming: false,
|
|
241
|
+
runtime: isCodeFastLocal(options) ? 'local Cursor SDK through backend' : 'authenticated Code Fast cloud scratch',
|
|
242
|
+
reasoning: 'Composer 2.5 fast lane; charges 10 credits per public turn'
|
|
243
|
+
};
|
|
244
|
+
}
|
|
176
245
|
const route = resolveRoute(options.message || 'doctor', options);
|
|
177
246
|
const payload = buildPayload(options.message || 'doctor', { mode, cwd, route });
|
|
178
247
|
return {
|
|
@@ -184,15 +253,17 @@ function buildRunProfile(options = {}) {
|
|
|
184
253
|
max_turns: payload.max_turns,
|
|
185
254
|
streaming: true,
|
|
186
255
|
runtime: route === 'cloud' ? 'authenticated cloud connectors/chat' : 'local workspace',
|
|
187
|
-
reasoning: mode === '
|
|
188
|
-
? 'backend reports run row;
|
|
189
|
-
:
|
|
256
|
+
reasoning: mode === 'max'
|
|
257
|
+
? 'backend reports run row; Max workspace tool loop uses high reasoning effort'
|
|
258
|
+
: mode === 'pro'
|
|
259
|
+
? 'backend reports run row; Pro workspace tool loop uses API default medium'
|
|
260
|
+
: 'backend reports run row; Fast workspace tool loop uses provider default'
|
|
190
261
|
};
|
|
191
262
|
}
|
|
192
263
|
|
|
193
264
|
function formatRunProfile(profile, options = {}) {
|
|
194
265
|
const rows = [
|
|
195
|
-
['mode',
|
|
266
|
+
['mode', profile.mode],
|
|
196
267
|
['endpoint', profile.endpoint],
|
|
197
268
|
['route', profile.route || 'auto'],
|
|
198
269
|
['workspace', formatPathSubject(profile.workspace_path, options)],
|
|
@@ -428,17 +499,75 @@ function resolveRoute(message, options = {}) {
|
|
|
428
499
|
return 'local';
|
|
429
500
|
}
|
|
430
501
|
|
|
431
|
-
function
|
|
432
|
-
|
|
502
|
+
function normalizeMode(mode) {
|
|
503
|
+
if (mode === 'code-fast' || mode === 'code') return 'code-fast';
|
|
504
|
+
if (mode === 'max') return 'max';
|
|
505
|
+
return mode === 'fast' ? 'fast' : 'pro';
|
|
433
506
|
}
|
|
434
507
|
|
|
435
|
-
function
|
|
508
|
+
function formatPrompt(mode, options = {}) {
|
|
509
|
+
if (!mode) return '› ';
|
|
510
|
+
const tier = normalizeMode(mode);
|
|
511
|
+
return `${paint(tier, [ANSI.bold, tierColor(tier)], options)} › `;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const TIER_COMMANDS = new Map([
|
|
515
|
+
['/fast', 'fast'],
|
|
516
|
+
['/pro', 'pro'],
|
|
517
|
+
['/max', 'max'],
|
|
518
|
+
]);
|
|
519
|
+
|
|
520
|
+
function chatTierCommand(line) {
|
|
521
|
+
return TIER_COMMANDS.get(String(line || '').trim().toLowerCase()) || null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const CHAT_COMMANDS = [
|
|
525
|
+
['/fast', 'quick answers, lowest latency'],
|
|
526
|
+
['/pro', 'deeper tool loop for real work'],
|
|
527
|
+
['/max', 'highest reasoning for the hardest jobs'],
|
|
528
|
+
['/help', 'show this menu'],
|
|
529
|
+
['exit', 'leave chat'],
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
function chatMenu(options = {}) {
|
|
533
|
+
return CHAT_COMMANDS
|
|
534
|
+
.map(([name, desc]) => {
|
|
535
|
+
const tier = TIER_COMMANDS.get(name);
|
|
536
|
+
const color = tier ? tierColor(tier) : ANSI.accent;
|
|
537
|
+
return ` ${paint(name.padEnd(6), [color], options)} ${paint(desc, [ANSI.muted], options)}`;
|
|
538
|
+
})
|
|
539
|
+
.join('\n');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function chatCompleter(line) {
|
|
543
|
+
const trimmed = String(line || '').trimStart().toLowerCase();
|
|
544
|
+
if (!trimmed.startsWith('/')) return [[], line];
|
|
545
|
+
const names = CHAT_COMMANDS.map(([name]) => name).filter(name => name.startsWith('/'));
|
|
546
|
+
const hits = names.filter(name => name.startsWith(trimmed));
|
|
547
|
+
return [hits.length ? hits : names, trimmed];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function formatWorkingLine(ms, verb, frame) {
|
|
436
551
|
const totalSeconds = Math.max(1, Math.round((Number(ms) || 0) / 1000));
|
|
552
|
+
if (verb) return `${frame || '•'} ${verb}… (${formatSeconds(totalSeconds)} · ctrl-c to interrupt)`;
|
|
437
553
|
return `• Working (${formatSeconds(totalSeconds)} • ctrl-c to interrupt)`;
|
|
438
554
|
}
|
|
439
555
|
|
|
440
|
-
function
|
|
441
|
-
|
|
556
|
+
function verbForTool(tool) {
|
|
557
|
+
const name = String(tool || '').toLowerCase();
|
|
558
|
+
if (name.includes('read')) return 'Reading';
|
|
559
|
+
if (name.includes('grep') || name.includes('glob') || name.includes('search')) return 'Searching';
|
|
560
|
+
if (name.includes('bash') || name.includes('command') || name.includes('run')) return 'Running';
|
|
561
|
+
if (name.includes('write') || name.includes('edit')) return 'Editing';
|
|
562
|
+
return 'Working';
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function formatDoneLine(ms, credits) {
|
|
566
|
+
const base = `— Worked for ${formatDuration(ms)}`;
|
|
567
|
+
if (Number.isFinite(credits) && credits > 0) {
|
|
568
|
+
return `${base} · ${credits} ${credits === 1 ? 'credit' : 'credits'} —`;
|
|
569
|
+
}
|
|
570
|
+
return `${base} —`;
|
|
442
571
|
}
|
|
443
572
|
|
|
444
573
|
function useColor(options = {}) {
|
|
@@ -566,19 +695,29 @@ function truncateMiddle(value, limit = 120) {
|
|
|
566
695
|
return `${text.slice(0, head)}...${text.slice(-tail)}`;
|
|
567
696
|
}
|
|
568
697
|
|
|
698
|
+
function hyperlinkPath(text, target, options = {}) {
|
|
699
|
+
if (!useColor(options)) return text;
|
|
700
|
+
const original = String(target || '').trim();
|
|
701
|
+
// Only link things that look like paths; skip prose like "cloud scratch".
|
|
702
|
+
if (!original || /\s/.test(original)) return text;
|
|
703
|
+
const href = `file://${encodeURI(path.resolve(original))}`;
|
|
704
|
+
return `\x1b]8;;${href}\x07${text}\x1b]8;;\x07`;
|
|
705
|
+
}
|
|
706
|
+
|
|
569
707
|
function formatPathSubject(value, options = {}) {
|
|
570
708
|
const raw = truncateMiddle(String(value || '').trim(), options.limit || 120);
|
|
571
709
|
if (!raw) return '';
|
|
572
710
|
if (raw === '.' || raw === '/' || raw.endsWith('/')) {
|
|
573
|
-
return paint(raw, [ANSI.accent], options);
|
|
711
|
+
return hyperlinkPath(paint(raw, [ANSI.accent], options), value, options);
|
|
574
712
|
}
|
|
575
713
|
|
|
576
714
|
const slash = raw.lastIndexOf('/');
|
|
577
|
-
if (slash === -1) return paint(raw, [ANSI.bold, ANSI.accent], options);
|
|
715
|
+
if (slash === -1) return hyperlinkPath(paint(raw, [ANSI.bold, ANSI.accent], options), value, options);
|
|
578
716
|
|
|
579
717
|
const parent = raw.slice(0, slash + 1);
|
|
580
718
|
const filename = raw.slice(slash + 1);
|
|
581
|
-
|
|
719
|
+
const text = `${paint(parent, [ANSI.muted], options)} ${paint(filename, [ANSI.bold, ANSI.accent], options)}`;
|
|
720
|
+
return hyperlinkPath(text, value, options);
|
|
582
721
|
}
|
|
583
722
|
|
|
584
723
|
function formatAuxRow(label, value, options = {}) {
|
|
@@ -591,6 +730,9 @@ function createProgressReporter(output, options = {}) {
|
|
|
591
730
|
const enabled = options.showProgress !== false;
|
|
592
731
|
const isTty = Boolean(output && output.isTTY);
|
|
593
732
|
const startedAt = Date.now();
|
|
733
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
734
|
+
let frameIndex = 0;
|
|
735
|
+
let verb = 'Working';
|
|
594
736
|
let interval = null;
|
|
595
737
|
let timeout = null;
|
|
596
738
|
let shown = false;
|
|
@@ -598,11 +740,11 @@ function createProgressReporter(output, options = {}) {
|
|
|
598
740
|
|
|
599
741
|
const render = () => {
|
|
600
742
|
if (!enabled || closed) return;
|
|
601
|
-
const line = formatWorkingLine(Date.now() - startedAt);
|
|
602
743
|
if (isTty) {
|
|
603
|
-
|
|
744
|
+
const frame = paint(frames[frameIndex++ % frames.length], [ANSI.accent], output);
|
|
745
|
+
output.write(`\r${formatWorkingLine(Date.now() - startedAt, verb, frame)}\x1b[K`);
|
|
604
746
|
} else if (!shown) {
|
|
605
|
-
output.write(`${
|
|
747
|
+
output.write(`${formatWorkingLine(Date.now() - startedAt)}\n`);
|
|
606
748
|
}
|
|
607
749
|
shown = true;
|
|
608
750
|
};
|
|
@@ -612,9 +754,12 @@ function createProgressReporter(output, options = {}) {
|
|
|
612
754
|
if (!enabled) return;
|
|
613
755
|
timeout = setTimeout(() => {
|
|
614
756
|
render();
|
|
615
|
-
if (isTty) interval = setInterval(render,
|
|
757
|
+
if (isTty) interval = setInterval(render, 120);
|
|
616
758
|
}, 700);
|
|
617
759
|
},
|
|
760
|
+
setVerb(next) {
|
|
761
|
+
verb = next || 'Working';
|
|
762
|
+
},
|
|
618
763
|
clear() {
|
|
619
764
|
if (!isTty || !shown || closed) return;
|
|
620
765
|
output.write('\r\x1b[K');
|
|
@@ -642,22 +787,34 @@ function parseSseBlock(block) {
|
|
|
642
787
|
return JSON.parse(data);
|
|
643
788
|
}
|
|
644
789
|
|
|
645
|
-
function
|
|
646
|
-
const options = arguments[1] || {};
|
|
647
|
-
const tool = block.tool || 'tool';
|
|
790
|
+
function summarizeToolArgs(block, options = {}) {
|
|
648
791
|
const input = block.input || {};
|
|
649
|
-
const toolName = paint(tool, [ANSI.bold], options);
|
|
650
792
|
const pathValue = input.file_path || input.path;
|
|
651
|
-
if (input.command) return
|
|
793
|
+
if (input.command) return truncateMiddle(input.command, 120);
|
|
652
794
|
if (input.pattern || input.query) {
|
|
653
795
|
const pattern = truncateMiddle(input.pattern || input.query, 80);
|
|
654
|
-
return pathValue
|
|
655
|
-
? `${toolName} ${pattern} in ${formatPathSubject(pathValue, options)}`
|
|
656
|
-
: `${toolName} ${pattern}`;
|
|
796
|
+
return pathValue ? `${pattern} in ${formatPathSubject(pathValue, options)}` : pattern;
|
|
657
797
|
}
|
|
658
|
-
if (pathValue) return
|
|
798
|
+
if (pathValue) return formatPathSubject(pathValue, options);
|
|
659
799
|
const subject = input.type || '';
|
|
660
|
-
return subject ?
|
|
800
|
+
return subject ? String(subject).slice(0, 120) : '';
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function summarizeToolInput(block) {
|
|
804
|
+
const options = arguments[1] || {};
|
|
805
|
+
const toolName = paint(block.tool || 'tool', [ANSI.bold], options);
|
|
806
|
+
const args = summarizeToolArgs(block, options);
|
|
807
|
+
return args ? `${toolName} ${args}` : toolName;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function formatToolCallLine(block, options = {}) {
|
|
811
|
+
const tool = paint(block.tool || 'tool', [ANSI.bold], options);
|
|
812
|
+
const args = summarizeToolArgs(block, options);
|
|
813
|
+
return `${paint('●', [ANSI.ok], options)} ${tool}(${args})`;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function formatToolResultLine(summary, options = {}) {
|
|
817
|
+
return ` ${paint('⎿', [ANSI.muted], options)} ${summary}`;
|
|
661
818
|
}
|
|
662
819
|
|
|
663
820
|
function summarizeToolResult(result) {
|
|
@@ -679,14 +836,24 @@ function summarizeToolResult(result) {
|
|
|
679
836
|
const where = parsed.path ? ` in ${formatPathSubject(parsed.path, options)}` : '';
|
|
680
837
|
return `${parts.join(' / ')}${where}`;
|
|
681
838
|
}
|
|
839
|
+
if (typeof parsed.content === 'string' && parsed.content) {
|
|
840
|
+
const lines = parsed.content.split('\n').length;
|
|
841
|
+
const where = parsed.path ? ` ${formatPathSubject(parsed.path, options)}` : '';
|
|
842
|
+
return `${lines} ${lines === 1 ? 'line' : 'lines'}${where}`;
|
|
843
|
+
}
|
|
682
844
|
if (parsed.path && parsed.status) return `${parsed.status} ${formatPathSubject(parsed.path, options)}`;
|
|
683
845
|
if (parsed.path) return formatPathSubject(parsed.path, options);
|
|
684
846
|
if (parsed.status) return parsed.status;
|
|
685
847
|
} catch (_) {
|
|
686
|
-
//
|
|
848
|
+
// Truncated relay JSON and bash output fall through to the text path.
|
|
687
849
|
}
|
|
688
850
|
|
|
689
|
-
|
|
851
|
+
const head = content.match(/^\{"status":\s*"(\w+)",\s*"path":\s*"([^"]+)"/);
|
|
852
|
+
if (head) return `${head[1]} ${formatPathSubject(head[2], options)}`;
|
|
853
|
+
|
|
854
|
+
const lines = content.split('\n');
|
|
855
|
+
const first = lines[0].length > 120 ? `${lines[0].slice(0, 117)}...` : lines[0];
|
|
856
|
+
return lines.length > 1 ? `${first} … +${lines.length - 1} lines` : first;
|
|
690
857
|
}
|
|
691
858
|
|
|
692
859
|
function formatStatusMessage(message) {
|
|
@@ -698,10 +865,9 @@ function formatStatusMessage(message) {
|
|
|
698
865
|
|
|
699
866
|
function formatSystemInit(event, options = {}) {
|
|
700
867
|
const runtime = event.tool_runtime || {};
|
|
701
|
-
const model = runtime.tool_model || runtime.chat_model || event.model || '';
|
|
702
868
|
const mode = runtime.mode ? String(runtime.mode).replace(/_/g, ' ') : '';
|
|
703
869
|
const thinking = runtime.reasoning_effort ? `thinking ${runtime.reasoning_effort}` : '';
|
|
704
|
-
const parts = [mode || 'runtime',
|
|
870
|
+
const parts = [mode || 'runtime', thinking].filter(Boolean);
|
|
705
871
|
return parts.length ? parts.join(' ') : null;
|
|
706
872
|
}
|
|
707
873
|
|
|
@@ -711,6 +877,7 @@ function clearRetriedText(state) {
|
|
|
711
877
|
state.wroteText = false;
|
|
712
878
|
state.lastChar = '\n';
|
|
713
879
|
state.inAuxBlock = false;
|
|
880
|
+
state.needsBullet = true;
|
|
714
881
|
resetMarkdownState(state);
|
|
715
882
|
}
|
|
716
883
|
|
|
@@ -729,6 +896,10 @@ function flushPendingText(state, output) {
|
|
|
729
896
|
if (!state.pendingText && !hasMarkdownRemainder) return;
|
|
730
897
|
stopProgress(state);
|
|
731
898
|
if (state.inAuxBlock && state.lastChar === '\n') output.write('\n');
|
|
899
|
+
if (state.pendingText && state.needsBullet && output && output.isTTY) {
|
|
900
|
+
output.write('● ');
|
|
901
|
+
state.needsBullet = false;
|
|
902
|
+
}
|
|
732
903
|
if (state.pendingText) output.write(output && output.isTTY ? renderTerminalMarkdown(state.pendingText, output) : state.pendingText);
|
|
733
904
|
const flushedMarkdown = hasMarkdownRemainder ? flushStreamingMarkdown(state, output) : '';
|
|
734
905
|
state.wroteText = true;
|
|
@@ -744,6 +915,10 @@ function writeStreamingText(state, output, content) {
|
|
|
744
915
|
stopProgress(state);
|
|
745
916
|
if (state.inAuxBlock && state.lastChar === '\n') output.write('\n');
|
|
746
917
|
const rendered = renderStreamingMarkdown(state, content, output);
|
|
918
|
+
if (rendered && state.needsBullet && output.isTTY) {
|
|
919
|
+
output.write('● ');
|
|
920
|
+
state.needsBullet = false;
|
|
921
|
+
}
|
|
747
922
|
if (rendered) output.write(rendered);
|
|
748
923
|
state.wroteText = true;
|
|
749
924
|
state.wroteActivity = true;
|
|
@@ -753,13 +928,15 @@ function writeStreamingText(state, output, content) {
|
|
|
753
928
|
|
|
754
929
|
function writeAuxLine(state, output, line) {
|
|
755
930
|
if (!line) return;
|
|
756
|
-
|
|
931
|
+
// Keep the spinner alive between tool lines — just clear it off the row.
|
|
932
|
+
if (state.progress) state.progress.clear();
|
|
757
933
|
if (state.wroteText && state.lastChar !== '\n') output.write('\n');
|
|
758
934
|
if (!state.inAuxBlock && state.wroteActivity && state.lastChar === '\n') output.write('\n');
|
|
759
935
|
output.write(`${line}\n`);
|
|
760
936
|
state.wroteActivity = true;
|
|
761
937
|
state.lastChar = '\n';
|
|
762
938
|
state.inAuxBlock = true;
|
|
939
|
+
state.needsBullet = true;
|
|
763
940
|
}
|
|
764
941
|
|
|
765
942
|
function compactHistory(history, limit = 8) {
|
|
@@ -784,16 +961,23 @@ function buildMessage(message, history = []) {
|
|
|
784
961
|
}
|
|
785
962
|
|
|
786
963
|
function buildPayload(message, options = {}) {
|
|
787
|
-
const mode = options.mode
|
|
788
|
-
const route = resolveRoute(message, options);
|
|
964
|
+
const mode = normalizeMode(options.mode);
|
|
965
|
+
const route = options.business ? 'local' : resolveRoute(message, options);
|
|
789
966
|
const local = route !== 'cloud';
|
|
967
|
+
const verifyCommand = String(options.verify || '').trim();
|
|
790
968
|
const payload = {
|
|
791
969
|
message: buildMessage(message, options.history || []),
|
|
792
970
|
model: modelForMode(mode),
|
|
793
|
-
max_turns: local ? (mode === '
|
|
794
|
-
verify_command: 'true'
|
|
971
|
+
max_turns: local ? (mode === 'fast' ? 8 : 14) : 1,
|
|
972
|
+
verify_command: verifyCommand || 'true'
|
|
795
973
|
};
|
|
796
|
-
if (
|
|
974
|
+
if (options.business) {
|
|
975
|
+
// Business cloud workspace: the model loop stays on the backend and every
|
|
976
|
+
// file/bash tool call relays here, executing on the business EC2 via the
|
|
977
|
+
// /terminal endpoint. The workspace_path is a label, not a local path.
|
|
978
|
+
payload.workspace_path = `/workspace/${options.business.slug}`;
|
|
979
|
+
payload.local_executor = true;
|
|
980
|
+
} else if (local) {
|
|
797
981
|
payload.workspace_path = options.cwd || process.cwd();
|
|
798
982
|
}
|
|
799
983
|
if (options.connectionContext) {
|
|
@@ -805,18 +989,40 @@ function buildPayload(message, options = {}) {
|
|
|
805
989
|
if (!local && connectorWriteIntent(message)) {
|
|
806
990
|
payload.allow_external_actions = true;
|
|
807
991
|
payload.cleanup_external_actions = true;
|
|
808
|
-
payload.max_turns = mode === '
|
|
992
|
+
payload.max_turns = mode === 'fast' ? 2 : 4;
|
|
993
|
+
}
|
|
994
|
+
return payload;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function buildCodeFastPayload(message, options = {}) {
|
|
998
|
+
const payload = {
|
|
999
|
+
message: buildMessage(message, options.history || []),
|
|
1000
|
+
model: CODE_FAST.model,
|
|
1001
|
+
timeout_seconds: Number(options.timeoutSeconds || 180)
|
|
1002
|
+
};
|
|
1003
|
+
if (isCodeFastLocal(options)) {
|
|
1004
|
+
payload.workspace_path = options.cwd || process.cwd();
|
|
809
1005
|
}
|
|
810
1006
|
return payload;
|
|
811
1007
|
}
|
|
812
1008
|
|
|
1009
|
+
function codeFastWorkspaceNotice(result) {
|
|
1010
|
+
const workspace = result && typeof result === 'object' ? result.workspace || {} : {};
|
|
1011
|
+
const billing = result && typeof result === 'object' ? result.billing || {} : {};
|
|
1012
|
+
const mode = workspace.mode || billing.workspace_mode || '';
|
|
1013
|
+
const persistence = workspace.persistence || billing.workspace_persistence || '';
|
|
1014
|
+
if (mode === 'cloud_scratch' || persistence === 'ephemeral') {
|
|
1015
|
+
return 'cloud scratch files are temporary and are not saved to your Mac';
|
|
1016
|
+
}
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
813
1020
|
function handleEvent(event, state, output) {
|
|
814
1021
|
if (!event || typeof event !== 'object') return;
|
|
815
1022
|
state.events.push(event);
|
|
816
1023
|
|
|
817
1024
|
if (event.type === 'system_init') {
|
|
818
1025
|
state.runtime = event;
|
|
819
|
-
writeAuxLine(state, output, formatAuxRow('run', formatSystemInit(event, output), output));
|
|
820
1026
|
return;
|
|
821
1027
|
}
|
|
822
1028
|
|
|
@@ -831,7 +1037,10 @@ function handleEvent(event, state, output) {
|
|
|
831
1037
|
for (const block of event.blocks) {
|
|
832
1038
|
if (block && block.type === 'tool_use') {
|
|
833
1039
|
flushPendingText(state, output);
|
|
834
|
-
|
|
1040
|
+
if (state.lastAux === 'result' && state.inAuxBlock) output.write('\n');
|
|
1041
|
+
writeAuxLine(state, output, formatToolCallLine(block, output));
|
|
1042
|
+
state.lastAux = 'call';
|
|
1043
|
+
if (state.progress && state.progress.setVerb) state.progress.setVerb(verbForTool(block.tool));
|
|
835
1044
|
}
|
|
836
1045
|
}
|
|
837
1046
|
return;
|
|
@@ -840,18 +1049,41 @@ function handleEvent(event, state, output) {
|
|
|
840
1049
|
if (event.type === 'tool_results' && Array.isArray(event.results)) {
|
|
841
1050
|
for (const result of event.results) {
|
|
842
1051
|
flushPendingText(state, output);
|
|
843
|
-
writeAuxLine(state, output,
|
|
1052
|
+
writeAuxLine(state, output, formatToolResultLine(summarizeToolResult(result, output), output));
|
|
1053
|
+
state.lastAux = 'result';
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (event.type === 'tool_call_request') {
|
|
1059
|
+
flushPendingText(state, output);
|
|
1060
|
+
const args = event.args || {};
|
|
1061
|
+
const cloudBlock = { tool: `cloud:${args.type || event.name || 'tool'}`, input: { command: args.command, path: args.path } };
|
|
1062
|
+
if (state.lastAux === 'result' && state.inAuxBlock) output.write('\n');
|
|
1063
|
+
writeAuxLine(state, output, formatToolCallLine(cloudBlock, output));
|
|
1064
|
+
state.lastAux = 'call';
|
|
1065
|
+
if (state.progress && state.progress.setVerb) state.progress.setVerb(verbForTool(args.type || event.name));
|
|
1066
|
+
if (state.relay) {
|
|
1067
|
+
// Relayed calls run sequentially: the backend awaits each tool-result
|
|
1068
|
+
// before continuing its loop, so a promise chain preserves order.
|
|
1069
|
+
state.relay.chain = state.relay.chain
|
|
1070
|
+
.then(() => state.relay.execute(event.name, args))
|
|
1071
|
+
.catch((err) => ({ status: 'error', error: String(err.message || err).slice(0, 500) }))
|
|
1072
|
+
.then((result) => state.relay.post(event.call_id, result))
|
|
1073
|
+
.catch((err) => state.errors.push(`tool relay failed: ${err.message}`));
|
|
844
1074
|
}
|
|
845
1075
|
return;
|
|
846
1076
|
}
|
|
847
1077
|
|
|
848
1078
|
if (event.type === 'status' && event.message && event.message !== 'complete') {
|
|
849
|
-
|
|
1079
|
+
// Any retry status supersedes the draft answer already buffered — drop it
|
|
1080
|
+
// silently instead of printing the abandoned text plus an info row.
|
|
1081
|
+
if (/^retrying/i.test(event.message)) {
|
|
850
1082
|
clearRetriedText(state);
|
|
851
1083
|
return;
|
|
852
1084
|
}
|
|
853
1085
|
flushPendingText(state, output);
|
|
854
|
-
writeAuxLine(state, output,
|
|
1086
|
+
writeAuxLine(state, output, paint(`· ${formatStatusMessage(event.message)}`, [ANSI.muted], output));
|
|
855
1087
|
return;
|
|
856
1088
|
}
|
|
857
1089
|
|
|
@@ -869,7 +1101,7 @@ function handleEvent(event, state, output) {
|
|
|
869
1101
|
}
|
|
870
1102
|
|
|
871
1103
|
async function postTurn(message, options = {}) {
|
|
872
|
-
const route = resolveRoute(message, options);
|
|
1104
|
+
const route = options.business ? 'local' : resolveRoute(message, options);
|
|
873
1105
|
const local = route !== 'cloud';
|
|
874
1106
|
const token = authToken();
|
|
875
1107
|
const shouldSendConnectionContext = options.connectionContext
|
|
@@ -883,7 +1115,10 @@ async function postTurn(message, options = {}) {
|
|
|
883
1115
|
const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId });
|
|
884
1116
|
const postData = JSON.stringify(payload);
|
|
885
1117
|
const output = options.output || process.stdout;
|
|
886
|
-
|
|
1118
|
+
// Relayed business turns wait on EC2 terminal calls (up to 60s each) with no
|
|
1119
|
+
// SSE traffic in between, so the socket-idle timeout needs more headroom.
|
|
1120
|
+
const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
|
|
1121
|
+
const timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
|
|
887
1122
|
const turnUrl = new URL(backendUrl());
|
|
888
1123
|
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
889
1124
|
const state = {
|
|
@@ -897,9 +1132,16 @@ async function postTurn(message, options = {}) {
|
|
|
897
1132
|
lastChar: '\n',
|
|
898
1133
|
progress: null,
|
|
899
1134
|
inAuxBlock: false,
|
|
1135
|
+
needsBullet: true,
|
|
1136
|
+
lastAux: '',
|
|
900
1137
|
markdownMode: 'normal',
|
|
901
1138
|
markdownBuffer: '',
|
|
902
|
-
markdownCarry: ''
|
|
1139
|
+
markdownCarry: '',
|
|
1140
|
+
relay: options.business ? {
|
|
1141
|
+
chain: Promise.resolve(),
|
|
1142
|
+
execute: options.business.executor,
|
|
1143
|
+
post: (callId, result) => options.business.postToolResult(callId, result, backendBaseUrl())
|
|
1144
|
+
} : null
|
|
903
1145
|
};
|
|
904
1146
|
|
|
905
1147
|
return new Promise((resolve, reject) => {
|
|
@@ -993,8 +1235,121 @@ async function postTurn(message, options = {}) {
|
|
|
993
1235
|
});
|
|
994
1236
|
}
|
|
995
1237
|
|
|
1238
|
+
async function postCodeFastTurn(message, options = {}) {
|
|
1239
|
+
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
1240
|
+
if (route === 'local' && !isCodeFastLocal(options)) {
|
|
1241
|
+
throw new Error('Atris Code Fast local workspace requires AX_CODE_FAST_BACKEND_URL=http://127.0.0.1:8000; use --cloud for the public lane.');
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const payload = buildCodeFastPayload(message, { ...options, route });
|
|
1245
|
+
const postData = JSON.stringify(payload);
|
|
1246
|
+
const output = options.output || process.stdout;
|
|
1247
|
+
const token = authToken();
|
|
1248
|
+
const timeoutMs = Math.max(10000, Math.min(600000, Number(payload.timeout_seconds || 180) * 1000));
|
|
1249
|
+
const turnUrl = new URL(codeFastUrl());
|
|
1250
|
+
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
1251
|
+
const state = {
|
|
1252
|
+
events: [],
|
|
1253
|
+
errors: [],
|
|
1254
|
+
output: '',
|
|
1255
|
+
pendingText: '',
|
|
1256
|
+
wroteText: false,
|
|
1257
|
+
wroteActivity: false,
|
|
1258
|
+
durationMs: 0,
|
|
1259
|
+
lastChar: '\n',
|
|
1260
|
+
progress: null,
|
|
1261
|
+
inAuxBlock: false,
|
|
1262
|
+
needsBullet: true,
|
|
1263
|
+
lastAux: '',
|
|
1264
|
+
markdownMode: 'normal',
|
|
1265
|
+
markdownBuffer: '',
|
|
1266
|
+
markdownCarry: ''
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
return new Promise((resolve, reject) => {
|
|
1270
|
+
let settled = false;
|
|
1271
|
+
const startedAt = Date.now();
|
|
1272
|
+
state.progress = createProgressReporter(output, options);
|
|
1273
|
+
state.progress.start();
|
|
1274
|
+
|
|
1275
|
+
const finish = (error, value) => {
|
|
1276
|
+
if (settled) return;
|
|
1277
|
+
settled = true;
|
|
1278
|
+
if (state.progress) state.progress.stop();
|
|
1279
|
+
state.progress = null;
|
|
1280
|
+
state.durationMs = Date.now() - startedAt;
|
|
1281
|
+
if (error) reject(error);
|
|
1282
|
+
else resolve(value);
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
const req = transport.request({
|
|
1286
|
+
hostname: turnUrl.hostname,
|
|
1287
|
+
port: turnUrl.port || (turnUrl.protocol === 'https:' ? 443 : 80),
|
|
1288
|
+
path: `${turnUrl.pathname}${turnUrl.search}`,
|
|
1289
|
+
method: 'POST',
|
|
1290
|
+
headers: {
|
|
1291
|
+
'Content-Type': 'application/json',
|
|
1292
|
+
'Content-Length': Buffer.byteLength(postData),
|
|
1293
|
+
Accept: 'application/json',
|
|
1294
|
+
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
1295
|
+
}
|
|
1296
|
+
}, (res) => {
|
|
1297
|
+
const chunks = [];
|
|
1298
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
1299
|
+
res.on('end', () => {
|
|
1300
|
+
const rawBody = Buffer.concat(chunks).toString('utf8');
|
|
1301
|
+
let parsed = null;
|
|
1302
|
+
try {
|
|
1303
|
+
parsed = rawBody ? JSON.parse(rawBody) : null;
|
|
1304
|
+
} catch (_) {
|
|
1305
|
+
parsed = null;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
1309
|
+
const detail = parsed && (parsed.detail || parsed.error || parsed.message);
|
|
1310
|
+
finish(new Error(detail || rawBody || `HTTP ${res.statusCode}`));
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
const text = String((parsed && (parsed.text || parsed.result || parsed.message)) || '').trim();
|
|
1315
|
+
if (text) {
|
|
1316
|
+
state.output = text;
|
|
1317
|
+
if (output && output.isTTY) output.write(renderTerminalMarkdown(text, output));
|
|
1318
|
+
else output.write(text);
|
|
1319
|
+
state.wroteText = true;
|
|
1320
|
+
state.wroteActivity = true;
|
|
1321
|
+
state.lastChar = text.slice(-1);
|
|
1322
|
+
}
|
|
1323
|
+
const notice = codeFastWorkspaceNotice(parsed);
|
|
1324
|
+
if (notice) {
|
|
1325
|
+
if (state.lastChar && state.lastChar !== '\n') output.write('\n');
|
|
1326
|
+
output.write(`${formatAuxRow('note', notice, output)}\n`);
|
|
1327
|
+
state.wroteActivity = true;
|
|
1328
|
+
state.lastChar = '\n';
|
|
1329
|
+
}
|
|
1330
|
+
state.events.push({ type: 'result', result: text, raw: parsed });
|
|
1331
|
+
state.billing = parsed && parsed.billing ? parsed.billing : null;
|
|
1332
|
+
if (parsed && parsed.duration_ms) state.durationMs = parsed.duration_ms;
|
|
1333
|
+
finish(null, state);
|
|
1334
|
+
});
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
req.on('error', finish);
|
|
1338
|
+
req.setTimeout(timeoutMs, () => {
|
|
1339
|
+
finish(new Error(`Request timeout after ${timeoutMs / 1000}s`));
|
|
1340
|
+
req.destroy();
|
|
1341
|
+
});
|
|
1342
|
+
req.write(postData);
|
|
1343
|
+
req.end();
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function turnFunctionForMode(mode) {
|
|
1348
|
+
return normalizeMode(mode) === 'code-fast' ? postCodeFastTurn : postTurn;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
996
1351
|
async function chat(options = {}) {
|
|
997
|
-
|
|
1352
|
+
let mode = normalizeMode(options.mode);
|
|
998
1353
|
const cwd = options.cwd || process.cwd();
|
|
999
1354
|
const input = options.input || process.stdin;
|
|
1000
1355
|
const baseOutput = options.output || process.stdout;
|
|
@@ -1002,7 +1357,7 @@ async function chat(options = {}) {
|
|
|
1002
1357
|
const output = logger ? logger.output : baseOutput;
|
|
1003
1358
|
const history = [];
|
|
1004
1359
|
|
|
1005
|
-
output.write(`${formatHeader({ mode, cwd, chat: true })}\n\n`);
|
|
1360
|
+
output.write(`${formatHeader({ mode, cwd, chat: true }, output)}\n\n`);
|
|
1006
1361
|
if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
|
|
1007
1362
|
|
|
1008
1363
|
const runLine = async (line) => {
|
|
@@ -1010,11 +1365,29 @@ async function chat(options = {}) {
|
|
|
1010
1365
|
if (!trimmed) return false;
|
|
1011
1366
|
if (EXIT_WORDS.has(trimmed.toLowerCase())) return true;
|
|
1012
1367
|
|
|
1368
|
+
if (trimmed === '/' || trimmed.toLowerCase() === '/help') {
|
|
1369
|
+
output.write(`${chatMenu(output)}\n\n`);
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
const tier = chatTierCommand(trimmed);
|
|
1374
|
+
if (tier) {
|
|
1375
|
+
mode = tier;
|
|
1376
|
+
if (logger) logger.write(`${trimmed}\n`);
|
|
1377
|
+
output.write(`${paint(`· ${tierLabel(mode)}`, [tierColor(mode)], output)}\n\n`);
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (trimmed.startsWith('/')) {
|
|
1382
|
+
output.write(`${chatMenu(output)}\n\n`);
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1013
1386
|
if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
|
|
1014
1387
|
output.write('\n');
|
|
1015
|
-
const result = await
|
|
1388
|
+
const result = await turnFunctionForMode(mode)(trimmed, { mode, cwd, history, output, route: options.route, business: options.business, verify: options.verify });
|
|
1016
1389
|
if (result.output && !result.output.endsWith('\n')) output.write('\n');
|
|
1017
|
-
output.write(`${formatDoneLine(result.durationMs)}\n\n`);
|
|
1390
|
+
output.write(`${formatDoneLine(result.durationMs, creditsFromState(result))}\n\n`);
|
|
1018
1391
|
history.push({ role: 'user', content: trimmed });
|
|
1019
1392
|
history.push({ role: 'assistant', content: result.output || '' });
|
|
1020
1393
|
return false;
|
|
@@ -1029,8 +1402,8 @@ async function chat(options = {}) {
|
|
|
1029
1402
|
return;
|
|
1030
1403
|
}
|
|
1031
1404
|
|
|
1032
|
-
const rl = readline.createInterface({ input, output: baseOutput });
|
|
1033
|
-
const ask = () => new Promise(resolve => rl.question(formatPrompt(mode), resolve));
|
|
1405
|
+
const rl = readline.createInterface({ input, output: baseOutput, completer: chatCompleter });
|
|
1406
|
+
const ask = () => new Promise(resolve => rl.question(formatPrompt(mode, baseOutput), resolve));
|
|
1034
1407
|
while (true) {
|
|
1035
1408
|
const line = await ask();
|
|
1036
1409
|
if (await runLine(line)) {
|
|
@@ -1066,6 +1439,14 @@ function receiptFromState(state) {
|
|
|
1066
1439
|
return event ? event.receipt : null;
|
|
1067
1440
|
}
|
|
1068
1441
|
|
|
1442
|
+
function creditsFromState(state) {
|
|
1443
|
+
const receipt = receiptFromState(state);
|
|
1444
|
+
const billing = receipt && receipt.billing;
|
|
1445
|
+
if (!billing || billing.billed === false) return null;
|
|
1446
|
+
const credits = Number(billing.credits_charged);
|
|
1447
|
+
return Number.isFinite(credits) && credits > 0 ? credits : null;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1069
1450
|
function toolEventsFromState(state) {
|
|
1070
1451
|
const receipt = receiptFromState(state);
|
|
1071
1452
|
if (receipt && Array.isArray(receipt.tool_events)) return receipt.tool_events;
|
|
@@ -1096,13 +1477,14 @@ async function runBenchmarkCase(label, fn, results, output) {
|
|
|
1096
1477
|
}
|
|
1097
1478
|
|
|
1098
1479
|
async function runBenchmark(options = {}) {
|
|
1099
|
-
const
|
|
1480
|
+
const normalized = normalizeMode(options.mode);
|
|
1481
|
+
const mode = normalized === 'code-fast' ? 'fast' : normalized;
|
|
1100
1482
|
const output = options.output || process.stdout;
|
|
1101
1483
|
const results = [];
|
|
1102
1484
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ax-atris2-benchmark-'));
|
|
1103
1485
|
const localOut = () => bufferedOutput();
|
|
1104
1486
|
|
|
1105
|
-
output.write(`Atris 2 ${mode === 'fast' ? 'Fast' : 'Pro'} benchmark\n`);
|
|
1487
|
+
output.write(`Atris 2 ${mode === 'fast' ? 'Fast' : mode === 'max' ? 'Max' : 'Pro'} benchmark\n`);
|
|
1106
1488
|
output.write(`${backendUrl()}\n\n`);
|
|
1107
1489
|
|
|
1108
1490
|
try {
|
|
@@ -1221,17 +1603,74 @@ async function main() {
|
|
|
1221
1603
|
return;
|
|
1222
1604
|
}
|
|
1223
1605
|
|
|
1224
|
-
|
|
1606
|
+
// --business <slug>: chat against that business's cloud workspace. The flag
|
|
1607
|
+
// takes a value, so splice the pair out before building the prompt.
|
|
1608
|
+
let businessSlug = null;
|
|
1609
|
+
const bizIdx = args.indexOf('--business');
|
|
1610
|
+
if (bizIdx !== -1) {
|
|
1611
|
+
businessSlug = String(args[bizIdx + 1] || '').toLowerCase();
|
|
1612
|
+
if (!/^[a-z0-9-]+$/.test(businessSlug)) {
|
|
1613
|
+
console.error('Usage: ax --business <slug> [prompt]');
|
|
1614
|
+
process.exit(1);
|
|
1615
|
+
}
|
|
1616
|
+
args.splice(bizIdx, 2);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// --verify <cmd>: opt-in turn verifier. The flag takes a value, so splice
|
|
1620
|
+
// the pair out before building the prompt.
|
|
1621
|
+
let verify = '';
|
|
1622
|
+
const verifyIdx = args.indexOf('--verify');
|
|
1623
|
+
if (verifyIdx !== -1) {
|
|
1624
|
+
verify = String(args[verifyIdx + 1] || '').trim();
|
|
1625
|
+
if (!verify) {
|
|
1626
|
+
console.error('Usage: ax --verify "<cmd>" [prompt]');
|
|
1627
|
+
process.exit(1);
|
|
1628
|
+
}
|
|
1629
|
+
args.splice(verifyIdx, 2);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const mode = args.includes('--code-fast') || args.includes('--code') ? 'code-fast' : args.includes('--max') ? 'max' : args.includes('--fast') ? 'fast' : 'pro';
|
|
1225
1633
|
const doctor = args.includes('--doctor');
|
|
1226
1634
|
const benchmark = args.includes('--benchmark');
|
|
1227
1635
|
const forceCloud = args.includes('--cloud');
|
|
1228
1636
|
const forceLocal = args.includes('--local');
|
|
1229
1637
|
const route = forceCloud ? 'cloud' : forceLocal ? 'local' : 'auto';
|
|
1230
1638
|
const prompt = args
|
|
1231
|
-
.filter(arg => !['--fast', '--pro', '--chat', '--doctor', '--benchmark', '--local', '--cloud', '--help', '-h'].includes(arg))
|
|
1639
|
+
.filter(arg => !['--max', '--fast', '--pro', '--code-fast', '--code', '--chat', '--doctor', '--benchmark', '--local', '--cloud', '--help', '-h'].includes(arg))
|
|
1232
1640
|
.join(' ')
|
|
1233
1641
|
.trim();
|
|
1234
1642
|
|
|
1643
|
+
let business = null;
|
|
1644
|
+
if (businessSlug) {
|
|
1645
|
+
if (normalizeMode(mode) === 'code-fast') {
|
|
1646
|
+
console.error('--business is not supported with --code-fast');
|
|
1647
|
+
process.exit(1);
|
|
1648
|
+
}
|
|
1649
|
+
const creds = loadCredentials();
|
|
1650
|
+
if (!creds || !creds.token) {
|
|
1651
|
+
console.error('Not logged in. Run: atris login');
|
|
1652
|
+
process.exit(1);
|
|
1653
|
+
}
|
|
1654
|
+
const { resolveBusiness, ensureAwake } = require('./commands/terminal');
|
|
1655
|
+
const { makeCloudExecutor, postToolResult } = require('./commands/workflow');
|
|
1656
|
+
const biz = await resolveBusiness(creds.token, businessSlug);
|
|
1657
|
+
if (!biz || !biz.workspaceId) {
|
|
1658
|
+
console.error(`Business "${businessSlug}" not found or has no workspace.`);
|
|
1659
|
+
process.exit(1);
|
|
1660
|
+
}
|
|
1661
|
+
const awake = await ensureAwake(creds.token, biz.businessId);
|
|
1662
|
+
if (!awake) {
|
|
1663
|
+
console.error('Business computer did not become ready in time.');
|
|
1664
|
+
process.exit(1);
|
|
1665
|
+
}
|
|
1666
|
+
business = {
|
|
1667
|
+
slug: businessSlug,
|
|
1668
|
+
executor: makeCloudExecutor({ token: creds.token, businessId: biz.businessId, workspaceId: biz.workspaceId, slug: businessSlug }),
|
|
1669
|
+
postToolResult
|
|
1670
|
+
};
|
|
1671
|
+
console.log(`cloud workspace: ${biz.businessName || businessSlug}`);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1235
1674
|
try {
|
|
1236
1675
|
if (benchmark) {
|
|
1237
1676
|
await runBenchmark({ mode, cwd: process.cwd(), output: process.stdout });
|
|
@@ -1239,22 +1678,22 @@ async function main() {
|
|
|
1239
1678
|
}
|
|
1240
1679
|
|
|
1241
1680
|
if (doctor) {
|
|
1242
|
-
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }));
|
|
1681
|
+
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }, process.stdout));
|
|
1243
1682
|
console.log('');
|
|
1244
1683
|
console.log(formatRunProfile(buildRunProfile({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route }), process.stdout));
|
|
1245
1684
|
return;
|
|
1246
1685
|
}
|
|
1247
1686
|
|
|
1248
1687
|
if (!prompt || args.includes('--chat')) {
|
|
1249
|
-
await chat({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route });
|
|
1688
|
+
await chat({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
|
|
1250
1689
|
return;
|
|
1251
1690
|
}
|
|
1252
1691
|
|
|
1253
|
-
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }));
|
|
1692
|
+
console.log(formatHeader({ mode, cwd: process.cwd(), chat: false }, process.stdout));
|
|
1254
1693
|
console.log('');
|
|
1255
|
-
const result = await
|
|
1694
|
+
const result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
|
|
1256
1695
|
console.log('');
|
|
1257
|
-
console.log(formatDoneLine(result.durationMs));
|
|
1696
|
+
console.log(formatDoneLine(result.durationMs, creditsFromState(result)));
|
|
1258
1697
|
} catch (error) {
|
|
1259
1698
|
console.error(`x ${error.message}`);
|
|
1260
1699
|
printBackendHint();
|
|
@@ -1271,11 +1710,19 @@ module.exports = {
|
|
|
1271
1710
|
authUserId,
|
|
1272
1711
|
backendBaseUrl,
|
|
1273
1712
|
backendUrl,
|
|
1713
|
+
buildCodeFastPayload,
|
|
1274
1714
|
buildPayload,
|
|
1275
1715
|
cachedIntegrationStatus,
|
|
1276
1716
|
buildConnectionContext,
|
|
1277
1717
|
buildRunProfile,
|
|
1278
1718
|
chat,
|
|
1719
|
+
chatCompleter,
|
|
1720
|
+
chatMenu,
|
|
1721
|
+
chatTierCommand,
|
|
1722
|
+
codeFastWorkspaceNotice,
|
|
1723
|
+
creditsFromState,
|
|
1724
|
+
codeFastBaseUrl,
|
|
1725
|
+
codeFastUrl,
|
|
1279
1726
|
createRunLogger,
|
|
1280
1727
|
createProgressReporter,
|
|
1281
1728
|
formatDoneLine,
|
|
@@ -1291,11 +1738,13 @@ module.exports = {
|
|
|
1291
1738
|
handleEvent,
|
|
1292
1739
|
modelForMode,
|
|
1293
1740
|
parseSseBlock,
|
|
1741
|
+
postCodeFastTurn,
|
|
1294
1742
|
postTurn,
|
|
1295
1743
|
renderStreamingMarkdown,
|
|
1296
1744
|
renderTerminalMarkdown,
|
|
1297
1745
|
resolveRoute,
|
|
1298
1746
|
runBenchmark,
|
|
1299
1747
|
summarizeToolInput,
|
|
1300
|
-
summarizeToolResult
|
|
1748
|
+
summarizeToolResult,
|
|
1749
|
+
tierLabel
|
|
1301
1750
|
};
|