atris 3.69.0 → 3.71.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/README.md +40 -1
- package/atris/skills/README.md +1 -0
- package/atris/skills/campaigns/SKILL.md +81 -0
- package/atris/skills/meta/SKILL.md +52 -8
- package/ax +226 -42
- package/bin/atris.js +26 -6
- package/commands/autoland.js +176 -7
- package/commands/autopilot.js +71 -11
- package/commands/campaign.js +759 -0
- package/commands/compile.js +19 -8
- package/commands/doctor.js +58 -0
- package/commands/dream.js +18 -3
- package/commands/engine.js +147 -5
- package/commands/improve.js +119 -9
- package/commands/init.js +18 -1
- package/commands/interview.js +15 -3
- package/commands/land.js +22 -6
- package/commands/link.js +1 -0
- package/commands/member.js +433 -99
- package/commands/mission.js +332 -41
- package/commands/report.js +42 -8
- package/commands/run.js +9 -2
- package/commands/scout.js +9 -1
- package/commands/task.js +132 -16
- package/commands/team.js +212 -4
- package/commands/unknowns.js +15 -1
- package/commands/verify.js +46 -0
- package/commands/worktree.js +148 -18
- package/commands/write.js +12 -1
- package/commands/youtube.js +17 -2
- package/lib/accept-verify-gate.js +1 -0
- package/lib/auto-accept-certified.js +1 -0
- package/lib/autoland.js +13 -4
- package/lib/campaign-cadence.js +131 -0
- package/lib/campaign-core.js +330 -0
- package/lib/campaign-holdout.js +65 -0
- package/lib/campaign-loop.js +242 -0
- package/lib/campaign-missions.js +237 -0
- package/lib/campaign-new.js +251 -0
- package/lib/campaign-protect.js +75 -0
- package/lib/campaign-reading.js +147 -0
- package/lib/campaign-schema.js +196 -0
- package/lib/campaign-store.js +380 -0
- package/lib/campaign-sync.js +302 -0
- package/lib/campaign-trace.js +300 -0
- package/lib/conductor-artifacts.js +15 -1
- package/lib/daily-log.js +11 -1
- package/lib/dispatch-scout.js +18 -0
- package/lib/engine-ask.js +35 -4
- package/lib/engine-registry.js +2 -0
- package/lib/engine-resource.js +518 -0
- package/lib/engine-validate.js +189 -9
- package/lib/fleet.js +125 -16
- package/lib/init-commit.js +80 -0
- package/lib/intents.js +8 -0
- package/lib/known-commands.js +1 -1
- package/lib/land-green.js +58 -7
- package/lib/landing-proof.js +352 -0
- package/lib/member-autoland.js +479 -10
- package/lib/member-board.js +86 -10
- package/lib/member-map-fold.js +126 -0
- package/lib/member-map-inbox.js +80 -0
- package/lib/member-progress.js +48 -1
- package/lib/member-report.js +433 -0
- package/lib/member-scaffold.js +1 -2
- package/lib/member-self-next-job.js +97 -0
- package/lib/member-switches.js +7 -0
- package/lib/member-task-pick.js +41 -14
- package/lib/member-up.js +249 -16
- package/lib/roster-prep.js +25 -2
- package/lib/runner-command.js +5 -0
- package/lib/task-db.js +63 -11
- package/lib/task-explanation.js +7 -0
- package/lib/task-list-keeper.js +8 -1
- package/lib/task-receipt.js +6 -1
- package/mcp/atris-mcp/index.mjs +212 -10
- package/package.json +2 -1
- package/scripts/det/engine-reading.js +37 -0
- package/scripts/det/ytnotes +29 -5
package/ax
CHANGED
|
@@ -97,8 +97,76 @@ function tierColor(mode) {
|
|
|
97
97
|
return TIER_COLORS[mode] || ANSI.accent;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Any model by name: `/model <name>` or `--model <name>` rides as mode `model:<spec>`.
|
|
101
|
+
const MODEL_ALIASES = Object.freeze({
|
|
102
|
+
mercury: 'mercury',
|
|
103
|
+
'mercury-3': 'mercury',
|
|
104
|
+
'inception:mercury-3-preview-1002': 'mercury',
|
|
105
|
+
fast: 'fast',
|
|
106
|
+
pro: 'pro',
|
|
107
|
+
max: 'max',
|
|
108
|
+
rapid: 'rapid',
|
|
109
|
+
alpha: 'alpha',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// The /model pick list. Each row answered as itself on the backend 2026-10-14.
|
|
113
|
+
const MODEL_CHOICES = Object.freeze([
|
|
114
|
+
{ mode: 'fast', label: 'Atris 2 Fast' },
|
|
115
|
+
{ mode: 'pro', label: 'Atris 2 Pro' },
|
|
116
|
+
{ mode: 'max', label: 'Atris 2 Max' },
|
|
117
|
+
{ mode: 'rapid', label: 'Atris Rapid' },
|
|
118
|
+
{ mode: 'alpha', label: 'Atris Alpha' },
|
|
119
|
+
{ mode: 'mercury', label: 'Mercury 3 (test)' },
|
|
120
|
+
{ mode: 'model:openrouter:anthropic/claude-opus-5.5', label: 'Claude Opus 5.5' },
|
|
121
|
+
{ mode: 'model:openai:gpt-6-astra', label: 'GPT-6 Astra' },
|
|
122
|
+
{ mode: 'model:openai:gpt-5.6-terra', label: 'GPT-5.6 Terra' },
|
|
123
|
+
{ mode: 'model:openrouter:google/gemini-3.8-flash', label: 'Gemini 3.8 Flash' },
|
|
124
|
+
{ mode: 'model:xai:grok-4.6', label: 'Grok 4.6' },
|
|
125
|
+
{ mode: 'model:openrouter:deepseek/deepseek-v4.1-flash', label: 'DeepSeek V4.1 Flash' },
|
|
126
|
+
{ mode: 'model:openrouter:z-ai/glm-5.3-flash', label: 'GLM 5.3 Flash' },
|
|
127
|
+
].map(choice => Object.freeze(choice)));
|
|
128
|
+
|
|
129
|
+
function squashModelName(value) {
|
|
130
|
+
return String(value || '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function modeForModelName(name) {
|
|
134
|
+
const raw = String(name || '').trim();
|
|
135
|
+
if (!raw) return null;
|
|
136
|
+
if (/^\d+$/.test(raw)) {
|
|
137
|
+
const choice = MODEL_CHOICES[Number(raw) - 1];
|
|
138
|
+
return choice ? choice.mode : null;
|
|
139
|
+
}
|
|
140
|
+
const alias = MODEL_ALIASES[raw.toLowerCase()];
|
|
141
|
+
if (alias) return alias;
|
|
142
|
+
// "opus", "gemini", "gpt-6": a unique start of a pick-list name picks it.
|
|
143
|
+
const query = squashModelName(raw);
|
|
144
|
+
const exact = MODEL_CHOICES.find(choice => squashModelName(choice.label) === query);
|
|
145
|
+
if (exact) return exact.mode;
|
|
146
|
+
const hits = MODEL_CHOICES.filter(choice => squashModelName(choice.label).includes(query));
|
|
147
|
+
if (hits.length === 1) return hits[0].mode;
|
|
148
|
+
if (!raw.includes(':')) return null;
|
|
149
|
+
return `model:${raw}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function modelChoiceList(currentMode, options = {}) {
|
|
153
|
+
const width = MODEL_CHOICES.reduce((max, choice) => Math.max(max, choice.label.length), 0);
|
|
154
|
+
const rows = MODEL_CHOICES.map((choice, index) => {
|
|
155
|
+
const mark = choice.mode === currentMode ? '●' : ' ';
|
|
156
|
+
const num = String(index + 1).padStart(2);
|
|
157
|
+
const id = choice.mode.startsWith('model:') ? choice.mode.slice('model:'.length) : modelForMode(choice.mode);
|
|
158
|
+
return ` ${mark} ${num} ${paint(choice.label.padEnd(width), [tierColor(choice.mode)], options)} ${paint(id, [ANSI.muted], options)}`;
|
|
159
|
+
});
|
|
160
|
+
if (String(currentMode || '').startsWith('model:') && !MODEL_CHOICES.some(choice => choice.mode === currentMode)) {
|
|
161
|
+
rows.push(` ● - ${currentMode.slice('model:'.length)}`);
|
|
162
|
+
}
|
|
163
|
+
return [...rows, paint(' pick: /model 6 · /model opus · /model provider:model-name', [ANSI.muted], options)].join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
100
166
|
function modelForMode(mode) {
|
|
167
|
+
if (String(mode || '').startsWith('model:')) return mode.slice('model:'.length);
|
|
101
168
|
if (mode === 'code-fast') return CODE_FAST.model;
|
|
169
|
+
if (mode === 'mercury') return 'inception:mercury-3-preview-1002';
|
|
102
170
|
if (mode === 'max') return 'atris:max';
|
|
103
171
|
if (mode === 'alpha') return 'atris:alpha';
|
|
104
172
|
if (mode === 'rapid') return 'atris:rapid';
|
|
@@ -147,7 +215,12 @@ function formatSeconds(totalSeconds) {
|
|
|
147
215
|
}
|
|
148
216
|
|
|
149
217
|
function tierLabel(mode) {
|
|
218
|
+
if (String(mode || '').startsWith('model:')) {
|
|
219
|
+
const choice = MODEL_CHOICES.find(item => item.mode === mode);
|
|
220
|
+
return choice ? choice.label : mode.slice('model:'.length);
|
|
221
|
+
}
|
|
150
222
|
if (mode === 'code-fast') return 'Atris Code Fast';
|
|
223
|
+
if (mode === 'mercury') return 'mercury 3 (test model)';
|
|
151
224
|
if (mode === 'max') return 'Atris 2 Max';
|
|
152
225
|
if (mode === 'alpha') return 'Atris Alpha';
|
|
153
226
|
if (mode === 'rapid') return 'Atris Rapid';
|
|
@@ -160,7 +233,7 @@ function formatHeader({ mode = 'pro', cwd = process.cwd(), chat = false, reasoni
|
|
|
160
233
|
return [
|
|
161
234
|
paint(`${tierLabel(mode)}${chat ? ' chat' : ''}${effortSuffix}`, [ANSI.bold, tierColor(normalizeMode(mode))], options),
|
|
162
235
|
cwd,
|
|
163
|
-
chat ? paint('shift-tab bypass · /auto /log /today /now /who · /
|
|
236
|
+
chat ? paint('shift-tab bypass · /auto /log /today /now /who · /model to switch models · /effort · exit to leave', [ANSI.muted], options) : '',
|
|
164
237
|
].filter(Boolean).join('\n');
|
|
165
238
|
}
|
|
166
239
|
|
|
@@ -169,11 +242,11 @@ function formatUsage() {
|
|
|
169
242
|
'ax - Atris local/code agent',
|
|
170
243
|
'',
|
|
171
244
|
'Usage:',
|
|
172
|
-
' ax [--auto|--max|--pro|--fast|--rapid|--alpha|--code-fast] [--local|--cloud] <message>',
|
|
173
|
-
' ax [--auto|--max|--pro|--fast|--rapid|--alpha|--code-fast] [--local|--cloud] --print <message>',
|
|
174
|
-
' ax [--auto|--max|--pro|--fast|--rapid|--alpha|--code-fast] [--local|--cloud] --chat',
|
|
245
|
+
' ax [--auto|--max|--pro|--fast|--rapid|--alpha|--mercury|--code-fast] [--local|--cloud] <message>',
|
|
246
|
+
' ax [--auto|--max|--pro|--fast|--rapid|--alpha|--mercury|--code-fast] [--local|--cloud] --print <message>',
|
|
247
|
+
' ax [--auto|--max|--pro|--fast|--rapid|--alpha|--mercury|--code-fast] [--local|--cloud] --chat',
|
|
175
248
|
' ax [--max|--pro|--fast|--rapid|--alpha] --business <slug> [<message>|--chat]',
|
|
176
|
-
' ax [--max|--pro|--fast|--rapid|--alpha|--code-fast] --doctor',
|
|
249
|
+
' ax [--max|--pro|--fast|--rapid|--alpha|--mercury|--code-fast] --doctor',
|
|
177
250
|
' ax --approvals',
|
|
178
251
|
' ax --approve <approval-id>',
|
|
179
252
|
' ax --deny <approval-id>',
|
|
@@ -191,6 +264,9 @@ function formatUsage() {
|
|
|
191
264
|
' --pro hosted Atris 2, deeper tool loop',
|
|
192
265
|
' --fast hosted Atris 2, faster low-latency turns',
|
|
193
266
|
' --rapid hosted Atris Rapid, cheapest chat and first-hop tools',
|
|
267
|
+
' --mercury mercury 3 test model, with turn timing',
|
|
268
|
+
' --model <name> any model by name (mercury, or provider:model); in chat use /model <name>',
|
|
269
|
+
' --diffuse watch mercury text refine live in a terminal',
|
|
194
270
|
' --alpha hosted experimental lane (free preview model, swap via ATRIS2_ALPHA_MODEL)',
|
|
195
271
|
' --effort <level> reasoning effort: none, low, medium, high, xhigh, or default',
|
|
196
272
|
' --code-fast Atris Code Fast public lane',
|
|
@@ -986,7 +1062,9 @@ function resolveRoute(message, options = {}) {
|
|
|
986
1062
|
}
|
|
987
1063
|
|
|
988
1064
|
function normalizeMode(mode) {
|
|
1065
|
+
if (String(mode || '').startsWith('model:')) return mode;
|
|
989
1066
|
if (mode === 'code-fast' || mode === 'code') return 'code-fast';
|
|
1067
|
+
if (mode === 'mercury') return 'mercury';
|
|
990
1068
|
if (mode === 'max') return 'max';
|
|
991
1069
|
if (mode === 'alpha') return 'alpha';
|
|
992
1070
|
if (mode === 'rapid') return 'rapid';
|
|
@@ -1113,7 +1191,9 @@ function nextApproveMode(arg, current) {
|
|
|
1113
1191
|
function formatPrompt(mode, options = {}) {
|
|
1114
1192
|
if (!mode) return '› ';
|
|
1115
1193
|
const tier = normalizeMode(mode);
|
|
1116
|
-
|
|
1194
|
+
// Picked models show their short name: "claude-opus-5.5", not "model:openrouter:...".
|
|
1195
|
+
const short = tier.startsWith('model:') ? tier.split(/[:/]/).pop() : tier;
|
|
1196
|
+
const label = paint(short, [ANSI.bold, tierColor(tier)], options);
|
|
1117
1197
|
const tag = approveModeTag(options.approveMode);
|
|
1118
1198
|
if (tag) {
|
|
1119
1199
|
return `${label} ${paint(`[${tag}]`, [ANSI.muted], options)} › `;
|
|
@@ -1167,7 +1247,7 @@ function formatReasoningEffortLabel(value) {
|
|
|
1167
1247
|
|
|
1168
1248
|
const CHAT_COMMANDS = Object.freeze([
|
|
1169
1249
|
{ name: '/help', description: 'list all commands', type: 'help' },
|
|
1170
|
-
{ name: '/model', description: '
|
|
1250
|
+
{ name: '/model', description: 'pick a model from the list', type: 'model' },
|
|
1171
1251
|
{ name: '/status', description: 'show runtime health', type: 'status' },
|
|
1172
1252
|
{ name: '/fast', description: 'switch to quick answers with low latency', type: 'tier', mode: 'fast' },
|
|
1173
1253
|
{ name: '/pro', description: 'switch to a deeper tool loop', type: 'tier', mode: 'pro' },
|
|
@@ -1299,10 +1379,14 @@ function runChatSlash(line, options = {}) {
|
|
|
1299
1379
|
};
|
|
1300
1380
|
}
|
|
1301
1381
|
if (command.type === 'model') {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1382
|
+
if (parsed.arg) {
|
|
1383
|
+
const next = modeForModelName(parsed.arg);
|
|
1384
|
+
if (!next) {
|
|
1385
|
+
return { type: 'model', output: `no single model matches "${parsed.arg}"\n${modelChoiceList(mode, options)}` };
|
|
1386
|
+
}
|
|
1387
|
+
return { type: 'tier', mode: next, output: `· ${tierLabel(next)}` };
|
|
1388
|
+
}
|
|
1389
|
+
return { type: 'model', output: modelChoiceList(mode, options) };
|
|
1306
1390
|
}
|
|
1307
1391
|
if (command.type === 'effort') {
|
|
1308
1392
|
const current = formatReasoningEffortLabel(options.reasoningEffort);
|
|
@@ -1384,13 +1468,21 @@ function attachSlashMenu(rl, output, options = {}) {
|
|
|
1384
1468
|
const original = rl._ttyWrite.bind(rl);
|
|
1385
1469
|
let visibleRows = 0;
|
|
1386
1470
|
|
|
1471
|
+
// Move back by row count, not a saved screen position: drawing the menu at
|
|
1472
|
+
// the bottom of a short window scrolls the screen, and a saved position then
|
|
1473
|
+
// points into the menu, so typed text landed on a menu row (seen 2026-10-14).
|
|
1474
|
+
const backToInput = (rowsBelow) => {
|
|
1475
|
+
if (rowsBelow) output.write(`\x1b[${rowsBelow}A`);
|
|
1476
|
+
const cols = typeof rl.getCursorPos === 'function' ? rl.getCursorPos().cols : 0;
|
|
1477
|
+
output.write(`\r${cols ? `\x1b[${cols}C` : ''}`);
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1387
1480
|
const clear = () => {
|
|
1388
1481
|
if (!visibleRows) return;
|
|
1389
|
-
output.write('\x1b7');
|
|
1390
1482
|
for (let index = 0; index < visibleRows; index += 1) {
|
|
1391
1483
|
output.write('\x1b[1B\r\x1b[2K');
|
|
1392
1484
|
}
|
|
1393
|
-
|
|
1485
|
+
backToInput(visibleRows);
|
|
1394
1486
|
visibleRows = 0;
|
|
1395
1487
|
};
|
|
1396
1488
|
|
|
@@ -1399,10 +1491,10 @@ function attachSlashMenu(rl, output, options = {}) {
|
|
|
1399
1491
|
if (typeof options.isActive === 'function' && !options.isActive()) return;
|
|
1400
1492
|
const matches = filterChatCommands(rl.line);
|
|
1401
1493
|
if (!matches.length) return;
|
|
1402
|
-
const
|
|
1403
|
-
output.
|
|
1494
|
+
const room = Math.max(3, (Number(output.rows) || 24) - 2);
|
|
1495
|
+
const rows = chatMenu(output, matches.slice(0, room)).split('\n');
|
|
1404
1496
|
for (const row of rows) output.write(`\r\n${row}`);
|
|
1405
|
-
|
|
1497
|
+
backToInput(rows.length);
|
|
1406
1498
|
visibleRows = rows.length;
|
|
1407
1499
|
};
|
|
1408
1500
|
|
|
@@ -2963,28 +3055,19 @@ function latestPendingApprovalRequest(history = []) {
|
|
|
2963
3055
|
return null;
|
|
2964
3056
|
}
|
|
2965
3057
|
|
|
3058
|
+
// A yes made only of these words runs the staged action: "approved do it",
|
|
3059
|
+
// "yes go ahead please". Any other word ("no", "wait", "but", "change") falls
|
|
3060
|
+
// through to the model, so a hedged or edited reply never fires the action.
|
|
3061
|
+
const APPROVAL_YES_WORDS = new Set(['yes', 'y', 'yep', 'yeah', 'yup', 'sure', 'ok', 'okay', 'approve', 'approved', 'confirm', 'confirmed']);
|
|
3062
|
+
const APPROVAL_FILLER_WORDS = new Set(['do', 'it', 'go', 'ahead', 'please', 'pls', 'create', 'send', 'post', 'run', 'now', 'that', 'looks', 'good']);
|
|
3063
|
+
|
|
2966
3064
|
function messageApprovesPendingApproval(message) {
|
|
2967
|
-
const normalized = String(message || '').trim().toLowerCase().replace(/[
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
'ok',
|
|
2974
|
-
'okay',
|
|
2975
|
-
'approve',
|
|
2976
|
-
'approved',
|
|
2977
|
-
'confirm',
|
|
2978
|
-
'create it',
|
|
2979
|
-
'send it',
|
|
2980
|
-
'post it',
|
|
2981
|
-
'do it',
|
|
2982
|
-
'go ahead',
|
|
2983
|
-
'looks good',
|
|
2984
|
-
'yes create it',
|
|
2985
|
-
'yes send it',
|
|
2986
|
-
'yes post it',
|
|
2987
|
-
]).has(normalized);
|
|
3065
|
+
const normalized = String(message || '').trim().toLowerCase().replace(/[.!?,]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
3066
|
+
if (['do it', 'go ahead', 'looks good', 'create it', 'send it', 'post it'].includes(normalized)) return true;
|
|
3067
|
+
const words = normalized.split(' ').filter(Boolean);
|
|
3068
|
+
if (!words.length || words.length > 6) return false;
|
|
3069
|
+
if (!words.some(word => APPROVAL_YES_WORDS.has(word))) return false;
|
|
3070
|
+
return words.every(word => APPROVAL_YES_WORDS.has(word) || APPROVAL_FILLER_WORDS.has(word));
|
|
2988
3071
|
}
|
|
2989
3072
|
|
|
2990
3073
|
function messageCancelsPendingApproval(message) {
|
|
@@ -3027,6 +3110,7 @@ function buildPayload(message, options = {}) {
|
|
|
3027
3110
|
verify_command: verifyCommand || 'true',
|
|
3028
3111
|
local_tools: [backendApiToolDescriptor()]
|
|
3029
3112
|
};
|
|
3113
|
+
if (mode === 'mercury' && options.diffuse) payload.diffuse = true;
|
|
3030
3114
|
if (previousMessages.length) {
|
|
3031
3115
|
payload.previous_messages = previousMessages;
|
|
3032
3116
|
}
|
|
@@ -3059,6 +3143,13 @@ function buildPayload(message, options = {}) {
|
|
|
3059
3143
|
if (!local && options.connectionUserId) {
|
|
3060
3144
|
payload.connection_user_id = options.connectionUserId;
|
|
3061
3145
|
}
|
|
3146
|
+
if (payload.diffuse) {
|
|
3147
|
+
// The refining view is plain chat: the backend skips tools while diffusing,
|
|
3148
|
+
// and a workspace or tool list would route the turn away from it.
|
|
3149
|
+
delete payload.local_tools;
|
|
3150
|
+
delete payload.workspace_path;
|
|
3151
|
+
payload.max_turns = 1;
|
|
3152
|
+
}
|
|
3062
3153
|
return payload;
|
|
3063
3154
|
}
|
|
3064
3155
|
|
|
@@ -3092,6 +3183,42 @@ function codeFastWorkspaceNotice(result) {
|
|
|
3092
3183
|
return null;
|
|
3093
3184
|
}
|
|
3094
3185
|
|
|
3186
|
+
// Drafts own a temporary area ending at the start of an empty row.
|
|
3187
|
+
// Clip to the visible terminal so wrapping and scrolling cannot leave debris.
|
|
3188
|
+
function clearTextSnapshot(state, output) {
|
|
3189
|
+
if (!state.snapshotRows || !output || !output.isTTY) return;
|
|
3190
|
+
output.write(`\r\x1b[${state.snapshotRows}A\x1b[J`);
|
|
3191
|
+
state.snapshotRows = 0;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
function renderTextSnapshot(state, output, content) {
|
|
3195
|
+
stopProgress(state);
|
|
3196
|
+
clearTextSnapshot(state, output);
|
|
3197
|
+
const width = Math.max(1, (output.columns || 80) - 1);
|
|
3198
|
+
const height = Math.max(1, (output.rows || 24) - 1);
|
|
3199
|
+
const clipped = [];
|
|
3200
|
+
for (const line of String(content).replace(/[\x00-\x09\x0b-\x1f\x7f]/g, '').split('\n')) {
|
|
3201
|
+
// Wrap each line to the terminal width so the whole draft stays visible.
|
|
3202
|
+
let used = 0;
|
|
3203
|
+
let text = '';
|
|
3204
|
+
for (const char of line) {
|
|
3205
|
+
// Non-ASCII cells are conservatively two columns, including emoji.
|
|
3206
|
+
const cells = /\p{Mark}/u.test(char) ? 0 : char.codePointAt(0) > 0xff ? 2 : 1;
|
|
3207
|
+
if (used + cells > width) {
|
|
3208
|
+
clipped.push(text);
|
|
3209
|
+
text = '';
|
|
3210
|
+
used = 0;
|
|
3211
|
+
}
|
|
3212
|
+
text += char;
|
|
3213
|
+
used += cells;
|
|
3214
|
+
}
|
|
3215
|
+
clipped.push(text);
|
|
3216
|
+
}
|
|
3217
|
+
clipped.splice(height);
|
|
3218
|
+
output.write(`${paint(clipped.join('\n'), [ANSI.dim], output)}\n`);
|
|
3219
|
+
state.snapshotRows = clipped.length;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3095
3222
|
function handleEvent(event, state, output) {
|
|
3096
3223
|
if (!event || typeof event !== 'object') return;
|
|
3097
3224
|
state.events.push(event);
|
|
@@ -3114,6 +3241,19 @@ function handleEvent(event, state, output) {
|
|
|
3114
3241
|
return;
|
|
3115
3242
|
}
|
|
3116
3243
|
|
|
3244
|
+
if ((event.type === 'text_snapshot' || event.type === 'text_delta' || event.type === 'text')
|
|
3245
|
+
&& event.content && state.firstTextMs == null) {
|
|
3246
|
+
state.firstTextMs = Date.now() - state.startedAt;
|
|
3247
|
+
}
|
|
3248
|
+
if (event.type === 'text_snapshot') {
|
|
3249
|
+
if (state.diffuse) {
|
|
3250
|
+
state.snapshotCount += 1;
|
|
3251
|
+
if (output && output.isTTY) renderTextSnapshot(state, output, event.content || '');
|
|
3252
|
+
}
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
clearTextSnapshot(state, output);
|
|
3117
3257
|
if ((event.type === 'text_delta' || event.type === 'text') && event.content) {
|
|
3118
3258
|
state.output += event.content;
|
|
3119
3259
|
if (output && output.isTTY) writeStreamingText(state, output, event.content);
|
|
@@ -3184,6 +3324,13 @@ function handleEvent(event, state, output) {
|
|
|
3184
3324
|
}
|
|
3185
3325
|
|
|
3186
3326
|
if (event.type === 'receipt' && event.receipt) {
|
|
3327
|
+
// A picked model the backend could not reach is swapped silently; say so.
|
|
3328
|
+
const answeredBy = String(event.receipt.model || '');
|
|
3329
|
+
const asked = String(state.requestedModel || '');
|
|
3330
|
+
if (answeredBy && asked && !asked.startsWith('atris:') && !asked.endsWith(answeredBy) && !answeredBy.endsWith(asked.split(':').pop())) {
|
|
3331
|
+
flushPendingText(state, output);
|
|
3332
|
+
writeAuxLine(state, output, paint(`· ${asked} did not answer; fell back to ${answeredBy}`, [ANSI.muted], output));
|
|
3333
|
+
}
|
|
3187
3334
|
const approvalLine = formatApprovalReceipt(event.receipt);
|
|
3188
3335
|
const previewRows = formatTaskPreviewRows(event.receipt, output);
|
|
3189
3336
|
if (approvalLine) {
|
|
@@ -3234,6 +3381,7 @@ function isTurnInterruptedError(error) {
|
|
|
3234
3381
|
}
|
|
3235
3382
|
|
|
3236
3383
|
async function postTurn(message, options = {}) {
|
|
3384
|
+
const startedAt = Date.now();
|
|
3237
3385
|
let route = options.business ? 'local' : resolveRoute(message, options);
|
|
3238
3386
|
if (route === 'local' && !options.business && !pinLocalCheckout(options)
|
|
3239
3387
|
&& !(await localBackendReachable())) {
|
|
@@ -3265,6 +3413,12 @@ async function postTurn(message, options = {}) {
|
|
|
3265
3413
|
wroteText: false,
|
|
3266
3414
|
wroteActivity: false,
|
|
3267
3415
|
durationMs: 0,
|
|
3416
|
+
startedAt,
|
|
3417
|
+
firstTextMs: null,
|
|
3418
|
+
diffuse: Boolean(payload.diffuse),
|
|
3419
|
+
requestedModel: String(payload.model || ''),
|
|
3420
|
+
snapshotCount: 0,
|
|
3421
|
+
snapshotRows: 0,
|
|
3268
3422
|
lastChar: '\n',
|
|
3269
3423
|
progress: null,
|
|
3270
3424
|
inAuxBlock: false,
|
|
@@ -3306,7 +3460,6 @@ async function postTurn(message, options = {}) {
|
|
|
3306
3460
|
return new Promise((resolve, reject) => {
|
|
3307
3461
|
let settled = false;
|
|
3308
3462
|
let abortListener = null;
|
|
3309
|
-
const startedAt = Date.now();
|
|
3310
3463
|
state.progress = createProgressReporter(output, options);
|
|
3311
3464
|
state.progress.start();
|
|
3312
3465
|
|
|
@@ -3316,7 +3469,16 @@ async function postTurn(message, options = {}) {
|
|
|
3316
3469
|
if (abortListener && options.signal) options.signal.removeEventListener('abort', abortListener);
|
|
3317
3470
|
if (state.progress) state.progress.stop();
|
|
3318
3471
|
state.progress = null;
|
|
3472
|
+
clearTextSnapshot(state, output);
|
|
3319
3473
|
state.durationMs = Date.now() - startedAt;
|
|
3474
|
+
const timedMode = normalizeMode(options.mode);
|
|
3475
|
+
if (timedMode === 'mercury' || timedMode.startsWith('model:')) {
|
|
3476
|
+
const first = state.firstTextMs == null ? 'none' : `${(state.firstTextMs / 1000).toFixed(2)}s`;
|
|
3477
|
+
const steps = state.diffuse ? `, ${state.snapshotCount} refining steps` : '';
|
|
3478
|
+
const stderr = options.stderr || process.stderr;
|
|
3479
|
+
if (output && output.isTTY && state.lastChar !== '\n') output.write('\n');
|
|
3480
|
+
stderr.write(`${paint(`${timedMode === 'mercury' ? 'mercury 3' : tierLabel(timedMode)}: first text ${first}, done ${(state.durationMs / 1000).toFixed(2)}s${steps}`, [ANSI.dim], stderr)}\n`);
|
|
3481
|
+
}
|
|
3320
3482
|
if (error) reject(error);
|
|
3321
3483
|
else resolve(value);
|
|
3322
3484
|
};
|
|
@@ -3564,6 +3726,8 @@ async function runHeadlessTurn(message, options = {}) {
|
|
|
3564
3726
|
business: options.business,
|
|
3565
3727
|
verify: options.verify,
|
|
3566
3728
|
reasoningEffort: options.reasoningEffort,
|
|
3729
|
+
diffuse: options.diffuse,
|
|
3730
|
+
stderr: options.stderr,
|
|
3567
3731
|
output: sink,
|
|
3568
3732
|
showProgress: false,
|
|
3569
3733
|
});
|
|
@@ -3684,7 +3848,7 @@ async function chat(options = {}) {
|
|
|
3684
3848
|
}
|
|
3685
3849
|
if (logger) logger.write(`${trimmed}\n`);
|
|
3686
3850
|
const color = slash.mode ? [tierColor(slash.mode)] : [ANSI.muted];
|
|
3687
|
-
const preservesOwnFormatting = slash.type === 'help' || slash.type === 'status';
|
|
3851
|
+
const preservesOwnFormatting = slash.type === 'help' || slash.type === 'status' || slash.type === 'model';
|
|
3688
3852
|
output.write(`${preservesOwnFormatting ? slash.output : paint(slash.output, color, output)}\n\n`);
|
|
3689
3853
|
return false;
|
|
3690
3854
|
}
|
|
@@ -3814,6 +3978,7 @@ async function chat(options = {}) {
|
|
|
3814
3978
|
business: options.business,
|
|
3815
3979
|
verify: options.verify,
|
|
3816
3980
|
reasoningEffort: session.reasoningEffort,
|
|
3981
|
+
diffuse: options.diffuse,
|
|
3817
3982
|
conversationId,
|
|
3818
3983
|
turnId,
|
|
3819
3984
|
approveBackendApi: options.approveBackendApi,
|
|
@@ -4733,8 +4898,22 @@ async function main() {
|
|
|
4733
4898
|
args.splice(verifyIdx, 2);
|
|
4734
4899
|
}
|
|
4735
4900
|
|
|
4736
|
-
|
|
4901
|
+
// --model <name>: any backend model by name, or a lane alias like mercury.
|
|
4902
|
+
let modelLane = null;
|
|
4903
|
+
const modelIdx = args.indexOf('--model');
|
|
4904
|
+
if (modelIdx !== -1) {
|
|
4905
|
+
modelLane = modeForModelName(args[modelIdx + 1]);
|
|
4906
|
+
if (!modelLane) {
|
|
4907
|
+
console.error('Usage: ax --model <name> [prompt] e.g. --model mercury or --model fireworks:glm-5.2');
|
|
4908
|
+
process.exit(1);
|
|
4909
|
+
}
|
|
4910
|
+
args.splice(modelIdx, 2);
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4913
|
+
const explicitLane = modelLane || (args.includes('--code-fast') || args.includes('--code')
|
|
4737
4914
|
? 'code-fast'
|
|
4915
|
+
: args.includes('--mercury')
|
|
4916
|
+
? 'mercury'
|
|
4738
4917
|
: args.includes('--max')
|
|
4739
4918
|
? 'max'
|
|
4740
4919
|
: args.includes('--rapid')
|
|
@@ -4745,7 +4924,8 @@ async function main() {
|
|
|
4745
4924
|
? 'fast'
|
|
4746
4925
|
: args.includes('--pro')
|
|
4747
4926
|
? 'pro'
|
|
4748
|
-
: null;
|
|
4927
|
+
: null);
|
|
4928
|
+
const diffuse = args.includes('--diffuse');
|
|
4749
4929
|
const autoEnabled = args.includes('--auto') && !explicitLane;
|
|
4750
4930
|
let mode = explicitLane || 'pro';
|
|
4751
4931
|
const doctor = args.includes('--doctor');
|
|
@@ -4828,7 +5008,7 @@ async function main() {
|
|
|
4828
5008
|
|
|
4829
5009
|
const route = forceCloud ? 'cloud' : forceLocal ? 'local' : 'auto';
|
|
4830
5010
|
const prompt = args
|
|
4831
|
-
.filter(arg => !['--auto', '--auto-approve', '--max', '--fast', '--rapid', '--alpha', '--pro', '--code-fast', '--code', '--chat', '--doctor', '--approvals', '--self-test', '--benchmark', '--print', '--headless', '--local', '--cloud', '--help', '-h', '--effort'].includes(arg))
|
|
5011
|
+
.filter(arg => !['--auto', '--auto-approve', '--max', '--fast', '--rapid', '--alpha', '--mercury', '--diffuse', '--pro', '--code-fast', '--code', '--chat', '--doctor', '--approvals', '--self-test', '--benchmark', '--print', '--headless', '--local', '--cloud', '--help', '-h', '--effort'].includes(arg))
|
|
4832
5012
|
.join(' ')
|
|
4833
5013
|
.trim();
|
|
4834
5014
|
|
|
@@ -4990,6 +5170,7 @@ async function main() {
|
|
|
4990
5170
|
verify,
|
|
4991
5171
|
autoPick,
|
|
4992
5172
|
reasoningEffort,
|
|
5173
|
+
diffuse,
|
|
4993
5174
|
})
|
|
4994
5175
|
: { ok: false, model: modelForMode(mode), output: '', durationMs: 0, error: 'missing prompt' };
|
|
4995
5176
|
// Drain stdout before exiting: process.exit right after a large write
|
|
@@ -5013,6 +5194,7 @@ async function main() {
|
|
|
5013
5194
|
auto: autoEnabled,
|
|
5014
5195
|
autoApprove,
|
|
5015
5196
|
reasoningEffort,
|
|
5197
|
+
diffuse,
|
|
5016
5198
|
});
|
|
5017
5199
|
return;
|
|
5018
5200
|
}
|
|
@@ -5032,6 +5214,7 @@ async function main() {
|
|
|
5032
5214
|
business,
|
|
5033
5215
|
verify,
|
|
5034
5216
|
reasoningEffort,
|
|
5217
|
+
diffuse,
|
|
5035
5218
|
});
|
|
5036
5219
|
appendAutoOutcome(autoPick, {
|
|
5037
5220
|
ok: true,
|
|
@@ -5156,6 +5339,7 @@ module.exports = {
|
|
|
5156
5339
|
messageApprovesPendingApproval,
|
|
5157
5340
|
messageCancelsPendingApproval,
|
|
5158
5341
|
modelForMode,
|
|
5342
|
+
normalizeMode,
|
|
5159
5343
|
normalizeReasoningEffort,
|
|
5160
5344
|
parseSseBlock,
|
|
5161
5345
|
persistPendingApproval,
|
package/bin/atris.js
CHANGED
|
@@ -34,6 +34,13 @@ function exitWhenFlushed(code) {
|
|
|
34
34
|
setTimeout(() => process.exit(code), 3000).unref();
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Exit right after stdout drains: a write past the 64KB pipe buffer is async,
|
|
38
|
+
// and an empty write's callback fires only once every earlier write is out.
|
|
39
|
+
function exitAfterStdoutDrain(code) {
|
|
40
|
+
if (process.stdout.writableLength === 0) process.exit(code);
|
|
41
|
+
else process.stdout.write('', () => process.exit(code));
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
let CLI_VERSION = 'unknown';
|
|
38
45
|
try {
|
|
39
46
|
const pkgRaw = fs.readFileSync(PACKAGE_JSON_PATH, 'utf8');
|
|
@@ -513,6 +520,11 @@ function showStartHelp() {
|
|
|
513
520
|
function showHelpShort() {
|
|
514
521
|
console.log('atris: you say what you want. already won. one next step.');
|
|
515
522
|
console.log('');
|
|
523
|
+
console.log('Start here:');
|
|
524
|
+
console.log(' 1. atris init set up this folder');
|
|
525
|
+
console.log(' 2. atris doctor check this computer is ready');
|
|
526
|
+
console.log(' 3. atris "what you want" say it, the work starts');
|
|
527
|
+
console.log('');
|
|
516
528
|
console.log(' atris "what do you want here?" empty folder');
|
|
517
529
|
console.log(' atris later "..." remember without a room');
|
|
518
530
|
console.log(' atris guide "..." translate your words');
|
|
@@ -534,6 +546,7 @@ function helpCatalog() {
|
|
|
534
546
|
{ name: 'review', summary: 'validate work and capture learnings', json: false, interactive_risk: 'low' },
|
|
535
547
|
{ name: 'recap', summary: 'spoken recap of what your team did', json: true, interactive_risk: 'low' },
|
|
536
548
|
{ name: 'mission', summary: 'durable goal + owner + verifier + tick', json: true, interactive_risk: 'medium' },
|
|
549
|
+
{ name: 'campaign', summary: 'long goal + judge + budget + seats, made of missions', json: true, interactive_risk: 'low' },
|
|
537
550
|
{ name: 'log', summary: 'append an idea to today inbox (or --repl)', json: true, interactive_risk: 'low' },
|
|
538
551
|
{ name: 'wish', summary: 'headless inbox / mission intake', json: true, interactive_risk: 'low' },
|
|
539
552
|
{ name: 'learn', summary: 'structured project learnings', json: false, interactive_risk: 'low' },
|
|
@@ -624,6 +637,7 @@ function showHelpAll() {
|
|
|
624
637
|
console.log(' atris autoland tick # second check runs, task lands');
|
|
625
638
|
console.log(' mission - Goal + loop + member owner + verifier + receipt; --budget quick|long|deep sets bounded tiers');
|
|
626
639
|
console.log(' decide - Answer open mission asks and send the decision to the next tick');
|
|
640
|
+
console.log(' campaign - Long-running goals with a judge, a budget, and members in seats');
|
|
627
641
|
console.log(' release - Tag release, bump version, create GitHub release, draft /launch; atris release preflight before a v* tag');
|
|
628
642
|
console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
|
|
629
643
|
console.log(' study - On-demand learning feed: ingest topic, start server, open browser');
|
|
@@ -1156,7 +1170,8 @@ function showAutopilotHelp() {
|
|
|
1156
1170
|
|
|
1157
1171
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
1158
1172
|
showHelp({ args: process.argv.slice(3) });
|
|
1159
|
-
|
|
1173
|
+
exitAfterStdoutDrain(0);
|
|
1174
|
+
return;
|
|
1160
1175
|
}
|
|
1161
1176
|
|
|
1162
1177
|
// Core command handlers, loaded eagerly (used by interactiveEntry default path)
|
|
@@ -1893,12 +1908,13 @@ function showWelcomeVisualization() {
|
|
|
1893
1908
|
}
|
|
1894
1909
|
|
|
1895
1910
|
// The guarantee gauge, only when it has bad news: landings a human had to
|
|
1896
|
-
// fix this week. Zero is the healthy state and stays silent; no git history
|
|
1911
|
+
// fix this week. Coaching (role, goal, context edits) is not bad news. Zero is the healthy state and stays silent; no git history
|
|
1897
1912
|
// (not a repo, git missing) also stays silent.
|
|
1898
1913
|
try {
|
|
1899
1914
|
const rev = require('../commands/improve').collectRevisionSignals(cwd, { days: 7 });
|
|
1900
|
-
|
|
1901
|
-
|
|
1915
|
+
const fixed = Number.isFinite(rev.fixed) ? rev.fixed : rev.revised;
|
|
1916
|
+
if (fixed > 0) {
|
|
1917
|
+
console.log(` ${fixed} landing${fixed === 1 ? '' : 's'} this week needed a human fix; run atris improve revisions`);
|
|
1902
1918
|
}
|
|
1903
1919
|
} catch {
|
|
1904
1920
|
// Silent: the gauge never blocks boot.
|
|
@@ -2000,6 +2016,10 @@ if (command === 'guide') {
|
|
|
2000
2016
|
Promise.resolve(require('../commands/team').teamCommand(process.argv.slice(3)))
|
|
2001
2017
|
.then((code) => process.exit(code || 0))
|
|
2002
2018
|
.catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
|
|
2019
|
+
} else if (command === 'campaign') {
|
|
2020
|
+
Promise.resolve(require('../commands/campaign').campaignCommand(process.argv.slice(3)))
|
|
2021
|
+
.then((code) => process.exit(code || 0))
|
|
2022
|
+
.catch((err) => { console.error(`\nerror: ${err.message || err}`); process.exit(1); });
|
|
2003
2023
|
} else if (command === 'who') {
|
|
2004
2024
|
Promise.resolve(require('../commands/who').whoCommand(process.argv.slice(3)))
|
|
2005
2025
|
.then((code) => process.exit(code || 0))
|
|
@@ -3246,8 +3266,8 @@ if (command === 'guide') {
|
|
|
3246
3266
|
// both (mirrors the `compile` command dispatch just above).
|
|
3247
3267
|
const engineArgs = command === 'engines' && process.argv.length <= 3 ? [] : process.argv.slice(3);
|
|
3248
3268
|
Promise.resolve(require('../commands/engine').engineCommand(engineArgs))
|
|
3249
|
-
.then((code) =>
|
|
3250
|
-
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`);
|
|
3269
|
+
.then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
|
|
3270
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); exitAfterStdoutDrain(1); });
|
|
3251
3271
|
} else if (command === 'link') {
|
|
3252
3272
|
// Link: ask the roster's engine what two things could become together.
|
|
3253
3273
|
Promise.resolve(require('../commands/link').linkCommand(process.argv.slice(3)))
|