atris 3.30.12 → 3.31.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 +16 -3
- package/FOR_AGENTS.md +81 -0
- package/README.md +3 -1
- package/atris/atris.md +51 -19
- package/atris/skills/README.md +1 -0
- package/atris/skills/blocks/SKILL.md +134 -0
- package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
- package/atris/skills/youtube/SKILL.md +31 -11
- package/atris.md +7 -0
- package/ax +95 -3
- package/bin/atris.js +126 -153
- package/commands/autoland.js +319 -0
- package/commands/autopilot.js +94 -4
- package/commands/business.js +1 -1
- package/commands/clean.js +72 -9
- package/commands/codex-goal.js +72 -22
- package/commands/computer.js +48 -3
- package/commands/gm.js +1 -1
- package/commands/harvest.js +179 -0
- package/commands/init.js +1 -1
- package/commands/land.js +442 -0
- package/commands/loop-front.js +122 -4
- package/commands/member.js +519 -19
- package/commands/mission.js +3330 -290
- package/commands/play.js +1 -1
- package/commands/pulse.js +65 -7
- package/commands/run.js +3 -3
- package/commands/strings.js +301 -0
- package/commands/task.js +575 -102
- package/commands/truth.js +170 -0
- package/commands/xp.js +32 -8
- package/commands/youtube.js +72 -5
- package/decks/README.md +6 -12
- package/lib/auto-accept-certified.js +10 -0
- package/lib/autoland.js +283 -0
- package/lib/context-gatherer.js +0 -8
- package/lib/mission-artifact.js +504 -0
- package/lib/mission-room.js +846 -0
- package/lib/next-moves.js +212 -6
- package/lib/pulse.js +74 -1
- package/lib/runner-command.js +20 -8
- package/lib/runs-prune.js +242 -0
- package/lib/task-proof.js +1 -1
- package/package.json +3 -3
- package/decks/atris-seed-pitch-v3.json +0 -118
- package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
- package/decks/atris-seed-pitch-v5.json +0 -109
- package/decks/atris-seed-pitch-v6.json +0 -137
- package/decks/atris-seed-pitch-v7.json +0 -133
- package/decks/mark-pincus-narrative.json +0 -102
- package/decks/mark-pincus-sourcery.json +0 -94
- package/decks/yash-applied-compute-detailed.json +0 -150
- package/decks/yash-applied-compute-generalist.json +0 -82
- package/decks/yash-applied-compute-narrative.json +0 -54
- package/lib/ax-chat-input.js +0 -164
- package/lib/ax-goal.js +0 -307
- package/lib/ax-prefs.js +0 -63
- package/lib/ax-shimmer.js +0 -63
package/commands/member.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const os = require('os');
|
|
5
|
-
const { execFileSync } = require('child_process');
|
|
5
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
6
6
|
const { loadCredentials } = require('../utils/auth');
|
|
7
7
|
const { apiRequestJson } = require('../utils/api');
|
|
8
8
|
const { runAliveTick } = require('../lib/member-alive');
|
|
@@ -406,39 +406,245 @@ function missionPurpose(paths) {
|
|
|
406
406
|
};
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
const MEMBER_RUN_RUNNABLE_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
410
|
+
|
|
411
|
+
function memberRunMissionMap() {
|
|
412
|
+
try {
|
|
413
|
+
return require('./mission').loadMissionMap(process.cwd());
|
|
414
|
+
} catch {
|
|
415
|
+
return new Map();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function memberRunRunnableMissionId(candidateId, missionMap) {
|
|
420
|
+
const id = String(candidateId || '').trim();
|
|
421
|
+
if (!id) return '';
|
|
422
|
+
const mission = missionMap.get(id);
|
|
423
|
+
// Unknown here can mean a worktree-held mission; let mission run decide.
|
|
424
|
+
if (!mission) return id;
|
|
425
|
+
return MEMBER_RUN_RUNNABLE_STATUSES.has(mission.status) ? id : '';
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function memberRunOwnedActiveMissionId(name, missionMap) {
|
|
429
|
+
// Discovery fallback when the member's own pointers are stale: resume the
|
|
430
|
+
// member's newest moving mission. ready is excluded — it waits for review.
|
|
431
|
+
const owner = String(name || '').trim().toLowerCase();
|
|
432
|
+
const candidates = Array.from(missionMap.values())
|
|
433
|
+
.filter((mission) => String(mission?.owner || '').trim().toLowerCase() === owner)
|
|
434
|
+
.filter((mission) => mission.status === 'running' || mission.status === 'planning')
|
|
435
|
+
.filter((mission) => !/^decide and start the next useful mission after:/i.test(String(mission?.objective || '')))
|
|
436
|
+
.sort((a, b) => {
|
|
437
|
+
if (a.status !== b.status) return a.status === 'running' ? -1 : 1;
|
|
438
|
+
return String(b.updated_at || b.created_at || '').localeCompare(String(a.updated_at || a.created_at || ''));
|
|
439
|
+
});
|
|
440
|
+
return candidates[0]?.id || '';
|
|
441
|
+
}
|
|
442
|
+
|
|
409
443
|
function resolveMemberRunMissionId(name, args = []) {
|
|
410
444
|
const override = readFlag(args, '--mission', '') || readFlag(args, '--mission-id', '');
|
|
411
445
|
if (override) return override;
|
|
412
446
|
|
|
413
447
|
const paths = requireMemberDir(name);
|
|
448
|
+
const missionMap = memberRunMissionMap();
|
|
414
449
|
const purpose = missionPurpose(paths);
|
|
415
|
-
|
|
450
|
+
const runtimeId = memberRunRunnableMissionId(purpose.runtimeMission?.id, missionMap);
|
|
451
|
+
if (runtimeId) return runtimeId;
|
|
416
452
|
|
|
417
453
|
const goals = loadMemberGoals(name, paths);
|
|
418
454
|
const goal = activeGoal(goals);
|
|
419
|
-
|
|
455
|
+
const goalId = memberRunRunnableMissionId(goal?.mission_id, missionMap);
|
|
456
|
+
if (goalId) return goalId;
|
|
457
|
+
|
|
458
|
+
return memberRunOwnedActiveMissionId(paths.storageName || name, missionMap);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const MEMBER_RUN_START_VALUE_FLAGS = [
|
|
462
|
+
'--mission',
|
|
463
|
+
'--mission-id',
|
|
464
|
+
'--owner',
|
|
465
|
+
'--runner',
|
|
466
|
+
'--lane',
|
|
467
|
+
'--cadence',
|
|
468
|
+
'--verify',
|
|
469
|
+
'--stop',
|
|
470
|
+
'--model',
|
|
471
|
+
'--max-ticks',
|
|
472
|
+
'--max-wall',
|
|
473
|
+
'--minutes',
|
|
474
|
+
'--hours',
|
|
475
|
+
'--industry',
|
|
476
|
+
'--domain',
|
|
477
|
+
'--value',
|
|
478
|
+
'--outcome',
|
|
479
|
+
'--truth',
|
|
480
|
+
'--proof',
|
|
481
|
+
];
|
|
482
|
+
|
|
483
|
+
const MEMBER_RUN_START_BOOLEAN_FLAGS = [
|
|
484
|
+
'--json',
|
|
485
|
+
'--worktree',
|
|
486
|
+
'--shared-checkout',
|
|
487
|
+
'--no-worktree',
|
|
488
|
+
'--always-on',
|
|
489
|
+
'--xp-task',
|
|
490
|
+
'--agent-xp',
|
|
491
|
+
'--spend-full-budget',
|
|
492
|
+
'--use-whole-budget',
|
|
493
|
+
'--stop-when-done',
|
|
494
|
+
'--choose-work',
|
|
495
|
+
'--auto',
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
function memberRunMissionText(args = []) {
|
|
499
|
+
return stripKnownFlags(args, MEMBER_RUN_START_VALUE_FLAGS, MEMBER_RUN_START_BOOLEAN_FLAGS).join(' ').trim();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function cleanMemberRunPhrase(value) {
|
|
503
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function memberRunDomain(args = []) {
|
|
507
|
+
return cleanMemberRunPhrase(readFlag(args, '--industry', '') || readFlag(args, '--domain', '')) || 'this workspace and its users';
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function memberRunOutcome(args = []) {
|
|
511
|
+
return cleanMemberRunPhrase(readFlag(args, '--outcome', '') || readFlag(args, '--value', ''))
|
|
512
|
+
|| 'revenue, reliability, speed, security, workflow clarity, or customer trust';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function memberRunTruthRule(args = []) {
|
|
516
|
+
return cleanMemberRunPhrase(readFlag(args, '--truth', '') || readFlag(args, '--proof', ''))
|
|
517
|
+
|| 'say what changed, what was checked, and what is still unproven';
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function memberRunUsefulTarget() {
|
|
521
|
+
try {
|
|
522
|
+
const { nextMoves: pickNextMoves, isGenericInboxPlaceholder } = require('../lib/next-moves');
|
|
523
|
+
const moves = pickNextMoves(process.cwd(), 5);
|
|
524
|
+
const candidate = moves.find((move) => {
|
|
525
|
+
const title = cleanMemberRunPhrase(move?.title);
|
|
526
|
+
if (!title) return false;
|
|
527
|
+
if (isGenericInboxPlaceholder(title)) return false;
|
|
528
|
+
if (/^mission xp\s*:/i.test(title)) return false;
|
|
529
|
+
if (/^decide and start the next useful mission after:/i.test(title)) return false;
|
|
530
|
+
return true;
|
|
531
|
+
});
|
|
532
|
+
return candidate?.title ? cleanMemberRunPhrase(candidate.title) : '';
|
|
533
|
+
} catch {
|
|
534
|
+
return '';
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function memberRunAutoMissionText(name, args = []) {
|
|
539
|
+
const domain = memberRunDomain(args);
|
|
540
|
+
const outcome = memberRunOutcome(args);
|
|
541
|
+
const truth = memberRunTruthRule(args);
|
|
542
|
+
const target = memberRunUsefulTarget();
|
|
543
|
+
const work = target
|
|
544
|
+
? `Start with this concrete candidate: ${target}.`
|
|
545
|
+
: 'First inspect current Atris state, then choose one bounded useful task.';
|
|
546
|
+
return [
|
|
547
|
+
`Run ${name} on meaningful work for ${domain}.`,
|
|
548
|
+
work,
|
|
549
|
+
`Useful means it improves ${outcome}.`,
|
|
550
|
+
`Truth rule: ${truth}.`,
|
|
551
|
+
'Explain the choice in plain language, prove the result before Review, and stop instead of doing fake busywork when no useful task exists.',
|
|
552
|
+
].join(' ');
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function memberRunBudgetSeconds(args = []) {
|
|
556
|
+
const hours = readNumberFlag(args, '--hours', null);
|
|
557
|
+
if (Number.isFinite(hours) && hours > 0) return Math.round(hours * 3600);
|
|
558
|
+
const minutes = readNumberFlag(args, '--minutes', null);
|
|
559
|
+
if (Number.isFinite(minutes) && minutes > 0) return Math.round(minutes * 60);
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function pushFlagValue(out, args, name) {
|
|
564
|
+
const value = readFlag(args, name, '');
|
|
565
|
+
if (value) out.push(name, String(value));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function pushFlagWhenPresent(out, args, name) {
|
|
569
|
+
if (hasFlag(args, name)) out.push(name);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function startMemberRunMission(name, missionText, args = []) {
|
|
573
|
+
const paths = requireMemberDir(name);
|
|
574
|
+
const owner = paths.storageName || name;
|
|
575
|
+
const startArgs = [
|
|
576
|
+
'mission',
|
|
577
|
+
'start',
|
|
578
|
+
missionText,
|
|
579
|
+
'--owner',
|
|
580
|
+
owner,
|
|
581
|
+
'--runner',
|
|
582
|
+
readFlag(args, '--runner', 'codex_goal'),
|
|
583
|
+
'--lane',
|
|
584
|
+
readFlag(args, '--lane', 'workspace'),
|
|
585
|
+
];
|
|
586
|
+
pushFlagValue(startArgs, args, '--cadence');
|
|
587
|
+
pushFlagValue(startArgs, args, '--verify');
|
|
588
|
+
pushFlagValue(startArgs, args, '--stop');
|
|
589
|
+
pushFlagValue(startArgs, args, '--model');
|
|
590
|
+
pushFlagValue(startArgs, args, '--max-wall');
|
|
591
|
+
pushFlagValue(startArgs, args, '--minutes');
|
|
592
|
+
pushFlagValue(startArgs, args, '--hours');
|
|
593
|
+
pushFlagValue(startArgs, args, '--base');
|
|
594
|
+
pushFlagWhenPresent(startArgs, args, '--always-on');
|
|
595
|
+
pushFlagWhenPresent(startArgs, args, '--xp-task');
|
|
596
|
+
pushFlagWhenPresent(startArgs, args, '--agent-xp');
|
|
597
|
+
pushFlagWhenPresent(startArgs, args, '--spend-full-budget');
|
|
598
|
+
pushFlagWhenPresent(startArgs, args, '--use-whole-budget');
|
|
599
|
+
pushFlagWhenPresent(startArgs, args, '--stop-when-done');
|
|
600
|
+
if (!hasFlag(args, '--shared-checkout') && !hasFlag(args, '--no-worktree')) startArgs.push('--worktree');
|
|
601
|
+
if (hasFlag(args, '--json')) startArgs.push('--json');
|
|
602
|
+
|
|
603
|
+
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
604
|
+
try {
|
|
605
|
+
execFileSync(process.execPath, [cliPath, ...startArgs], {
|
|
606
|
+
cwd: process.cwd(),
|
|
607
|
+
stdio: 'inherit',
|
|
608
|
+
});
|
|
609
|
+
} catch (error) {
|
|
610
|
+
process.exitCode = Number(error?.status) || 1;
|
|
611
|
+
}
|
|
420
612
|
}
|
|
421
613
|
|
|
422
614
|
function memberRun(name, ...args) {
|
|
423
615
|
if (!name || name === '--help' || name === '-h' || hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
424
|
-
console.log('Usage: atris member run <name> [mission
|
|
425
|
-
console.log('
|
|
426
|
-
console.log('
|
|
616
|
+
console.log('Usage: atris member run <name> ["mission text"] [--minutes N|--hours N] [--json]');
|
|
617
|
+
console.log('New mission: atris member run growth "Improve onboarding proof" --minutes 30 --json');
|
|
618
|
+
console.log('Choose work: atris member run growth --industry logistics --value "reduce support time" --minutes 30 --json');
|
|
619
|
+
console.log('Existing mission: atris member run growth --mission <mission-id> --max-ticks 1 --json');
|
|
620
|
+
console.log('Meaning: if mission text is omitted, the member chooses one useful bounded task from Atris state.');
|
|
621
|
+
console.log('Truth: useful work must improve revenue, reliability, speed, security, clarity, or trust, then show plain proof.');
|
|
622
|
+
console.log('Isolation: new missions use --worktree by default; add --shared-checkout to stay here.');
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const missionText = memberRunMissionText(args);
|
|
627
|
+
const hasMissionOverride = Boolean(readFlag(args, '--mission', '') || readFlag(args, '--mission-id', ''));
|
|
628
|
+
if (missionText && !hasMissionOverride) {
|
|
629
|
+
startMemberRunMission(name, missionText, args);
|
|
427
630
|
return;
|
|
428
631
|
}
|
|
429
632
|
|
|
430
633
|
const missionId = resolveMemberRunMissionId(name, args);
|
|
431
634
|
if (!missionId) {
|
|
432
|
-
|
|
433
|
-
console.error(`Try: atris member goal-from-mission ${name} --json`);
|
|
434
|
-
console.error(`Or: atris mission start "..." --owner ${name}`);
|
|
435
|
-
process.exitCode = 1;
|
|
635
|
+
startMemberRunMission(name, memberRunAutoMissionText(name, args), args);
|
|
436
636
|
return;
|
|
437
637
|
}
|
|
438
638
|
|
|
439
|
-
const runArgs = stripKnownFlags(args, ['--mission', '--mission-id']);
|
|
440
|
-
|
|
441
|
-
|
|
639
|
+
const runArgs = stripKnownFlags(args, ['--mission', '--mission-id', '--minutes', '--hours'], ['--worktree', '--shared-checkout', '--no-worktree']);
|
|
640
|
+
const budgetSeconds = memberRunBudgetSeconds(args);
|
|
641
|
+
// A timed run is a loop contract, not one tick: keep picking the next useful
|
|
642
|
+
// move until the wall clock (--max-wall) spends the budget. Only untimed runs
|
|
643
|
+
// default to a single tick.
|
|
644
|
+
if (!readFlag(runArgs, '--max-ticks', '')) {
|
|
645
|
+
runArgs.push('--max-ticks', budgetSeconds ? String(Math.max(4, Math.ceil(budgetSeconds / 300))) : '1');
|
|
646
|
+
}
|
|
647
|
+
if (!readFlag(runArgs, '--max-wall', '')) runArgs.push('--max-wall', String(budgetSeconds || 900));
|
|
442
648
|
|
|
443
649
|
const cliPath = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
444
650
|
try {
|
|
@@ -2023,6 +2229,247 @@ function memberLoopPaths(name) {
|
|
|
2023
2229
|
lockPath: path.join(stateDir, `${name}.lock.json`),
|
|
2024
2230
|
stopPath: path.join(stateDir, `${name}.stop.json`),
|
|
2025
2231
|
latestPath: path.join(stateDir, `${name}.latest.json`),
|
|
2232
|
+
cronScriptPath: path.join(stateDir, `${name}.hourly-alive.sh`),
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function memberAliveCronMarker(name) {
|
|
2237
|
+
return `ATRIS_MEMBER_ALIVE_${String(name || '').replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}`;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
function shellSingleQuote(value) {
|
|
2241
|
+
return `'${String(value || '').replace(/'/g, "'\\''")}'`;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function resolveAtrisCommand() {
|
|
2245
|
+
return path.join(__dirname, '..', 'bin', 'atris.js');
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
function buildMemberAliveCronScript(name, args = [], paths = memberLoopPaths(name)) {
|
|
2249
|
+
const root = process.cwd();
|
|
2250
|
+
const atrisBin = resolveAtrisCommand();
|
|
2251
|
+
const logDir = path.join(paths.stateDir, 'logs');
|
|
2252
|
+
const command = [
|
|
2253
|
+
process.execPath,
|
|
2254
|
+
atrisBin,
|
|
2255
|
+
'member',
|
|
2256
|
+
'alive',
|
|
2257
|
+
name,
|
|
2258
|
+
'--hourly',
|
|
2259
|
+
'--forever',
|
|
2260
|
+
'--ticks',
|
|
2261
|
+
'1',
|
|
2262
|
+
'--json',
|
|
2263
|
+
...(hasFlag(args, '--execute') ? ['--execute'] : []),
|
|
2264
|
+
...(hasFlag(args, '--confirm-autonomy-policy') ? ['--confirm-autonomy-policy'] : []),
|
|
2265
|
+
];
|
|
2266
|
+
const agent = String(readFlag(args, '--agent', '') || '').trim();
|
|
2267
|
+
const model = String(readFlag(args, '--model', '') || '').trim();
|
|
2268
|
+
const maxWall = String(readFlag(args, '--operate-max-wall', readFlag(args, '--max-wall', '')) || '').trim();
|
|
2269
|
+
const autoAcceptLimit = String(readFlag(args, '--auto-accept-limit', '') || '').trim();
|
|
2270
|
+
if (agent) command.push('--agent', agent);
|
|
2271
|
+
if (model) command.push('--model', model);
|
|
2272
|
+
if (maxWall) command.push('--operate-max-wall', maxWall);
|
|
2273
|
+
if (autoAcceptLimit) command.push('--auto-accept-limit', autoAcceptLimit);
|
|
2274
|
+
const renderedCommand = command.map(shellSingleQuote).join(' ');
|
|
2275
|
+
return {
|
|
2276
|
+
command,
|
|
2277
|
+
rendered_command: renderedCommand,
|
|
2278
|
+
script: [
|
|
2279
|
+
'#!/bin/zsh',
|
|
2280
|
+
'set -u',
|
|
2281
|
+
`ROOT=${shellSingleQuote(root)}`,
|
|
2282
|
+
`LOG_DIR=${shellSingleQuote(logDir)}`,
|
|
2283
|
+
'mkdir -p "$LOG_DIR"',
|
|
2284
|
+
'stamp="$(date +"%Y%m%d-%H%M%S")"',
|
|
2285
|
+
'cd "$ROOT" || exit 1',
|
|
2286
|
+
'export ATRIS_SKIP_UPDATE_CHECK=1',
|
|
2287
|
+
`${renderedCommand} >> "$LOG_DIR/$stamp.log" 2>&1`,
|
|
2288
|
+
'echo "done: $(date -Iseconds) exit=$?" >> "$LOG_DIR/$stamp.log"',
|
|
2289
|
+
'',
|
|
2290
|
+
].join('\n'),
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
function memberAliveCronCadence(args = []) {
|
|
2295
|
+
const pulse = require('../lib/pulse');
|
|
2296
|
+
const raw = hasFlag(args, '--hourly') || hasFlag(args, '--every-hour')
|
|
2297
|
+
? 'hourly'
|
|
2298
|
+
: readFlag(args, '--cadence', readFlag(args, '--cron', readFlag(args, '--interval', 'hourly')));
|
|
2299
|
+
return pulse.normalizeCronCadence(raw);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
function buildMemberAliveCrontabLine(name, args = [], paths = memberLoopPaths(name)) {
|
|
2303
|
+
return `${memberAliveCronCadence(args)} ${paths.cronScriptPath} # ${memberAliveCronMarker(name)}`;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
function dirtyGitStatus(root = process.cwd()) {
|
|
2307
|
+
const result = spawnSync('git', ['status', '--porcelain'], { cwd: root, encoding: 'utf8', timeout: 10000 });
|
|
2308
|
+
if (result.status !== 0) {
|
|
2309
|
+
return { inside_git: false, dirty: false, dirty_count: 0, dirty_files: [] };
|
|
2310
|
+
}
|
|
2311
|
+
const dirtyFiles = String(result.stdout || '')
|
|
2312
|
+
.split(/\r?\n/)
|
|
2313
|
+
.map((line) => line.trim())
|
|
2314
|
+
.filter(Boolean);
|
|
2315
|
+
return {
|
|
2316
|
+
inside_git: true,
|
|
2317
|
+
dirty: dirtyFiles.length > 0,
|
|
2318
|
+
dirty_count: dirtyFiles.length,
|
|
2319
|
+
dirty_files: dirtyFiles.slice(0, 12),
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
function memberAliveInstallPreview(name, args = [], paths = memberLoopPaths(name), script = buildMemberAliveCronScript(name, args, paths)) {
|
|
2324
|
+
return {
|
|
2325
|
+
schema: 'atris.member_alive_install_preview.v1',
|
|
2326
|
+
member: name,
|
|
2327
|
+
cadence: memberAliveCronCadence(args),
|
|
2328
|
+
mission_text: memberRunAutoMissionText(name, args),
|
|
2329
|
+
run: script.rendered_command,
|
|
2330
|
+
verify: `atris member alive ${name} --status --json`,
|
|
2331
|
+
stop_when: `Run atris member alive ${name} --uninstall, or create the stop marker at ${paths.stopPath}.`,
|
|
2332
|
+
receipts: path.join(paths.stateDir, 'logs'),
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
function memberAliveInstallPreviewLines(preview) {
|
|
2337
|
+
return [
|
|
2338
|
+
'Every hour this will:',
|
|
2339
|
+
` run: ${preview.run}`,
|
|
2340
|
+
` verify: ${preview.verify}`,
|
|
2341
|
+
` stop when: ${preview.stop_when}`,
|
|
2342
|
+
` receipts: ${preview.receipts}`,
|
|
2343
|
+
];
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
function installMemberAliveCron(name, args = []) {
|
|
2347
|
+
const asJson = hasFlag(args, '--json');
|
|
2348
|
+
const dryRun = hasFlag(args, '--dry-run');
|
|
2349
|
+
if (hasFlag(args, '--execute') && !hasFlag(args, '--confirm-autonomy-policy')) {
|
|
2350
|
+
const payload = {
|
|
2351
|
+
ok: false,
|
|
2352
|
+
action: 'alive_install',
|
|
2353
|
+
member: name,
|
|
2354
|
+
status: 'blocked',
|
|
2355
|
+
reason: 'execute_requires_confirm_autonomy_policy',
|
|
2356
|
+
};
|
|
2357
|
+
printJsonOrText(payload, [
|
|
2358
|
+
`Install blocked for ${name}: execute requires --confirm-autonomy-policy.`,
|
|
2359
|
+
], asJson);
|
|
2360
|
+
process.exitCode = 1;
|
|
2361
|
+
return payload;
|
|
2362
|
+
}
|
|
2363
|
+
const paths = memberLoopPaths(name);
|
|
2364
|
+
fs.mkdirSync(paths.stateDir, { recursive: true });
|
|
2365
|
+
const script = buildMemberAliveCronScript(name, args, paths);
|
|
2366
|
+
const crontabLine = buildMemberAliveCrontabLine(name, args, paths);
|
|
2367
|
+
const preview = memberAliveInstallPreview(name, args, paths, script);
|
|
2368
|
+
if (hasFlag(args, '--execute') && !dryRun) {
|
|
2369
|
+
const gitStatus = dirtyGitStatus();
|
|
2370
|
+
if (gitStatus.dirty) {
|
|
2371
|
+
const payload = {
|
|
2372
|
+
ok: false,
|
|
2373
|
+
action: 'alive_install',
|
|
2374
|
+
member: name,
|
|
2375
|
+
status: 'blocked',
|
|
2376
|
+
reason: 'install_requires_clean_git',
|
|
2377
|
+
git: gitStatus,
|
|
2378
|
+
preview,
|
|
2379
|
+
};
|
|
2380
|
+
printJsonOrText(payload, [
|
|
2381
|
+
`Install blocked for ${name}: commit or clean local changes before installing an execute loop.`,
|
|
2382
|
+
...memberAliveInstallPreviewLines(preview),
|
|
2383
|
+
], asJson);
|
|
2384
|
+
process.exitCode = 1;
|
|
2385
|
+
return payload;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
fs.writeFileSync(paths.cronScriptPath, script.script, { mode: 0o755 });
|
|
2389
|
+
const marker = memberAliveCronMarker(name);
|
|
2390
|
+
let installed = false;
|
|
2391
|
+
let error = null;
|
|
2392
|
+
if (!dryRun) {
|
|
2393
|
+
const existing = spawnSync('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
|
|
2394
|
+
const current = existing.status === 0 ? existing.stdout : '';
|
|
2395
|
+
const kept = String(current || '').split(/\r?\n/).filter((line) => line.trim() && !line.includes(marker));
|
|
2396
|
+
const next = `${kept.join('\n')}${kept.length ? '\n' : ''}${crontabLine}\n`;
|
|
2397
|
+
const apply = spawnSync('crontab', ['-'], { input: next, encoding: 'utf8', timeout: 10000 });
|
|
2398
|
+
installed = apply.status === 0;
|
|
2399
|
+
if (!installed) error = apply.stderr || `crontab exited ${apply.status}`;
|
|
2400
|
+
}
|
|
2401
|
+
const payload = {
|
|
2402
|
+
ok: dryRun || installed,
|
|
2403
|
+
action: 'alive_install',
|
|
2404
|
+
member: name,
|
|
2405
|
+
cadence: 'hourly',
|
|
2406
|
+
forever: true,
|
|
2407
|
+
installed,
|
|
2408
|
+
dry_run: dryRun,
|
|
2409
|
+
script_path: paths.cronScriptPath,
|
|
2410
|
+
crontab_line: crontabLine,
|
|
2411
|
+
command: script.command,
|
|
2412
|
+
preview,
|
|
2413
|
+
stop_command: `atris member alive ${name} --uninstall`,
|
|
2414
|
+
error,
|
|
2415
|
+
};
|
|
2416
|
+
writeJsonFile(paths.latestPath, payload);
|
|
2417
|
+
printJsonOrText(payload, [
|
|
2418
|
+
`${dryRun ? 'Would install' : installed ? 'Installed' : 'Install failed'} hourly alive loop: ${name}`,
|
|
2419
|
+
`Cron: ${crontabLine}`,
|
|
2420
|
+
...memberAliveInstallPreviewLines(preview),
|
|
2421
|
+
`Stop: ${payload.stop_command}`,
|
|
2422
|
+
], asJson);
|
|
2423
|
+
if (!payload.ok) process.exitCode = 1;
|
|
2424
|
+
return payload;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
function uninstallMemberAliveCron(name, args = []) {
|
|
2428
|
+
const asJson = hasFlag(args, '--json');
|
|
2429
|
+
const paths = memberLoopPaths(name);
|
|
2430
|
+
const marker = memberAliveCronMarker(name);
|
|
2431
|
+
const existing = spawnSync('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
|
|
2432
|
+
if (existing.status !== 0) {
|
|
2433
|
+
const payload = { ok: true, action: 'alive_uninstall', member: name, removed: false, reason: 'no_crontab' };
|
|
2434
|
+
printJsonOrText(payload, [`No hourly alive crontab found for ${name}.`], asJson);
|
|
2435
|
+
return payload;
|
|
2436
|
+
}
|
|
2437
|
+
const before = String(existing.stdout || '').split(/\r?\n/);
|
|
2438
|
+
const after = before.filter((line) => !line.includes(marker));
|
|
2439
|
+
const removed = after.length !== before.length;
|
|
2440
|
+
const apply = spawnSync('crontab', ['-'], { input: after.filter(Boolean).join('\n') + (after.filter(Boolean).length ? '\n' : ''), encoding: 'utf8', timeout: 10000 });
|
|
2441
|
+
const payload = {
|
|
2442
|
+
ok: apply.status === 0,
|
|
2443
|
+
action: 'alive_uninstall',
|
|
2444
|
+
member: name,
|
|
2445
|
+
removed,
|
|
2446
|
+
marker,
|
|
2447
|
+
script_path: paths.cronScriptPath,
|
|
2448
|
+
error: apply.status === 0 ? null : apply.stderr || `crontab exited ${apply.status}`,
|
|
2449
|
+
};
|
|
2450
|
+
printJsonOrText(payload, [
|
|
2451
|
+
removed ? `Removed hourly alive loop: ${name}` : `No hourly alive line found for ${name}.`,
|
|
2452
|
+
], asJson);
|
|
2453
|
+
if (!payload.ok) process.exitCode = 1;
|
|
2454
|
+
return payload;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
function memberAliveCronStatus(name, paths = memberLoopPaths(name)) {
|
|
2458
|
+
const marker = memberAliveCronMarker(name);
|
|
2459
|
+
let crontabLine = null;
|
|
2460
|
+
const crontab = spawnSync('crontab', ['-l'], { encoding: 'utf8', timeout: 10000 });
|
|
2461
|
+
if (crontab.status === 0) {
|
|
2462
|
+
crontabLine = String(crontab.stdout || '').split(/\r?\n/).find((line) => line.includes(marker)) || null;
|
|
2463
|
+
}
|
|
2464
|
+
return {
|
|
2465
|
+
schema: 'atris.member_alive_cron_status.v1',
|
|
2466
|
+
marker,
|
|
2467
|
+
installed: Boolean(crontabLine),
|
|
2468
|
+
crontab_line: crontabLine,
|
|
2469
|
+
script_path: paths.cronScriptPath,
|
|
2470
|
+
script_exists: fs.existsSync(paths.cronScriptPath),
|
|
2471
|
+
install_command: `atris member alive ${name} --install --hourly --forever --execute --confirm-autonomy-policy --json`,
|
|
2472
|
+
uninstall_command: `atris member alive ${name} --uninstall`,
|
|
2026
2473
|
};
|
|
2027
2474
|
}
|
|
2028
2475
|
|
|
@@ -2145,6 +2592,11 @@ function stripAutoImproverTitleNoise(text) {
|
|
|
2145
2592
|
function isAutoImproverGeneratedLogLine(line, relativePath = '') {
|
|
2146
2593
|
const text = String(line || '').trim();
|
|
2147
2594
|
if (!text) return false;
|
|
2595
|
+
if (/^[-*]?\s*Next tick will stop until a human looks at the error\.?$/i.test(text)) return true;
|
|
2596
|
+
if (/^[-*]?\s*Next tick will verify failed, halting\.?$/i.test(text)) return true;
|
|
2597
|
+
if (/^##\s+\d{1,2}:\d{2}\s+·\s+Task failed$/i.test(text)) return true;
|
|
2598
|
+
if (/^[-*]?\s*(status|action|state|verifier)\s*:\s*(failed|blocked)\s*$/i.test(text)) return true;
|
|
2599
|
+
if (/^[-*]?\s*(title|proof):\s*/i.test(text)) return true;
|
|
2148
2600
|
if (String(relativePath || '').startsWith('atris/team/auto-improver/logs/') && /^-\s*summary:\s*/i.test(text)) return true;
|
|
2149
2601
|
if (/\bauto[- ]improver\b/i.test(text) && /\b(dogfood|receipt|prevented|pain_)\b/i.test(text)) return true;
|
|
2150
2602
|
if (/\bauto_improver\b/i.test(text)) return true;
|
|
@@ -2162,12 +2614,28 @@ function normalizeFailurePattern(line) {
|
|
|
2162
2614
|
.trim());
|
|
2163
2615
|
}
|
|
2164
2616
|
|
|
2617
|
+
function failureCoveredByPassLesson(line, passLessonText = '') {
|
|
2618
|
+
const text = String(line || '');
|
|
2619
|
+
if (!text || !passLessonText) return false;
|
|
2620
|
+
if (/spawnSync\s+\/bin\/sh\s+ETIMEDOUT/i.test(text)) {
|
|
2621
|
+
const target = text.match(/"([^"]+)"/)?.[1] || '';
|
|
2622
|
+
const targetTail = target ? target.split('/').slice(-2).join('/') : '';
|
|
2623
|
+
return /spawnSync\s+\/bin\/sh\s+ETIMEDOUT/i.test(passLessonText)
|
|
2624
|
+
&& (!target || passLessonText.includes(target) || (targetTail && passLessonText.includes(targetTail)));
|
|
2625
|
+
}
|
|
2626
|
+
return false;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2165
2629
|
function collectAutoImproverLogSignals(root) {
|
|
2166
2630
|
const roots = [
|
|
2167
2631
|
path.join(root, 'atris', 'logs'),
|
|
2168
2632
|
path.join(root, 'atris', 'team'),
|
|
2169
2633
|
path.join(root, 'atris', 'wiki'),
|
|
2170
2634
|
].filter((candidate) => fs.existsSync(candidate));
|
|
2635
|
+
const passLessonText = safeReadText(path.join(root, 'atris', 'lessons.md'), 500000)
|
|
2636
|
+
.split(/\r?\n/)
|
|
2637
|
+
.filter((line) => /\s+pass\s+—/.test(line))
|
|
2638
|
+
.join('\n');
|
|
2171
2639
|
const failureRegex = /\b(error|failed|failure|blocked|timeout|regression|crash|missing proof|naraka|suffering)\b/i;
|
|
2172
2640
|
const unclearRegex = /\b(tbd|unclear|unknown|needs user|needs owner|needs proof|no next|blocked)\b/i;
|
|
2173
2641
|
const counts = new Map();
|
|
@@ -2203,6 +2671,7 @@ function collectAutoImproverLogSignals(root) {
|
|
|
2203
2671
|
if (/^\s*[-*]?\s*check:\s/i.test(line)) continue;
|
|
2204
2672
|
if (/\b(errors?|fail(?:ed|ures?)|blocked|timeouts?)\s*:\s*0\b/i.test(line)) continue;
|
|
2205
2673
|
if (/\bblocked\s*(?:->|→|to)\s*ready\b/i.test(line)) continue;
|
|
2674
|
+
if (failureCoveredByPassLesson(line, passLessonText)) continue;
|
|
2206
2675
|
if (unclearRegex.test(line)) {
|
|
2207
2676
|
unclearNextActions += 1;
|
|
2208
2677
|
unclearActions.push({
|
|
@@ -6900,6 +7369,14 @@ async function memberLoop(name, ...args) {
|
|
|
6900
7369
|
const paths = memberLoopPaths(name);
|
|
6901
7370
|
fs.mkdirSync(paths.stateDir, { recursive: true });
|
|
6902
7371
|
|
|
7372
|
+
if (hasFlag(args, '--install')) {
|
|
7373
|
+
return installMemberAliveCron(name, args);
|
|
7374
|
+
}
|
|
7375
|
+
|
|
7376
|
+
if (hasFlag(args, '--uninstall')) {
|
|
7377
|
+
return uninstallMemberAliveCron(name, args);
|
|
7378
|
+
}
|
|
7379
|
+
|
|
6903
7380
|
if (status) {
|
|
6904
7381
|
const active = readJsonIfExists(paths.lockPath);
|
|
6905
7382
|
const latest = readJsonIfExists(paths.latestPath);
|
|
@@ -6910,12 +7387,14 @@ async function memberLoop(name, ...args) {
|
|
|
6910
7387
|
active: Boolean(active && Number(active.expires_at_ms || 0) > Date.now() && isPidAlive(active.pid)),
|
|
6911
7388
|
lease: active || null,
|
|
6912
7389
|
latest: latest || null,
|
|
7390
|
+
hourly_alive: memberAliveCronStatus(name, paths),
|
|
6913
7391
|
lock_path: paths.lockPath,
|
|
6914
7392
|
latest_path: paths.latestPath,
|
|
6915
7393
|
};
|
|
6916
7394
|
printJsonOrText(payload, [
|
|
6917
7395
|
`Loop status: ${name}`,
|
|
6918
7396
|
`Active: ${payload.active ? 'yes' : 'no'}`,
|
|
7397
|
+
`Hourly: ${payload.hourly_alive.installed ? 'installed' : payload.hourly_alive.script_exists ? 'script ready' : 'not installed'}`,
|
|
6919
7398
|
`Latest: ${latest?.receipt_path ? path.relative(process.cwd(), latest.receipt_path) : 'none'}`,
|
|
6920
7399
|
], asJson);
|
|
6921
7400
|
return;
|
|
@@ -6953,11 +7432,21 @@ async function memberLoop(name, ...args) {
|
|
|
6953
7432
|
const ticksFlag = readNumberFlag(args, '--ticks', null);
|
|
6954
7433
|
const minutes = readNumberFlag(args, '--minutes', null);
|
|
6955
7434
|
const durationSeconds = readNumberFlag(args, '--duration-seconds', readNumberFlag(args, '--seconds', null));
|
|
6956
|
-
const
|
|
7435
|
+
const hourly = hasFlag(args, '--hourly') || hasFlag(args, '--every-hour');
|
|
7436
|
+
const forever = hasFlag(args, '--forever') || hasFlag(args, '--until-stopped');
|
|
7437
|
+
const intervalSeconds = hourly
|
|
7438
|
+
? 3600
|
|
7439
|
+
: readNumberFlag(args, '--interval', readNumberFlag(args, '--interval-seconds', 60));
|
|
6957
7440
|
const intervalMs = Math.max(0, Math.floor(Number(intervalSeconds == null ? 60 : intervalSeconds) * 1000));
|
|
6958
|
-
const durationMs =
|
|
7441
|
+
const durationMs = forever && ticksFlag == null
|
|
7442
|
+
? 0
|
|
7443
|
+
: Math.max(0, Math.floor(durationSeconds != null ? Number(durationSeconds) * 1000 : Number(minutes == null ? 10 : minutes) * 60 * 1000));
|
|
6959
7444
|
const ticks = ticksFlag != null
|
|
6960
7445
|
? Math.max(1, Math.floor(Number(ticksFlag)))
|
|
7446
|
+
: forever
|
|
7447
|
+
? execute && confirmed
|
|
7448
|
+
? Number.MAX_SAFE_INTEGER
|
|
7449
|
+
: 1
|
|
6961
7450
|
: intervalMs > 0
|
|
6962
7451
|
? Math.max(1, Math.floor(durationMs / intervalMs))
|
|
6963
7452
|
: 1;
|
|
@@ -6967,7 +7456,7 @@ async function memberLoop(name, ...args) {
|
|
|
6967
7456
|
const ttlMs = Math.max(
|
|
6968
7457
|
30000,
|
|
6969
7458
|
Math.floor(Number(ttlSeconds == null ? 0 : ttlSeconds) * 1000),
|
|
6970
|
-
durationMs + 60000,
|
|
7459
|
+
forever ? intervalMs + (2 * 60 * 60 * 1000) : durationMs + 60000,
|
|
6971
7460
|
intervalMs + 60000,
|
|
6972
7461
|
);
|
|
6973
7462
|
|
|
@@ -7162,6 +7651,12 @@ async function memberLoop(name, ...args) {
|
|
|
7162
7651
|
alive: aliveMode,
|
|
7163
7652
|
status: failed ? 'failed' : stopped ? 'stopped' : earlyExit ? (earlyExit.blocked_on_human ? 'blocked_on_human' : 'idle') : 'completed',
|
|
7164
7653
|
mode: execute ? 'execute' : 'dry_run',
|
|
7654
|
+
cadence: hourly ? 'hourly' : 'custom',
|
|
7655
|
+
forever,
|
|
7656
|
+
stop_command: `atris member ${aliveMode ? 'alive' : 'loop'} ${name} --stop`,
|
|
7657
|
+
operating_contract: forever
|
|
7658
|
+
? 'Run on this cadence until stopped; each tick must choose useful work, prove it plainly, or stop/ask instead of faking activity.'
|
|
7659
|
+
: 'Run for the requested bounded window; each tick must choose useful work, prove it plainly, or stop/ask instead of faking activity.',
|
|
7165
7660
|
run_id: runId,
|
|
7166
7661
|
ticks_requested: ticks,
|
|
7167
7662
|
ticks: tickResults.length,
|
|
@@ -7191,8 +7686,10 @@ async function memberLoop(name, ...args) {
|
|
|
7191
7686
|
printJsonOrText(payload, [
|
|
7192
7687
|
`${aliveMode ? 'Alive' : 'Loop'}: ${name}`,
|
|
7193
7688
|
`Status: ${payload.status}`,
|
|
7689
|
+
`Cadence: ${payload.cadence}${payload.forever ? ' forever' : ''}`,
|
|
7194
7690
|
`Ticks: ${payload.ticks}/${payload.ticks_requested}`,
|
|
7195
7691
|
`Decisions: ${Object.entries(decisions).map(([key, count]) => `${key} x${count}`).join(', ') || 'none'}`,
|
|
7692
|
+
`Stop: ${payload.stop_command}`,
|
|
7196
7693
|
`Receipt: ${path.relative(process.cwd(), receiptPath)}`,
|
|
7197
7694
|
], asJson);
|
|
7198
7695
|
}
|
|
@@ -7533,7 +8030,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
7533
8030
|
// Subcommands that take a member name as args[0] otherwise treat `--help` as
|
|
7534
8031
|
// a name and error with "Member '--help' not found". `create`/`new` handle
|
|
7535
8032
|
// help themselves (with subcommand-specific usage) — leave those alone.
|
|
7536
|
-
const HELP_AWARE_SUBCOMMANDS = new Set(['create', 'new']);
|
|
8033
|
+
const HELP_AWARE_SUBCOMMANDS = new Set(['create', 'new', 'run']);
|
|
7537
8034
|
if (!HELP_AWARE_SUBCOMMANDS.has(subcommand) && (args[0] === '-h' || args[0] === '--help')) {
|
|
7538
8035
|
subcommand = undefined;
|
|
7539
8036
|
}
|
|
@@ -7603,7 +8100,7 @@ async function memberCommand(subcommand, ...args) {
|
|
|
7603
8100
|
console.log(' goal-from-mission <name> Create/reuse a goal from MISSION.md and now.md');
|
|
7604
8101
|
console.log(' goal-from-score <name> Create/reuse an active goal from Team score evidence');
|
|
7605
8102
|
console.log(' wake <name> Read Mission state and decide tick/wait/ask/stop');
|
|
7606
|
-
console.log(
|
|
8103
|
+
console.log(' run <name> ["..."] Start a budgeted member mission, or run its active Mission Runtime');
|
|
7607
8104
|
console.log(' loop <name> Repeat wake on a bounded cadence with a no-overlap lease');
|
|
7608
8105
|
console.log(' tick <name> Propose the next bounded experiment');
|
|
7609
8106
|
console.log(' review <name> <id> Accept/discard an experiment with proof');
|
|
@@ -7633,13 +8130,16 @@ async function memberCommand(subcommand, ...args) {
|
|
|
7633
8130
|
console.log(' atris member goal-from-mission growth --json');
|
|
7634
8131
|
console.log(' atris member goal-from-score growth --score-json team-score.json --json');
|
|
7635
8132
|
console.log(' atris member wake growth --json');
|
|
7636
|
-
console.log(' atris member run growth
|
|
8133
|
+
console.log(' atris member run growth "Improve onboarding proof" --minutes 30 --json');
|
|
8134
|
+
console.log(' atris member run growth --mission <mission-id> --max-ticks 1 --json');
|
|
7637
8135
|
console.log(' atris member wake growth --execute --confirm-autonomy-policy');
|
|
7638
8136
|
console.log(' atris member supervisor recommendations --json');
|
|
7639
8137
|
console.log(' atris member objective-generator proposals --json');
|
|
7640
8138
|
console.log(' atris member generalist proof --json');
|
|
7641
8139
|
console.log(' atris member generalist patterns --json');
|
|
7642
8140
|
console.log(' atris member loop growth --minutes 10 --interval 60 --json');
|
|
8141
|
+
console.log(' atris member alive growth --hourly --forever --execute --confirm-autonomy-policy --json');
|
|
8142
|
+
console.log(' atris member alive growth --install --hourly --forever --execute --confirm-autonomy-policy --json');
|
|
7643
8143
|
console.log(' atris member alive growth --minutes 480 --interval 900 --execute --confirm-autonomy-policy --json');
|
|
7644
8144
|
console.log(' atris member loop growth --ticks 2 --interval 0 --json');
|
|
7645
8145
|
console.log(' atris member tick growth --json');
|