atris 3.31.0 → 3.32.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 +3 -3
- package/atris/CLAUDE.md +8 -0
- package/atris.md +8 -0
- package/ax +94 -4
- package/bin/atris.js +148 -73
- package/commands/autoland.js +70 -10
- package/commands/autopilot-front.js +273 -0
- package/commands/init.js +21 -0
- package/commands/member.js +32 -0
- package/commands/mission.js +386 -30
- package/commands/run-front.js +144 -0
- package/commands/run.js +9 -6
- package/commands/sign.js +90 -0
- package/commands/task.js +207 -6
- package/commands/truth.js +2 -1
- package/lib/autoland.js +133 -25
- package/lib/operator-next.js +7 -0
- package/lib/pulse.js +4 -3
- package/lib/runner-command.js +32 -13
- package/lib/task-db.js +19 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ atris
|
|
|
81
81
|
|
|
82
82
|
`atris init` scaffolds the workspace, including `atris/wiki/`. `atris` loads context and hands the workflow off to `atris/atris.md`.
|
|
83
83
|
|
|
84
|
-
If you're still shaping the idea, use `atris brainstorm`. If you want
|
|
84
|
+
If you're still shaping the idea, use `atris brainstorm`. If you want one bounded mission pursued to proof, use `atris run "<objective>"`. If you want Atris to keep picking the next mission or member until you say stop, use `atris autopilot`. If you want project memory checked for stale pages and missing context, use `atris loop`. `atris activate` surfaces wiki state from `atris/wiki/STATUS.md` when it exists.
|
|
85
85
|
|
|
86
86
|
Core loop: `plan` -> `do` -> `review`
|
|
87
87
|
|
|
@@ -186,14 +186,14 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
|
|
|
186
186
|
| `atris plan` | Create the plan/spec |
|
|
187
187
|
| `atris do` | Execute work |
|
|
188
188
|
| `atris review` | Validate work and capture learnings |
|
|
189
|
-
| `atris run` |
|
|
189
|
+
| `atris run` | One bounded mission pursuit: start or resume, tick, complete (`--legacy` for the old plan/do/review chain) |
|
|
190
190
|
| `atris run logs` | Browse glass run logs (phase reasoning persisted to disk) |
|
|
191
191
|
| `atris run prune-logs` | Prune old run logs, keeping only the most recent N |
|
|
192
192
|
| `atris run search` | Search phase reasoning across all run logs by keyword |
|
|
193
193
|
| `atris run stats` | Show run log stats: phase counts, avg durations |
|
|
194
194
|
| `atris run export` | Export all run logs as a JSON bundle |
|
|
195
195
|
| `atris run diff` | Compare two run logs side by side |
|
|
196
|
-
| `atris autopilot` |
|
|
196
|
+
| `atris autopilot` | Keep the workspace moving: mission/member legs until `atris autopilot stop` (`--legacy` for the old approval loop) |
|
|
197
197
|
| `atris pulse` | Install or run the durable overnight heartbeat |
|
|
198
198
|
| `atris log` | Add inbox items to today's journal |
|
|
199
199
|
| `atris now` | Show the current operating truth |
|
package/atris/CLAUDE.md
CHANGED
|
@@ -90,6 +90,14 @@ release/publish work; clean old merged worktrees with `atris worktree cleanup`.
|
|
|
90
90
|
|
|
91
91
|
If the task produces durable project knowledge, update `atris/wiki/` or run the local wiki flow (`atris ingest`, `atris query`, `atris lint`).
|
|
92
92
|
|
|
93
|
+
## How to Report
|
|
94
|
+
|
|
95
|
+
The human approves work by reading, so how you report IS the product.
|
|
96
|
+
|
|
97
|
+
- **Results are capabilities.** State what someone can do now that they couldn't before, then what it means for them or the business. Tests are one word ("verified"). Shape: "We did X, so you can now Y." Detail stays under the hood; the reader asks if they want more.
|
|
98
|
+
- **Three results, air between them, rest on ask.** A report fits one screen with no scrolling.
|
|
99
|
+
- **Stake first, then the move.** "Agents burn tokens hand-rolling parsers: add one shared view." Flags, ids, and identifiers belong in the body, never the headline.
|
|
100
|
+
|
|
93
101
|
## Rules (Non‑Negotiable)
|
|
94
102
|
|
|
95
103
|
- Plan = ASCII visualization + approval gate. Do not execute during planning.
|
package/atris.md
CHANGED
|
@@ -80,6 +80,14 @@ The same discipline for words. Output stays sharp no matter how bloated the cont
|
|
|
80
80
|
- **Cut filler.** Drop "it's worth noting", "in order to", "leverage", "seamless", "robust", "delve", stacked hedges, and em dashes. `atris slop` flags the prose tells (em-dash, hype-copy) too.
|
|
81
81
|
- **Bound verbosity by information, not context.** Say the load-bearing thing and stop. Length tracks what the reader needs to act, nothing more.
|
|
82
82
|
- **Match the register.** The operator wants the next move; a spec wants the contract; a journal wants one line. Jargon is a lever only when shared: use the reader's precise terms, define a new one once.
|
|
83
|
+
- **Specific AND workable.** Vocabulary carries both: precise enough that another agent can act on it, plain enough that the operator gets it on a phone. A sentence only one of those audiences can use is half-written.
|
|
84
|
+
- **Every queue item earns its surface.** Anything an operator might see (task title, mission objective, roadmap item, tick summary) carries its own why in the same sentence: what it costs to skip, or what it buys. "Agents burn tokens hand-rolling state parsers: add one shared inspect view" surfaces; "novel goal-chain demo" does not. Digests refuse raw titles that can't explain themselves.
|
|
85
|
+
- **Day-one PM test.** Every sentence that reaches an operator must make sense to a PM who joined the company this morning: no internal codenames, no problem-titles standing in for results. If they'd have to ask "what does that mean?", the sentence is unfinished. Detail stays one ask away, always.
|
|
86
|
+
- **Results are capabilities, not test counts.** When work lands, the sentence states what someone can do now that they couldn't before, then what it means for them or the business. Tests are one word ("verified"); the meaning is the sentence. Copy these shapes:
|
|
87
|
+
- "One word now runs the work: `atris autopilot` picks the next mission and keeps going until you say stop. Autonomy stopped being a setup and became a feature."
|
|
88
|
+
- "Missions survive everything: one engine starts the work, another picks it up cold, a third lands it. Work is no longer tied to a chat window, a session, or a vendor."
|
|
89
|
+
- "The system reports in plain English: one daily message with what landed, what waits on you, and who should own what's next. One person can supervise many projects."
|
|
90
|
+
- **Fit the screen.** An operator-facing report shows three results, air between them, and holds the rest on ask. No scrolling: reading it is one glance, and the reader asks for more if they want more.
|
|
83
91
|
|
|
84
92
|
`expected`: this is how an Atris agent writes and builds. Shipping slop or rambling is a failure smell, same as drift or a stale task.
|
|
85
93
|
|
package/ax
CHANGED
|
@@ -306,6 +306,20 @@ function formatRunProfile(profile, options = {}) {
|
|
|
306
306
|
return rows.map(([label, value]) => formatAuxRow(label, value, options)).join('\n');
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
// Auto-routed local turns must never die on a missing local backend — a
|
|
310
|
+
// consumer machine without the launchd service gets a degraded cloud chat
|
|
311
|
+
// turn, not ECONNREFUSED. Probed once per process (REPL lines reuse it).
|
|
312
|
+
// Explicit --local skips the downgrade so devs see the real error. Interim
|
|
313
|
+
// guard until the cloud relay executes workspace tools in-process and
|
|
314
|
+
// consumers stop needing a local backend at all.
|
|
315
|
+
let _localBackendUp = null;
|
|
316
|
+
async function localBackendReachable() {
|
|
317
|
+
if (_localBackendUp !== null) return _localBackendUp;
|
|
318
|
+
const res = await requestJson(ATRIS2_HEALTH_PATH, { timeoutMs: 1200, route: 'local' });
|
|
319
|
+
_localBackendUp = Boolean(res.ok || Number(res.status || 0) > 0);
|
|
320
|
+
return _localBackendUp;
|
|
321
|
+
}
|
|
322
|
+
|
|
309
323
|
async function buildRuntimeHealth(options = {}) {
|
|
310
324
|
const route = options.route === 'local' || options.forceLocal ? 'local' : 'cloud';
|
|
311
325
|
const healthRes = options.healthRes
|
|
@@ -621,9 +635,49 @@ function githubWorkspaceIntent(message) {
|
|
|
621
635
|
return /\b(push|commit|commits?|branch|branches|checkout|merge|rebase|tag|release|pr|pull request|pull-request|repo change|code change|small change)\b/i.test(text);
|
|
622
636
|
}
|
|
623
637
|
|
|
638
|
+
// Walk up from cwd looking for a workspace marker (.git or atris/ or atris.md).
|
|
639
|
+
// The process.cwd() answer is cached — resolveRoute runs per chat line — but an
|
|
640
|
+
// explicit startDir (callers pass their own cwd) is always checked fresh.
|
|
641
|
+
let _workspaceCwd = null;
|
|
642
|
+
function insideWorkspaceCwd(startDir) {
|
|
643
|
+
const explicit = typeof startDir === 'string' && startDir.length > 0;
|
|
644
|
+
if (!explicit && _workspaceCwd !== null) return _workspaceCwd;
|
|
645
|
+
let dir = explicit ? startDir : process.cwd();
|
|
646
|
+
let found = false;
|
|
647
|
+
for (let i = 0; i < 24; i += 1) {
|
|
648
|
+
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'atris.md')) || fs.existsSync(path.join(dir, 'atris'))) {
|
|
649
|
+
found = true;
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
const parent = path.dirname(dir);
|
|
653
|
+
if (parent === dir) break;
|
|
654
|
+
dir = parent;
|
|
655
|
+
}
|
|
656
|
+
if (!explicit) _workspaceCwd = found;
|
|
657
|
+
return found;
|
|
658
|
+
}
|
|
659
|
+
|
|
624
660
|
function resolveRoute(message, options = {}) {
|
|
625
661
|
if (options.route === 'local' || options.forceLocal) return 'local';
|
|
626
662
|
if (options.route === 'cloud' || options.forceCloud) return 'cloud';
|
|
663
|
+
// Workspace standard v2 (2026-07-02, supersedes intent routing): inside a
|
|
664
|
+
// workspace, tools are ALWAYS attached and the model decides whether to use
|
|
665
|
+
// them — the Claude Code / Codex shape. v1 guessed intent with regexes and
|
|
666
|
+
// lost three times in one day ("in scripts/fast-coach/tick.mjs, what does
|
|
667
|
+
// SLOW_MS control?", "what did Atris ship last night?", "find it lol" — all
|
|
668
|
+
// tool-less misses that told the user to paste files they were sitting on).
|
|
669
|
+
// Measured cost of always-tools: pure math 3s vs 2s; and "plan my morning"
|
|
670
|
+
// through the tool lane reads the real task board instead of guessing.
|
|
671
|
+
// Non-workspace cwds stay pure cloud (the privacy line); explicit flags
|
|
672
|
+
// always win. One carve-out: GitHub-connector phrasing prefers the cloud
|
|
673
|
+
// GitHub API lane unless it names local-checkout work.
|
|
674
|
+
const text = String(message || '');
|
|
675
|
+
// Local-checkout work includes every git mutation verb: cloud chat can only
|
|
676
|
+
// read the GitHub connector, so "do a quick github push" through the cloud
|
|
677
|
+
// lane dead-ends in a write-blocked upsell (live miss 2026-07-02). Inside a
|
|
678
|
+
// workspace the local tool loop just runs git.
|
|
679
|
+
if (/\bgithub\b/i.test(text) && !/\b(commits?|branch(?:es)?|checkout|merge|rebase|changes?|diff|push(?:es|ed|ing)?|pull(?:s|ed|ing)?|fetch|clone|stash|tags?)\b/i.test(text)) return 'cloud';
|
|
680
|
+
if (insideWorkspaceCwd(options.cwd)) return 'local';
|
|
627
681
|
return 'cloud';
|
|
628
682
|
}
|
|
629
683
|
|
|
@@ -1871,6 +1925,19 @@ function codeFastWorkspaceNotice(result) {
|
|
|
1871
1925
|
function handleEvent(event, state, output) {
|
|
1872
1926
|
if (!event || typeof event !== 'object') return;
|
|
1873
1927
|
state.events.push(event);
|
|
1928
|
+
// AX_TIMING=1: phase scoreboard to stderr: one line per SSE event with ms
|
|
1929
|
+
// since turn start and since the previous event, so latency work can see
|
|
1930
|
+
// WHERE a slow turn spends its time (model TTFT vs tool exec vs loop count).
|
|
1931
|
+
if (process.env.AX_TIMING === '1') {
|
|
1932
|
+
const now = Date.now();
|
|
1933
|
+
if (!state._t0) state._t0 = now;
|
|
1934
|
+
const kind = event.type || event.status || 'event';
|
|
1935
|
+
const detail = event.type === 'assistant_blocks' && Array.isArray(event.blocks)
|
|
1936
|
+
? event.blocks.map((b) => (b && b.type === 'tool_use' ? `tool:${b.tool}` : b && b.type)).join(',')
|
|
1937
|
+
: event.type === 'tool_results' ? `results:${(event.results || []).length}` : '';
|
|
1938
|
+
process.stderr.write(`[ax-timing] +${String(now - state._t0).padStart(6)}ms (gap ${String(now - (state._tPrev || now)).padStart(5)}ms) ${kind}${detail ? ' ' + detail : ''}\n`);
|
|
1939
|
+
state._tPrev = now;
|
|
1940
|
+
}
|
|
1874
1941
|
|
|
1875
1942
|
if (event.type === 'system_init') {
|
|
1876
1943
|
state.runtime = event;
|
|
@@ -1968,7 +2035,11 @@ function handleEvent(event, state, output) {
|
|
|
1968
2035
|
}
|
|
1969
2036
|
|
|
1970
2037
|
async function postTurn(message, options = {}) {
|
|
1971
|
-
|
|
2038
|
+
let route = options.business ? 'local' : resolveRoute(message, options);
|
|
2039
|
+
if (route === 'local' && !options.business && options.route !== 'local' && !options.forceLocal
|
|
2040
|
+
&& !(await localBackendReachable())) {
|
|
2041
|
+
route = 'cloud';
|
|
2042
|
+
}
|
|
1972
2043
|
const local = route !== 'cloud';
|
|
1973
2044
|
const endpointRoute = options.business ? 'cloud' : route;
|
|
1974
2045
|
const token = authToken();
|
|
@@ -1987,7 +2058,11 @@ async function postTurn(message, options = {}) {
|
|
|
1987
2058
|
// Relayed business turns wait on EC2 terminal calls (up to 60s each) with no
|
|
1988
2059
|
// SSE traffic in between, so the socket-idle timeout needs more headroom.
|
|
1989
2060
|
const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
|
|
1990
|
-
|
|
2061
|
+
let timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
|
|
2062
|
+
// Local workspace tool loops (max_turns 8/14) legitimately run past the fast
|
|
2063
|
+
// lane's 60s chat wall — SwapBench 2026-07-02: three tool-loop tasks died at
|
|
2064
|
+
// ~60s as "Atris cloud did not respond". Same headroom as business relays.
|
|
2065
|
+
if (local) timeoutMs = Math.max(timeoutMs, 180000);
|
|
1991
2066
|
const turnUrl = new URL(backendUrl({ route: endpointRoute }));
|
|
1992
2067
|
const transport = turnUrl.protocol === 'https:' ? https : http;
|
|
1993
2068
|
const state = {
|
|
@@ -2357,7 +2432,15 @@ async function chat(options = {}) {
|
|
|
2357
2432
|
const pendingTaskPreview = latestPendingTaskPreview(history);
|
|
2358
2433
|
const route = pendingTaskPreview ? 'cloud' : options.route;
|
|
2359
2434
|
maybeWriteAtrisGoalBanner(output, cwd);
|
|
2360
|
-
|
|
2435
|
+
let turnRoute = options.business ? 'cloud' : resolveRoute(trimmed, { route, cwd });
|
|
2436
|
+
// Same downgrade as postTurn: an auto-routed local turn with no local
|
|
2437
|
+
// backend listening becomes a cloud turn instead of a blocked preflight
|
|
2438
|
+
// that dumps localhost start instructions at a consumer. Explicit --local
|
|
2439
|
+
// keeps the real error.
|
|
2440
|
+
if (turnRoute === 'local' && route !== 'local' && !options.forceLocal) {
|
|
2441
|
+
const probeLocal = options.localBackendProbe || localBackendReachable;
|
|
2442
|
+
if (!(await probeLocal())) turnRoute = 'cloud';
|
|
2443
|
+
}
|
|
2361
2444
|
if (!(await ensureRuntimeReady(turnRoute))) return false;
|
|
2362
2445
|
const turnFunction = options.turnFunction || turnFunctionForMode(mode);
|
|
2363
2446
|
const turnId = options.turnId || createTurnId();
|
|
@@ -3311,7 +3394,14 @@ async function main() {
|
|
|
3311
3394
|
console.log(formatDoneLine(result.durationMs, creditsFromState(result)));
|
|
3312
3395
|
} catch (error) {
|
|
3313
3396
|
console.error(`x ${error.message}`);
|
|
3314
|
-
|
|
3397
|
+
// Only name a lane when the user picked one. An auto-routed turn that
|
|
3398
|
+
// failed locally must not claim "Atris cloud did not respond" (seen live
|
|
3399
|
+
// 2026-07-02 after a local workspace-root rejection).
|
|
3400
|
+
if (route === 'auto') {
|
|
3401
|
+
console.error('\nRetry, or run ax --doctor to check the runtime.');
|
|
3402
|
+
} else {
|
|
3403
|
+
printBackendHint({ route });
|
|
3404
|
+
}
|
|
3315
3405
|
process.exit(1);
|
|
3316
3406
|
}
|
|
3317
3407
|
}
|
package/bin/atris.js
CHANGED
|
@@ -128,7 +128,16 @@ function isOptionValue(args, index, optionNames) {
|
|
|
128
128
|
|
|
129
129
|
function applyRunnerFlags(args) {
|
|
130
130
|
const runnerProfile = readOptionArg(args, '--runner-profile');
|
|
131
|
-
if (runnerProfile)
|
|
131
|
+
if (runnerProfile) {
|
|
132
|
+
// Fail fast at the CLI boundary: an unknown profile otherwise stays silent
|
|
133
|
+
// until a heartbeat spawn resolves it mid-loop (silent overnight outage).
|
|
134
|
+
const { RUNNER_PROFILES, RUNNER_PROFILE_NAMES } = require('../lib/runner-command');
|
|
135
|
+
if (!Object.prototype.hasOwnProperty.call(RUNNER_PROFILES, runnerProfile)) {
|
|
136
|
+
console.error(`Unknown --runner-profile "${runnerProfile}". Known profiles: ${RUNNER_PROFILE_NAMES.join(', ')}.`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
process.env.ATRIS_RUNNER_PROFILE = runnerProfile;
|
|
140
|
+
}
|
|
132
141
|
const runnerBin = readOptionArg(args, '--runner-bin');
|
|
133
142
|
if (runnerBin) {
|
|
134
143
|
process.env.ATRIS_RUNNER_BIN = runnerBin;
|
|
@@ -449,7 +458,7 @@ function showHelp() {
|
|
|
449
458
|
console.log(' plan - Create build spec with visualization');
|
|
450
459
|
console.log(' do - Execute tasks');
|
|
451
460
|
console.log(' review - Validate work (tests, safety checks, docs)');
|
|
452
|
-
console.log(' run -
|
|
461
|
+
console.log(' run - One bounded mission pursuit: start or resume, tick, complete');
|
|
453
462
|
console.log(' run logs - Browse glass run logs (phase reasoning persisted to disk)');
|
|
454
463
|
console.log(' run search - Search phase reasoning across all run logs');
|
|
455
464
|
console.log(' pulse - Durable overnight self-improvement heartbeat (OS cron, install/status/tick)');
|
|
@@ -483,11 +492,12 @@ function showHelp() {
|
|
|
483
492
|
console.log('');
|
|
484
493
|
console.log('Optional helpers:');
|
|
485
494
|
console.log(' brainstorm - Explore ideas conversationally before planning');
|
|
486
|
-
console.log(' autopilot -
|
|
495
|
+
console.log(' autopilot - Keep the workspace moving: mission/member loop until you stop it');
|
|
487
496
|
console.log(' improve - Run one paid RL tick (POST /api/improve, deducts credits)');
|
|
488
497
|
console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
|
|
489
498
|
console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
|
|
490
499
|
console.log(' autoland - Approve the policy once; certified work lands itself, you keep irreversible calls');
|
|
500
|
+
console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
|
|
491
501
|
console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
|
|
492
502
|
console.log(' youtube - Process YouTube videos with timestamped transcript-first analysis');
|
|
493
503
|
console.log('');
|
|
@@ -547,7 +557,7 @@ function showHelp() {
|
|
|
547
557
|
console.log(' soul - Show, snapshot, or fork workspace identity');
|
|
548
558
|
console.log(' fleet - Inspect local fleet status');
|
|
549
559
|
console.log(' agent - Select cloud agent, spawn worker requests, or run `agent doctor`');
|
|
550
|
-
console.log(' chat - Chat with
|
|
560
|
+
console.log(' chat - Chat with Atris 2 Fast in this workspace (--agent for cloud agent; or: atris chat scan)');
|
|
551
561
|
console.log(' fast - Chat with Atris2 Fast');
|
|
552
562
|
console.log(' login - Sign in or add another account');
|
|
553
563
|
console.log(' logout - Sign out of current account');
|
|
@@ -885,7 +895,7 @@ function showAutopilotHelp() {
|
|
|
885
895
|
console.log(' --runner-bin PATH Runner binary for this run');
|
|
886
896
|
console.log(' --runner-template CMD Runner command template for this run');
|
|
887
897
|
console.log(' --runner-model MODEL Runner model for this run');
|
|
888
|
-
console.log(
|
|
898
|
+
console.log(` --runner-profile NAME Runner profile for this run (one of: ${require('../lib/runner-command').RUNNER_PROFILE_NAMES.join(', ')})`);
|
|
889
899
|
console.log('');
|
|
890
900
|
console.log('Examples:');
|
|
891
901
|
console.log(' atris autopilot # Suggest from existing work');
|
|
@@ -927,7 +937,7 @@ const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'stat
|
|
|
927
937
|
'clean', 'harvest', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
|
|
928
938
|
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'land', 'autoland', 'aeo', 'slop', 'strings', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'moves',
|
|
929
939
|
'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
|
|
930
|
-
'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile', 'spaceship', 'truth'];
|
|
940
|
+
'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile', 'spaceship', 'truth', 'sign'];
|
|
931
941
|
|
|
932
942
|
// Check if command is an atris.md spec file - triggers welcome visualization
|
|
933
943
|
function isSpecFile(cmd) {
|
|
@@ -1831,23 +1841,26 @@ if (command === 'init') {
|
|
|
1831
1841
|
}
|
|
1832
1842
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
1833
1843
|
console.log('');
|
|
1834
|
-
console.log('Usage: atris run [options]');
|
|
1844
|
+
console.log('Usage: atris run ["objective"] [options]');
|
|
1835
1845
|
console.log('');
|
|
1836
|
-
console.log('
|
|
1837
|
-
console.log('
|
|
1846
|
+
console.log('One bounded mission pursuit: start a mission from the objective');
|
|
1847
|
+
console.log('(or resume the most logical runnable mission), tick it through the');
|
|
1848
|
+
console.log('mission runtime, complete on pass, then exit.');
|
|
1849
|
+
console.log('For an ongoing loop, use: atris autopilot');
|
|
1838
1850
|
console.log('');
|
|
1839
1851
|
console.log('Options:');
|
|
1840
|
-
console.log(' --
|
|
1841
|
-
console.log(' --
|
|
1842
|
-
console.log(' --
|
|
1843
|
-
console.log(' --
|
|
1844
|
-
console.log(' --
|
|
1845
|
-
console.log(' --
|
|
1846
|
-
console.log(' --
|
|
1847
|
-
console.log('
|
|
1848
|
-
console.log('
|
|
1849
|
-
console.log(' --
|
|
1850
|
-
console.log(' --
|
|
1852
|
+
console.log(' --owner NAME Mission owner (default: mission-lead)');
|
|
1853
|
+
console.log(' --minutes N | --hours N Time budget for the pursuit');
|
|
1854
|
+
console.log(' --max-ticks N Override the tick budget');
|
|
1855
|
+
console.log(' --max-wall N Override the wall clock in seconds');
|
|
1856
|
+
console.log(' --cadence C Mission cadence (e.g. 15m)');
|
|
1857
|
+
console.log(' --no-complete Skip auto-complete after a passing run');
|
|
1858
|
+
console.log(' --legacy Old plan→do→review loop (claude -p cycles)');
|
|
1859
|
+
console.log('');
|
|
1860
|
+
console.log('Legacy options (with --legacy):');
|
|
1861
|
+
console.log(' --cycles=N --once --verbose --dry-run --timeout=N --no-push');
|
|
1862
|
+
console.log(' --runner-bin PATH / --runner-template CMD / --runner-model MODEL');
|
|
1863
|
+
console.log(` --runner-profile NAME Runner profile for this run (one of: ${require('../lib/runner-command').RUNNER_PROFILE_NAMES.join(', ')})`);
|
|
1851
1864
|
console.log('');
|
|
1852
1865
|
console.log('Subcommands:');
|
|
1853
1866
|
console.log(' atris run logs [--tail N] [--cat FILE] [--json] Browse glass run logs');
|
|
@@ -1860,62 +1873,81 @@ if (command === 'init') {
|
|
|
1860
1873
|
process.exit(0);
|
|
1861
1874
|
}
|
|
1862
1875
|
|
|
1863
|
-
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
1864
|
-
const dryRun = args.includes('--dry-run');
|
|
1865
|
-
const once = args.includes('--once');
|
|
1866
|
-
const push = !args.includes('--no-push');
|
|
1867
1876
|
applyRunnerFlags(args);
|
|
1868
|
-
const cyclesArg = args.find(a => a.startsWith('--cycles='));
|
|
1869
|
-
const maxCycles = cyclesArg ? parseInt(cyclesArg.split('=')[1]) : 5;
|
|
1870
|
-
const timeoutArg = args.find(a => a.startsWith('--timeout='));
|
|
1871
|
-
const timeout = timeoutArg ? parseInt(timeoutArg.split('=')[1]) * 1000 : undefined;
|
|
1872
1877
|
|
|
1873
|
-
|
|
1874
|
-
.
|
|
1875
|
-
.
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1878
|
+
if (args.includes('--legacy')) {
|
|
1879
|
+
const legacyArgs = args.filter(a => a !== '--legacy');
|
|
1880
|
+
const verbose = legacyArgs.includes('--verbose') || legacyArgs.includes('-v');
|
|
1881
|
+
const dryRun = legacyArgs.includes('--dry-run');
|
|
1882
|
+
const once = legacyArgs.includes('--once');
|
|
1883
|
+
const push = !legacyArgs.includes('--no-push');
|
|
1884
|
+
const cyclesArg = legacyArgs.find(a => a.startsWith('--cycles='));
|
|
1885
|
+
const maxCycles = cyclesArg ? parseInt(cyclesArg.split('=')[1]) : 5;
|
|
1886
|
+
const timeoutArg = legacyArgs.find(a => a.startsWith('--timeout='));
|
|
1887
|
+
const timeout = timeoutArg ? parseInt(timeoutArg.split('=')[1]) * 1000 : undefined;
|
|
1888
|
+
|
|
1889
|
+
require('../commands/run').runAtris({ maxCycles, verbose, dryRun, once, push, timeout })
|
|
1890
|
+
.then(() => process.exit(0))
|
|
1891
|
+
.catch((error) => {
|
|
1892
|
+
console.error(`\u2717 Run failed: ${error.message || error}`);
|
|
1893
|
+
process.exit(1);
|
|
1894
|
+
});
|
|
1895
|
+
} else {
|
|
1896
|
+
require('../commands/run-front').runMissionFront(args)
|
|
1897
|
+
.then((code) => process.exit(code || 0))
|
|
1898
|
+
.catch((error) => {
|
|
1899
|
+
console.error(`\u2717 Run failed: ${error.message || error}`);
|
|
1900
|
+
process.exit(1);
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1879
1903
|
} else if (command === 'launchpad') {
|
|
1880
1904
|
const code = require('../commands/launchpad').launchpadCommand(process.argv.slice(3));
|
|
1881
1905
|
process.exit(code);
|
|
1882
1906
|
} else if (command === 'autopilot') {
|
|
1883
1907
|
const args = process.argv.slice(3);
|
|
1884
|
-
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
1885
|
-
showAutopilotHelp();
|
|
1886
|
-
process.exit(0);
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
// Parse options
|
|
1890
|
-
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
1891
|
-
const dryRun = args.includes('--dry-run');
|
|
1892
|
-
const auto = args.includes('--auto');
|
|
1893
1908
|
applyRunnerFlags(args);
|
|
1894
|
-
const maxIterationsArg = args.find(a => a.startsWith('--iterations='));
|
|
1895
|
-
const maxIterations = maxIterationsArg ? parseInt(maxIterationsArg.split('=')[1]) : undefined;
|
|
1896
|
-
const durationArg = args.find(a => a.startsWith('--duration='));
|
|
1897
|
-
const duration = durationArg ? durationArg.split('=')[1] : null;
|
|
1898
|
-
|
|
1899
|
-
// Get description (non-flag args)
|
|
1900
|
-
const description = args.filter((a, i) => !a.startsWith('-') && !isOptionValue(args, i, RUNNER_FLAG_NAMES)).join(' ').trim() || null;
|
|
1901
|
-
|
|
1902
|
-
const options = {
|
|
1903
|
-
...(maxIterations !== undefined && { maxIterations }),
|
|
1904
|
-
verbose,
|
|
1905
|
-
dryRun,
|
|
1906
|
-
auto,
|
|
1907
|
-
duration
|
|
1908
|
-
};
|
|
1909
1909
|
|
|
1910
|
-
|
|
1911
|
-
|
|
1910
|
+
if (args.includes('--legacy')) {
|
|
1911
|
+
const legacyArgs = args.filter(a => a !== '--legacy');
|
|
1912
|
+
if (legacyArgs.includes('--help') || legacyArgs.includes('-h') || legacyArgs[0] === 'help') {
|
|
1913
|
+
showAutopilotHelp();
|
|
1914
|
+
process.exit(0);
|
|
1915
|
+
}
|
|
1912
1916
|
|
|
1913
|
-
|
|
1914
|
-
.
|
|
1915
|
-
.
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1917
|
+
// Parse options
|
|
1918
|
+
const verbose = legacyArgs.includes('--verbose') || legacyArgs.includes('-v');
|
|
1919
|
+
const dryRun = legacyArgs.includes('--dry-run');
|
|
1920
|
+
const auto = legacyArgs.includes('--auto');
|
|
1921
|
+
const maxIterationsArg = legacyArgs.find(a => a.startsWith('--iterations='));
|
|
1922
|
+
const maxIterations = maxIterationsArg ? parseInt(maxIterationsArg.split('=')[1]) : undefined;
|
|
1923
|
+
const durationArg = legacyArgs.find(a => a.startsWith('--duration='));
|
|
1924
|
+
const duration = durationArg ? durationArg.split('=')[1] : null;
|
|
1925
|
+
|
|
1926
|
+
// Get description (non-flag args)
|
|
1927
|
+
const description = legacyArgs.filter((a, i) => !a.startsWith('-') && !isOptionValue(legacyArgs, i, RUNNER_FLAG_NAMES)).join(' ').trim() || null;
|
|
1928
|
+
|
|
1929
|
+
const options = {
|
|
1930
|
+
...(maxIterations !== undefined && { maxIterations }),
|
|
1931
|
+
verbose,
|
|
1932
|
+
dryRun,
|
|
1933
|
+
auto,
|
|
1934
|
+
duration
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
require('../commands/autopilot').autopilotAtris(description, options)
|
|
1938
|
+
.then(() => process.exit(0))
|
|
1939
|
+
.catch((error) => {
|
|
1940
|
+
console.error(`✗ Autopilot failed: ${error.message || error}`);
|
|
1941
|
+
process.exit(1);
|
|
1942
|
+
});
|
|
1943
|
+
} else {
|
|
1944
|
+
Promise.resolve(require('../commands/autopilot-front').autopilotFront(args))
|
|
1945
|
+
.then((code) => process.exit(code || 0))
|
|
1946
|
+
.catch((error) => {
|
|
1947
|
+
console.error(`✗ Autopilot failed: ${error.message || error}`);
|
|
1948
|
+
process.exit(1);
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1919
1951
|
} else if (command === 'brainstorm') {
|
|
1920
1952
|
require('../commands/brainstorm').brainstormAtris()
|
|
1921
1953
|
.then(() => process.exit(0))
|
|
@@ -2240,6 +2272,10 @@ if (command === 'init') {
|
|
|
2240
2272
|
Promise.resolve(require('../commands/compile').compileCommand(subcommand, ...args))
|
|
2241
2273
|
.then(() => process.exit(process.exitCode || 0))
|
|
2242
2274
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2275
|
+
} else if (command === 'sign') {
|
|
2276
|
+
// Sign: prepare-commit-msg hook that credits Atris as co-author on commits in atris workspaces.
|
|
2277
|
+
try { process.exit(require('../commands/sign').signCommand(process.argv[3]) || 0); }
|
|
2278
|
+
catch (err) { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); }
|
|
2243
2279
|
} else if (command === 'slop') {
|
|
2244
2280
|
// Slop: deterministic frontend-slop detector (no LLM). Exit 1 = slop found, for CI + the autopilot gate.
|
|
2245
2281
|
Promise.resolve(require('../commands/slop').slopCommand(process.argv.slice(3)))
|
|
@@ -2638,18 +2674,21 @@ async function agentAtris() {
|
|
|
2638
2674
|
|
|
2639
2675
|
|
|
2640
2676
|
async function chatAtris() {
|
|
2641
|
-
// Get message from command line args
|
|
2642
|
-
const
|
|
2677
|
+
// Get message from command line args; --agent forces the legacy cloud-agent lane
|
|
2678
|
+
const rawArgs = process.argv.slice(3);
|
|
2679
|
+
const agentLane = rawArgs.includes('--agent');
|
|
2680
|
+
const message = rawArgs.filter(arg => arg !== '--agent').join(' ').trim();
|
|
2643
2681
|
|
|
2644
2682
|
// Respect -h / --help before any auth/state checks
|
|
2645
2683
|
if (message === '-h' || message === '--help' || message === 'help') {
|
|
2646
2684
|
console.log('Usage: atris chat ["message"]');
|
|
2647
2685
|
console.log('');
|
|
2648
|
-
console.log('
|
|
2649
|
-
console.log(' Requires `atris login
|
|
2686
|
+
console.log(' Chat with Atris 2 Fast in this workspace: tools attached, same turn as `ax --fast`.');
|
|
2687
|
+
console.log(' Requires `atris login`.');
|
|
2650
2688
|
console.log('');
|
|
2651
|
-
console.log(' atris chat Interactive
|
|
2652
|
-
console.log(' atris chat "what now?" One-shot message');
|
|
2689
|
+
console.log(' atris chat Interactive chat (ax --fast --chat)');
|
|
2690
|
+
console.log(' atris chat "what now?" One-shot message (ax --fast)');
|
|
2691
|
+
console.log(' atris chat --agent [...] Legacy cloud-agent lane (needs `atris agent`)');
|
|
2653
2692
|
process.exit(0);
|
|
2654
2693
|
}
|
|
2655
2694
|
|
|
@@ -2665,6 +2704,22 @@ async function chatAtris() {
|
|
|
2665
2704
|
process.exit(await runLocalFastMission(missionIntent));
|
|
2666
2705
|
}
|
|
2667
2706
|
|
|
2707
|
+
// Workspace standard v2: `atris chat` is the same turn as `ax --fast`,
|
|
2708
|
+
// Atris 2 Fast with tools attached, ax owning routing (local tool loop,
|
|
2709
|
+
// cloud fallback when no backend listens). The pro-chat cloud-agent lane
|
|
2710
|
+
// survives behind --agent only; it 404s the moment a saved agent is
|
|
2711
|
+
// deleted server-side and cannot see the workspace.
|
|
2712
|
+
if (!agentLane) {
|
|
2713
|
+
try {
|
|
2714
|
+
const axPath = path.join(__dirname, '..', 'ax');
|
|
2715
|
+
const axArgs = message ? ['--fast', message] : ['--fast', '--chat'];
|
|
2716
|
+
const run = spawnSync(process.execPath, [axPath, ...axArgs], { stdio: 'inherit' });
|
|
2717
|
+
process.exit(run.status || 0);
|
|
2718
|
+
} catch {
|
|
2719
|
+
// ax unavailable: fall through to the agent lane.
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2668
2723
|
printAtrisGoalBanner(process.cwd());
|
|
2669
2724
|
|
|
2670
2725
|
// Check agent selected
|
|
@@ -2714,7 +2769,13 @@ async function chatOnce(config, credentials, message) {
|
|
|
2714
2769
|
await streamProChat(endpoint, credentials.token, body);
|
|
2715
2770
|
console.log('\n\n✓ Complete\n');
|
|
2716
2771
|
} catch (error) {
|
|
2717
|
-
|
|
2772
|
+
const detail = String(error.message || error);
|
|
2773
|
+
if (/404/.test(detail) && /agent not found/i.test(detail)) {
|
|
2774
|
+
console.error(`\n✗ Error: Agent "${config.agent_name || agentId}" no longer exists on the server.`);
|
|
2775
|
+
console.error(' Run "atris agent" to pick a new one, or drop --agent to use the fast workspace lane.');
|
|
2776
|
+
} else {
|
|
2777
|
+
console.error(`\n✗ Error: ${detail}`);
|
|
2778
|
+
}
|
|
2718
2779
|
process.exit(1);
|
|
2719
2780
|
}
|
|
2720
2781
|
}
|
|
@@ -2914,6 +2975,20 @@ async function atrisFastChat() {
|
|
|
2914
2975
|
process.exit(await runLocalFastMission(missionIntent));
|
|
2915
2976
|
}
|
|
2916
2977
|
|
|
2978
|
+
// One routing brain: `atris fast` defers to ax's AX Context Standard. A
|
|
2979
|
+
// workspace-shaped question asked from inside a workspace needs local tools
|
|
2980
|
+
// (this lane is a tool-less cloud one-shot and confabulates on repo
|
|
2981
|
+
// questions — SwapBench 2026-07-02), so delegate the whole turn to ax.
|
|
2982
|
+
try {
|
|
2983
|
+
const axModule = require('../ax');
|
|
2984
|
+
if (axModule.resolveRoute(message) === 'local') {
|
|
2985
|
+
const run = spawnSync(process.execPath, [path.join(__dirname, '..', 'ax'), '--fast', message], { stdio: 'inherit' });
|
|
2986
|
+
process.exit(run.status || 0);
|
|
2987
|
+
}
|
|
2988
|
+
} catch {
|
|
2989
|
+
// ax unavailable: fall through to the plain cloud one-shot.
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2917
2992
|
printAtrisGoalBanner(process.cwd());
|
|
2918
2993
|
|
|
2919
2994
|
const ensured = await ensureValidCredentials();
|