atris 3.37.1 → 3.40.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 +25 -6
- package/README.md +2 -0
- package/atris/PERSONA.md +11 -11
- package/atris.md +17 -2
- package/ax +160 -34
- package/bin/atris.js +78 -6
- package/commands/activate.js +16 -3
- package/commands/agent-spawn.js +13 -18
- package/commands/autoland.js +76 -47
- package/commands/autopilot-front.js +0 -4
- package/commands/autopilot.js +0 -12
- package/commands/bench.js +10 -12
- package/commands/brain.js +0 -7
- package/commands/brief.js +0 -2
- package/commands/business-sync.js +0 -1
- package/commands/business.js +452 -12
- package/commands/chat-scan.js +5 -7
- package/commands/clean.js +31 -2
- package/commands/close.js +46 -11
- package/commands/cloud.js +0 -1
- package/commands/codex-goal.js +8 -12
- package/commands/computer.js +6 -5
- package/commands/console.js +64 -11
- package/commands/decide.js +166 -0
- package/commands/deck.js +30 -12
- package/commands/dream.js +0 -7
- package/commands/drill.js +14 -25
- package/commands/engine.js +206 -26
- package/commands/experiments.js +0 -4
- package/commands/fleet-report.js +12 -4
- package/commands/game.js +261 -0
- package/commands/gm.js +17 -7
- package/commands/goal.js +0 -6
- package/commands/harvest.js +1 -5
- package/commands/improve.js +29 -8
- package/commands/init.js +41 -37
- package/commands/integrations.js +0 -7
- package/commands/land.js +77 -20
- package/commands/launchpad.js +1 -16
- package/commands/lesson.js +17 -7
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/loop-front.js +0 -2
- package/commands/loops.js +6 -5
- package/commands/meet.js +0 -2
- package/commands/member.js +622 -573
- package/commands/mission.js +790 -314
- package/commands/next.js +0 -1
- package/commands/now.js +0 -12
- package/commands/one-lap.js +8 -8
- package/commands/orb.js +0 -4
- package/commands/pack-craft.js +0 -1
- package/commands/pack.js +3599 -424
- package/commands/play.js +6 -6
- package/commands/probe.js +12 -6
- package/commands/pulse.js +88 -64
- package/commands/push.js +0 -4
- package/commands/radar.js +14 -11
- package/commands/recover.js +340 -0
- package/commands/release.js +10 -9
- package/commands/report.js +0 -1
- package/commands/router.js +320 -0
- package/commands/run-front.js +0 -2
- package/commands/search.js +0 -3
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/skill.js +7 -2
- package/commands/slop.js +237 -7
- package/commands/status.js +6 -0
- package/commands/stream.js +4 -20
- package/commands/study.js +0 -3
- package/commands/sync-checkout.js +51 -0
- package/commands/sync.js +11 -40
- package/commands/task.js +999 -595
- package/commands/taste.js +101 -0
- package/commands/teach.js +86 -0
- package/commands/team.js +83 -3
- package/commands/unknowns.js +0 -10
- package/commands/update.js +0 -1
- package/commands/vercel.js +4 -2
- package/commands/visualize.js +0 -5
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -28
- package/commands/wiki.js +42 -15
- package/commands/wish.js +0 -1
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +88 -28
- package/commands/xp.js +28 -30
- package/commands/youtube.js +0 -2
- package/lib/accept-verify-gate.js +126 -0
- package/lib/activity-stream.js +0 -2
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +261 -3
- package/lib/autoland.js +66 -3
- package/lib/ax-auto-lane.js +68 -7
- package/lib/bench/context.js +0 -2
- package/lib/bench/report.js +0 -1
- package/lib/bench/runner.js +18 -11
- package/lib/brief-ledger.js +0 -6
- package/lib/business-simulate.js +382 -0
- package/lib/chat-log-scan.js +0 -5
- package/lib/checkout-sync.js +52 -0
- package/lib/claude-boot-block.js +48 -0
- package/lib/cloud-mission.js +0 -6
- package/lib/codex-flight.js +0 -1
- package/lib/conductor-artifacts.js +25 -0
- package/lib/context-gatherer.js +7 -2
- package/lib/deck-compose.js +0 -1
- package/lib/deck-history.js +0 -1
- package/lib/deck-layout.js +0 -5
- package/lib/deck-review.js +0 -7
- package/lib/deck-schema.js +0 -1
- package/lib/default-verifier.js +0 -2
- package/lib/engine-registry.js +157 -28
- package/lib/experiments/daily.js +0 -6
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +275 -42
- package/lib/functional-owner.js +0 -3
- package/lib/git-spawn.js +15 -0
- package/lib/inspect-fields.js +0 -1
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/learnings.js +0 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +11 -19
- package/lib/manifest.js +0 -2
- package/lib/member-alive.js +0 -1
- package/lib/member-switches.js +0 -5
- package/lib/mission-artifact.js +0 -1
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +296 -0
- package/lib/mission-room.js +0 -2
- package/lib/mission-runtime-loop.js +0 -1
- package/lib/next-moves.js +0 -18
- package/lib/official-cli-integration.js +47 -6
- package/lib/orb-context.js +14 -4
- package/lib/orb-scorecard.js +0 -1
- package/lib/outbound-send-gate.js +0 -6
- package/lib/pack-capabilities.js +685 -0
- package/lib/permission-grants.js +0 -6
- package/lib/policy-lessons.js +0 -2
- package/lib/pulse.js +68 -15
- package/lib/review-integrity.js +0 -2
- package/lib/router-brain.js +51 -3
- package/lib/runner-command.js +0 -10
- package/lib/runtime-bootstrap.js +0 -1
- package/lib/scorecard.js +0 -1
- package/lib/self-drive.js +45 -14
- package/lib/state-detection.js +0 -6
- package/lib/task-db.js +137 -7
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/team-presence.js +0 -1
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +129 -2
- package/lib/wiki.js +152 -9
- package/lib/wish-audit.js +21 -7
- package/lib/wish-delegate.js +27 -21
- package/lib/wish-stats.js +0 -5
- package/lib/wish-store.js +0 -1
- package/lib/workspace-safety.js +0 -2
- package/lib/zip.js +95 -8
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/deck.js
CHANGED
|
@@ -17,6 +17,7 @@ const fs = require('fs');
|
|
|
17
17
|
const https = require('https');
|
|
18
18
|
const os = require('os');
|
|
19
19
|
const path = require('path');
|
|
20
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
20
21
|
const { buildDeck, THEMES, notesRequests } = require('../lib/slides-deck');
|
|
21
22
|
const {
|
|
22
23
|
lintSpec,
|
|
@@ -118,10 +119,6 @@ function flag(argv, name) {
|
|
|
118
119
|
return i !== -1 ? argv[i + 1] : null;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
function hasFlag(argv, name) {
|
|
122
|
-
return argv.includes(name);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
122
|
// Flags that take a value, so their following token is consumed (not a path).
|
|
126
123
|
const VALUE_FLAGS = new Set(['--theme', '--title', '--update', '--out', '--style', '--url', '--md']);
|
|
127
124
|
|
|
@@ -141,6 +138,23 @@ function readSpec(specPath) {
|
|
|
141
138
|
return JSON.parse(fs.readFileSync(specPath, 'utf8'));
|
|
142
139
|
}
|
|
143
140
|
|
|
141
|
+
// Pluralize a count + noun ("1 error", "2 errors").
|
|
142
|
+
function countLabel(n, word) {
|
|
143
|
+
return `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// True when the args carry an explicit help flag.
|
|
147
|
+
function wantsHelp(argv) {
|
|
148
|
+
return argv.includes('--help') || argv.includes('-h');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// One-line lint tally with correct singular/plural nouns.
|
|
152
|
+
function lintSummary(findings) {
|
|
153
|
+
const errs = findings.filter((f) => f.severity === 'error').length;
|
|
154
|
+
const warns = findings.filter((f) => f.severity === 'warn').length;
|
|
155
|
+
return `${countLabel(errs, 'error')}, ${countLabel(warns, 'warning')}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
144
158
|
function printLint(findings) {
|
|
145
159
|
if (!findings.length) {
|
|
146
160
|
console.log('\n ✓ spec lint clean\n');
|
|
@@ -369,15 +383,13 @@ async function run(argv) {
|
|
|
369
383
|
const findings = checkSpec(spec, lintSpec);
|
|
370
384
|
if (out) {
|
|
371
385
|
fs.writeFileSync(out, `${JSON.stringify(spec, null, 2)}\n`);
|
|
372
|
-
console.error(`\n ✓ composed ${spec.slides.length}
|
|
386
|
+
console.error(`\n ✓ composed ${countLabel(spec.slides.length, 'slide')} (${spec.theme}) -> ${out}`);
|
|
373
387
|
printLint(findings);
|
|
374
388
|
console.error(` next: atris deck build ${out} --review\n`);
|
|
375
389
|
} else {
|
|
376
390
|
// stdout stays clean JSON for piping; lint goes to stderr
|
|
377
391
|
console.log(JSON.stringify(spec, null, 2));
|
|
378
|
-
|
|
379
|
-
const warns = findings.filter((f) => f.severity === 'warn').length;
|
|
380
|
-
console.error(` composed ${spec.slides.length} slides (${spec.theme}) · lint: ${errs} errors, ${warns} warnings`);
|
|
392
|
+
console.error(` composed ${countLabel(spec.slides.length, 'slide')} (${spec.theme}) · lint: ${lintSummary(findings)}`);
|
|
381
393
|
}
|
|
382
394
|
return hasErrors(findings) ? 1 : 0;
|
|
383
395
|
}
|
|
@@ -385,8 +397,11 @@ async function run(argv) {
|
|
|
385
397
|
if (sub === 'lint') {
|
|
386
398
|
const specPath = argv.slice(1).find((a) => !a.startsWith('-'));
|
|
387
399
|
if (!specPath) {
|
|
388
|
-
|
|
389
|
-
|
|
400
|
+
// Explicit -h/--help is a help request (stdout, exit 0); a genuinely
|
|
401
|
+
// missing spec path is a usage error (stderr, exit 2).
|
|
402
|
+
const help = wantsHelp(argv);
|
|
403
|
+
console[help ? 'log' : 'error'](' usage: atris deck lint <spec.json>');
|
|
404
|
+
return help ? 0 : 2;
|
|
390
405
|
}
|
|
391
406
|
let spec;
|
|
392
407
|
try { spec = readSpec(specPath); }
|
|
@@ -488,8 +503,9 @@ async function run(argv) {
|
|
|
488
503
|
}
|
|
489
504
|
const specPath = specPaths[0];
|
|
490
505
|
if (!specPath) {
|
|
491
|
-
|
|
492
|
-
|
|
506
|
+
const help = wantsHelp(argv);
|
|
507
|
+
console[help ? 'log' : 'error'](' usage: atris deck build <spec.json> [<spec2.json> ...] [--title T] [--theme x] [--review]');
|
|
508
|
+
return help ? 0 : 2;
|
|
493
509
|
}
|
|
494
510
|
let spec;
|
|
495
511
|
try { spec = readSpec(specPath); }
|
|
@@ -576,6 +592,8 @@ module.exports = {
|
|
|
576
592
|
lintSpec,
|
|
577
593
|
validateSpec,
|
|
578
594
|
checkSpec,
|
|
595
|
+
countLabel,
|
|
596
|
+
lintSummary,
|
|
579
597
|
planBatch,
|
|
580
598
|
publishDeck,
|
|
581
599
|
videoToDeck,
|
package/commands/dream.js
CHANGED
|
@@ -353,13 +353,6 @@ async function dreamCommand(args = [], root = process.cwd(), options = {}) {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
module.exports = {
|
|
356
|
-
DREAM_TIMEOUT_MS,
|
|
357
356
|
dreamsPath,
|
|
358
|
-
collectDreamMaterial,
|
|
359
|
-
buildDreamPrompt,
|
|
360
|
-
parseDreamCards,
|
|
361
|
-
dreamModels,
|
|
362
|
-
defaultDreamRunner,
|
|
363
|
-
runDream,
|
|
364
357
|
dreamCommand,
|
|
365
358
|
};
|
package/commands/drill.js
CHANGED
|
@@ -3,22 +3,12 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const {
|
|
6
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
7
|
+
const { runGit } = require('../lib/git-spawn');
|
|
7
8
|
|
|
8
9
|
const VERIFY_COMMAND = 'node -e "process.exit(0)"';
|
|
9
10
|
const COAUTHOR = 'Co-authored-by: Atris <299057014+atris-builder[bot]@users.noreply.github.com>';
|
|
10
11
|
|
|
11
|
-
function hasFlag(args, name) {
|
|
12
|
-
return args.includes(name);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function runGit(args, cwd, options = {}) {
|
|
16
|
-
const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
17
|
-
if (options.check === false || result.status === 0) return result;
|
|
18
|
-
const output = (result.stderr || result.stdout || `git ${args.join(' ')} failed`).trim();
|
|
19
|
-
throw new Error(output);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
function parseJsonOutput(text) {
|
|
23
13
|
const raw = String(text || '').trim();
|
|
24
14
|
if (!raw) throw new Error('expected JSON output, got empty output');
|
|
@@ -97,9 +87,9 @@ async function createSandbox(ctx) {
|
|
|
97
87
|
ctx.tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-drill-'));
|
|
98
88
|
ctx.sandboxPath = path.join(ctx.tmpRoot, 'sandbox');
|
|
99
89
|
fs.mkdirSync(ctx.sandboxPath, { recursive: true });
|
|
100
|
-
runGit(['init', '-b', 'master'], ctx.sandboxPath);
|
|
101
|
-
runGit(['config', 'user.email', 'drill@example.invalid'], ctx.sandboxPath);
|
|
102
|
-
runGit(['config', 'user.name', 'Atris Drill'], ctx.sandboxPath);
|
|
90
|
+
runGit(['init', '-b', 'master'], { cwd: ctx.sandboxPath });
|
|
91
|
+
runGit(['config', 'user.email', 'drill@example.invalid'], { cwd: ctx.sandboxPath });
|
|
92
|
+
runGit(['config', 'user.name', 'Atris Drill'], { cwd: ctx.sandboxPath });
|
|
103
93
|
fs.writeFileSync(path.join(ctx.sandboxPath, 'README.md'), '# Atris drill sandbox\n', 'utf8');
|
|
104
94
|
await withSandboxProcessState(ctx.sandboxPath, {
|
|
105
95
|
ATRIS_SKIP_UPDATE_CHECK: '1',
|
|
@@ -108,8 +98,8 @@ async function createSandbox(ctx) {
|
|
|
108
98
|
}, async () => {
|
|
109
99
|
await callInitAtris(ctx.sandboxPath);
|
|
110
100
|
});
|
|
111
|
-
runGit(['add', '-A'], ctx.sandboxPath);
|
|
112
|
-
runGit(['commit', '-m', 'initial sandbox'], ctx.sandboxPath);
|
|
101
|
+
runGit(['add', '-A'], { cwd: ctx.sandboxPath });
|
|
102
|
+
runGit(['commit', '-m', 'initial sandbox'], { cwd: ctx.sandboxPath });
|
|
113
103
|
}
|
|
114
104
|
|
|
115
105
|
async function wishCaptured(ctx) {
|
|
@@ -199,10 +189,10 @@ async function landingShippedLocal(ctx) {
|
|
|
199
189
|
try {
|
|
200
190
|
require('../lib/task-db').close();
|
|
201
191
|
} catch {}
|
|
202
|
-
const dirty = runGit(['status', '--porcelain'], ctx.sandboxPath).stdout.trim();
|
|
192
|
+
const dirty = runGit(['status', '--porcelain'], { cwd: ctx.sandboxPath }).stdout.trim();
|
|
203
193
|
if (dirty) {
|
|
204
|
-
runGit(['add', '-A'], ctx.sandboxPath);
|
|
205
|
-
runGit(['commit', '-m', 'record drill pipeline state'], ctx.sandboxPath);
|
|
194
|
+
runGit(['add', '-A'], { cwd: ctx.sandboxPath });
|
|
195
|
+
runGit(['commit', '-m', 'record drill pipeline state'], { cwd: ctx.sandboxPath });
|
|
206
196
|
}
|
|
207
197
|
ctx.landingWorktree = path.join(ctx.tmpRoot, 'landing-worktree');
|
|
208
198
|
const created = worktree.createAgentWorktree({
|
|
@@ -229,9 +219,9 @@ async function landingShippedLocal(ctx) {
|
|
|
229
219
|
assertOk(shipped.value === 0, `worktree ship exited ${shipped.value}`);
|
|
230
220
|
assertOk(/local mode/.test(`${shipped.stdout}\n${shipped.stderr}`), 'worktree ship did not report local mode');
|
|
231
221
|
});
|
|
232
|
-
runGit(['worktree', 'remove', ctx.landingWorktree, '--force'], ctx.sandboxPath);
|
|
233
|
-
runGit(['branch', '-d', ctx.landingBranch], ctx.sandboxPath);
|
|
234
|
-
const landed = runGit(['show', 'master:drill-landing.txt'], ctx.sandboxPath).stdout;
|
|
222
|
+
runGit(['worktree', 'remove', ctx.landingWorktree, '--force'], { cwd: ctx.sandboxPath });
|
|
223
|
+
runGit(['branch', '-d', ctx.landingBranch], { cwd: ctx.sandboxPath });
|
|
224
|
+
const landed = runGit(['show', 'master:drill-landing.txt'], { cwd: ctx.sandboxPath }).stdout;
|
|
235
225
|
assertOk(landed.trim() === 'local landing ok', 'landing file was not merged into sandbox master');
|
|
236
226
|
}
|
|
237
227
|
|
|
@@ -244,7 +234,7 @@ async function ledgerChecked(ctx) {
|
|
|
244
234
|
assertOk(currentMission, `mission not found: ${ctx.missionId}`);
|
|
245
235
|
assertOk(currentMission.verifier_result && currentMission.verifier_result.passed === true, 'mission verifier result is not passing');
|
|
246
236
|
assertOk(currentMission.receipt_path && fs.existsSync(path.join(ctx.sandboxPath, currentMission.receipt_path)), `mission receipt missing: ${currentMission.receipt_path}`);
|
|
247
|
-
const unmerged = runGit(['branch', '--no-merged', 'master'], ctx.sandboxPath).stdout
|
|
237
|
+
const unmerged = runGit(['branch', '--no-merged', 'master'], { cwd: ctx.sandboxPath }).stdout
|
|
248
238
|
.split(/\r?\n/)
|
|
249
239
|
.map((line) => line.replace(/^\*/, '').trim())
|
|
250
240
|
.filter(Boolean)
|
|
@@ -365,7 +355,6 @@ async function drillCommand(args = []) {
|
|
|
365
355
|
|
|
366
356
|
module.exports = {
|
|
367
357
|
COAUTHOR,
|
|
368
|
-
VERIFY_COMMAND,
|
|
369
358
|
drillCommand,
|
|
370
359
|
runDrill,
|
|
371
360
|
};
|
package/commands/engine.js
CHANGED
|
@@ -26,6 +26,7 @@ const {
|
|
|
26
26
|
} = require('../lib/runner-command');
|
|
27
27
|
const {
|
|
28
28
|
ENGINE_ROLES,
|
|
29
|
+
ENGINE_DUTIES,
|
|
29
30
|
ENGINE_HEALTH_STATUSES,
|
|
30
31
|
binInstalled,
|
|
31
32
|
canonicalEngineName,
|
|
@@ -33,6 +34,10 @@ const {
|
|
|
33
34
|
engineRegistryView,
|
|
34
35
|
readEngineRegistry,
|
|
35
36
|
resolveEngineForRole,
|
|
37
|
+
resolveEngineForRoleRanked,
|
|
38
|
+
requireEngineBin,
|
|
39
|
+
engineDoctorReport,
|
|
40
|
+
setEngineOverrides,
|
|
36
41
|
setEngineHealth,
|
|
37
42
|
} = require('../lib/engine-registry');
|
|
38
43
|
const { FLEET_CAPABLE, runDispatchFlight } = require('../lib/fleet');
|
|
@@ -89,14 +94,22 @@ function readSavedEngine(root = process.cwd()) {
|
|
|
89
94
|
|
|
90
95
|
// The default engine for this workspace, in precedence order:
|
|
91
96
|
// env (per-run flags land here) -> .atris/engine.json -> house default.
|
|
92
|
-
//
|
|
97
|
+
// When nothing is configured the pick comes from the registry's saved health
|
|
98
|
+
// (seeded once on first sight, refreshed by `atris engine doctor`), never a
|
|
99
|
+
// machine probe: policy over probes. A stale "ready" is the execution stage's
|
|
100
|
+
// problem, where requireEngineBin fails in one plain sentence.
|
|
93
101
|
function resolveDefaultEngine(root = process.cwd()) {
|
|
94
102
|
const env = canonicalEngineName(process.env.ATRIS_RUNNER_PROFILE);
|
|
95
103
|
if (env) return { name: env, source: 'env' };
|
|
96
104
|
const saved = readSavedEngine(root);
|
|
97
105
|
if (saved) return { name: saved, source: 'saved' };
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
const ready = new Set(
|
|
107
|
+
engineRegistryView(root)
|
|
108
|
+
.filter((engine) => engine.health && engine.health.status === 'ready')
|
|
109
|
+
.map((engine) => engine.id)
|
|
110
|
+
);
|
|
111
|
+
if (ready.has(HOUSE_ENGINE)) return { name: HOUSE_ENGINE, source: 'house' };
|
|
112
|
+
const fallback = RUNNER_PROFILE_NAMES.find((name) => ready.has(name));
|
|
100
113
|
return fallback ? { name: fallback, source: 'detected' } : { name: HOUSE_ENGINE, source: 'none' };
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -886,13 +899,16 @@ function printRoster(root) {
|
|
|
886
899
|
const found = list.filter((e) => e.installed).length;
|
|
887
900
|
const current = resolveDefaultEngine(root);
|
|
888
901
|
console.log('');
|
|
889
|
-
console.log(` engines
|
|
902
|
+
console.log(` engines: ${found} intelligence${found === 1 ? '' : 's'} found`);
|
|
890
903
|
console.log('');
|
|
891
904
|
for (const engine of list) {
|
|
892
905
|
const mark = engine.default ? '→' : ' ';
|
|
893
906
|
const state = engine.health.status === 'not_installed' ? 'not installed' : engine.health.status.replace(/_/g, ' ');
|
|
894
907
|
const roles = engine.roles.join(',');
|
|
895
908
|
console.log(` ${mark} ${engine.id.padEnd(12)} ${state.padEnd(13)} ${engine.tier.padEnd(4)} ${roles}`);
|
|
909
|
+
const details = [`models: ${engine.models.join(', ')}`];
|
|
910
|
+
if (engine.duty) details.push(`duty: ${engine.duty}`);
|
|
911
|
+
console.log(` ${details.join(' ')}`);
|
|
896
912
|
}
|
|
897
913
|
console.log('');
|
|
898
914
|
console.log(` default: ${current.name}${current.source === 'saved' ? ' (set here)' : current.source === 'env' ? ' (this session)' : ''}`);
|
|
@@ -900,6 +916,139 @@ function printRoster(root) {
|
|
|
900
916
|
console.log('');
|
|
901
917
|
}
|
|
902
918
|
|
|
919
|
+
function chartEngineLabel(engine) {
|
|
920
|
+
const id = String(engine && (engine.id || engine.name) || '').trim();
|
|
921
|
+
const models = Array.isArray(engine && engine.models)
|
|
922
|
+
? engine.models.map((model) => String(model || '').trim()).filter(Boolean)
|
|
923
|
+
: [];
|
|
924
|
+
return models.length ? `${id}: ${models.join(', ')}` : id;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function chartBox(title, engines, note = '', emptyLabel = 'unassigned') {
|
|
928
|
+
const labels = engines.length ? engines.map(chartEngineLabel) : (emptyLabel ? [emptyLabel] : []);
|
|
929
|
+
const body = [title, ...labels, ...(note ? [note] : [])];
|
|
930
|
+
const innerWidth = Math.max(...body.map((line) => line.length)) + 2;
|
|
931
|
+
return {
|
|
932
|
+
width: innerWidth + 2,
|
|
933
|
+
lines: [
|
|
934
|
+
`┌${'─'.repeat(innerWidth)}┐`,
|
|
935
|
+
...body.map((line) => `│ ${line.padEnd(innerWidth - 2)} │`),
|
|
936
|
+
`└${'─'.repeat(innerWidth)}┘`,
|
|
937
|
+
],
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function centerChartLine(line, width, center = Math.floor((width - 1) / 2)) {
|
|
942
|
+
const left = Math.max(0, center - Math.floor(line.length / 2));
|
|
943
|
+
return `${' '.repeat(left)}${line}`.padEnd(width);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function renderEngineChart(registry) {
|
|
947
|
+
const engines = Array.isArray(registry) ? registry : ((registry && registry.engines) || []);
|
|
948
|
+
const specialDuties = new Set(ENGINE_DUTIES);
|
|
949
|
+
const leaders = engines.filter((entry) => entry.duty === 'leader');
|
|
950
|
+
const rolePool = engines.filter((entry) => !specialDuties.has(entry.duty));
|
|
951
|
+
const builders = rolePool.filter((entry) => Array.isArray(entry.roles) && entry.roles.includes('executor'));
|
|
952
|
+
const checkers = rolePool.filter((entry) => Array.isArray(entry.roles) && entry.roles.includes('validator'));
|
|
953
|
+
const errands = engines.filter((entry) => entry.duty === 'errands');
|
|
954
|
+
const apprentices = engines.filter((entry) => entry.duty === 'learning');
|
|
955
|
+
|
|
956
|
+
const ownerBox = chartBox('owner', [], '', '');
|
|
957
|
+
const leaderBox = chartBox('leader', leaders);
|
|
958
|
+
const columns = [
|
|
959
|
+
chartBox('builders', builders),
|
|
960
|
+
chartBox('checkers', checkers),
|
|
961
|
+
chartBox('errands', errands),
|
|
962
|
+
];
|
|
963
|
+
const gap = 3;
|
|
964
|
+
const totalWidth = columns.reduce((sum, column) => sum + column.width, 0) + (gap * (columns.length - 1));
|
|
965
|
+
const columnHeight = Math.max(...columns.map((column) => column.lines.length));
|
|
966
|
+
const columnLines = [];
|
|
967
|
+
for (let row = 0; row < columnHeight; row += 1) {
|
|
968
|
+
columnLines.push(columns.map((column) => (column.lines[row] || '').padEnd(column.width)).join(' '.repeat(gap)));
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const centers = [];
|
|
972
|
+
let offset = 0;
|
|
973
|
+
for (const column of columns) {
|
|
974
|
+
centers.push(offset + Math.floor(column.width / 2));
|
|
975
|
+
offset += column.width + gap;
|
|
976
|
+
}
|
|
977
|
+
const fleetCenter = centers[1];
|
|
978
|
+
const branch = Array(totalWidth).fill(' ');
|
|
979
|
+
for (let i = centers[0]; i <= centers[centers.length - 1]; i += 1) branch[i] = '─';
|
|
980
|
+
branch[centers[0]] = '┌';
|
|
981
|
+
branch[centers[centers.length - 1]] = '┐';
|
|
982
|
+
for (const center of centers.slice(1, -1)) branch[center] = '┬';
|
|
983
|
+
branch[fleetCenter] = centers.includes(fleetCenter) ? '┼' : '┴';
|
|
984
|
+
const stems = Array(totalWidth).fill(' ');
|
|
985
|
+
for (const center of centers) stems[center] = '│';
|
|
986
|
+
|
|
987
|
+
const lines = [
|
|
988
|
+
...ownerBox.lines.map((line) => centerChartLine(line, totalWidth, fleetCenter)),
|
|
989
|
+
centerChartLine('│', totalWidth, fleetCenter),
|
|
990
|
+
...leaderBox.lines.map((line) => centerChartLine(line, totalWidth, fleetCenter)),
|
|
991
|
+
centerChartLine('│', totalWidth, fleetCenter),
|
|
992
|
+
branch.join(''),
|
|
993
|
+
stems.join(''),
|
|
994
|
+
...columnLines,
|
|
995
|
+
];
|
|
996
|
+
if (apprentices.length) {
|
|
997
|
+
const apprenticeBox = chartBox('apprentice', apprentices, 'learning the system');
|
|
998
|
+
lines.push(centerChartLine('│', totalWidth, fleetCenter));
|
|
999
|
+
lines.push(...apprenticeBox.lines.map((line) => centerChartLine(line, totalWidth, fleetCenter)));
|
|
1000
|
+
}
|
|
1001
|
+
return lines.map((line) => line.replace(/\s+$/, '')).join('\n');
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function printEngineChart(root) {
|
|
1005
|
+
console.log(renderEngineChart(readEngineRegistry(root)));
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function flagValue(args, flag) {
|
|
1009
|
+
const equals = `${flag}=`;
|
|
1010
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1011
|
+
const value = String(args[i] || '');
|
|
1012
|
+
if (value === flag) return { present: true, value: args[i + 1] || '' };
|
|
1013
|
+
if (value.startsWith(equals)) return { present: true, value: value.slice(equals.length) };
|
|
1014
|
+
}
|
|
1015
|
+
return { present: false, value: '' };
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function runSetEngineCommand(args, root) {
|
|
1019
|
+
const name = String(args[0] || '').trim();
|
|
1020
|
+
const dutyFlag = flagValue(args.slice(1), '--duty');
|
|
1021
|
+
const modelsFlag = flagValue(args.slice(1), '--models');
|
|
1022
|
+
const duty = String(dutyFlag.value || '').trim();
|
|
1023
|
+
const models = String(modelsFlag.value || '').split(',').map((model) => model.trim()).filter(Boolean);
|
|
1024
|
+
if (!name || (!dutyFlag.present && !modelsFlag.present)) {
|
|
1025
|
+
console.error('usage: atris engine set <name> [--duty leader|errands|learning] [--models "a, b"]');
|
|
1026
|
+
return 2;
|
|
1027
|
+
}
|
|
1028
|
+
if (dutyFlag.present && !ENGINE_DUTIES.includes(duty)) {
|
|
1029
|
+
console.error(`unknown duty "${duty}". known duties: ${ENGINE_DUTIES.join(', ')}`);
|
|
1030
|
+
return 2;
|
|
1031
|
+
}
|
|
1032
|
+
if (modelsFlag.present && !models.length) {
|
|
1033
|
+
console.error('models must include at least one name');
|
|
1034
|
+
return 2;
|
|
1035
|
+
}
|
|
1036
|
+
try {
|
|
1037
|
+
const updated = setEngineOverrides(name, {
|
|
1038
|
+
...(dutyFlag.present ? { duty } : {}),
|
|
1039
|
+
...(modelsFlag.present ? { models } : {}),
|
|
1040
|
+
}, root);
|
|
1041
|
+
const details = [];
|
|
1042
|
+
if (updated.duty) details.push(`duty ${updated.duty}`);
|
|
1043
|
+
if (updated.models) details.push(`models ${updated.models.join(', ')}`);
|
|
1044
|
+
console.log(`${updated.id} updated: ${details.join('; ')}`);
|
|
1045
|
+
return 0;
|
|
1046
|
+
} catch (err) {
|
|
1047
|
+
console.error(String(err.message || err).replace(/^Unknown/, 'unknown'));
|
|
1048
|
+
return 2;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
903
1052
|
function registryPayload(root) {
|
|
904
1053
|
const current = resolveDefaultEngine(root);
|
|
905
1054
|
const registry = readEngineRegistry(root);
|
|
@@ -932,22 +1081,48 @@ function runResolveCommand(args, root) {
|
|
|
932
1081
|
else console.error(message);
|
|
933
1082
|
return 2;
|
|
934
1083
|
}
|
|
935
|
-
let
|
|
1084
|
+
let picked;
|
|
936
1085
|
try {
|
|
937
|
-
|
|
1086
|
+
picked = resolveEngineForRoleRanked(role, root);
|
|
938
1087
|
} catch (err) {
|
|
939
1088
|
if (json) console.log(JSON.stringify({ ok: false, error: err.message }, null, 2));
|
|
940
1089
|
else console.error(err.message);
|
|
941
1090
|
return 2;
|
|
942
1091
|
}
|
|
1092
|
+
const engine = picked.engine;
|
|
943
1093
|
if (!engine) {
|
|
944
1094
|
const message = `No ready installed engine can fill role "${role}".`;
|
|
945
|
-
if (json) console.log(JSON.stringify({ ok: false, role, error: message }, null, 2));
|
|
1095
|
+
if (json) console.log(JSON.stringify({ ok: false, role, error: message, won_reason: picked.reason }, null, 2));
|
|
946
1096
|
else console.error(message);
|
|
947
1097
|
return 1;
|
|
948
1098
|
}
|
|
949
|
-
if (json) console.log(JSON.stringify(engine, null, 2));
|
|
950
|
-
else
|
|
1099
|
+
if (json) console.log(JSON.stringify({ ...engine, won_reason: picked.reason }, null, 2));
|
|
1100
|
+
else {
|
|
1101
|
+
console.error(picked.reason);
|
|
1102
|
+
console.log(engine.id);
|
|
1103
|
+
}
|
|
1104
|
+
return 0;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Doctor is the one opt-in place that probes the machine: it checks every
|
|
1108
|
+
// engine binary, reports installed state, and folds ready/not_installed flips
|
|
1109
|
+
// back into the policy file. Routing itself never probes.
|
|
1110
|
+
function runDoctorCommand(args, root) {
|
|
1111
|
+
const json = args.includes('--json');
|
|
1112
|
+
const engines = engineDoctorReport(root);
|
|
1113
|
+
if (json) {
|
|
1114
|
+
console.log(JSON.stringify({ ok: true, engines }, null, 2));
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
console.log('');
|
|
1118
|
+
for (const engine of engines) {
|
|
1119
|
+
const state = engine.installed ? 'installed' : 'missing';
|
|
1120
|
+
const health = engine.health.status.replace(/_/g, ' ');
|
|
1121
|
+
console.log(` ${engine.id.padEnd(12)} ${String(engine.bin).padEnd(14)} ${state.padEnd(10)} health: ${health}`);
|
|
1122
|
+
}
|
|
1123
|
+
console.log('');
|
|
1124
|
+
console.log(' probed just now and saved to the registry; routing reads that saved policy, never the machine.');
|
|
1125
|
+
console.log('');
|
|
951
1126
|
return 0;
|
|
952
1127
|
}
|
|
953
1128
|
|
|
@@ -1198,9 +1373,12 @@ function runDispatchCommand(args, root) {
|
|
|
1198
1373
|
return 2;
|
|
1199
1374
|
}
|
|
1200
1375
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1376
|
+
// Execution stage: this is where the binary must exist. Routing above never
|
|
1377
|
+
// probed the machine; a missing CLI fails loudly here instead.
|
|
1378
|
+
try {
|
|
1379
|
+
requireEngineBin(canonical);
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
console.error(`engine dispatch: ${err.message}`);
|
|
1204
1382
|
return 2;
|
|
1205
1383
|
}
|
|
1206
1384
|
return runDispatchFlight({ root, taskIds, engine: canonical, prompt: promptOverride, yolo, ...(base ? { checkoutBase: base } : {}) }).then((flight) => {
|
|
@@ -1243,6 +1421,19 @@ function engineCommand(args = [], deps = {}) {
|
|
|
1243
1421
|
return runHealthCommand(args.slice(args.indexOf('health') + 1), root);
|
|
1244
1422
|
}
|
|
1245
1423
|
|
|
1424
|
+
if (sub === 'doctor') {
|
|
1425
|
+
return runDoctorCommand(args.slice(args.indexOf('doctor') + 1), root);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (sub === 'set') {
|
|
1429
|
+
return runSetEngineCommand(args.slice(args.indexOf('set') + 1), root);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
if (sub === 'chart' || args.includes('--chart')) {
|
|
1433
|
+
printEngineChart(root);
|
|
1434
|
+
return 0;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1246
1437
|
if (!sub || sub === 'list' || sub === 'status') {
|
|
1247
1438
|
if (json) {
|
|
1248
1439
|
console.log(JSON.stringify(registryPayload(root), null, 2));
|
|
@@ -1261,7 +1452,7 @@ function engineCommand(args = [], deps = {}) {
|
|
|
1261
1452
|
}
|
|
1262
1453
|
|
|
1263
1454
|
if (sub === 'help') {
|
|
1264
|
-
console.log('\n atris engine roster + current default\n atris engine list --json full registry: default + engines with tier, roles, fallback, health\n atris engine resolve <role> [--json]\n choose the best ready engine for navigator|executor|validator\n atris engine health <name> --set ready|not_installed|credit_out\n flip runtime health, for example when credits run out\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine dispatch <task-id> [<task-id> ...] --engine cursor|codex [--prompt-file <f>] [--yolo]\n one-command claim, worktree, build, verify, ship, ready\n atris engine login <provider> --yes\n upload a local provider CLI login to the backend vault\n atris engine login <provider> --computer [--seat <name>]\n atris engine login <provider> --business <id> [--seat <name>]\n sign in on an Atris computer by device flow\n atris engine login --list | --remove <provider>\n list or remove vaulted provider logins\n atris engine seats show which named accounts are ready to work\n atris engine seed <provider> --business <id>|--user\n push a vaulted login onto an Atris computer\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
|
|
1455
|
+
console.log('\n atris engine roster + current default\n atris engines --chart show the fleet as an org chart\n atris engine list --json full registry: default + engines with tier, roles, fallback, health\n atris engine set <name> --duty leader|errands|learning [--models "a, b"]\n arrange the fleet and save its model policy\n atris engine resolve <role> [--json]\n choose the best ready engine for navigator|executor|validator\n atris engine health <name> --set ready|not_installed|credit_out\n flip runtime health, for example when credits run out\n atris engine doctor [--json]\n probe which engine CLIs are installed here and sync that into health policy\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine dispatch <task-id> [<task-id> ...] --engine cursor|codex [--prompt-file <f>] [--yolo]\n one-command claim, worktree, build, verify, ship, ready\n atris engine login <provider> --yes\n upload a local provider CLI login to the backend vault\n atris engine login <provider> --computer [--seat <name>]\n atris engine login <provider> --business <id> [--seat <name>]\n sign in on an Atris computer by device flow\n atris engine login --list | --remove <provider>\n list or remove vaulted provider logins\n atris engine seats show which named accounts are ready to work\n atris engine seed <provider> --business <id>|--user\n push a vaulted login onto an Atris computer\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
|
|
1265
1456
|
return 0;
|
|
1266
1457
|
}
|
|
1267
1458
|
|
|
@@ -1294,36 +1485,25 @@ module.exports = {
|
|
|
1294
1485
|
resolveDefaultEngine,
|
|
1295
1486
|
canonicalEngineName,
|
|
1296
1487
|
readSavedEngine,
|
|
1297
|
-
setEngine,
|
|
1298
|
-
resetEngine,
|
|
1299
1488
|
roster,
|
|
1300
|
-
registryPayload,
|
|
1301
1489
|
engineRegistryFile,
|
|
1302
1490
|
readEngineRegistry,
|
|
1303
1491
|
resolveEngineForRole,
|
|
1304
1492
|
setEngineHealth,
|
|
1305
|
-
|
|
1306
|
-
|
|
1493
|
+
setEngineOverrides,
|
|
1494
|
+
renderEngineChart,
|
|
1307
1495
|
parseDispatchArgs,
|
|
1308
1496
|
runDispatchCommand,
|
|
1309
1497
|
ENGINE_LOGIN_MANIFESTS,
|
|
1310
|
-
MAX_ENGINE_LOGIN_FILE_BYTES,
|
|
1311
1498
|
expandHomePath,
|
|
1312
1499
|
normalizeLoginProvider,
|
|
1313
1500
|
normalizeEngineLoginSeat,
|
|
1314
1501
|
validEngineLoginSeat,
|
|
1315
1502
|
engineLoginSeatError,
|
|
1316
|
-
detectedEmailFromContent,
|
|
1317
|
-
readEngineLoginFiles,
|
|
1318
1503
|
parseEngineLoginArgs,
|
|
1319
|
-
parseEngineSeedArgs,
|
|
1320
|
-
redactBackendResponse,
|
|
1321
|
-
buildEngineLoginPayload,
|
|
1322
|
-
listEngineLogins,
|
|
1323
1504
|
listEngineSeats,
|
|
1324
1505
|
readyCheckEngineLogin,
|
|
1325
1506
|
formatEngineSeats,
|
|
1326
|
-
runEngineSeatsCommand,
|
|
1327
1507
|
runEngineDeviceLoginCommand,
|
|
1328
1508
|
runEngineLoginCommand,
|
|
1329
1509
|
runEngineSeedCommand,
|
package/commands/experiments.js
CHANGED
package/commands/fleet-report.js
CHANGED
|
@@ -79,6 +79,16 @@ async function writeReportToComputer(token, businessId, workspaceId, report) {
|
|
|
79
79
|
return remotePath;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// A missing scoreboard field must never reach the owner as "$NaN" or
|
|
83
|
+
// "$undefined"; coerce every number to 0 before it renders.
|
|
84
|
+
function formatScoreboardPnl(board) {
|
|
85
|
+
if (!board) return '';
|
|
86
|
+
const mrr = Number(board.revenue_mrr_usd) || 0;
|
|
87
|
+
const cost = (Number(board.compute_cost_usd) || 0) + (Number(board.ec2_cost_usd) || 0);
|
|
88
|
+
const profit = Number(board.profit_daily_usd) || 0;
|
|
89
|
+
return ` | mrr $${mrr} cost $${cost.toFixed(2)}/d profit $${profit}/d`;
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
async function deliverToBusiness(token, biz, { wake, dryRun }) {
|
|
83
93
|
const label = biz.slug || biz.name || biz.id;
|
|
84
94
|
const status = await computerStatus(token, biz.id);
|
|
@@ -97,9 +107,7 @@ async function deliverToBusiness(token, biz, { wake, dryRun }) {
|
|
|
97
107
|
|
|
98
108
|
const report = await fetchDailyReport(token, biz.id);
|
|
99
109
|
const board = report.scoreboard || null;
|
|
100
|
-
const pnl = board
|
|
101
|
-
? ` | mrr $${board.revenue_mrr_usd} cost $${(board.compute_cost_usd + (board.ec2_cost_usd || 0)).toFixed(2)}/d profit $${board.profit_daily_usd}/d`
|
|
102
|
-
: '';
|
|
110
|
+
const pnl = formatScoreboardPnl(board);
|
|
103
111
|
if (dryRun) {
|
|
104
112
|
console.log(` ${label}: dry-run, report ${String(report.markdown || '').length} chars${pnl}`);
|
|
105
113
|
return { business: label, delivered: false, reason: 'dry-run', scoreboard: board };
|
|
@@ -203,4 +211,4 @@ async function fleetReport() {
|
|
|
203
211
|
process.exitCode = delivered > 0 || dryRun || results.every((r) => r.reason === 'asleep') ? 0 : 1;
|
|
204
212
|
}
|
|
205
213
|
|
|
206
|
-
module.exports = { fleetReport };
|
|
214
|
+
module.exports = { fleetReport, formatScoreboardPnl };
|