atris 3.56.1 → 3.56.2
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/bin/atris.js +17 -5
- package/commands/auth.js +6 -1
- package/commands/engine.js +7 -0
- package/commands/experiments.js +178 -0
- package/commands/founder.js +12 -0
- package/commands/human-missions.js +46 -8
- package/commands/launchpad.js +45 -10
- package/commands/now.js +36 -1
- package/commands/recap.js +34 -3
- package/commands/status.js +10 -2
- package/commands/workflow.js +4 -3
- package/commands/youtube.js +310 -8
- package/lib/apply-gate.js +15 -7
- package/lib/config-guard.js +106 -0
- package/lib/engine-registry.js +14 -4
- package/lib/first-minute.js +21 -2
- package/lib/pack-capabilities.js +13 -3
- package/lib/workspace-scaffold.js +3 -1
- package/package.json +1 -1
- package/utils/auth.js +76 -7
package/bin/atris.js
CHANGED
|
@@ -211,7 +211,7 @@ function parseNaturalEntryArgs(args = []) {
|
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
function applyRunnerFlags(args) {
|
|
214
|
+
function applyRunnerFlags(args, options = {}) {
|
|
215
215
|
// --engine <name> is the operator-facing spelling of --runner-profile:
|
|
216
216
|
// one flag rents a specific intelligence for this run.
|
|
217
217
|
const engineFlag = readOptionArg(args, '--engine');
|
|
@@ -251,6 +251,9 @@ function applyRunnerFlags(args) {
|
|
|
251
251
|
process.env.ATRIS_RUNNER_MODEL = runnerModel;
|
|
252
252
|
process.env.ATRIS_CLAUDE_MODEL = runnerModel;
|
|
253
253
|
}
|
|
254
|
+
// Talk-only and unbound scratch still validate --engine / --runner-profile.
|
|
255
|
+
// They skip this seed so resolveDefaultEngine cannot mint engines.json.
|
|
256
|
+
if (options.seed === false) return;
|
|
254
257
|
// No per-run choice and no env: the workspace's saved engine
|
|
255
258
|
// (.atris/engine.json, written by `atris engine <name>`) becomes the
|
|
256
259
|
// profile for every loop spawn. For the loop commands themselves, fall all
|
|
@@ -632,6 +635,8 @@ function showHelpAll() {
|
|
|
632
635
|
console.log(' experiments init [slug] - Prepare atris/experiments/ or scaffold a pack');
|
|
633
636
|
console.log(' experiments validate - Validate experiment packs');
|
|
634
637
|
console.log(' experiments run <slug> - Execute a pack or record an Endstate receipt');
|
|
638
|
+
console.log(' experiments keep <slug> - Keep a pack only when measure.py moves 0 to 1');
|
|
639
|
+
console.log(' experiments revert <slug> - Run a pack reset.py after a refused keep');
|
|
635
640
|
console.log(' experiments benchmark [m] - Run validate/runtime experiment benchmarks');
|
|
636
641
|
console.log(' bench - run core benchmark gates');
|
|
637
642
|
console.log('');
|
|
@@ -2442,7 +2447,14 @@ if (command === 'init') {
|
|
|
2442
2447
|
process.exit(0);
|
|
2443
2448
|
}
|
|
2444
2449
|
|
|
2445
|
-
|
|
2450
|
+
const { isUnboundScratchFolder } = require('../lib/scratch-root');
|
|
2451
|
+
const { runObjective, runMissionFront } = require('../commands/run-front');
|
|
2452
|
+
const runTalkOnly = !runObjective(args) && !hasYesFlag(args) && !args.includes('--legacy');
|
|
2453
|
+
// Flag validation always runs. The engine-registry seed is what mints
|
|
2454
|
+
// .atris/state/engines.json, so first-talk and unbound scratch skip only that.
|
|
2455
|
+
applyRunnerFlags(args, {
|
|
2456
|
+
seed: !runTalkOnly && !isUnboundScratchFolder(process.cwd()),
|
|
2457
|
+
});
|
|
2446
2458
|
|
|
2447
2459
|
if (args.includes('--legacy')) {
|
|
2448
2460
|
const legacyArgs = args.filter(a => a !== '--legacy');
|
|
@@ -2462,7 +2474,7 @@ if (command === 'init') {
|
|
|
2462
2474
|
process.exit(1);
|
|
2463
2475
|
});
|
|
2464
2476
|
} else {
|
|
2465
|
-
|
|
2477
|
+
runMissionFront(args)
|
|
2466
2478
|
.then((code) => process.exit(code || 0))
|
|
2467
2479
|
.catch((error) => {
|
|
2468
2480
|
console.error(`\u2717 Run failed: ${error.message || error}`);
|
|
@@ -2486,8 +2498,8 @@ if (command === 'init') {
|
|
|
2486
2498
|
if (!autopilotHelp && !autopilotControl && !autopilotJsonPlan && !autopilotTalkOnly && isUnboundScratchFolder(autopilotRoot)) {
|
|
2487
2499
|
process.exit(refuseUnboundScratch());
|
|
2488
2500
|
}
|
|
2489
|
-
if (!autopilotHelp
|
|
2490
|
-
applyRunnerFlags(args);
|
|
2501
|
+
if (!autopilotHelp) {
|
|
2502
|
+
applyRunnerFlags(args, { seed: !isUnboundScratchFolder(autopilotRoot) });
|
|
2491
2503
|
}
|
|
2492
2504
|
|
|
2493
2505
|
if (args.includes('--legacy')) {
|
package/commands/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, ensureValidCredentials, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, saveProfile, getTokenExpiryEpochSeconds, getTerminalSessionId, setSessionProfile, getSessionProfile, clearSessionProfile, cleanStaleSessions, getSessionsDir } = require('../utils/auth');
|
|
1
|
+
const { AGENT_TOKEN_EXPIRED_DETAIL, loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, ensureValidCredentials, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, saveProfile, getTokenExpiryEpochSeconds, getTerminalSessionId, setSessionProfile, getSessionProfile, clearSessionProfile, cleanStaleSessions, getSessionsDir } = require('../utils/auth');
|
|
2
2
|
const { getAppBaseUrl, apiRequestJson } = require('../utils/api');
|
|
3
3
|
const { isNonInteractive, wantsJson } = require('../lib/noninteractive');
|
|
4
4
|
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
@@ -281,6 +281,11 @@ async function mintAgentToken(args = [], deps = {}) {
|
|
|
281
281
|
async function printWhoamiPayload(asJson) {
|
|
282
282
|
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
283
283
|
if (ensured.error) {
|
|
284
|
+
if (!asJson && ensured.detail === AGENT_TOKEN_EXPIRED_DETAIL) {
|
|
285
|
+
console.error('the agent key expired.');
|
|
286
|
+
console.error('atris login --agent');
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
284
289
|
if (asJson) {
|
|
285
290
|
console.log(JSON.stringify({
|
|
286
291
|
ok: false,
|
package/commands/engine.js
CHANGED
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
buildRunnerCommand,
|
|
26
26
|
} = require('../lib/runner-command');
|
|
27
27
|
const { parseScopeFlag } = require('../lib/cli-scope');
|
|
28
|
+
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
28
29
|
const {
|
|
29
30
|
ENGINE_ROLES,
|
|
30
31
|
ENGINE_DUTIES,
|
|
@@ -1541,6 +1542,9 @@ function engineCommand(args = [], deps = {}) {
|
|
|
1541
1542
|
}
|
|
1542
1543
|
|
|
1543
1544
|
if (sub === 'chart' || scope.args.includes('--chart') || args.includes('--chart')) {
|
|
1545
|
+
if (isFreshWorkspace(root)) {
|
|
1546
|
+
return speakFirstMinute({ root, fresh: true, asJson: json });
|
|
1547
|
+
}
|
|
1544
1548
|
printEngineChart(root);
|
|
1545
1549
|
return 0;
|
|
1546
1550
|
}
|
|
@@ -1551,6 +1555,9 @@ function engineCommand(args = [], deps = {}) {
|
|
|
1551
1555
|
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 ask "<question>" --engine <name> [--engine <name> ...]\n ask several engines in parallel without allowing edits\n atris engine ask --jobs <jobs.json>\n ask different read-only questions in parallel\n atris engine validate <receipt-path|latest> [--engine <name>]\n check ask answers with a different read-only referee\n atris engine validate scoreboard\n show pass rates by worker engine\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');
|
|
1552
1556
|
return 0;
|
|
1553
1557
|
}
|
|
1558
|
+
if (isFreshWorkspace(root)) {
|
|
1559
|
+
return speakFirstMinute({ root, fresh: true, asJson: json });
|
|
1560
|
+
}
|
|
1554
1561
|
if (json) {
|
|
1555
1562
|
console.log(JSON.stringify(registryPayload(root, { scope: scope.kind }), null, 2));
|
|
1556
1563
|
return 0;
|
package/commands/experiments.js
CHANGED
|
@@ -138,6 +138,176 @@ function runPython(scriptPath, args = [], cwd = process.cwd()) {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
function parseMeasureScore(stdout) {
|
|
142
|
+
const line = String(stdout || '').trim().split(/\r?\n/).filter(Boolean).pop();
|
|
143
|
+
if (!line) return { ok: false, error: 'measure.py printed no score' };
|
|
144
|
+
try {
|
|
145
|
+
const payload = JSON.parse(line);
|
|
146
|
+
const score = Number(payload.score);
|
|
147
|
+
if (!Number.isFinite(score)) return { ok: false, error: 'measure.py printed no score' };
|
|
148
|
+
return { ok: true, score, payload };
|
|
149
|
+
} catch {
|
|
150
|
+
return { ok: false, error: 'measure.py printed no score' };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function runMeasureJson(scriptPath, workspaceDir = process.cwd()) {
|
|
155
|
+
const python = resolvePython();
|
|
156
|
+
if (!python) {
|
|
157
|
+
return { ok: false, error: 'python not found. set ATRIS_EXPERIMENTS_PYTHON or install python3.' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const result = spawnSync(python, [scriptPath], {
|
|
161
|
+
cwd: workspaceDir,
|
|
162
|
+
encoding: 'utf8',
|
|
163
|
+
env: {
|
|
164
|
+
...process.env,
|
|
165
|
+
PYTHONDONTWRITEBYTECODE: '1',
|
|
166
|
+
ATRIS_REPO_ROOT: workspaceDir,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (result.error) {
|
|
171
|
+
return { ok: false, error: result.error.message || 'measure.py failed to start' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const parsed = parseMeasureScore(result.stdout);
|
|
175
|
+
if (!parsed.ok) {
|
|
176
|
+
const detail = String(result.stderr || '').trim().split(/\r?\n/).filter(Boolean).pop();
|
|
177
|
+
return { ok: false, error: detail || parsed.error };
|
|
178
|
+
}
|
|
179
|
+
return parsed;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function experimentsKeep(name) {
|
|
183
|
+
const token = String(name || '').trim();
|
|
184
|
+
if (!token || token === '--help' || token === '-h' || token === 'help') {
|
|
185
|
+
console.error('usage: atris experiments keep <slug>');
|
|
186
|
+
process.exit(2);
|
|
187
|
+
}
|
|
188
|
+
if (!SLUG_RE.test(token)) {
|
|
189
|
+
console.error('invalid experiment name. use a lowercase-hyphen slug.');
|
|
190
|
+
process.exit(2);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const workspaceDir = process.cwd();
|
|
194
|
+
if (!fs.existsSync(path.join(workspaceDir, 'atris'))) {
|
|
195
|
+
console.error('atris/ folder not found. run "atris init" first.');
|
|
196
|
+
process.exit(2);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const packDir = path.join(workspaceDir, 'atris', 'experiments', token);
|
|
200
|
+
if (!fs.existsSync(packDir) || !fs.statSync(packDir).isDirectory()) {
|
|
201
|
+
console.error(`experiment "${token}" not found.`);
|
|
202
|
+
process.exit(2);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const measurePath = path.join(packDir, 'measure.py');
|
|
206
|
+
if (!fs.existsSync(measurePath)) {
|
|
207
|
+
console.error(`experiment "${token}" has no measure.py.`);
|
|
208
|
+
process.exit(2);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Change is already on disk. Minted packs start at 0; keep only if current score is 1.
|
|
212
|
+
const measured = runMeasureJson(measurePath, workspaceDir);
|
|
213
|
+
if (!measured.ok) {
|
|
214
|
+
console.error(`revert ${token}: ${measured.error}. refuse keep.`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (measured.score === 1) {
|
|
219
|
+
console.log(`keep ${token}: measure.py moved 0→1`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const stayed = measured.score === 0 ? '0' : String(measured.score);
|
|
224
|
+
console.error(`revert ${token}: measure.py stayed ${stayed}. refuse keep.`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function lastProcessLine(result) {
|
|
229
|
+
const text = `${result && result.stderr ? result.stderr : ''}\n${result && result.stdout ? result.stdout : ''}`;
|
|
230
|
+
return text.trim().split(/\r?\n/).filter(Boolean).pop() || '';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function runResetScript(scriptPath, workspaceDir = process.cwd()) {
|
|
234
|
+
const python = resolvePython();
|
|
235
|
+
if (!python) {
|
|
236
|
+
return { ok: false, error: 'python not found. set ATRIS_EXPERIMENTS_PYTHON or install python3.' };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const result = spawnSync(python, [scriptPath], {
|
|
240
|
+
cwd: workspaceDir,
|
|
241
|
+
encoding: 'utf8',
|
|
242
|
+
env: {
|
|
243
|
+
...process.env,
|
|
244
|
+
PYTHONDONTWRITEBYTECODE: '1',
|
|
245
|
+
ATRIS_REPO_ROOT: workspaceDir,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (result.error) {
|
|
250
|
+
return { ok: false, error: result.error.message || 'reset.py failed to start' };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (typeof result.status === 'number' && result.status !== 0) {
|
|
254
|
+
return { ok: false, error: lastProcessLine(result) || `reset.py exited ${result.status}` };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (result.status == null) {
|
|
258
|
+
return { ok: false, error: lastProcessLine(result) || 'reset.py failed' };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { ok: true };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function experimentsRevert(name) {
|
|
265
|
+
const token = String(name || '').trim();
|
|
266
|
+
if (!token || token === '--help' || token === '-h' || token === 'help') {
|
|
267
|
+
console.error('usage: atris experiments revert <slug>');
|
|
268
|
+
process.exit(2);
|
|
269
|
+
}
|
|
270
|
+
if (!SLUG_RE.test(token)) {
|
|
271
|
+
console.error('invalid experiment name. use a lowercase-hyphen slug.');
|
|
272
|
+
process.exit(2);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const workspaceDir = process.cwd();
|
|
276
|
+
if (!fs.existsSync(path.join(workspaceDir, 'atris'))) {
|
|
277
|
+
console.error('atris/ folder not found. run "atris init" first.');
|
|
278
|
+
process.exit(2);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const packDir = path.join(workspaceDir, 'atris', 'experiments', token);
|
|
282
|
+
if (!fs.existsSync(packDir) || !fs.statSync(packDir).isDirectory()) {
|
|
283
|
+
console.error(`experiment "${token}" not found.`);
|
|
284
|
+
process.exit(2);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const resetPath = path.join(packDir, 'reset.py');
|
|
288
|
+
if (!fs.existsSync(resetPath)) {
|
|
289
|
+
console.error(`experiment "${token}" has no reset.py.`);
|
|
290
|
+
process.exit(2);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const reset = runResetScript(resetPath, workspaceDir);
|
|
294
|
+
if (!reset.ok) {
|
|
295
|
+
console.error(`revert ${token}: ${reset.error}. refuse revert.`);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const measurePath = path.join(packDir, 'measure.py');
|
|
300
|
+
if (fs.existsSync(measurePath)) {
|
|
301
|
+
const measured = runMeasureJson(measurePath, workspaceDir);
|
|
302
|
+
if (measured.ok) {
|
|
303
|
+
console.log(`revert ${token}: reset.py ran. measure.py ${measured.score}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log(`revert ${token}: reset.py ran`);
|
|
309
|
+
}
|
|
310
|
+
|
|
141
311
|
function experimentsInit(name) {
|
|
142
312
|
const { experimentsDir } = ensureExperimentsFramework();
|
|
143
313
|
|
|
@@ -573,6 +743,10 @@ function experimentsCommand(subcommand, ...args) {
|
|
|
573
743
|
return experimentsReplay(args[0] || 'endstate');
|
|
574
744
|
case 'run':
|
|
575
745
|
return experimentsRun(args[0], ...args.slice(1));
|
|
746
|
+
case 'keep':
|
|
747
|
+
return experimentsKeep(args[0]);
|
|
748
|
+
case 'revert':
|
|
749
|
+
return experimentsRevert(args[0]);
|
|
576
750
|
default:
|
|
577
751
|
console.log('');
|
|
578
752
|
console.log('Usage: atris experiments <subcommand> [name]');
|
|
@@ -581,6 +755,8 @@ function experimentsCommand(subcommand, ...args) {
|
|
|
581
755
|
console.log(' init [slug] Prepare atris/experiments/ or scaffold a new pack');
|
|
582
756
|
console.log(' validate [path|slug] Run structural validation on packs or a single pack');
|
|
583
757
|
console.log(' run <slug> Execute a pack or record an Endstate benchmark receipt');
|
|
758
|
+
console.log(' keep <slug> Keep a pack only when measure.py moves 0 to 1');
|
|
759
|
+
console.log(' revert <slug> Run a pack reset.py to restore the working tree');
|
|
584
760
|
console.log(' compare endstate Compare the latest baseline and stack receipts');
|
|
585
761
|
console.log(' replay endstate Validate, dry-run, and compare the public benchmark flow');
|
|
586
762
|
console.log(' benchmark [mode] Run validate/runtime/all benchmark harness');
|
|
@@ -592,6 +768,8 @@ function experimentsCommand(subcommand, ...args) {
|
|
|
592
768
|
console.log(' atris experiments init self-heal');
|
|
593
769
|
console.log(' atris experiments validate');
|
|
594
770
|
console.log(' atris experiments run endstate-baseline --dry-run');
|
|
771
|
+
console.log(' atris experiments keep teach-teach01-s1');
|
|
772
|
+
console.log(' atris experiments revert teach-teach01-s1');
|
|
595
773
|
console.log(' atris experiments compare endstate');
|
|
596
774
|
console.log(' atris experiments replay endstate');
|
|
597
775
|
console.log(' atris experiments benchmark runtime');
|
package/commands/founder.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { spawnSync } = require('node:child_process');
|
|
6
|
+
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
6
7
|
|
|
7
8
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
8
9
|
const DEFAULT_DAYS = 28;
|
|
@@ -321,6 +322,17 @@ function founderCommand(args = [], options = {}) {
|
|
|
321
322
|
|
|
322
323
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
323
324
|
const currentWorkspace = workspaceRoot(cwd);
|
|
325
|
+
|
|
326
|
+
// Empty folder talks like bare atris. Do not write scorecard.jsonl.
|
|
327
|
+
// After init, founder still lists and scores.
|
|
328
|
+
if (isFreshWorkspace(cwd) || isFreshWorkspace(currentWorkspace)) {
|
|
329
|
+
return speakFirstMinute({
|
|
330
|
+
root: cwd,
|
|
331
|
+
fresh: true,
|
|
332
|
+
asJson: parsed.json,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
324
336
|
const root = parsed.root
|
|
325
337
|
? path.resolve(cwd, parsed.root)
|
|
326
338
|
: (parsed.global ? defaultScanRoot(cwd, currentWorkspace) : currentWorkspace);
|
|
@@ -14,7 +14,15 @@ const {
|
|
|
14
14
|
const { apiRequestJson } = require('../utils/api');
|
|
15
15
|
const { decodeJwtClaims, loadCredentials } = require('../utils/auth');
|
|
16
16
|
const { isHelpToken } = require('../lib/noninteractive');
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
buildFirstMinute,
|
|
19
|
+
isClaimMinute,
|
|
20
|
+
isFreshWorkspace,
|
|
21
|
+
isKeepWorkingMinute,
|
|
22
|
+
speakFirstMinute,
|
|
23
|
+
speakKeepWorkingMinute,
|
|
24
|
+
speakNothingRunning,
|
|
25
|
+
} = require('../lib/first-minute');
|
|
18
26
|
|
|
19
27
|
const PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
|
|
20
28
|
const HUMAN_STATES = Object.freeze({
|
|
@@ -168,8 +176,10 @@ function parseMissionId(args = []) {
|
|
|
168
176
|
|
|
169
177
|
/**
|
|
170
178
|
* Scratch / unbound folders must not drive account-current cloud work.
|
|
171
|
-
* Empty-folder stop talks first-talk instead. After
|
|
172
|
-
*
|
|
179
|
+
* Empty-folder stop talks first-talk instead. After do/claim, nothing
|
|
180
|
+
* running talks keep-working instead. After init, claim-ready talks
|
|
181
|
+
* the claim instead. After a room exists without that keep-working
|
|
182
|
+
* or claim next, require an explicit id: atris stop --mission <id>
|
|
173
183
|
*/
|
|
174
184
|
function refuseUnboundCloudComputer(args = [], options = {}, commandName = 'stop') {
|
|
175
185
|
const root = options.root || process.cwd();
|
|
@@ -739,6 +749,15 @@ async function approveCommand(args, options = {}) {
|
|
|
739
749
|
return changeCurrentMission('approve', {}, args, options);
|
|
740
750
|
}
|
|
741
751
|
|
|
752
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
753
|
+
try {
|
|
754
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
755
|
+
return Boolean(pickLiveLocalMission(root));
|
|
756
|
+
} catch {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
742
761
|
async function stopCommand(args, options = {}) {
|
|
743
762
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
744
763
|
(options.log || console.log)('Usage: atris stop [--mission <id>] [--json]');
|
|
@@ -746,16 +765,35 @@ async function stopCommand(args, options = {}) {
|
|
|
746
765
|
}
|
|
747
766
|
const root = options.root || process.cwd();
|
|
748
767
|
const asJson = args.includes('--json');
|
|
768
|
+
const speak = {
|
|
769
|
+
root,
|
|
770
|
+
asJson,
|
|
771
|
+
log: options.log || console.log,
|
|
772
|
+
};
|
|
749
773
|
// Fresh folder: empty talks first-talk. A file already here
|
|
750
774
|
// names that file, same as bare atris. Do not mint a room.
|
|
751
775
|
if (isFreshWorkspace(root) && !parseMissionId(args)) {
|
|
752
776
|
const unbound = refuseUnboundCloudComputer(args, options, 'stop');
|
|
753
777
|
if (unbound) {
|
|
754
|
-
return speakNothingRunning(
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
778
|
+
return speakNothingRunning(speak);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// After do/claim, nothing running: same two keep-working
|
|
782
|
+
// lines as bare atris / status / recap. After init, next
|
|
783
|
+
// is claim: same two lines as bare atris / status / now.
|
|
784
|
+
// Not factory cloud-computer. Do not mint a room or bind
|
|
785
|
+
// a computer. A bound computer still stops the current
|
|
786
|
+
// mission.
|
|
787
|
+
if (!parseMissionId(args) && !hasLiveKeepWorkingRun(root)) {
|
|
788
|
+
const unbound = refuseUnboundCloudComputer(args, options, 'stop');
|
|
789
|
+
if (unbound) {
|
|
790
|
+
const minute = buildFirstMinute({ root });
|
|
791
|
+
if (isKeepWorkingMinute(minute)) {
|
|
792
|
+
return speakKeepWorkingMinute(speak);
|
|
793
|
+
}
|
|
794
|
+
if (isClaimMinute(minute)) {
|
|
795
|
+
return speakFirstMinute(speak);
|
|
796
|
+
}
|
|
759
797
|
}
|
|
760
798
|
}
|
|
761
799
|
return changeCurrentMission('stop', {}, args, options);
|
package/commands/launchpad.js
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
6
|
+
const {
|
|
7
|
+
isFreshWorkspace,
|
|
8
|
+
isKeepWorkingMinute,
|
|
9
|
+
isClaimMinute,
|
|
10
|
+
buildFirstMinute,
|
|
11
|
+
speakFirstMinute,
|
|
12
|
+
speakKeepWorkingMinute,
|
|
13
|
+
} = require('../lib/first-minute');
|
|
6
14
|
|
|
7
15
|
const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
8
16
|
|
|
@@ -185,15 +193,18 @@ function loadTaskProjection(root) {
|
|
|
185
193
|
};
|
|
186
194
|
}
|
|
187
195
|
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
why: 'No atris/ directory exists yet.',
|
|
195
|
-
};
|
|
196
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
197
|
+
try {
|
|
198
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
199
|
+
return Boolean(pickLiveLocalMission(root));
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
196
202
|
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function chooseNextAction({ actor, tasks, missions, brain, endgame }) {
|
|
206
|
+
// No-room folders belong to first-minute, not factory init.
|
|
207
|
+
// launchpadCommand speaks those two lines before this ranking runs.
|
|
197
208
|
|
|
198
209
|
const ownClaimed = newest(tasks.filter(task => (
|
|
199
210
|
task.status === 'claimed'
|
|
@@ -609,8 +620,32 @@ function launchpadCommand(args = []) {
|
|
|
609
620
|
showLaunchpadHelp();
|
|
610
621
|
return 0;
|
|
611
622
|
}
|
|
612
|
-
const
|
|
613
|
-
|
|
623
|
+
const asJson = hasFlag(args, '--json');
|
|
624
|
+
const root = process.cwd();
|
|
625
|
+
|
|
626
|
+
// Fresh folder: empty talks first-talk. A file already here
|
|
627
|
+
// names that file, same as bare atris. Do not mint a room.
|
|
628
|
+
if (isFreshWorkspace(root)) {
|
|
629
|
+
return speakFirstMinute({ root, fresh: true, asJson });
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
633
|
+
// first-minute / the next keep-working ready. After init, next is claim: same
|
|
634
|
+
// two lines as bare atris / status / now. Not factory init.
|
|
635
|
+
// A live mission still gets the board. --json on the claim
|
|
636
|
+
// path keeps the factory card for scripts.
|
|
637
|
+
if (!hasLiveKeepWorkingRun(root)) {
|
|
638
|
+
const minute = buildFirstMinute({ root });
|
|
639
|
+
if (isKeepWorkingMinute(minute)) {
|
|
640
|
+
return speakKeepWorkingMinute({ root, asJson });
|
|
641
|
+
}
|
|
642
|
+
if (!asJson && isClaimMinute(minute)) {
|
|
643
|
+
return speakFirstMinute({ root });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const payload = collectLaunchpad(root, args);
|
|
648
|
+
if (asJson) {
|
|
614
649
|
console.log(JSON.stringify(payload, null, 2));
|
|
615
650
|
} else {
|
|
616
651
|
process.stdout.write(renderLaunchpad(payload));
|
package/commands/now.js
CHANGED
|
@@ -3,6 +3,13 @@ const path = require('path');
|
|
|
3
3
|
const { hasRenderedSections, isOpenSection } = require('../lib/todo-sections');
|
|
4
4
|
const { renderMorningCardRow } = require('../lib/receipt-block');
|
|
5
5
|
const { historicalLandingText } = require('../lib/autoland');
|
|
6
|
+
const {
|
|
7
|
+
buildFirstMinute,
|
|
8
|
+
isFreshWorkspace,
|
|
9
|
+
isKeepWorkingMinute,
|
|
10
|
+
speakFirstMinute,
|
|
11
|
+
speakKeepWorkingMinute,
|
|
12
|
+
} = require('../lib/first-minute');
|
|
6
13
|
|
|
7
14
|
const NOW_PATH = path.join('atris', 'now.md');
|
|
8
15
|
const TASK_EPISODES_PATH = path.join('.atris', 'state', 'task_episodes.jsonl');
|
|
@@ -589,8 +596,16 @@ function printNowJson(payload) {
|
|
|
589
596
|
console.log(JSON.stringify(payload, null, 2));
|
|
590
597
|
}
|
|
591
598
|
|
|
599
|
+
function hasLiveKeepWorkingRun(root = process.cwd()) {
|
|
600
|
+
try {
|
|
601
|
+
const { pickLiveLocalMission } = require('./mission');
|
|
602
|
+
return Boolean(pickLiveLocalMission(root));
|
|
603
|
+
} catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
592
608
|
function nowJsonPayload(root = process.cwd()) {
|
|
593
|
-
const { buildFirstMinute, isFreshWorkspace } = require('../lib/first-minute');
|
|
594
609
|
const fresh = isFreshWorkspace(root);
|
|
595
610
|
const screen = buildFirstMinute({ root, fresh });
|
|
596
611
|
const payload = { ok: !fresh };
|
|
@@ -630,6 +645,26 @@ function nowAtris(args = process.argv.slice(3), root = process.cwd()) {
|
|
|
630
645
|
return payload.ok ? 0 : 2;
|
|
631
646
|
}
|
|
632
647
|
|
|
648
|
+
// Fresh folder: empty talks first-talk. A file already here
|
|
649
|
+
// names that file, same as bare atris / now --json. Do not mint.
|
|
650
|
+
// Just-minted file folder, nothing running: same two lines as
|
|
651
|
+
// first-minute / status / recap. Not factory MAP.md.
|
|
652
|
+
// After init, next is claim: same two lines as bare atris /
|
|
653
|
+
// now --json. Not factory now.md.
|
|
654
|
+
// --init / --refresh / --all / --path still write now.md.
|
|
655
|
+
if (!init && !refresh && !all && !pathOnly) {
|
|
656
|
+
if (isFreshWorkspace(root)) {
|
|
657
|
+
return speakFirstMinute({ root, fresh: true });
|
|
658
|
+
}
|
|
659
|
+
if (!hasLiveKeepWorkingRun(root)) {
|
|
660
|
+
const minute = buildFirstMinute({ root });
|
|
661
|
+
if (isKeepWorkingMinute(minute)) {
|
|
662
|
+
return speakKeepWorkingMinute({ root });
|
|
663
|
+
}
|
|
664
|
+
return speakFirstMinute({ root });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
633
668
|
let result;
|
|
634
669
|
if (all) {
|
|
635
670
|
const workspaces = findChildWorkspaces(root);
|
package/commands/recap.js
CHANGED
|
@@ -4,11 +4,13 @@ const {
|
|
|
4
4
|
isCertifiedReview,
|
|
5
5
|
isFreshWorkspace,
|
|
6
6
|
isKeepWorkingMinute,
|
|
7
|
+
isClaimMinute,
|
|
7
8
|
buildFirstMinute,
|
|
8
9
|
listUserVisibleWork,
|
|
9
10
|
personName,
|
|
10
11
|
speakFirstMinute,
|
|
11
12
|
speakKeepWorkingMinute,
|
|
13
|
+
taskCommand,
|
|
12
14
|
} = require('../lib/first-minute');
|
|
13
15
|
const { isRealTestRunnerProof, quoteVerifierCommand } = require('../lib/verifier-quality');
|
|
14
16
|
|
|
@@ -319,7 +321,7 @@ function renderRecapMinute(data, { person } = {}) {
|
|
|
319
321
|
return [
|
|
320
322
|
`${greet}${named} is already yours.`,
|
|
321
323
|
'',
|
|
322
|
-
|
|
324
|
+
`next: ${taskCommand({ display_id: item.id, status: 'claimed' })}`,
|
|
323
325
|
].join('\n');
|
|
324
326
|
}
|
|
325
327
|
if (named) return `${greet}${named} is ready to claim.`;
|
|
@@ -348,7 +350,22 @@ function recapAtris(args = []) {
|
|
|
348
350
|
printRecapHelp();
|
|
349
351
|
return;
|
|
350
352
|
}
|
|
351
|
-
const
|
|
353
|
+
const cwd = process.cwd();
|
|
354
|
+
const taskDb = loadTaskDb();
|
|
355
|
+
let root = cwd;
|
|
356
|
+
try {
|
|
357
|
+
root = taskDb ? taskDb.workspaceRoot(cwd) : cwd;
|
|
358
|
+
} catch {
|
|
359
|
+
root = cwd;
|
|
360
|
+
}
|
|
361
|
+
// cd src in a real git project still recaps that project. An empty
|
|
362
|
+
// child under /tmp stays its own room and does not inherit /tmp.
|
|
363
|
+
let inProjectSubdir = false;
|
|
364
|
+
try {
|
|
365
|
+
inProjectSubdir = fs.realpathSync(cwd) !== fs.realpathSync(root);
|
|
366
|
+
} catch {
|
|
367
|
+
inProjectSubdir = path.resolve(cwd) !== path.resolve(root);
|
|
368
|
+
}
|
|
352
369
|
const visibleBefore = listUserVisibleWork(root);
|
|
353
370
|
const daysIdx = args.indexOf('--days');
|
|
354
371
|
const days = daysIdx !== -1 ? Number(args[daysIdx + 1]) : DEFAULT_DAYS;
|
|
@@ -356,6 +373,7 @@ function recapAtris(args = []) {
|
|
|
356
373
|
if (
|
|
357
374
|
data.empty
|
|
358
375
|
&& isFreshWorkspace(root)
|
|
376
|
+
&& !inProjectSubdir
|
|
359
377
|
&& !args.includes('--verbose')
|
|
360
378
|
&& !args.includes('--full')
|
|
361
379
|
&& !args.includes('--share')
|
|
@@ -367,13 +385,18 @@ function recapAtris(args = []) {
|
|
|
367
385
|
});
|
|
368
386
|
}
|
|
369
387
|
// Just-minted file folder, nothing running: same two lines as
|
|
370
|
-
// first-minute / status / the
|
|
388
|
+
// first-minute / status / recap. After the work is yours, next
|
|
389
|
+
// is task ready so keep-working is not a do loop. Not factory MAP.md.
|
|
390
|
+
// After init, next is claim: same two lines as bare atris /
|
|
391
|
+
// status / now / stop. --json keeps the recap receipt and
|
|
392
|
+
// fills next from first-minute so it is not silent.
|
|
371
393
|
// A claimed non-seed task still recaps that work. A live mission
|
|
372
394
|
// and --verbose / --share keep the recap report.
|
|
373
395
|
if (
|
|
374
396
|
!args.includes('--verbose')
|
|
375
397
|
&& !args.includes('--full')
|
|
376
398
|
&& !args.includes('--share')
|
|
399
|
+
&& !inProjectSubdir
|
|
377
400
|
&& !hasLiveKeepWorkingRun(root)
|
|
378
401
|
) {
|
|
379
402
|
const minute = buildFirstMinute({ root, files: visibleBefore });
|
|
@@ -383,6 +406,14 @@ function recapAtris(args = []) {
|
|
|
383
406
|
asJson: args.includes('--json'),
|
|
384
407
|
});
|
|
385
408
|
}
|
|
409
|
+
if (isClaimMinute(minute)) {
|
|
410
|
+
if (args.includes('--json')) {
|
|
411
|
+
data.next = minute.nextCommand || null;
|
|
412
|
+
console.log(JSON.stringify(data, null, 2));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
return speakFirstMinute({ root });
|
|
416
|
+
}
|
|
386
417
|
}
|
|
387
418
|
if (args.includes('--json')) {
|
|
388
419
|
console.log(JSON.stringify(data, null, 2));
|